From 04d1c89195e08a9706708a8f4f9bb52908a56290 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 00:53:45 +0530 Subject: [PATCH 01/26] converted RegisterMonitor and UnregisterMonitor to async --- .../implementation/linux/ble_v2_medium.cc | 63 +++++++++++++++---- .../implementation/linux/ble_v2_medium.h | 13 ++++ .../bluez_advertisement_monitor_manager.h | 49 +++++++++++++++ .../advertisement_monitor_manager_client.h | 15 +++-- ...org.bluez.AdvertisementMonitorManager1.xml | 2 + 5 files changed, 125 insertions(+), 17 deletions(-) diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc index 50d0eb2c..3d3c529d 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.cc +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -68,7 +68,7 @@ BleL2capSocket::ProtocolMode GetL2capProtocolMode() { BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) : system_bus_(adapter.GetConnection()), adapter_(adapter), - gatt_discovery_(std::make_shared(system_bus_)), + //gatt_discovery_(std::make_shared(system_bus_)), observers_(std::make_shared>()), devices_(std::make_unique( system_bus_, adapter_.GetObjectPath(), *observers_)), @@ -89,11 +89,14 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) << __func__ << ": Registering path /com/google/nearby/medium/ble/advertisement/monitor with AdvertisementMonitorManager at " << adv_monitor_manager_->getProxy().getObjectPath(); - try { - adv_monitor_manager_->RegisterMonitor(root_object_manager_->getObject().getObjectPath()); - } catch (const sdbus::Error &e) { - DBUS_LOG_METHOD_CALL_ERROR(adv_monitor_manager_, "RegisterMonitor", e); - } + adv_monitor_manager_->SetRegisterMonitorReplyCallback( + [this](std::optional error) { + OnRegisterMonitorReply(std::move(error)); + }); + adv_monitor_manager_->RegisterMonitor( + root_object_manager_->getObject().getObjectPath()); + } else { + adv_monitor_manager_ready_notification_.Notify(); } // if (gatt_discovery_->InitializeKnownServices()) { // LOG(ERROR) << __func__ @@ -101,6 +104,46 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) // } } +void BleV2Medium::OnRegisterMonitorReply(std::optional error) { + { + absl::MutexLock lock(&adv_monitor_manager_ready_mutex_); + adv_monitor_manager_ready_ = !error.has_value() || !error->isValid(); + if (error.has_value() && error->isValid()) { + adv_monitor_manager_error_name_ = error->getName(); + adv_monitor_manager_error_message_ = error->getMessage(); + } + } + + if (error.has_value() && error->isValid()) { + LOG(ERROR) << __func__ << ": Got error '" << error->getName() + << "' with message '" << error->getMessage() + << "' while calling RegisterMonitor on object " + << adv_monitor_manager_->getProxy().getObjectPath(); + } + + adv_monitor_manager_ready_notification_.Notify(); +} + +bool BleV2Medium::WaitForAdvertisementMonitorManager() { + if (adv_monitor_manager_ == nullptr) { + LOG(WARNING) << __func__ << ": Advertising monitor not supported by BlueZ"; + return false; + } + + adv_monitor_manager_ready_notification_.WaitForNotification(); + + absl::MutexLock lock(&adv_monitor_manager_ready_mutex_); + if (adv_monitor_manager_ready_) { + return true; + } + + LOG(WARNING) << __func__ + << ": AdvertisementMonitorManager registration failed with name '" + << adv_monitor_manager_error_name_ << "' and message '" + << adv_monitor_manager_error_message_ << "'"; + return false; +} + // sync api // called twice. Once with extended regular advertisement ( when IsExtendedAdvertisementsAvailable() == true ) // and another for GATT-backed header advertisement for legacy devices @@ -240,9 +283,7 @@ bool BleV2Medium::StartScanning(const Uuid &service_uuid, return false; } - if (adv_monitor_manager_ == nullptr) { - LOG(WARNING) << __func__ - << ": Advertising monitor not supported by BlueZ"; + if (!WaitForAdvertisementMonitorManager()) { // TODO: Implement manual monitoring. return false; } @@ -314,7 +355,7 @@ bool BleV2Medium::StopScanning() { return false; } - if (adv_monitor_manager_ == nullptr) { + if (!WaitForAdvertisementMonitorManager()) { // TODO: Implement manual monitoring. return false; } @@ -349,7 +390,7 @@ bool BleV2Medium::StopScanning() { BleV2Medium::StartScanning(const Uuid &service_uuid, api::ble::TxPowerLevel tx_power_level, ScanningCallback callback) { - if (adv_monitor_manager_ == nullptr) { + if (!WaitForAdvertisementMonitorManager()) { // TODO: Implement manual monitoring. return nullptr; } diff --git a/internal/platform/implementation/linux/ble_v2_medium.h b/internal/platform/implementation/linux/ble_v2_medium.h index e9c3baea..686013ee 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.h +++ b/internal/platform/implementation/linux/ble_v2_medium.h @@ -17,8 +17,11 @@ #include #include +#include #include +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/notification.h" #include #include "absl/base/attributes.h" @@ -118,6 +121,8 @@ class BleV2Medium final : public api::ble::BleMedium { private: bool StartLEDiscovery(); + bool WaitForAdvertisementMonitorManager(); + void OnRegisterMonitorReply(std::optional error); bool MonitorManagerSupportsOr() { std::vector supported_types; @@ -148,6 +153,14 @@ class BleV2Medium final : public api::ble::BleMedium { std::unique_ptr root_object_manager_; std::unique_ptr adv_monitor_manager_; + absl::Notification adv_monitor_manager_ready_notification_; + absl::Mutex adv_monitor_manager_ready_mutex_; + bool adv_monitor_manager_ready_ ABSL_GUARDED_BY(adv_monitor_manager_ready_mutex_) = + false; + std::string adv_monitor_manager_error_name_ + ABSL_GUARDED_BY(adv_monitor_manager_ready_mutex_); + std::string adv_monitor_manager_error_message_ + ABSL_GUARDED_BY(adv_monitor_manager_ready_mutex_); absl::Mutex active_adv_monitors_mutex_; absl::flat_hash_map< Uuid, diff --git a/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h b/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h index 5c285fbb..3db1e3c0 100644 --- a/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h +++ b/internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h @@ -15,9 +15,13 @@ #ifndef PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_ #define PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_ +#include + #include #include +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluez.h" #include "internal/platform/implementation/linux/dbus.h" @@ -29,6 +33,9 @@ namespace bluez { class AdvertisementMonitorManager final : public sdbus::ProxyInterfaces< org::bluez::AdvertisementMonitorManager1_proxy> { + public: + using ReplyCallback = absl::AnyInvocable)>; + private: friend std::unique_ptr std::make_unique( @@ -50,6 +57,16 @@ class AdvertisementMonitorManager final delete; ~AdvertisementMonitorManager() { unregisterProxy(); } + void SetRegisterMonitorReplyCallback(ReplyCallback callback) { + absl::MutexLock lock(&callbacks_mutex_); + register_monitor_reply_callback_ = std::move(callback); + } + + void SetUnregisterMonitorReplyCallback(ReplyCallback callback) { + absl::MutexLock lock(&callbacks_mutex_); + unregister_monitor_reply_callback_ = std::move(callback); + } + static std::unique_ptr DiscoverAdvertisementMonitorManager( sdbus::IConnection &system_bus, @@ -82,6 +99,38 @@ class AdvertisementMonitorManager final return std::make_unique(system_bus, adapter); } + + protected: + void onRegisterMonitorReply(std::optional error) override { + std::optional callback; + { + absl::MutexLock lock(&callbacks_mutex_); + callback = std::move(register_monitor_reply_callback_); + register_monitor_reply_callback_.reset(); + } + if (callback.has_value()) { + (*callback)(std::move(error)); + } + } + + void onUnregisterMonitorReply(std::optional error) override { + std::optional callback; + { + absl::MutexLock lock(&callbacks_mutex_); + callback = std::move(unregister_monitor_reply_callback_); + unregister_monitor_reply_callback_.reset(); + } + if (callback.has_value()) { + (*callback)(std::move(error)); + } + } + + private: + absl::Mutex callbacks_mutex_; + std::optional register_monitor_reply_callback_ + ABSL_GUARDED_BY(callbacks_mutex_); + std::optional unregister_monitor_reply_callback_ + ABSL_GUARDED_BY(callbacks_mutex_); }; } // namespace bluez } // namespace linux diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h index 69821277..8f2db261 100644 --- a/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h +++ b/internal/platform/implementation/linux/generated/dbus/bluez/advertisement_monitor_manager_client.h @@ -3,8 +3,8 @@ * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! */ -#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_advertisement_monitor_manager_client_v2_h__proxy__H__ -#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_advertisement_monitor_manager_client_v2_h__proxy__H__ +#ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_advertisement_monitor_manager_client_h__proxy__H__ +#define __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_advertisement_monitor_manager_client_h__proxy__H__ #include #include @@ -35,15 +35,18 @@ protected: { } + virtual void onRegisterMonitorReply(std::optional error) = 0; + virtual void onUnregisterMonitorReply(std::optional error) = 0; + public: - void RegisterMonitor(const sdbus::ObjectPath& application) + sdbus::PendingAsyncCall RegisterMonitor(const sdbus::ObjectPath& application) { - m_proxy.callMethod("RegisterMonitor").onInterface(INTERFACE_NAME).withArguments(application); + return m_proxy.callMethodAsync("RegisterMonitor").onInterface(INTERFACE_NAME).withArguments(application).uponReplyInvoke([this](std::optional error){ this->onRegisterMonitorReply(std::move(error)); }); } - void UnregisterMonitor(const sdbus::ObjectPath& application) + sdbus::PendingAsyncCall UnregisterMonitor(const sdbus::ObjectPath& application) { - m_proxy.callMethod("UnregisterMonitor").onInterface(INTERFACE_NAME).withArguments(application); + return m_proxy.callMethodAsync("UnregisterMonitor").onInterface(INTERFACE_NAME).withArguments(application).uponReplyInvoke([this](std::optional error){ this->onUnregisterMonitorReply(std::move(error)); }); } public: diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml index 5b9f2e4c..135cff46 100644 --- a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AdvertisementMonitorManager1.xml @@ -3,9 +3,11 @@ + + From 043411700815d8c8871b427c7b968a098fef8607 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 00:54:04 +0530 Subject: [PATCH 02/26] disabled gatt server + client on linux --- .../platform/implementation/linux/ble_v2_medium.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc index 3d3c529d..b936dd73 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.cc +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -34,8 +34,8 @@ #include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "absl/types/span.h" #include "absl/time/time.h" -#include "ble_gatt_client.h" -#include "ble_gatt_server.h" +//#include "ble_gatt_client.h" +//#include "ble_gatt_server.h" #include "ble_l2cap_server_socket.h" #include "ble_l2cap_socket.h" #include "internal/base/observer_list.h" @@ -479,11 +479,11 @@ bool BleV2Medium::StopScanning() { std::unique_ptr BleV2Medium::StartGattServer( api::ble::ServerGattConnectionCallback callback) { // (void)callback; - // return nullptr; + return nullptr; - return std::make_unique( - *system_bus_, adapter_, devices_,std::move(callback) - ); + //return std::make_unique( + //*system_bus_, adapter_, devices_,std::move(callback) + //); } std::unique_ptr BleV2Medium::ConnectToGattServer( From 59aaf997092e26b6265542861d1806eafb57f24b Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 01:12:51 +0530 Subject: [PATCH 03/26] enabled ble for transfer by default in linux sharing platform --- sharing/linux/nearby_sharing_api.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sharing/linux/nearby_sharing_api.cc b/sharing/linux/nearby_sharing_api.cc index 2eb37bb3..f20b7277 100644 --- a/sharing/linux/nearby_sharing_api.cc +++ b/sharing/linux/nearby_sharing_api.cc @@ -20,6 +20,7 @@ #include "sharing/analytics/analytics_recorder.h" #include "sharing/attachment_container.h" #include "sharing/file_attachment.h" +#include "sharing/flags/generated/nearby_sharing_feature_flags.h" #include "sharing/linux/platform/linux_sharing_platform.h" #include "sharing/local_device_data/nearby_share_local_device_data_manager.h" #include "sharing/nearby_sharing_service_factory.h" @@ -39,6 +40,10 @@ void EnableBleL2capDefaults() { nearby::connections::config_package_nearby::nearby_connections_feature:: kEnableBleL2cap, true); + nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( + nearby::sharing::config_package_nearby::nearby_sharing_feature:: + kEnableBleForTransfer, + true); //nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( // nearby::connections::config_package_nearby::nearby_connections_feature:: // kRefactorBleL2cap, From 5f4c039e01e8f068da6af502302252947c35d08e Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 08:35:22 +0530 Subject: [PATCH 04/26] refractored file_share_tray_controller. removed linux namespace definition --- sharing/linux/nearby_sharing_api.cc | 4 +- sharing/linux/nearby_sharing_api.h | 8 - .../linux/platform/linux_sharing_platform.cc | 2 +- .../linux/platform/linux_sharing_platform.h | 2 +- sharing/linux/qml_tray_app/CMakeLists.txt | 10 + sharing/linux/qml_tray_app/FileShareTray.qml | 7 +- .../cmake/BuildNearbySoIfMissing.cmake | 2 +- .../linux/qml_tray_app/file_share_state.cc | 156 ++++ sharing/linux/qml_tray_app/file_share_state.h | 118 +++ .../file_share_tray_controller.cc | 850 +++++++----------- .../qml_tray_app/file_share_tray_controller.h | 116 +-- .../linux/qml_tray_app/qr_code_generator.cc | 48 + .../linux/qml_tray_app/qr_code_generator.h | 18 + .../linux/qml_tray_app/share_target_model.h | 30 + sharing/linux/qml_tray_app/status_mapper.cc | 44 + sharing/linux/qml_tray_app/status_mapper.h | 21 + sharing/linux/qml_tray_app/string_utils.cc | 18 + sharing/linux/qml_tray_app/string_utils.h | 17 + sharing/linux/qml_tray_app/transfer_model.h | 50 ++ 19 files changed, 892 insertions(+), 629 deletions(-) create mode 100644 sharing/linux/qml_tray_app/file_share_state.cc create mode 100644 sharing/linux/qml_tray_app/file_share_state.h create mode 100644 sharing/linux/qml_tray_app/qr_code_generator.cc create mode 100644 sharing/linux/qml_tray_app/qr_code_generator.h create mode 100644 sharing/linux/qml_tray_app/share_target_model.h create mode 100644 sharing/linux/qml_tray_app/status_mapper.cc create mode 100644 sharing/linux/qml_tray_app/status_mapper.h create mode 100644 sharing/linux/qml_tray_app/string_utils.cc create mode 100644 sharing/linux/qml_tray_app/string_utils.h create mode 100644 sharing/linux/qml_tray_app/transfer_model.h diff --git a/sharing/linux/nearby_sharing_api.cc b/sharing/linux/nearby_sharing_api.cc index f20b7277..ef6acbec 100644 --- a/sharing/linux/nearby_sharing_api.cc +++ b/sharing/linux/nearby_sharing_api.cc @@ -29,7 +29,7 @@ #include "sharing/transfer_metadata.h" #include "sharing/transfer_update_callback.h" -namespace nearby::sharing::linux { +namespace nearby::sharing { namespace { @@ -662,4 +662,4 @@ std::string NearbySharingApi::TransferStatusToString(TransferStatus status) { return "Unknown"; } -} // namespace nearby::sharing::linux +} // namespace nearby::sharing diff --git a/sharing/linux/nearby_sharing_api.h b/sharing/linux/nearby_sharing_api.h index ba140f9a..22569b27 100644 --- a/sharing/linux/nearby_sharing_api.h +++ b/sharing/linux/nearby_sharing_api.h @@ -13,15 +13,8 @@ #include #include -// Some toolchains define `linux` as a macro (e.g. `#define linux 1`), which -// breaks namespace tokens like `nearby::sharing::linux`. -#ifdef linux -#undef linux -#endif - namespace nearby { namespace sharing { -namespace linux { class __attribute__((visibility("default"))) NearbySharingApi { public: @@ -131,7 +124,6 @@ class __attribute__((visibility("default"))) NearbySharingApi { std::unique_ptr impl_; }; -} // namespace linux } // namespace sharing } // namespace nearby diff --git a/sharing/linux/platform/linux_sharing_platform.cc b/sharing/linux/platform/linux_sharing_platform.cc index a11d363a..eeb76612 100644 --- a/sharing/linux/platform/linux_sharing_platform.cc +++ b/sharing/linux/platform/linux_sharing_platform.cc @@ -60,7 +60,7 @@ #include "sharing/internal/public/pref_names.h" #include "sharing/proto/rpc_resources.pb.h" -namespace nearby::sharing::linux { +namespace nearby::sharing { namespace { using Json = nlohmann::json; diff --git a/sharing/linux/platform/linux_sharing_platform.h b/sharing/linux/platform/linux_sharing_platform.h index 95f2bcd0..58e3f57a 100644 --- a/sharing/linux/platform/linux_sharing_platform.h +++ b/sharing/linux/platform/linux_sharing_platform.h @@ -15,7 +15,7 @@ #include "internal/platform/task_runner.h" #include "sharing/internal/api/sharing_platform.h" -namespace nearby::sharing::linux { +namespace nearby::sharing { class LinuxSharingPlatform final : public nearby::sharing::api::SharingPlatform { public: diff --git a/sharing/linux/qml_tray_app/CMakeLists.txt b/sharing/linux/qml_tray_app/CMakeLists.txt index 4de8a930..2f87b894 100644 --- a/sharing/linux/qml_tray_app/CMakeLists.txt +++ b/sharing/linux/qml_tray_app/CMakeLists.txt @@ -26,6 +26,16 @@ qt_add_executable(nearby_qml_file_tray_app file_share_tray_main.cpp file_share_tray_controller.cc file_share_tray_controller.h + file_share_state.cc + file_share_state.h + share_target_model.h + transfer_model.h + string_utils.cc + string_utils.h + status_mapper.cc + status_mapper.h + qr_code_generator.cc + qr_code_generator.h notification_manager.cpp notification_manager.h third_party/libqrencode/qrencode_compat.h diff --git a/sharing/linux/qml_tray_app/FileShareTray.qml b/sharing/linux/qml_tray_app/FileShareTray.qml index 4dc0c168..9ef509d9 100644 --- a/sharing/linux/qml_tray_app/FileShareTray.qml +++ b/sharing/linux/qml_tray_app/FileShareTray.qml @@ -51,18 +51,16 @@ ApplicationWindow { clip: true readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0 - readonly property bool isIdle: fileShareController.discoveredTargets.length === 0 - && fileShareController.transfers.length === 0 // ── Idle: animated blob ─────────────────────────────────── - AnimatedBlob { visible: mainContent.isIdle } + AnimatedBlob { visible: !mainContent.isSendMode } // ── Non-idle: scrollable device + transfer cards ────────── Flickable { id: mainFlickable anchors.fill: parent clip: true - visible: !mainContent.isIdle + visible: mainContent.isSendMode contentWidth: width contentHeight: mainCol.implicitHeight + 96 ScrollBar.vertical: ScrollBar {} @@ -77,7 +75,6 @@ ApplicationWindow { SendUrlPanel { Layout.alignment: Qt.AlignHCenter width: Math.max(240, Math.min(mainCol.width, 420)) - visible: mainContent.isSendMode } diff --git a/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake b/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake index 9446245d..693c780c 100644 --- a/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake +++ b/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake @@ -37,7 +37,7 @@ if(EXISTS "${_output_so}") ERROR_QUIET ) if(_nm_result EQUAL 0) - string(FIND "${_nm_output}" "nearby::sharing::linux::NearbySharingApi::NearbySharingApi()" _api_ctor_idx) + string(FIND "${_nm_output}" "nearby::sharing::NearbySharingApi::NearbySharingApi()" _api_ctor_idx) string(FIND "${_nm_output}" " U nearby::api::ImplementationPlatform::CreateScheduledExecutor()" _undef_platform_idx) string(FIND "${_nm_output}" " U nearby::SystemClock::ElapsedRealtime()" _undef_clock_idx) string(FIND "${_nm_output}" " U nearby::Crypto::Sha256(" _undef_crypto_idx) diff --git a/sharing/linux/qml_tray_app/file_share_state.cc b/sharing/linux/qml_tray_app/file_share_state.cc new file mode 100644 index 00000000..968783f2 --- /dev/null +++ b/sharing/linux/qml_tray_app/file_share_state.cc @@ -0,0 +1,156 @@ +#include "file_share_state.h" +#include "status_mapper.h" + +FileShareState::FileShareState() = default; + +void FileShareState::AddOrUpdateTarget(qlonglong id, const QString& name, + bool is_incoming) { + target_names_[id] = name; + + if (discovered_row_by_target_.contains(id)) { + const int row_index = discovered_row_by_target_.value(id); + if (row_index >= 0 && row_index < discovered_targets_.size()) { + QVariantMap target; + target[QStringLiteral("id")] = id; + target[QStringLiteral("name")] = name; + target[QStringLiteral("isIncoming")] = is_incoming; + discovered_targets_[row_index] = target; + return; + } + } + + QVariantMap target; + target[QStringLiteral("id")] = id; + target[QStringLiteral("name")] = name; + target[QStringLiteral("isIncoming")] = is_incoming; + discovered_row_by_target_[id] = discovered_targets_.size(); + discovered_targets_.append(target); +} + +void FileShareState::RemoveTarget(qlonglong id) { + if (HasActiveTransferForTarget(id)) { + AddPendingTargetRemoval(id); + return; + } + + RemovePendingTargetRemoval(id); + target_names_.remove(id); + + if (!discovered_row_by_target_.contains(id)) { + return; + } + + const int removed_index = discovered_row_by_target_.take(id); + if (removed_index < 0 || removed_index >= discovered_targets_.size()) { + return; + } + + discovered_targets_.removeAt(removed_index); + for (auto it = discovered_row_by_target_.begin(); + it != discovered_row_by_target_.end(); ++it) { + if (it.value() > removed_index) { + it.value() = it.value() - 1; + } + } +} + +QString FileShareState::GetTargetName(qlonglong id) const { + const QString name = target_names_.value(id).trimmed(); + return name.isEmpty() ? QStringLiteral("Unknown device") : name; +} + +bool FileShareState::HasTarget(qlonglong id) const { + return discovered_row_by_target_.contains(id); +} + +void FileShareState::AddOrUpdateTransfer( + qlonglong target_id, const QString& target_name, const QString& status, + double progress, qulonglong transferred_bytes, const QString& direction, + const QString& file_name) { + QVariantMap transfer{ + {QStringLiteral("targetId"), target_id}, + {QStringLiteral("targetName"), target_name}, + {QStringLiteral("status"), status}, + {QStringLiteral("progress"), progress}, + {QStringLiteral("transferredBytes"), transferred_bytes}, + {QStringLiteral("direction"), direction}, + {QStringLiteral("fileName"), file_name}, + }; + + if (transfer_row_by_target_.contains(target_id)) { + const int row_index = transfer_row_by_target_.value(target_id); + if (row_index >= 0 && row_index < transfers_.size()) { + transfers_[row_index] = transfer; + return; + } + } + + transfer_row_by_target_.insert(target_id, transfers_.size()); + transfers_.append(transfer); +} + +void FileShareState::RemoveTransfer(qlonglong target_id) { + if (!transfer_row_by_target_.contains(target_id)) { + return; + } + + const int removed_index = transfer_row_by_target_.take(target_id); + if (removed_index < 0 || removed_index >= transfers_.size()) { + return; + } + + transfers_.removeAt(removed_index); + for (auto it = transfer_row_by_target_.begin(); + it != transfer_row_by_target_.end(); ++it) { + if (it.value() > removed_index) { + it.value() = it.value() - 1; + } + } +} + +bool FileShareState::HasActiveTransferForTarget(qlonglong target_id) const { + for (const QVariant& row_value : transfers_) { + const QVariantMap row = row_value.toMap(); + if (row.value(QStringLiteral("targetId")).toLongLong() != target_id) { + continue; + } + + const QString status = row.value(QStringLiteral("status")).toString(); + if (StatusMapper::IsActiveTransferStatus(status)) { + return true; + } + } + return false; +} + +bool FileShareState::HasActiveTransfers() const { + for (const QVariant& row_value : transfers_) { + const QVariantMap row = row_value.toMap(); + const QString status = row.value(QStringLiteral("status")).toString(); + if (StatusMapper::IsActiveTransferStatus(status)) { + return true; + } + } + return false; +} + +void FileShareState::AddPendingTargetRemoval(qlonglong id) { + pending_target_removals_.insert(id); +} + +void FileShareState::RemovePendingTargetRemoval(qlonglong id) { + pending_target_removals_.remove(id); +} + +bool FileShareState::IsPendingTargetRemoval(qlonglong id) const { + return pending_target_removals_.contains(id); +} + +void FileShareState::ClearAll() { + discovered_targets_.clear(); + discovered_row_by_target_.clear(); + target_names_.clear(); + transfers_.clear(); + transfer_row_by_target_.clear(); + pending_target_removals_.clear(); +} diff --git a/sharing/linux/qml_tray_app/file_share_state.h b/sharing/linux/qml_tray_app/file_share_state.h new file mode 100644 index 00000000..05eaea5c --- /dev/null +++ b/sharing/linux/qml_tray_app/file_share_state.h @@ -0,0 +1,118 @@ +#ifndef SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_ +#define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_ + +#include +#include +#include +#include +#include +#include +#include "share_target_model.h" +#include "transfer_model.h" + +class FileShareState { + public: + FileShareState(); + + // Getters + QString mode() const { return mode_; } + QString deviceName() const { return device_name_; } + QString statusMessage() const { return status_message_; } + bool running() const { return running_; } + bool autoAcceptIncoming() const { return auto_accept_incoming_; } + + QString pendingSendFileName() const { return pending_send_file_name_; } + QString pendingSendFilePath() const { return pending_send_file_path_; } + qlonglong pendingSendTargetId() const { return pending_send_target_id_; } + + QVariantList discoveredTargets() const { return discovered_targets_; } + QVariantList transfers() const { return transfers_; } + + QString qrCodeUrl() const { return qr_code_url_; } + QStringList qrCodeRows() const { return qr_code_rows_; } + int qrCodeSize() const { return qr_code_size_; } + + QString logPath() const { return log_path_; } + + // Setters + void SetMode(const QString& mode) { mode_ = mode; } + void SetDeviceName(const QString& name) { device_name_ = name; } + void SetStatusMessage(const QString& message) { status_message_ = message; } + void SetRunning(bool running) { running_ = running; } + void SetAutoAcceptIncoming(bool enabled) { auto_accept_incoming_ = enabled; } + + void SetPendingSendFile(const QString& file_path, const QString& file_name, + qlonglong target_id) { + pending_send_file_path_ = file_path; + pending_send_file_name_ = file_name; + pending_send_target_id_ = target_id; + } + + void ClearPendingSendFile() { + pending_send_file_path_.clear(); + pending_send_file_name_.clear(); + pending_send_target_id_ = 0; + } + + void SetQrCodeData(const QString& url, const QStringList& rows, int size) { + qr_code_url_ = url; + qr_code_rows_ = rows; + qr_code_size_ = size; + } + + void SetLogPath(const QString& path) { log_path_ = path; } + + // Target management + void AddOrUpdateTarget(qlonglong id, const QString& name, bool is_incoming); + void RemoveTarget(qlonglong id); + QString GetTargetName(qlonglong id) const; + bool HasTarget(qlonglong id) const; + + // Transfer management + void AddOrUpdateTransfer(qlonglong target_id, const QString& target_name, + const QString& status, double progress, + qulonglong transferred_bytes, + const QString& direction, const QString& file_name); + void RemoveTransfer(qlonglong target_id); + bool HasActiveTransferForTarget(qlonglong target_id) const; + bool HasActiveTransfers() const; + + // Pending target removal management + void AddPendingTargetRemoval(qlonglong id); + void RemovePendingTargetRemoval(qlonglong id); + bool IsPendingTargetRemoval(qlonglong id) const; + + void ClearAll(); + + private: + QString mode_ = QStringLiteral("Receive"); + QString device_name_ = QStringLiteral("NearbyLinux"); + QString status_message_ = QStringLiteral("Idle"); + bool running_ = false; + bool auto_accept_incoming_ = true; + + // QR Code + QString qr_code_url_; + QStringList qr_code_rows_; + int qr_code_size_ = 0; + QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log"); + + // Pending send state + QString pending_send_file_path_; + QString pending_send_file_name_; + qlonglong pending_send_target_id_ = 0; + + // Discovered targets + QVariantList discovered_targets_; + QHash discovered_row_by_target_; + QHash target_names_; + + // Transfers + QVariantList transfers_; + QHash transfer_row_by_target_; + + // Pending removals + QSet pending_target_removals_; +}; + +#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_ diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.cc b/sharing/linux/qml_tray_app/file_share_tray_controller.cc index f75c76b5..bdb35d16 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.cc +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.cc @@ -1,6 +1,5 @@ #include "file_share_tray_controller.h" -#include #include #include #include @@ -9,66 +8,20 @@ #include #include #include -#include -#include "third_party/libqrencode/qrencode_compat.h" - -namespace { - -QString TrimmedOrFallback(const QString& value, const QString& fallback) { - const QString trimmed = value.trimmed(); - return trimmed.isEmpty() ? fallback : trimmed; -} - -QString TrimmedStdString(const std::string& value) { - return QString::fromStdString(value).trimmed(); -} - -QString FirstReceivedUrl(const NearbySharingApi::TransferUpdateInfo& update) { - for (const NearbySharingApi::TextAttachmentInfo& text : - update.text_attachments) { - if (text.type != NearbySharingApi::TextAttachmentType::kUrl) { - continue; - } - - const QString link = TrimmedStdString(text.text_body); - if (!link.isEmpty()) { - return link; - } - } - - return {}; -} - -QString FirstReceivedTextSummary( - const NearbySharingApi::TransferUpdateInfo& update) { - for (const NearbySharingApi::TextAttachmentInfo& text : - update.text_attachments) { - const QString title = TrimmedStdString(text.text_title); - if (!title.isEmpty()) { - return title; - } - - const QString body = TrimmedStdString(text.text_body); - if (!body.isEmpty()) { - return body; - } - } - - return {}; -} - -} // namespace +#include "string_utils.h" +#include "status_mapper.h" +#include "qr_code_generator.h" FileShareTrayController::FileShareTrayController(QObject* parent) : QObject(parent) { const QString host = QSysInfo::machineHostName().trimmed(); if (!host.isEmpty()) { - device_name_ = host; + state_.SetDeviceName(host); } - LoadSettings(); - CreateService(); + loadSettings(); + initializeService(); } FileShareTrayController::~FileShareTrayController() { @@ -78,293 +31,272 @@ FileShareTrayController::~FileShareTrayController() { } } -void FileShareTrayController::CreateService() { - service_ = std::make_unique(device_name_.toStdString()); - qr_code_url_ = QString::fromStdString(service_->GetQrCodeUrl()); - UpdateQrCodeData(); +void FileShareTrayController::initializeService() { + service_ = std::make_unique(state_.deviceName().toStdString()); + state_.SetQrCodeData(QString::fromStdString(service_->GetQrCodeUrl()), {}, 0); + updateQrCodeData(); emit qrCodeUrlChanged(); emit qrCodeChanged(); - AttachServiceListeners(); + attachServiceListeners(); } -void FileShareTrayController::UpdateQrCodeData() { - qr_code_rows_.clear(); - qr_code_size_ = 0; - - const QByteArray encoded_url = qr_code_url_.trimmed().toUtf8(); - if (encoded_url.isEmpty()) { - return; - } - - QRcode* qr_code = QRcode_encodeData( - encoded_url.size(), - reinterpret_cast(encoded_url.constData()), 0, - QR_ECLEVEL_M); - if (qr_code == nullptr || qr_code->data == nullptr || qr_code->width <= 0) { - if (qr_code != nullptr) { - QRcode_free(qr_code); - } - return; - } - - qr_code_size_ = qr_code->width; - qr_code_rows_.reserve(qr_code_size_); - - for (int row = 0; row < qr_code_size_; ++row) { - QString row_data; - row_data.reserve(qr_code_size_); - - for (int col = 0; col < qr_code_size_; ++col) { - const unsigned char module = - qr_code->data[row * qr_code_size_ + col] & 0x1; - row_data.append(module ? QLatin1Char('1') : QLatin1Char('0')); - } - - qr_code_rows_.append(row_data); - } - - QRcode_free(qr_code); +void FileShareTrayController::updateQrCodeData() { + const auto qr_data = QrCodeGenerator::GenerateQrCode(state_.qrCodeUrl()); + state_.SetQrCodeData(state_.qrCodeUrl(), qr_data.rows, qr_data.size); + emit qrCodeChanged(); } -void FileShareTrayController::AttachServiceListeners() { +void FileShareTrayController::attachServiceListeners() { NearbySharingApi::Listener listener; listener.target_discovered_cb = [this](const NearbySharingApi::ShareTargetInfo& info) { - QMetaObject::invokeMethod( - this, - [this, info]() { - const QString name = TrimmedOrFallback( - QString::fromStdString(info.device_name), - QStringLiteral("Unknown device")); - UpsertTarget(info.id, name, info.is_incoming); - }, - Qt::QueuedConnection); + QMetaObject::invokeMethod(this, [this, info]() { updateTargetFromInfo(info); }, + Qt::QueuedConnection); }; listener.target_updated_cb = [this](const NearbySharingApi::ShareTargetInfo& info) { - QMetaObject::invokeMethod( - this, - [this, info]() { - const QString name = TrimmedOrFallback( - QString::fromStdString(info.device_name), - QStringLiteral("Unknown device")); - UpsertTarget(info.id, name, info.is_incoming); - }, - Qt::QueuedConnection); + QMetaObject::invokeMethod(this, [this, info]() { updateTargetFromInfo(info); }, + Qt::QueuedConnection); }; listener.target_lost_cb = [this](int64_t share_target_id) { QMetaObject::invokeMethod( - this, - [this, share_target_id]() { - RemoveTarget(share_target_id); + this, [this, share_target_id]() { + state_.RemoveTarget(share_target_id); + emit discoveredTargetsChanged(); }, Qt::QueuedConnection); }; - listener.transfer_update_cb = - [this](const NearbySharingApi::TransferUpdateInfo& update) { - QMetaObject::invokeMethod( - this, - [this, update]() { - const QString update_name = - QString::fromStdString(update.device_name).trimmed(); - if (!update_name.isEmpty()) { - target_names_[update.share_target_id] = update_name; - } - const QString name = TargetName(update.share_target_id); - const QString status = TransferStatusToString(update.status); - const QString direction = - update.is_incoming ? QStringLiteral("incoming") - : QStringLiteral("outgoing"); - QString file_name = QString::fromStdString(update.first_file_name); - if (file_name.isEmpty() && !update.is_incoming && - pending_send_target_id_ == update.share_target_id && - !pending_send_file_name_.isEmpty()) { - file_name = pending_send_file_name_; - } - - UpsertTransfer(update.share_target_id, name, status, update.progress, - update.transferred_bytes, direction, file_name); - SetStatus(QStringLiteral("%1 (%2)") - .arg(status) - .arg(name)); - - if (update.status == - NearbySharingApi::TransferStatus::kAwaitingLocalConfirmation) { - if (auto_accept_incoming_) { - service_->Accept( - update.share_target_id, [](NearbySharingApi::StatusCode) {}); - } - } - - if (!IsFinalTransferStatus(update.status)) { - return; - } - - const bool success = - update.status == NearbySharingApi::TransferStatus::kComplete; - - if (update.is_incoming) { - if (success) { - const QString received_link = FirstReceivedUrl(update); - if (!received_link.isEmpty()) { - emit requestCopyLinkTrayMessage( - QStringLiteral("Link received"), - QStringLiteral("%1 from %2") - .arg(received_link, name), - received_link); - } else if (!update.text_attachments.empty()) { - const QString received_text = - TrimmedOrFallback(FirstReceivedTextSummary(update), - QStringLiteral("Text")); - emit requestTrayMessage( - QStringLiteral("Text received"), - QStringLiteral("%1 from %2") - .arg(received_text, name)); - } else { - const QString received_name = - file_name.isEmpty() ? QStringLiteral("file") - : file_name; - emit requestTrayMessage( - QStringLiteral("File received"), - QStringLiteral("%1 from %2").arg(received_name, name)); - } - } else { - emit requestTrayMessage( - QStringLiteral("Receive failed"), - QStringLiteral("Transfer from %1 failed (%2)") - .arg(name, status)); - } - return; - } - - const QString sent_name = file_name.isEmpty() - ? QStringLiteral("file") - : file_name; - if (success) { - emit requestTrayMessage(QStringLiteral("Send complete"), - QStringLiteral("%1 sent to %2") - .arg(sent_name, name)); - } else { - emit requestTrayMessage( - QStringLiteral("Send failed"), - QStringLiteral("%1 failed to send to %2") - .arg(sent_name, name)); - } - - if (pending_send_target_id_ == update.share_target_id) { - pending_send_target_id_ = 0; - if (!pending_send_file_path_.isEmpty()) { - pending_send_file_path_.clear(); - emit pendingSendFilePathChanged(); - } - if (!pending_send_file_name_.isEmpty()) { - pending_send_file_name_.clear(); - emit pendingSendFileNameChanged(); - } - } - - if (pending_target_removals_.contains(update.share_target_id)) { - QTimer::singleShot(1400, this, [this, target_id = update.share_target_id]() { - if (!pending_target_removals_.contains(target_id)) { - return; - } - pending_target_removals_.remove(target_id); - RemoveTarget(target_id); - }); - } - }, - Qt::QueuedConnection); - }; + listener.transfer_update_cb = [this](const NearbySharingApi::TransferUpdateInfo& update) { + QMetaObject::invokeMethod(this, [this, update]() { handleTransferUpdate(update); }, + Qt::QueuedConnection); + }; service_->SetListener(std::move(listener)); } -void FileShareTrayController::LoadSettings() { - QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); +void FileShareTrayController::updateTargetFromInfo( + const NearbySharingApi::ShareTargetInfo& info) { + const QString name = StringUtils::TrimmedOrFallback( + StringUtils::FromStdString(info.device_name), + QStringLiteral("Unknown device")); + state_.AddOrUpdateTarget(info.id, name, info.is_incoming); + emit discoveredTargetsChanged(); +} - const QString stored_device_name = - settings.value(QStringLiteral("deviceName"), device_name_) - .toString() - .trimmed(); - if (!stored_device_name.isEmpty()) { - device_name_ = stored_device_name; +void FileShareTrayController::handleTransferUpdate( + const NearbySharingApi::TransferUpdateInfo& update) { + const QString target_name = StringUtils::TrimmedFromStdString(update.device_name); + if (!target_name.isEmpty()) { + state_.AddOrUpdateTarget(update.share_target_id, target_name, update.is_incoming); } - auto_accept_incoming_ = - settings.value(QStringLiteral("autoAcceptIncoming"), auto_accept_incoming_) - .toBool(); + const QString name = state_.GetTargetName(update.share_target_id); + const QString status = StatusMapper::TransferStatusToString(update.status); + const QString direction = + update.is_incoming ? QStringLiteral("incoming") : QStringLiteral("outgoing"); - const QString stored_log_path = - settings.value(QStringLiteral("logPath"), log_path_).toString().trimmed(); - if (!stored_log_path.isEmpty()) { - log_path_ = stored_log_path; + QString file_name = StringUtils::FromStdString(update.first_file_name); + if (file_name.isEmpty() && !update.is_incoming && + state_.pendingSendTargetId() == update.share_target_id && + !state_.pendingSendFileName().isEmpty()) { + file_name = state_.pendingSendFileName(); + } + + state_.AddOrUpdateTransfer(update.share_target_id, name, status, update.progress, + update.transferred_bytes, direction, file_name); + emit transfersChanged(); + + setStatus(QStringLiteral("%1 (%2)").arg(status, name)); + + // Auto-accept incoming transfers if enabled + if (update.status == NearbySharingApi::TransferStatus::kAwaitingLocalConfirmation && + state_.autoAcceptIncoming()) { + service_->Accept(update.share_target_id, [](NearbySharingApi::StatusCode) {}); + } + + // Handle final transfer status + if (StatusMapper::IsFinalTransferStatus(update.status)) { + handleTransferComplete(update); } } -void FileShareTrayController::SaveSettings() const { +void FileShareTrayController::handleTransferComplete( + const NearbySharingApi::TransferUpdateInfo& update) { + const bool success = update.status == NearbySharingApi::TransferStatus::kComplete; + const QString name = state_.GetTargetName(update.share_target_id); + + if (update.is_incoming) { + handleIncomingTransferComplete(update, name, success); + } else { + handleOutgoingTransferComplete(update, name, success); + } + + // Cleanup pending send state + if (state_.pendingSendTargetId() == update.share_target_id) { + state_.ClearPendingSendFile(); + emit pendingSendFilePathChanged(); + emit pendingSendFileNameChanged(); + + // Auto-switch to receive mode after successful send + if (!update.is_incoming && success) { + switchToReceiveMode(); + } + } + + // Deferred target removal + if (state_.IsPendingTargetRemoval(update.share_target_id)) { + QTimer::singleShot(1400, this, [this, target_id = update.share_target_id]() { + if (state_.IsPendingTargetRemoval(target_id)) { + state_.RemoveTarget(target_id); + emit discoveredTargetsChanged(); + } + }); + } +} + +void FileShareTrayController::handleIncomingTransferComplete( + const NearbySharingApi::TransferUpdateInfo& update, const QString& name, + bool success) { + if (!success) { + emit requestTrayMessage( + QStringLiteral("Receive failed"), + QStringLiteral("Transfer from %1 failed").arg(name)); + return; + } + + const QString file_name = + StringUtils::FromStdString(update.first_file_name).isEmpty() + ? QStringLiteral("file") + : StringUtils::FromStdString(update.first_file_name); + + // Check for received URL + for (const auto& text : update.text_attachments) { + if (text.type == NearbySharingApi::TextAttachmentType::kUrl) { + const QString link = StringUtils::TrimmedFromStdString(text.text_body); + if (!link.isEmpty()) { + emit requestCopyLinkTrayMessage(QStringLiteral("Link received"), + QStringLiteral("%1 from %2").arg(link, name), + link); + return; + } + } + } + + // Check for received text + if (!update.text_attachments.empty()) { + const QString text_summary = [&]() { + for (const auto& text : update.text_attachments) { + const QString title = StringUtils::TrimmedFromStdString(text.text_title); + if (!title.isEmpty()) return title; + const QString body = StringUtils::TrimmedFromStdString(text.text_body); + if (!body.isEmpty()) return body; + } + return QStringLiteral("Text"); + }(); + emit requestTrayMessage(QStringLiteral("Text received"), + QStringLiteral("%1 from %2").arg(text_summary, name)); + return; + } + + emit requestTrayMessage(QStringLiteral("File received"), + QStringLiteral("%1 from %2").arg(file_name, name)); +} + +void FileShareTrayController::handleOutgoingTransferComplete( + const NearbySharingApi::TransferUpdateInfo& update, const QString& name, + bool success) { + const QString file_name = + StringUtils::FromStdString(update.first_file_name).isEmpty() + ? QStringLiteral("file") + : StringUtils::FromStdString(update.first_file_name); + + if (success) { + emit requestTrayMessage(QStringLiteral("Send complete"), + QStringLiteral("%1 sent to %2").arg(file_name, name)); + } else { + emit requestTrayMessage(QStringLiteral("Send failed"), + QStringLiteral("%1 failed to send to %2").arg(file_name, name)); + } +} + +void FileShareTrayController::loadSettings() { QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); - settings.setValue(QStringLiteral("deviceName"), device_name_); - settings.setValue(QStringLiteral("autoAcceptIncoming"), auto_accept_incoming_); - settings.setValue(QStringLiteral("logPath"), log_path_); - settings.sync(); + + const QString stored_device_name = + settings.value(QStringLiteral("deviceName"), state_.deviceName()) + .toString() + .trimmed(); + if (!stored_device_name.isEmpty()) { + state_.SetDeviceName(stored_device_name); + } + + const bool stored_auto_accept = + settings.value(QStringLiteral("autoAcceptIncoming"), true).toBool(); + state_.SetAutoAcceptIncoming(stored_auto_accept); + + const QString stored_log_path = + settings.value(QStringLiteral("logPath"), QStringLiteral("/tmp/nearby_qml_file_tray.log")) + .toString() + .trimmed(); + if (!stored_log_path.isEmpty()) { + state_.SetLogPath(stored_log_path); + } +} + +void FileShareTrayController::saveSettings() const { + QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); + settings.setValue(QStringLiteral("deviceName"), state_.deviceName()); + settings.setValue(QStringLiteral("autoAcceptIncoming"), state_.autoAcceptIncoming()); + settings.setValue(QStringLiteral("logPath"), state_.logPath()); } void FileShareTrayController::setDeviceName(const QString& device_name) { const QString trimmed = device_name.trimmed(); - if (trimmed.isEmpty() || trimmed == device_name_) { + if (trimmed.isEmpty() || trimmed == state_.deviceName()) { return; } - const bool was_running = running_; - if (was_running) { - stop(); - } - - device_name_ = trimmed; + state_.SetDeviceName(trimmed); + saveSettings(); emit deviceNameChanged(); - SaveSettings(); - if (service_) { - //service_->SetDeviceName(device_name_.toStdString()); - //qr_code_url_ = QString::fromStdString(service_->GetQrCodeUrl()); - //UpdateQrCodeData(); - //emit qrCodeUrlChanged(); - //emit qrCodeChanged(); - service_->Shutdown([](NearbySharingApi::StatusCode) {}); - service_.reset(); - } - CreateService(); - if (was_running) { + if (state_.running()) { + stop(); + initializeService(); start(); } } void FileShareTrayController::setAutoAcceptIncoming(bool enabled) { - if (auto_accept_incoming_ == enabled) return; - auto_accept_incoming_ = enabled; + if (enabled == state_.autoAcceptIncoming()) { + return; + } + state_.SetAutoAcceptIncoming(enabled); + saveSettings(); emit autoAcceptIncomingChanged(); - SaveSettings(); } void FileShareTrayController::setLogPath(const QString& path) { const QString trimmed = path.trimmed(); - if (trimmed.isEmpty() || trimmed == log_path_) return; - log_path_ = trimmed; + if (trimmed.isEmpty() || trimmed == state_.logPath()) { + return; + } + state_.SetLogPath(trimmed); + saveSettings(); emit logPathChanged(); - SaveSettings(); } void FileShareTrayController::start() { - if (running_) { + if (state_.running()) { return; } - running_ = true; + state_.SetRunning(true); emit runningChanged(); - if (mode_ == QStringLiteral("Send")) { + if (state_.mode() == QStringLiteral("Send")) { startSendMode(); } else { startReceiveMode(); @@ -372,50 +304,85 @@ void FileShareTrayController::start() { } void FileShareTrayController::stop() { - if (!running_) { + if (!state_.running()) { return; } - running_ = false; + state_.SetRunning(false); emit runningChanged(); service_->StopSendMode([](NearbySharingApi::StatusCode) {}); service_->StopReceiveMode([](NearbySharingApi::StatusCode) {}); - discovered_targets_.clear(); - discovered_row_by_target_.clear(); - pending_target_removals_.clear(); - target_names_.clear(); - transfers_.clear(); - transfer_row_by_target_.clear(); - pending_send_target_id_ = 0; - + state_.ClearAll(); emit discoveredTargetsChanged(); emit transfersChanged(); - SetStatus(QStringLiteral("Stopped")); + setStatus(QStringLiteral("Stopped")); +} + +void FileShareTrayController::startSendMode() { + service_->StopReceiveMode([this](NearbySharingApi::StatusCode status) { + if (status == NearbySharingApi::StatusCode::kOk || + status == NearbySharingApi::StatusCode::kStatusAlreadyStopped) { + service_->StartSendMode([this](NearbySharingApi::StatusCode status) { + QMetaObject::invokeMethod( + this, + [this, status]() { + setStatus(QStringLiteral("StartSendMode: %1") + .arg(StatusMapper::ApiStatusToString(status))); + if (status != NearbySharingApi::StatusCode::kOk) { + state_.SetRunning(false); + emit runningChanged(); + } + }, + Qt::QueuedConnection); + }); + } + }); +} + +void FileShareTrayController::startReceiveMode() { + service_->StopSendMode([this](NearbySharingApi::StatusCode status) { + if (status == NearbySharingApi::StatusCode::kOk || + status == NearbySharingApi::StatusCode::kStatusAlreadyStopped) { + service_->StartReceiveMode([this](NearbySharingApi::StatusCode status) { + QMetaObject::invokeMethod( + this, + [this, status]() { + setStatus(QStringLiteral("StartReceiveMode: %1") + .arg(StatusMapper::ApiStatusToString(status))); + if (status != NearbySharingApi::StatusCode::kOk) { + state_.SetRunning(false); + emit runningChanged(); + } + }, + Qt::QueuedConnection); + }); + } + }); } void FileShareTrayController::switchToReceiveMode() { - if (running_ && HasActiveTransfers()) { - SetStatus(QStringLiteral("Cannot switch mode while transfer is active")); + if (state_.running() && state_.HasActiveTransfers()) { + setStatus(QStringLiteral("Cannot switch mode while transfer is active")); emit requestTrayMessage( QStringLiteral("Transfer in progress"), QStringLiteral("Wait for the current transfer to complete.")); return; } - if (mode_ != QStringLiteral("Receive")) { - mode_ = QStringLiteral("Receive"); + if (state_.mode() != QStringLiteral("Receive")) { + state_.SetMode(QStringLiteral("Receive")); emit modeChanged(); - if (running_) { + if (state_.running()) { stop(); start(); return; } } - if (!running_) { + if (!state_.running()) { start(); } } @@ -423,44 +390,44 @@ void FileShareTrayController::switchToReceiveMode() { void FileShareTrayController::switchToSendModeWithFile(const QString& file_path) { const QString trimmed_path = file_path.trimmed(); QFileInfo info(trimmed_path); + if (trimmed_path.isEmpty() || !info.exists() || !info.isFile()) { - SetStatus(QStringLiteral("Selected file is not valid")); + setStatus(QStringLiteral("Selected file is not valid")); emit requestTrayMessage(QStringLiteral("Send canceled"), QStringLiteral("Please choose a valid file.")); return; } - pending_send_file_path_ = info.absoluteFilePath(); - pending_send_file_name_ = info.fileName(); + state_.SetPendingSendFile(info.absoluteFilePath(), info.fileName(), 0); emit pendingSendFilePathChanged(); emit pendingSendFileNameChanged(); - if (running_ && HasActiveTransfers()) { - SetStatus(QStringLiteral("Cannot switch mode while transfer is active")); + if (state_.running() && state_.HasActiveTransfers()) { + setStatus(QStringLiteral("Cannot switch mode while transfer is active")); emit requestTrayMessage( QStringLiteral("Transfer in progress"), QStringLiteral("Wait for the current transfer to complete.")); return; } - if (mode_ != QStringLiteral("Send")) { - mode_ = QStringLiteral("Send"); + if (state_.mode() != QStringLiteral("Send")) { + state_.SetMode(QStringLiteral("Send")); emit modeChanged(); - if (running_) { + if (state_.running()) { stop(); start(); } } - if (!running_) { + if (!state_.running()) { start(); } - SetStatus(QStringLiteral("Discovery started. Choose a nearby device.")); + setStatus(QStringLiteral("Discovery started. Choose a nearby device.")); emit requestTrayMessage( QStringLiteral("Send mode"), QStringLiteral("Selected %1. Choose a nearby device to send.") - .arg(pending_send_file_name_)); + .arg(info.fileName())); } void FileShareTrayController::sendPendingFileToTarget(qlonglong share_target_id) { @@ -468,19 +435,22 @@ void FileShareTrayController::sendPendingFileToTarget(qlonglong share_target_id) return; } - QFileInfo file_info(pending_send_file_path_); - if (pending_send_file_path_.isEmpty() || !file_info.exists() || - !file_info.isFile()) { - SetStatus(QStringLiteral("Selected file is not available")); + const QString file_path = state_.pendingSendFilePath(); + QFileInfo file_info(file_path); + + if (file_path.isEmpty() || !file_info.exists() || !file_info.isFile()) { + setStatus(QStringLiteral("Selected file is not available")); emit requestTrayMessage(QStringLiteral("Send failed"), QStringLiteral("Selected file is not available.")); return; } - const QString target_name = TargetName(share_target_id); - pending_send_target_id_ = share_target_id; - UpsertTransfer(share_target_id, target_name, QStringLiteral("Queued"), 0.0, 0, - QStringLiteral("outgoing"), pending_send_file_name_); + const QString target_name = state_.GetTargetName(share_target_id); + state_.SetPendingSendFile(file_path, file_info.fileName(), share_target_id); + + state_.AddOrUpdateTransfer(share_target_id, target_name, QStringLiteral("Queued"), 0.0, 0, + QStringLiteral("outgoing"), file_info.fileName()); + emit transfersChanged(); service_->SendFile( share_target_id, file_info.absoluteFilePath().toStdString(), @@ -488,20 +458,23 @@ void FileShareTrayController::sendPendingFileToTarget(qlonglong share_target_id) QMetaObject::invokeMethod( this, [this, share_target_id, status]() { - const QString target_name = TargetName(share_target_id); + const QString target_name = state_.GetTargetName(share_target_id); if (status == NearbySharingApi::StatusCode::kOk) { - SetStatus(QStringLiteral("Sending %1 to %2") - .arg(pending_send_file_name_, target_name)); + setStatus(QStringLiteral("Sending %1 to %2") + .arg(state_.pendingSendFileName(), target_name)); return; } emit requestTrayMessage( QStringLiteral("Send failed"), QStringLiteral("Could not send to %1").arg(target_name)); - UpsertTransfer(share_target_id, target_name, QStringLiteral("Failed"), - 0.0, 0, QStringLiteral("outgoing"), - pending_send_file_name_); - pending_send_target_id_ = 0; + + state_.AddOrUpdateTransfer(share_target_id, target_name, + QStringLiteral("Failed"), 0.0, 0, + QStringLiteral("outgoing"), + state_.pendingSendFileName()); + emit transfersChanged(); + state_.SetPendingSendFile("", "", 0); }, Qt::QueuedConnection); }); @@ -521,228 +494,43 @@ void FileShareTrayController::copyTextToClipboard(const QString& text) { } clipboard->setText(trimmed, QClipboard::Clipboard); - SetStatus(QStringLiteral("Connection URL copied to clipboard")); + setStatus(QStringLiteral("Connection URL copied to clipboard")); emit requestTrayMessage(QStringLiteral("URL copied"), - QStringLiteral("Connection URL copied to clipboard.")); + QStringLiteral("Link copied to clipboard.")); } void FileShareTrayController::clearTransfers() { - transfers_.clear(); - transfer_row_by_target_.clear(); + state_.ClearAll(); + emit discoveredTargetsChanged(); emit transfersChanged(); } void FileShareTrayController::hideToTray() { - emit requestTrayMessage( - QStringLiteral("Nearby File Tray"), - QStringLiteral("App is still running in the system tray.")); + // This is handled by the main window, but can be extended here if needed } -void FileShareTrayController::startSendMode() { - service_->StopReceiveMode([this](NearbySharingApi::StatusCode status) - { - if (status == NearbySharingApi::StatusCode::kOk || status == NearbySharingApi::StatusCode::kStatusAlreadyStopped) - { - service_->StartSendMode([this](NearbySharingApi::StatusCode status) { - QMetaObject::invokeMethod( - this, - [this, status]() { - SetStatus(QStringLiteral("StartSendMode: %1").arg(StatusToString(status))); - if (status != NearbySharingApi::StatusCode::kOk) { - running_ = false; - emit runningChanged(); - } - }, - Qt::QueuedConnection); - }); - } - - }); -} - -void FileShareTrayController::startReceiveMode() { - service_->StopSendMode([this](NearbySharingApi::StatusCode status) - { - if (status == NearbySharingApi::StatusCode::kOk || status == NearbySharingApi::StatusCode::kStatusAlreadyStopped) - { - - service_->StartReceiveMode([this](NearbySharingApi::StatusCode status) { - QMetaObject::invokeMethod( - this, - [this, status]() { - SetStatus( - QStringLiteral("StartReceiveMode: %1").arg(StatusToString(status))); - if (status != NearbySharingApi::StatusCode::kOk) { - running_ = false; - emit runningChanged(); - } - }, - Qt::QueuedConnection); - }); - } - - }); -} - -void FileShareTrayController::UpsertTarget(qlonglong share_target_id, - const QString& name, - bool is_incoming) { - pending_target_removals_.remove(share_target_id); - target_names_[share_target_id] = name; - if (is_incoming) { +void FileShareTrayController::setStatus(const QString& status) { + if (status == state_.statusMessage()) { return; } - - QVariantMap row{ - {QStringLiteral("id"), share_target_id}, - {QStringLiteral("name"), name}, - }; - - if (discovered_row_by_target_.contains(share_target_id)) { - const int row_index = discovered_row_by_target_.value(share_target_id); - if (row_index >= 0 && row_index < discovered_targets_.size()) { - discovered_targets_[row_index] = row; - emit discoveredTargetsChanged(); - return; - } - } - - discovered_row_by_target_.insert(share_target_id, discovered_targets_.size()); - discovered_targets_.append(row); - emit discoveredTargetsChanged(); -} - -void FileShareTrayController::RemoveTarget(qlonglong share_target_id) { - if (HasActiveTransferForTarget(share_target_id)) { - pending_target_removals_.insert(share_target_id); - return; - } - - pending_target_removals_.remove(share_target_id); - target_names_.remove(share_target_id); - if (!discovered_row_by_target_.contains(share_target_id)) { - return; - } - - const int removed_index = discovered_row_by_target_.take(share_target_id); - if (removed_index < 0 || removed_index >= discovered_targets_.size()) { - return; - } - - discovered_targets_.removeAt(removed_index); - for (auto it = discovered_row_by_target_.begin(); - it != discovered_row_by_target_.end(); ++it) { - if (it.value() > removed_index) { - it.value() = it.value() - 1; - } - } - emit discoveredTargetsChanged(); -} - -QString FileShareTrayController::TargetName(qlonglong share_target_id) const { - const QString name = target_names_.value(share_target_id).trimmed(); - return name.isEmpty() ? QStringLiteral("Unknown device") : name; -} - -bool FileShareTrayController::HasActiveTransferForTarget( - qlonglong share_target_id) const { - for (const QVariant& row_value : transfers_) { - const QVariantMap row = row_value.toMap(); - if (row.value(QStringLiteral("targetId")).toLongLong() != share_target_id) { - continue; - } - - if (IsActiveTransferStatus(row.value(QStringLiteral("status")).toString())) { - return true; - } - } - return false; -} - -void FileShareTrayController::UpsertTransfer( - qlonglong share_target_id, const QString& target_name, const QString& status, - double progress, qulonglong transferred_bytes, const QString& direction, - const QString& file_name) { - QVariantMap transfer{ - {QStringLiteral("targetId"), share_target_id}, - {QStringLiteral("targetName"), target_name}, - {QStringLiteral("status"), status}, - {QStringLiteral("progress"), progress}, - {QStringLiteral("transferredBytes"), transferred_bytes}, - {QStringLiteral("direction"), direction}, - {QStringLiteral("fileName"), file_name}, - }; - - if (transfer_row_by_target_.contains(share_target_id)) { - const int row_index = transfer_row_by_target_.value(share_target_id); - if (row_index >= 0 && row_index < transfers_.size()) { - transfers_[row_index] = transfer; - emit transfersChanged(); - return; - } - } - - transfer_row_by_target_.insert(share_target_id, transfers_.size()); - transfers_.append(transfer); - emit transfersChanged(); -} - -void FileShareTrayController::SetStatus(const QString& status) { - if (status == status_message_) { - return; - } - status_message_ = status; + state_.SetStatusMessage(status); emit statusMessageChanged(); } -bool FileShareTrayController::HasActiveTransfers() const { - for (const QVariant& row_value : transfers_) { - const QVariantMap row = row_value.toMap(); - const QString status = row.value(QStringLiteral("status")).toString(); - if (IsActiveTransferStatus(status)) { - return true; - } +void FileShareTrayController::notifyStateChange(const QString& property) { + if (property == QStringLiteral("mode")) { + emit modeChanged(); + } else if (property == QStringLiteral("deviceName")) { + emit deviceNameChanged(); + } else if (property == QStringLiteral("statusMessage")) { + emit statusMessageChanged(); + } else if (property == QStringLiteral("running")) { + emit runningChanged(); + } else if (property == QStringLiteral("autoAcceptIncoming")) { + emit autoAcceptIncomingChanged(); + } else if (property == QStringLiteral("discoveredTargets")) { + emit discoveredTargetsChanged(); + } else if (property == QStringLiteral("transfers")) { + emit transfersChanged(); } - return false; -} - -bool FileShareTrayController::IsActiveTransferStatus(const QString& status) { - return status == QStringLiteral("Queued") || - status == QStringLiteral("Connecting") || - status == QStringLiteral("AwaitingLocalConfirmation") || - status == QStringLiteral("AwaitingRemoteAcceptance") || - status == QStringLiteral("InProgress"); -} - -QString FileShareTrayController::StatusToString(NearbySharingApi::StatusCode status) { - return QString::fromStdString(NearbySharingApi::StatusCodeToString(status)); -} - -QString FileShareTrayController::TransferStatusToString( - NearbySharingApi::TransferStatus status) { - return QString::fromStdString(NearbySharingApi::TransferStatusToString(status)); -} - -bool FileShareTrayController::IsFinalTransferStatus( - NearbySharingApi::TransferStatus status) { - switch (status) { - case NearbySharingApi::TransferStatus::kComplete: - case NearbySharingApi::TransferStatus::kFailed: - case NearbySharingApi::TransferStatus::kRejected: - case NearbySharingApi::TransferStatus::kCancelled: - case NearbySharingApi::TransferStatus::kTimedOut: - case NearbySharingApi::TransferStatus::kMediaUnavailable: - case NearbySharingApi::TransferStatus::kNotEnoughSpace: - case NearbySharingApi::TransferStatus::kUnsupportedAttachmentType: - case NearbySharingApi::TransferStatus::kDeviceAuthenticationFailed: - case NearbySharingApi::TransferStatus::kIncompletePayloads: - return true; - case NearbySharingApi::TransferStatus::kUnknown: - case NearbySharingApi::TransferStatus::kConnecting: - case NearbySharingApi::TransferStatus::kAwaitingLocalConfirmation: - case NearbySharingApi::TransferStatus::kAwaitingRemoteAcceptance: - case NearbySharingApi::TransferStatus::kInProgress: - return false; - } - return false; } diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.h b/sharing/linux/qml_tray_app/file_share_tray_controller.h index f1d71b91..46fc21d8 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.h +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.h @@ -2,18 +2,12 @@ #define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_ #include - -#include -#include -#include -#include -#include - #include -#include "sharing/linux/nearby_sharing_api.h" +#include "file_share_state.h" +#include -using NearbySharingApi = nearby::sharing::linux::NearbySharingApi; +using NearbySharingApi = nearby::sharing::NearbySharingApi; class FileShareTrayController : public QObject { Q_OBJECT @@ -35,28 +29,24 @@ class FileShareTrayController : public QObject { explicit FileShareTrayController(QObject* parent = nullptr); ~FileShareTrayController() override; - QString mode() const { return mode_; } + // Property accessors + QString mode() const { return state_.mode(); } + QString deviceName() const { return state_.deviceName(); } + QString statusMessage() const { return state_.statusMessage(); } + bool running() const { return state_.running(); } + QString pendingSendFileName() const { return state_.pendingSendFileName(); } + QString pendingSendFilePath() const { return state_.pendingSendFilePath(); } + QVariantList discoveredTargets() const { return state_.discoveredTargets(); } + QVariantList transfers() const { return state_.transfers(); } + bool autoAcceptIncoming() const { return state_.autoAcceptIncoming(); } + QString qrCodeUrl() const { return state_.qrCodeUrl(); } + QStringList qrCodeRows() const { return state_.qrCodeRows(); } + int qrCodeSize() const { return state_.qrCodeSize(); } + QString logPath() const { return state_.logPath(); } - QString deviceName() const { return device_name_; } + // Public methods void setDeviceName(const QString& device_name); - - QString statusMessage() const { return status_message_; } - bool running() const { return running_; } - - QString pendingSendFileName() const { return pending_send_file_name_; } - QString pendingSendFilePath() const { return pending_send_file_path_; } - - QVariantList discoveredTargets() const { return discovered_targets_; } - QVariantList transfers() const { return transfers_; } - - bool autoAcceptIncoming() const { return auto_accept_incoming_; } void setAutoAcceptIncoming(bool enabled); - - QString qrCodeUrl() const { return qr_code_url_; } - QStringList qrCodeRows() const { return qr_code_rows_; } - int qrCodeSize() const { return qr_code_size_; } - - QString logPath() const { return log_path_; } void setLogPath(const QString& path); Q_INVOKABLE void start(); @@ -84,65 +74,31 @@ class FileShareTrayController : public QObject { void requestTrayMessage(const QString& title, const QString& body); void requestCopyLinkTrayMessage(const QString& title, const QString& body, - const QString& link); + const QString& link); private: - void CreateService(); - void AttachServiceListeners(); + void initializeService(); + void attachServiceListeners(); + void loadSettings(); + void saveSettings() const; + void updateQrCodeData(); void startSendMode(); void startReceiveMode(); - void LoadSettings(); - void SaveSettings() const; - void UpdateQrCodeData(); - - void UpsertTarget(qlonglong share_target_id, const QString& name, - bool is_incoming); - void RemoveTarget(qlonglong share_target_id); - QString TargetName(qlonglong share_target_id) const; - bool HasActiveTransferForTarget(qlonglong share_target_id) const; - - void UpsertTransfer(qlonglong share_target_id, const QString& target_name, - const QString& status, double progress, - qulonglong transferred_bytes, const QString& direction, - const QString& file_name); - - void SetStatus(const QString& status); - bool HasActiveTransfers() const; - - static bool IsActiveTransferStatus(const QString& status); - static QString StatusToString(NearbySharingApi::StatusCode status); - static QString TransferStatusToString(NearbySharingApi::TransferStatus status); - static bool IsFinalTransferStatus(NearbySharingApi::TransferStatus status); + + void updateTargetFromInfo(const NearbySharingApi::ShareTargetInfo& info); + void handleTransferUpdate(const NearbySharingApi::TransferUpdateInfo& update); + void handleTransferComplete(const NearbySharingApi::TransferUpdateInfo& update); + void handleIncomingTransferComplete(const NearbySharingApi::TransferUpdateInfo& update, + const QString& name, bool success); + void handleOutgoingTransferComplete(const NearbySharingApi::TransferUpdateInfo& update, + const QString& name, bool success); + + void setStatus(const QString& status); + void notifyStateChange(const QString& property); std::unique_ptr service_; - - QString mode_ = QStringLiteral("Receive"); - QString device_name_ = QStringLiteral("NearbyLinux"); - QString status_message_ = QStringLiteral("Idle"); - bool running_ = false; - - bool auto_accept_incoming_ = true; - - // qr stuff - QString qr_code_url_; - QStringList qr_code_rows_; - int qr_code_size_ = 0; - QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log"); - - // pending send stuff - QString pending_send_file_path_; - QString pending_send_file_name_; - qlonglong pending_send_target_id_ = 0; - - // send targets - QVariantList discovered_targets_; - QHash discovered_row_by_target_; - QSet pending_target_removals_; - QHash target_names_; - - QVariantList transfers_; - QHash transfer_row_by_target_; + FileShareState state_; }; #endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_ diff --git a/sharing/linux/qml_tray_app/qr_code_generator.cc b/sharing/linux/qml_tray_app/qr_code_generator.cc new file mode 100644 index 00000000..f105b7ec --- /dev/null +++ b/sharing/linux/qml_tray_app/qr_code_generator.cc @@ -0,0 +1,48 @@ +#include "qr_code_generator.h" +#include +#include "third_party/libqrencode/qrencode_compat.h" + +namespace QrCodeGenerator { + +QrCodeData GenerateQrCode(const QString& url) { + QrCodeData result; + result.size = 0; + + const QByteArray encoded_url = url.trimmed().toUtf8(); + if (encoded_url.isEmpty()) { + return result; + } + + QRcode* qr_code = QRcode_encodeData( + encoded_url.size(), + reinterpret_cast(encoded_url.constData()), 0, + QR_ECLEVEL_M); + + if (qr_code == nullptr || qr_code->data == nullptr || qr_code->width <= 0) { + if (qr_code != nullptr) { + QRcode_free(qr_code); + } + return result; + } + + result.size = qr_code->width; + result.rows.reserve(result.size); + + for (int row = 0; row < result.size; ++row) { + QString row_data; + row_data.reserve(result.size); + + for (int col = 0; col < result.size; ++col) { + const unsigned char module = + qr_code->data[row * result.size + col] & 0x1; + row_data.append(module ? QLatin1Char('1') : QLatin1Char('0')); + } + + result.rows.append(row_data); + } + + QRcode_free(qr_code); + return result; +} + +} // namespace QrCodeGenerator diff --git a/sharing/linux/qml_tray_app/qr_code_generator.h b/sharing/linux/qml_tray_app/qr_code_generator.h new file mode 100644 index 00000000..8506cfad --- /dev/null +++ b/sharing/linux/qml_tray_app/qr_code_generator.h @@ -0,0 +1,18 @@ +#ifndef SHARING_LINUX_QML_TRAY_APP_QR_CODE_GENERATOR_H_ +#define SHARING_LINUX_QML_TRAY_APP_QR_CODE_GENERATOR_H_ + +#include +#include + +namespace QrCodeGenerator { + +struct QrCodeData { + QStringList rows; + int size; +}; + +QrCodeData GenerateQrCode(const QString& url); + +} // namespace QrCodeGenerator + +#endif // SHARING_LINUX_QML_TRAY_APP_QR_CODE_GENERATOR_H_ diff --git a/sharing/linux/qml_tray_app/share_target_model.h b/sharing/linux/qml_tray_app/share_target_model.h new file mode 100644 index 00000000..33e3a3df --- /dev/null +++ b/sharing/linux/qml_tray_app/share_target_model.h @@ -0,0 +1,30 @@ +#ifndef SHARING_LINUX_QML_TRAY_APP_SHARE_TARGET_MODEL_H_ +#define SHARING_LINUX_QML_TRAY_APP_SHARE_TARGET_MODEL_H_ + +#include +#include + +class ShareTarget { + public: + explicit ShareTarget(qlonglong id, const QString& name, bool is_incoming) + : id_(id), name_(name), is_incoming_(is_incoming) {} + + qlonglong id() const { return id_; } + QString name() const { return name_; } + bool isIncoming() const { return is_incoming_; } + + QVariantMap toVariantMap() const { + QVariantMap map; + map[QStringLiteral("id")] = id_; + map[QStringLiteral("name")] = name_; + map[QStringLiteral("isIncoming")] = is_incoming_; + return map; + } + + private: + qlonglong id_; + QString name_; + bool is_incoming_; +}; + +#endif // SHARING_LINUX_QML_TRAY_APP_SHARE_TARGET_MODEL_H_ diff --git a/sharing/linux/qml_tray_app/status_mapper.cc b/sharing/linux/qml_tray_app/status_mapper.cc new file mode 100644 index 00000000..13dc677d --- /dev/null +++ b/sharing/linux/qml_tray_app/status_mapper.cc @@ -0,0 +1,44 @@ +#include "status_mapper.h" + +namespace StatusMapper { + +QString TransferStatusToString(NearbySharingApi::TransferStatus status) { + return QString::fromStdString(NearbySharingApi::TransferStatusToString(status)); +} + +QString ApiStatusToString(NearbySharingApi::StatusCode status) { + return QString::fromStdString(NearbySharingApi::StatusCodeToString(status)); +} + +bool IsActiveTransferStatus(const QString& status) { + return status == QStringLiteral("Queued") || + status == QStringLiteral("Connecting") || + status == QStringLiteral("AwaitingLocalConfirmation") || + status == QStringLiteral("AwaitingRemoteAcceptance") || + status == QStringLiteral("InProgress"); +} + +bool IsFinalTransferStatus(NearbySharingApi::TransferStatus status) { + switch (status) { + case NearbySharingApi::TransferStatus::kComplete: + case NearbySharingApi::TransferStatus::kFailed: + case NearbySharingApi::TransferStatus::kRejected: + case NearbySharingApi::TransferStatus::kCancelled: + case NearbySharingApi::TransferStatus::kTimedOut: + case NearbySharingApi::TransferStatus::kMediaUnavailable: + case NearbySharingApi::TransferStatus::kNotEnoughSpace: + case NearbySharingApi::TransferStatus::kUnsupportedAttachmentType: + case NearbySharingApi::TransferStatus::kDeviceAuthenticationFailed: + case NearbySharingApi::TransferStatus::kIncompletePayloads: + return true; + case NearbySharingApi::TransferStatus::kUnknown: + case NearbySharingApi::TransferStatus::kConnecting: + case NearbySharingApi::TransferStatus::kAwaitingLocalConfirmation: + case NearbySharingApi::TransferStatus::kAwaitingRemoteAcceptance: + case NearbySharingApi::TransferStatus::kInProgress: + return false; + } + return false; +} + +} // namespace StatusMapper diff --git a/sharing/linux/qml_tray_app/status_mapper.h b/sharing/linux/qml_tray_app/status_mapper.h new file mode 100644 index 00000000..fc3efd8f --- /dev/null +++ b/sharing/linux/qml_tray_app/status_mapper.h @@ -0,0 +1,21 @@ +#ifndef SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_ +#define SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_ + +#include +#include "sharing/linux/nearby_sharing_api.h" + +using NearbySharingApi = nearby::sharing::NearbySharingApi; + +namespace StatusMapper { + +QString TransferStatusToString(NearbySharingApi::TransferStatus status); + +QString ApiStatusToString(NearbySharingApi::StatusCode status); + +bool IsActiveTransferStatus(const QString& status); + +bool IsFinalTransferStatus(NearbySharingApi::TransferStatus status); + +} // namespace StatusMapper + +#endif // SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_ diff --git a/sharing/linux/qml_tray_app/string_utils.cc b/sharing/linux/qml_tray_app/string_utils.cc new file mode 100644 index 00000000..9ef97e7a --- /dev/null +++ b/sharing/linux/qml_tray_app/string_utils.cc @@ -0,0 +1,18 @@ +#include "string_utils.h" + +namespace StringUtils { + +QString TrimmedOrFallback(const QString& value, const QString& fallback) { + const QString trimmed = value.trimmed(); + return trimmed.isEmpty() ? fallback : trimmed; +} + +QString FromStdString(const std::string& value) { + return QString::fromStdString(value); +} + +QString TrimmedFromStdString(const std::string& value) { + return QString::fromStdString(value).trimmed(); +} + +} // namespace StringUtils diff --git a/sharing/linux/qml_tray_app/string_utils.h b/sharing/linux/qml_tray_app/string_utils.h new file mode 100644 index 00000000..ebba9354 --- /dev/null +++ b/sharing/linux/qml_tray_app/string_utils.h @@ -0,0 +1,17 @@ +#ifndef SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_ +#define SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_ + +#include +#include + +namespace StringUtils { + +QString TrimmedOrFallback(const QString& value, const QString& fallback); + +QString FromStdString(const std::string& value); + +QString TrimmedFromStdString(const std::string& value); + +} // namespace StringUtils + +#endif // SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_ diff --git a/sharing/linux/qml_tray_app/transfer_model.h b/sharing/linux/qml_tray_app/transfer_model.h new file mode 100644 index 00000000..9241c3a5 --- /dev/null +++ b/sharing/linux/qml_tray_app/transfer_model.h @@ -0,0 +1,50 @@ +#ifndef SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_ +#define SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_ + +#include +#include + +class Transfer { + public: + Transfer(qlonglong target_id, const QString& target_name, + const QString& status, double progress, qulonglong transferred_bytes, + const QString& direction, const QString& file_name) + : target_id_(target_id), + target_name_(target_name), + status_(status), + progress_(progress), + transferred_bytes_(transferred_bytes), + direction_(direction), + file_name_(file_name) {} + + qlonglong targetId() const { return target_id_; } + QString targetName() const { return target_name_; } + QString status() const { return status_; } + double progress() const { return progress_; } + qulonglong transferredBytes() const { return transferred_bytes_; } + QString direction() const { return direction_; } + QString fileName() const { return file_name_; } + + QVariantMap toVariantMap() const { + QVariantMap map; + map[QStringLiteral("targetId")] = target_id_; + map[QStringLiteral("targetName")] = target_name_; + map[QStringLiteral("status")] = status_; + map[QStringLiteral("progress")] = progress_; + map[QStringLiteral("transferredBytes")] = static_cast(transferred_bytes_); + map[QStringLiteral("direction")] = direction_; + map[QStringLiteral("fileName")] = file_name_; + return map; + } + + private: + qlonglong target_id_; + QString target_name_; + QString status_; + double progress_; + qulonglong transferred_bytes_; + QString direction_; + QString file_name_; +}; + +#endif // SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_ From d37adbe457d5da1d5de56b4592942400251147b9 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 08:45:37 +0530 Subject: [PATCH 05/26] updated workflows --- .github/workflows/release-nearby-qml-tray.yaml | 2 ++ .github/workflows/validate.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/release-nearby-qml-tray.yaml b/.github/workflows/release-nearby-qml-tray.yaml index 1b569d3d..05ffe9c3 100644 --- a/.github/workflows/release-nearby-qml-tray.yaml +++ b/.github/workflows/release-nearby-qml-tray.yaml @@ -16,6 +16,8 @@ jobs: build-and-release-linux: name: Build and Release Linux Bundle runs-on: ubuntu-latest + container: + image: ubuntu:25.04 steps: - name: Checkout diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 668bcc19..02c3f44a 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -23,6 +23,8 @@ jobs: build-linux: name: Build Linux runs-on: ubuntu-latest + container: + image: ubuntu:25.04 steps: - uses: actions/checkout@v6 - name: Build Connections From 9cac4034844d64fc758e058123b518e7e52d24c6 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 08:54:28 +0530 Subject: [PATCH 06/26] updated workflows --- .github/workflows/release-nearby-qml-tray.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-nearby-qml-tray.yaml b/.github/workflows/release-nearby-qml-tray.yaml index 05ffe9c3..0eae3fbf 100644 --- a/.github/workflows/release-nearby-qml-tray.yaml +++ b/.github/workflows/release-nearby-qml-tray.yaml @@ -20,6 +20,11 @@ jobs: image: ubuntu:25.04 steps: + - name: Install git + run: | + apt-get update + apt-get install -y git ca-certificates + - name: Checkout uses: actions/checkout@v6 with: @@ -45,8 +50,8 @@ jobs: - name: Install Linux build dependencies run: | - sudo apt-get update - sudo apt-get install -y \ + apt-get update + apt-get install -y \ build-essential \ clang \ cmake \ From 06fea7c4eb6c3750e7649fcc8d7f19fb15fcbb44 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 08:56:39 +0530 Subject: [PATCH 07/26] updated workflows --- .github/workflows/validate.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 02c3f44a..48ead7d4 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -26,6 +26,10 @@ jobs: container: image: ubuntu:25.04 steps: + - name: Install git + run: | + apt-get update + apt-get install -y git ca-certificates - uses: actions/checkout@v6 - name: Build Connections run: CC=clang-18 CXX=clang-18++ BAZEL_CXXOPTS="-std=c++20" bazel build --@com_google_protobuf//bazel/toolchains:prefer_prebuilt_protoc=true --copt='-DGITHUB_BUILD' //connections:core From 8e3339693ddf38c34690e68422a3f12dc9907e59 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 14:40:17 +0530 Subject: [PATCH 08/26] added subtle blur effect to animated blob --- .../qml_tray_app/components/AnimatedBlob.qml | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml index 8a5e3c04..af2d48e2 100644 --- a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml +++ b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Effects Item { anchors.fill: parent @@ -52,9 +53,9 @@ Item { for (var i = 0; i < n; i++) { var a = (i / n) * Math.PI * 2 - Math.PI / 2 var r = 130 - + Math.sin(a * 2 + t) * 11 - + Math.cos(a * 3 - t * 0.6) * 8 - + Math.sin(a * 1.5 + t * 0.35) * 6 + + Math.sin(a * 2 + t) * 11 + + Math.cos(a * 3 - t * 0.6) * 8 + + Math.sin(a * 1.5 + t * 0.35) * 6 pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) } @@ -81,10 +82,17 @@ Item { } } - SendUrlPanel { + MultiEffect { anchors.centerIn: parent - width: parent.width - 96 - visible: isSendMode + width: blobCanvas.width + 10 + height: blobCanvas.height + 10 + source: blobCanvas + blurEnabled: true + blur: 1.0 + blurMax: 40 + saturation: 0.1 + brightness: 0.05 + autoPaddingEnabled: true } Label { From 3121aac81d1ad01db0b55482a67a5c5e64c9f193 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 14:53:45 +0530 Subject: [PATCH 09/26] cleaned up start/stop logic and mode switching. fixed not switching to receive mode after successful send bug --- .../file_share_tray_controller.cc | 32 ++++--------------- .../qml_tray_app/file_share_tray_main.cpp | 1 + 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.cc b/sharing/linux/qml_tray_app/file_share_tray_controller.cc index bdb35d16..927e82e6 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.cc +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.cc @@ -296,11 +296,6 @@ void FileShareTrayController::start() { state_.SetRunning(true); emit runningChanged(); - if (state_.mode() == QStringLiteral("Send")) { - startSendMode(); - } else { - startReceiveMode(); - } } void FileShareTrayController::stop() { @@ -371,19 +366,11 @@ void FileShareTrayController::switchToReceiveMode() { QStringLiteral("Wait for the current transfer to complete.")); return; } - - if (state_.mode() != QStringLiteral("Receive")) { + if (state_.running()) + { + startReceiveMode(); state_.SetMode(QStringLiteral("Receive")); emit modeChanged(); - if (state_.running()) { - stop(); - start(); - return; - } - } - - if (!state_.running()) { - start(); } } @@ -409,18 +396,11 @@ void FileShareTrayController::switchToSendModeWithFile(const QString& file_path) QStringLiteral("Wait for the current transfer to complete.")); return; } - - if (state_.mode() != QStringLiteral("Send")) { + if (state_.running()) + { + startSendMode(); state_.SetMode(QStringLiteral("Send")); emit modeChanged(); - if (state_.running()) { - stop(); - start(); - } - } - - if (!state_.running()) { - start(); } setStatus(QStringLiteral("Discovery started. Choose a nearby device.")); diff --git a/sharing/linux/qml_tray_app/file_share_tray_main.cpp b/sharing/linux/qml_tray_app/file_share_tray_main.cpp index 18f6c895..1ec24a63 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_main.cpp +++ b/sharing/linux/qml_tray_app/file_share_tray_main.cpp @@ -232,6 +232,7 @@ int main(int argc, char* argv[]) { tray.setContextMenu(&tray_menu); tray.show(); + controller.start(); controller.switchToReceiveMode(); return app.exec(); From 1c8761c02d43d8f50518b45d4d2508ea8a7ef839 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 21 Mar 2026 15:46:38 +0530 Subject: [PATCH 10/26] removed blur but added more blobs --- .../qml_tray_app/components/AnimatedBlob.qml | 139 ++++++++++++++++-- 1 file changed, 127 insertions(+), 12 deletions(-) diff --git a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml index af2d48e2..1b5387e3 100644 --- a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml +++ b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml @@ -20,6 +20,131 @@ Item { font.weight: Font.Medium color: textPrimary } + Canvas { + id: blobCanvas3 + width: 380; height: 380 + anchors.centerIn: parent + visible: !isSendMode + + property real t: 0 + property double startMs: Date.now() + + Timer { + interval: 16 + running: true + repeat: true + onTriggered: { + // seconds -> radians scale (t grows forever, no loop jump) + blobCanvas3.t = (Date.now() - blobCanvas3.startMs) * 0.001 * (Math.PI * 2 / 8.0) + blobCanvas3.requestPaint() + } + } + + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, width, height) + + var cx = width / 2 + var cy = height / 2 + var n = 10 + var pts = [] + + for (var i = 0; i < n; i++) { + var a = (i / n) * Math.PI * 2 - Math.PI / 2 + var r = 150 + + Math.sin(a * 2 + t) * 11 + + Math.cos(a * 3 - t * 0.2) * 8 + + Math.sin(a * 1.5 + t * 0.7) * 6 + + Math.sin(a * 1.5 + t * 1) * 4 + pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) + } + + var len = pts.length + ctx.beginPath() + for (var j = 0; j < len; j++) { + var p0 = pts[(j - 1 + len) % len] + var p1 = pts[j] + var p2 = pts[(j + 1) % len] + var p3 = pts[(j + 2) % len] + var cp1x = p1.x + (p2.x - p0.x) / 6 + var cp1y = p1.y + (p2.y - p0.y) / 6 + var cp2x = p2.x - (p3.x - p1.x) / 6 + var cp2y = p2.y - (p3.y - p1.y) / 6 + if (j === 0) ctx.moveTo(p1.x, p1.y) + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y) + } + ctx.closePath() + + var grad = ctx.createRadialGradient(cx - 40, cy - 40, 0, cx, cy, 150) + grad.addColorStop(0, "#e7faed") + ctx.fillStyle = grad + ctx.fill() + } + } + + Canvas { + id: blobCanvas2 + width: 380; height: 380 + anchors.centerIn: parent + visible: !isSendMode + + property real t: 0 + property double startMs: Date.now() + + Timer { + interval: 16 + running: true + repeat: true + onTriggered: { + // seconds -> radians scale (t grows forever, no loop jump) + blobCanvas2.t = (Date.now() - blobCanvas2.startMs) * 0.001 * (Math.PI * 2 / 8.0) + blobCanvas2.requestPaint() + } + } + + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, width, height) + + var cx = width / 2 + var cy = height / 2 + var n = 10 + var pts = [] + + for (var i = 0; i < n; i++) { + var a = (i / n) * Math.PI * 2 - Math.PI / 2 + var r = 140 + + Math.sin(a * 2 + t) * 11 + + Math.cos(a * 3 - t * 0.8) * 8 + + Math.sin(a * 1.5 + t * 0.23) * 6 + + Math.sin(a * 1.5 + t * 0.85) * 4 + pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) + } + + var len = pts.length + ctx.beginPath() + for (var j = 0; j < len; j++) { + var p0 = pts[(j - 1 + len) % len] + var p1 = pts[j] + var p2 = pts[(j + 1) % len] + var p3 = pts[(j + 2) % len] + var cp1x = p1.x + (p2.x - p0.x) / 6 + var cp1y = p1.y + (p2.y - p0.y) / 6 + var cp2x = p2.x - (p3.x - p1.x) / 6 + var cp2y = p2.y - (p3.y - p1.y) / 6 + if (j === 0) ctx.moveTo(p1.x, p1.y) + ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y) + } + ctx.closePath() + + var grad = ctx.createRadialGradient(cx - 40, cy - 40, 0, cx, cy, 150) + grad.addColorStop(0, "#caeada") + ctx.fillStyle = grad + ctx.fill() + } + } + + Canvas { id: blobCanvas @@ -56,6 +181,7 @@ Item { + Math.sin(a * 2 + t) * 11 + Math.cos(a * 3 - t * 0.6) * 8 + Math.sin(a * 1.5 + t * 0.35) * 6 + + Math.sin(a + t * 0.75) * 3 pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) } @@ -82,18 +208,7 @@ Item { } } - MultiEffect { - anchors.centerIn: parent - width: blobCanvas.width + 10 - height: blobCanvas.height + 10 - source: blobCanvas - blurEnabled: true - blur: 1.0 - blurMax: 40 - saturation: 0.1 - brightness: 0.05 - autoPaddingEnabled: true - } + Label { anchors.horizontalCenter: parent.horizontalCenter From f884cf34739ec80613e3e5c9352edc6ee0be8176 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sun, 22 Mar 2026 00:50:27 +0530 Subject: [PATCH 11/26] implemented gatt but removed it from ble_v2_medium. implemented bluez_agent and bluez agent manager for accepting incoming pairing requests --- internal/platform/implementation/linux/BUILD | 4 +- .../implementation/linux/ble_gatt_client.cc | 49 +++--- .../implementation/linux/ble_gatt_client.h | 4 +- .../implementation/linux/ble_gatt_server.cc | 160 +++++++++++++++--- .../implementation/linux/ble_gatt_server.h | 28 ++- .../implementation/linux/ble_v2_medium.cc | 20 +-- .../linux/bluetooth_classic_medium.cc | 84 +++++---- .../linux/bluetooth_classic_medium.h | 4 +- .../implementation/linux/bluez_agent.cc | 99 +++++++++++ .../implementation/linux/bluez_agent.h | 85 ++++++++++ .../linux/bluez_gatt_characteristic_server.cc | 5 +- .../implementation/linux/bluez_gatt_manager.h | 39 +++++ .../linux/generated/dbus/bluez/agent_server.h | 65 +++++++ .../dbus/bluez/agentmanager_client.h | 60 +++++++ .../dbus/bluez/gatt_manager_client.h | 15 +- .../generated/dbus/bluez/org.bluez.Agent1.xml | 45 +++++ .../dbus/bluez/org.bluez.AgentManager1.xml | 21 +++ .../dbus/bluez/org.bluez.GattManager1.xml | 2 + 18 files changed, 671 insertions(+), 118 deletions(-) create mode 100644 internal/platform/implementation/linux/bluez_agent.cc create mode 100644 internal/platform/implementation/linux/bluez_agent.h create mode 100644 internal/platform/implementation/linux/generated/dbus/bluez/agent_server.h create mode 100644 internal/platform/implementation/linux/generated/dbus/bluez/agentmanager_client.h create mode 100644 internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Agent1.xml create mode 100644 internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AgentManager1.xml diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD index d7c4db88..895c8ece 100644 --- a/internal/platform/implementation/linux/BUILD +++ b/internal/platform/implementation/linux/BUILD @@ -92,8 +92,8 @@ cc_library( "bluetooth_devices.h", "bluetooth_pairing.h", "bluez.h", + "bluez_agent.h", "bluez_device.h", - # "bluez_agent.h", "bluez_advertisement_monitor.h", "bluez_advertisement_monitor_manager.h", "bluez_gatt_characteristic_client.h", @@ -184,7 +184,7 @@ cc_library( "bluetooth_devices.cc", "bluetooth_pairing.cc", "bluez.cc", - #"bluez_agent.cc", + "bluez_agent.cc", "bluez_advertisement_monitor.cc", "bluez_gatt_characteristic_client.cc", "bluez_gatt_characteristic_server.cc", diff --git a/internal/platform/implementation/linux/ble_gatt_client.cc b/internal/platform/implementation/linux/ble_gatt_client.cc index 74047ab2..e88a1be8 100644 --- a/internal/platform/implementation/linux/ble_gatt_client.cc +++ b/internal/platform/implementation/linux/ble_gatt_client.cc @@ -208,7 +208,6 @@ bool BluezGattDiscovery::InitializeKnownServices() { return false; } - absl::flat_hash_map cached_services; absl::MutexLock lock(&mutex_); auto chr_it = std::find_if( objects.cbegin(), objects.cend(), @@ -219,29 +218,27 @@ bool BluezGattDiscovery::InitializeKnownServices() { sdbus::InterfaceName(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)) == 1; }); -for (; chr_it != objects.cend(); ++chr_it) { - const auto& [path, ifaces] = *chr_it; + for (; chr_it != objects.cend(); ++chr_it) { + const auto& [path, ifaces] = *chr_it; - auto iface_it = ifaces.find(sdbus::InterfaceName(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)); - if (iface_it == ifaces.end()) { - // Not a GattCharacteristic1 object (or interfaces map incomplete) -> skip - continue; + auto iface_it = ifaces.find(sdbus::InterfaceName( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)); + if (iface_it == ifaces.end()) { + continue; + } + + const auto& properties = iface_it->second; + auto maybe_props = characteristicProperties(path, properties); + if (!maybe_props.has_value()) continue; + + auto [service_uuid, chr_uuid, device_path] = *maybe_props; + + discovered_characteristics_.emplace( + std::make_tuple(service_uuid, chr_uuid, device_path), path); + characteristics_properties_.emplace( + path, std::make_tuple(service_uuid, chr_uuid, device_path)); } - const auto& properties = iface_it->second; - - auto maybe_props = characteristicProperties(path, properties); - if (!maybe_props.has_value()) continue; - - auto [chr_uuid, service_uuid, device_path] = *maybe_props; - - discovered_characteristics_.emplace( - std::make_tuple(chr_uuid, service_uuid, device_path), path); - - characteristics_properties_.emplace( - path, std::make_tuple(chr_uuid, service_uuid, device_path)); -} - return true; } @@ -337,7 +334,7 @@ BluezGattDiscovery::GetSubscribedCharacteristic( } return std::make_unique( - system_bus_, device_object_path, std::move(on_characteristic_changed_cb)); + system_bus_, path_it->second, std::move(on_characteristic_changed_cb)); } std::optional> @@ -396,7 +393,7 @@ try { return std::nullopt; } - return std::make_tuple(*chr_uuid, service_uuid, device_path); + return std::make_tuple(service_uuid, *chr_uuid, device_path); } void BluezGattDiscovery::onInterfacesAdded( @@ -414,12 +411,12 @@ void BluezGattDiscovery::onInterfacesAdded( absl::MutexLock lock(&mutex_); auto maybe_props = characteristicProperties(objectPath, properties); if (!maybe_props.has_value()) return; - auto [chr_uuid, service_uuid, device_path] = *maybe_props; + auto [service_uuid, chr_uuid, device_path] = *maybe_props; discovered_characteristics_.emplace( - std::make_tuple(chr_uuid, service_uuid, device_path), objectPath); + std::make_tuple(service_uuid, chr_uuid, device_path), objectPath); characteristics_properties_.emplace( - objectPath, std::make_tuple(chr_uuid, service_uuid, device_path)); + objectPath, std::make_tuple(service_uuid, chr_uuid, device_path)); } void BluezGattDiscovery::onInterfacesRemoved( diff --git a/internal/platform/implementation/linux/ble_gatt_client.h b/internal/platform/implementation/linux/ble_gatt_client.h index 108b4769..20718422 100644 --- a/internal/platform/implementation/linux/ble_gatt_client.h +++ b/internal/platform/implementation/linux/ble_gatt_client.h @@ -93,7 +93,7 @@ class BluezGattDiscovery final : public bluez::BluezObjectManager { absl::Mutex mutex_; absl::flat_hash_map> cached_services_ ABSL_GUARDED_BY(mutex_); - // Tuple order: service uuid, characteristic uuid, device object path + // Tuple order: service uuid, characteristic uuid, device object path. absl::flat_hash_map, sdbus::ObjectPath> discovered_characteristics_ ABSL_GUARDED_BY(mutex_); @@ -122,7 +122,7 @@ class GattClient : public api::ble::GattClient { peripheral_object_path_(peripheral_object_path), gatt_discovery_(std::move(gatt_discovery)), discovery_cancel_(false) { - disconnected_callback_it_ = gatt_discovery->AddPeripheralConnection( + disconnected_callback_it_ = gatt_discovery_->AddPeripheralConnection( peripheral_object_path_, std::move(disconnected_callback)); } ~GattClient() override { diff --git a/internal/platform/implementation/linux/ble_gatt_server.cc b/internal/platform/implementation/linux/ble_gatt_server.cc index cc761c22..ee0a0f40 100644 --- a/internal/platform/implementation/linux/ble_gatt_server.cc +++ b/internal/platform/implementation/linux/ble_gatt_server.cc @@ -24,6 +24,19 @@ namespace nearby { namespace linux { +namespace { + +void LogAsyncGattManagerError(const bluez::GattManager& manager, + const char* method_name, + const sdbus::Error& error) { + LOG(ERROR) << method_name << ": Got error '" << error.getName() + << "' with message '" << error.getMessage() + << "' while calling " << method_name << " on object " + << manager.getProxy().getObjectPath(); +} + +} // namespace + absl::optional GattServer::CreateCharacteristic( const Uuid& service_uuid, const Uuid& characteristic_uuid, @@ -60,23 +73,53 @@ GattServer::CreateCharacteristic( if (service->AddCharacteristic(service_uuid, characteristic_uuid, permission, property)) { - try { - LOG(INFO)<< __func__ << ": Registering service on gattmanager with characteristic_uuid: " - << std::string(characteristic_uuid) << " and service_uuid: " << std::string(service_uuid); - - gatt_manager_ -> RegisterApplication(gatt_service_root_object_manager -> getObject().getObjectPath(), {}); - } catch (const sdbus::Error& e) { - LOG(ERROR) - << __func__ - << ": error calling RegisterAplication for GattManager with object path " - << gatt_manager_->getProxy().getObjectPath() << " with name '" << e.getName() - << "' and message '" << e.getMessage() << "'"; - return std::nullopt; - } - - services_.insert({service_uuid, std::move(service)}); + { + absl::MutexLock profile_lock(&profiles_mutex_); + gatt_profiles_.insert({service_uuid, std::move(profile)}); + } + bool should_register_application = false; + { + absl::MutexLock registration_lock(®istration_mutex_); + should_register_application = + !gatt_application_registered_ && !gatt_application_register_pending_; + if (should_register_application) { + gatt_application_register_pending_ = true; + } + } + + if (should_register_application) { + try { + LOG(INFO) << __func__ + << ": Registering GATT application root " + << gatt_service_root_object_manager->getObject().getObjectPath() + << " with characteristic_uuid: " + << std::string(characteristic_uuid) + << " and service_uuid: " << std::string(service_uuid); + auto call = gatt_manager_->RegisterApplication( + gatt_service_root_object_manager->getObject().getObjectPath(), {}); + absl::MutexLock registration_lock(®istration_mutex_); + register_application_call_ = std::move(call); + } catch (const sdbus::Error& e) { + { + absl::MutexLock registration_lock(®istration_mutex_); + gatt_application_register_pending_ = false; + register_application_call_.reset(); + } + services_.erase(service_uuid); + { + absl::MutexLock profile_lock(&profiles_mutex_); + gatt_profiles_.erase(service_uuid); + } + LOG(ERROR) + << __func__ + << ": error calling RegisterApplication for GattManager with object path " + << gatt_manager_->getProxy().getObjectPath() << " with name '" + << e.getName() << "' and message '" << e.getMessage() << "'"; + return std::nullopt; + } + } api::ble::GattCharacteristic characteristic{ characteristic_uuid, service_uuid, permission, property}; @@ -137,19 +180,86 @@ absl::Status GattServer::NotifyCharacteristicChanged( return chr->NotifyChanged(confirm, new_value); } +void GattServer::OnRegisterApplicationReply(std::optional error) { + { + absl::MutexLock lock(®istration_mutex_); + gatt_application_register_pending_ = false; + register_application_call_.reset(); + gatt_application_registered_ = !error.has_value() || !error->isValid(); + } + + if (error.has_value() && error->isValid()) { + LogAsyncGattManagerError(*gatt_manager_, "RegisterApplication", *error); + } +} + +void GattServer::OnUnregisterApplicationReply( + std::optional error) { + { + absl::MutexLock lock(®istration_mutex_); + gatt_application_unregister_pending_ = false; + unregister_application_call_.reset(); + } + + if (error.has_value() && error->isValid()) { + LogAsyncGattManagerError(*gatt_manager_, "UnregisterApplication", *error); + } +} + void GattServer::Stop() { - bluez::GattManager manager(system_bus_, adapter_.GetObjectPath()); - absl::MutexLock lock(&services_mutex_); - for (auto& [uuid, service] : services_) { - LOG(INFO) << __func__ << ": Unregistering service " - << service->getObject().getObjectPath(); - try { - manager.UnregisterApplication(sdbus::ObjectPath("/")); - } catch (const sdbus::Error& e) { - DBUS_LOG_METHOD_CALL_ERROR(&manager, "UnregisterApplication", e); + sdbus::ObjectPath app_path = + gatt_service_root_object_manager->getObject().getObjectPath(); + bool should_unregister = false; + + { + absl::MutexLock registration_lock(®istration_mutex_); + if (register_application_call_.has_value() && + register_application_call_->isPending()) { + register_application_call_->cancel(); + } + register_application_call_.reset(); + gatt_application_register_pending_ = false; + + should_unregister = gatt_application_registered_ && + !gatt_application_unregister_pending_; + if (should_unregister) { + gatt_application_registered_ = false; + gatt_application_unregister_pending_ = true; } } - // services_.clear(); + + { + absl::MutexLock lock(&services_mutex_); + for (auto& [uuid, service] : services_) { + LOG(INFO) << __func__ << ": Unregistering service " + << service->getObject().getObjectPath(); + } + } + + if (should_unregister) { + try { + auto call = gatt_manager_->UnregisterApplication(app_path); + absl::MutexLock registration_lock(®istration_mutex_); + unregister_application_call_ = std::move(call); + } catch (const sdbus::Error& e) { + { + absl::MutexLock registration_lock(®istration_mutex_); + gatt_application_unregister_pending_ = false; + unregister_application_call_.reset(); + } + DBUS_LOG_METHOD_CALL_ERROR(gatt_manager_.get(), "UnregisterApplication", + e); + } + } + + { + absl::MutexLock lock(&services_mutex_); + services_.clear(); + } + { + absl::MutexLock profile_lock(&profiles_mutex_); + gatt_profiles_.clear(); + } } } // namespace linux diff --git a/internal/platform/implementation/linux/ble_gatt_server.h b/internal/platform/implementation/linux/ble_gatt_server.h index 1343a4be..2d41b4c5 100644 --- a/internal/platform/implementation/linux/ble_gatt_server.h +++ b/internal/platform/implementation/linux/ble_gatt_server.h @@ -16,6 +16,7 @@ #define PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_ #include +#include #include #include @@ -69,7 +70,16 @@ class GattServer : public api::ble::GattServer { sdbus::ObjectPath("/com/google/nearby/medium/ble/gatt"))), gatt_manager_(std::make_unique(system_bus_, adapter_.GetObjectPath())), server_cb_(std::make_shared( - std::move(server_cb))) {} + std::move(server_cb))) { + gatt_manager_->SetRegisterApplicationReplyCallback( + [this](std::optional error) { + OnRegisterApplicationReply(std::move(error)); + }); + gatt_manager_->SetUnregisterApplicationReplyCallback( + [this](std::optional error) { + OnUnregisterApplicationReply(std::move(error)); + }); + } ~GattServer() override = default; absl::optional CreateCharacteristic( @@ -85,6 +95,9 @@ class GattServer : public api::ble::GattServer { void Stop() override; private: + void OnRegisterApplicationReply(std::optional error); + void OnUnregisterApplicationReply(std::optional error); + sdbus::IConnection& system_bus_; std::shared_ptr devices_; BluetoothAdapter adapter_; @@ -93,9 +106,20 @@ class GattServer : public api::ble::GattServer { std::unique_ptr gatt_service_root_object_manager; absl::Mutex profiles_mutex_; absl::flat_hash_map> gatt_profiles_; - ABSL_GUARDED_BY(profiles_mutex_) + ABSL_GUARDED_BY(profiles_mutex_); std::unique_ptr gatt_manager_; std::shared_ptr server_cb_; + absl::Mutex registration_mutex_; + bool gatt_application_registered_ ABSL_GUARDED_BY(registration_mutex_) = + false; + bool gatt_application_register_pending_ ABSL_GUARDED_BY(registration_mutex_) = + false; + bool gatt_application_unregister_pending_ + ABSL_GUARDED_BY(registration_mutex_) = false; + std::optional register_application_call_ + ABSL_GUARDED_BY(registration_mutex_); + std::optional unregister_application_call_ + ABSL_GUARDED_BY(registration_mutex_); absl::Mutex services_mutex_; absl::flat_hash_map> services_ ABSL_GUARDED_BY(services_mutex_); diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc index b936dd73..d50c3a22 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.cc +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -27,17 +27,14 @@ #include "absl/synchronization/mutex.h" #include "internal/platform/implementation/ble.h" -// #include "internal/platform/implementation/linux/ble_gatt_client.h" -// #include "internal/platform/implementation/linux/ble_gatt_server.h" #include "internal/platform/implementation/linux/ble_v2_medium.h" #include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "absl/types/span.h" #include "absl/time/time.h" -//#include "ble_gatt_client.h" -//#include "ble_gatt_server.h" #include "ble_l2cap_server_socket.h" #include "ble_l2cap_socket.h" +#include "ble_gatt_server.h" #include "internal/base/observer_list.h" #include "internal/platform/implementation/linux/bluetooth_classic_device.h" #include "internal/platform/implementation/linux/bluetooth_devices.h" @@ -68,7 +65,6 @@ BleL2capSocket::ProtocolMode GetL2capProtocolMode() { BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) : system_bus_(adapter.GetConnection()), adapter_(adapter), - //gatt_discovery_(std::make_shared(system_bus_)), observers_(std::make_shared>()), devices_(std::make_unique( system_bus_, adapter_.GetObjectPath(), *observers_)), @@ -98,10 +94,6 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) } else { adv_monitor_manager_ready_notification_.Notify(); } - // if (gatt_discovery_->InitializeKnownServices()) { - // LOG(ERROR) << __func__ - // << ": Could not initialize known GATT services"; - // } } void BleV2Medium::OnRegisterMonitorReply(std::optional error) { @@ -478,12 +470,9 @@ bool BleV2Medium::StopScanning() { std::unique_ptr BleV2Medium::StartGattServer( api::ble::ServerGattConnectionCallback callback) { - // (void)callback; + (void)callback; + LOG(INFO) << __func__ << ": GATT is disabled on linux."; return nullptr; - - //return std::make_unique( - //*system_bus_, adapter_, devices_,std::move(callback) - //); } std::unique_ptr BleV2Medium::ConnectToGattServer( @@ -493,8 +482,7 @@ std::unique_ptr BleV2Medium::ConnectToGattServer( (void)peripheral_id; (void)tx_power_level; (void)callback; - LOG(WARNING) << __func__ - << ": GATT client connection is not supported on Linux yet."; + LOG(INFO) << __func__ << ": GATT is disabled on linux."; return nullptr; } diff --git a/internal/platform/implementation/linux/bluetooth_classic_medium.cc b/internal/platform/implementation/linux/bluetooth_classic_medium.cc index 0d3d23f7..208fb28e 100644 --- a/internal/platform/implementation/linux/bluetooth_classic_medium.cc +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.cc @@ -21,34 +21,48 @@ #include "absl/strings/string_view.h" #include "internal/base/observer_list.h" #include "internal/platform/implementation/bluetooth_classic.h" -#include "internal/platform/implementation/linux/bluetooth_adapter.h" -#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" -#include "internal/platform/implementation/linux/bluetooth_classic_device.h" -#include "internal/platform/implementation/linux/bluetooth_classic_medium.h" - -// #include "bluez_agent.h" -#include "internal/platform/implementation/linux/bluetooth_classic_server_socket.h" -#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" -#include "internal/platform/implementation/linux/bluetooth_pairing.h" -#include "internal/platform/logging.h" - -namespace nearby { -namespace linux { -BluetoothClassicMedium::BluetoothClassicMedium(BluetoothAdapter &adapter) - : system_bus_(adapter.GetConnection()), - adapter_(adapter), - observers_(nullptr), - devices_(nullptr), - device_watcher_(nullptr), - // agent_manager_(std::make_unique(*system_bus_)), - profile_manager_(nullptr) { - auto shared = - GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath()); - observers_ = shared->observers; - devices_ = shared->devices; - profile_manager_ = - std::make_unique(*system_bus_, *devices_); -} +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluez_agent.h" +#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_classic_medium.h" + +#include "internal/platform/implementation/linux/bluetooth_classic_server_socket.h" +#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" +#include "internal/platform/implementation/linux/bluetooth_pairing.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +namespace { + +constexpr char kBluezAgentPath[] = "/com/google/nearby/bluetooth/agent"; + +} // namespace + +BluetoothClassicMedium::BluetoothClassicMedium(BluetoothAdapter &adapter) + : system_bus_(adapter.GetConnection()), + adapter_(adapter), + observers_(nullptr), + devices_(nullptr), + device_watcher_(nullptr), + agent_manager_(std::make_unique(*system_bus_)), + profile_manager_(nullptr) { + auto shared = + GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath()); + observers_ = shared->observers; + devices_ = shared->devices; + profile_manager_ = + std::make_unique(*system_bus_, *devices_); + + if (!agent_manager_->Register( + /*capability=*/absl::string_view("NoInputNoOutput"), + sdbus::ObjectPath(kBluezAgentPath))) { + LOG(WARNING) << __func__ + << ": Failed to register default BlueZ agent at " + << kBluezAgentPath; + } +} bool BluetoothClassicMedium::StartDiscovery( DiscoveryCallback discovery_callback) { @@ -145,14 +159,12 @@ std::unique_ptr BluetoothClassicMedium::ConnectToService( new BluetoothSocket(device, fd.value())); } -std::unique_ptr -BluetoothClassicMedium::ListenForService(const std::string &service_name, - const std::string &service_uuid) { - LOG(INFO) << __func__ << ": Creating bluez agent on path: " << "/com/example/bluez_agent" ; - - if (!profile_manager_->ProfileRegistered(service_uuid)) { - if (!profile_manager_->Register(service_name, service_uuid)) { - LOG(ERROR) << __func__ << ": Could not register profile " +std::unique_ptr +BluetoothClassicMedium::ListenForService(const std::string &service_name, + const std::string &service_uuid) { + if (!profile_manager_->ProfileRegistered(service_uuid)) { + if (!profile_manager_->Register(service_name, service_uuid)) { + LOG(ERROR) << __func__ << ": Could not register profile " << service_name << " " << service_uuid << " with Bluez"; return nullptr; diff --git a/internal/platform/implementation/linux/bluetooth_classic_medium.h b/internal/platform/implementation/linux/bluetooth_classic_medium.h index 42edf168..0320d765 100644 --- a/internal/platform/implementation/linux/bluetooth_classic_medium.h +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.h @@ -26,7 +26,7 @@ #include #include -// #include "bluez_agent.h" +#include "internal/platform/implementation/linux/bluez_agent.h" #include "internal/base/observer_list.h" #include "internal/platform/implementation/bluetooth_classic.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h" @@ -107,7 +107,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium { std::shared_ptr devices_; std::unique_ptr device_watcher_; - // std::unique_ptr agent_manager_; + std::unique_ptr agent_manager_; std::unique_ptr profile_manager_; }; diff --git a/internal/platform/implementation/linux/bluez_agent.cc b/internal/platform/implementation/linux/bluez_agent.cc new file mode 100644 index 00000000..ba1cc14b --- /dev/null +++ b/internal/platform/implementation/linux/bluez_agent.cc @@ -0,0 +1,99 @@ +#include "internal/platform/implementation/linux/bluez_agent.h" + +#include + +#include "internal/platform/implementation/linux/generated/dbus/bluez/agent_server.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/agentmanager_client.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +void Agent::Release() { LOG(INFO) << "[agent] Release()"; } + +std::string Agent::RequestPinCode(const sdbus::ObjectPath& device) { + LOG(INFO) << "[agent] RequestPinCode(" << device << ")"; + return "0000"; +} + +void Agent::DisplayPinCode(const sdbus::ObjectPath& device, + const std::string& pincode) { + LOG(INFO) << "[agent] DisplayPinCode(" << device << ", " << pincode << ")"; +} + +uint32_t Agent::RequestPasskey(const sdbus::ObjectPath& device) { + LOG(INFO) << "[agent] RequestPasskey(" << device << ")"; + return 123456; +} + +void Agent::DisplayPasskey(const sdbus::ObjectPath& device, + const uint32_t& passkey, + const uint16_t& entered) { + LOG(INFO) << "[agent] DisplayPasskey(" << device << ", " << passkey + << ", entered=" << entered << ")"; +} + +void Agent::RequestConfirmation(const sdbus::ObjectPath& device, + const uint32_t& passkey) { + LOG(INFO) << "[agent] RequestConfirmation(" << device << ", " << passkey + << ") -> ACCEPT"; +} + +void Agent::RequestAuthorization(const sdbus::ObjectPath& device) { + LOG(INFO) << "[agent] RequestAuthorization(" << device << ") -> ACCEPT"; +} + +void Agent::AuthorizeService(const sdbus::ObjectPath& device, + const std::string& uuid) { + LOG(INFO) << "[agent] AuthorizeService(" << device << ", " << uuid + << ") -> ACCEPT"; +} + +void Agent::Cancel() { LOG(INFO) << "[agent] Cancel()"; } + +bool AgentManager::AgentRegistered(absl::string_view agent_object_path) { + registered_agents_mutex_.ReaderLock(); + bool registered = registered_agents_.count(std::string(agent_object_path)) == 1; + registered_agents_mutex_.ReaderUnlock(); + return registered; +} + +bool AgentManager::Register(std::optional capability, + const sdbus::ObjectPath& agent_object_path) { + absl::MutexLock l(®istered_agents_mutex_); + + const std::string agent_path_str = std::string(agent_object_path); + + if (registered_agents_.count(agent_path_str) == 1) { + LOG(WARNING) << __func__ << ": Trying to register agent " << agent_path_str + << " which was already registered."; + return true; + } + + auto agent = std::make_shared(getProxy().getConnection(), + sdbus::ObjectPath(agent_object_path)); + + try { + const std::string cap = + capability.has_value() ? std::string(*capability) : "NoInputNoOutput"; + RegisterAgent(agent->getObject().getObjectPath(), cap); + RequestDefaultAgent(agent->getObject().getObjectPath()); + } catch (const sdbus::Error& e) { + LOG(ERROR) << __func__ << ": Got error '" << e.getName() + << "' with message '" << e.getMessage() + << "' while calling RegisterAgent/RequestDefaultAgent on object " + << getProxy().getObjectPath(); + return false; + } + + registered_agents_.emplace(agent_path_str, agent); + + LOG(INFO) << __func__ << ": Registered agent instance at path " + << agent_path_str; + + return true; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_agent.h b/internal/platform/implementation/linux/bluez_agent.h new file mode 100644 index 00000000..c40f79f7 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_agent.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/agent_server.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/agentmanager_client.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/logging.h" + +namespace nearby::linux { + +class Agent final + : public sdbus::AdaptorInterfaces { + public: + Agent(const Agent&) = delete; + Agent(Agent&&) = delete; + Agent& operator=(const Agent&) = delete; + Agent& operator=(Agent&&) = delete; + + Agent(sdbus::IConnection& system_bus, sdbus::ObjectPath path) + : AdaptorInterfaces(system_bus, std::move(path)) { + registerAdaptor(); + LOG(INFO) << "Created new Agent at path: " << getObject().getObjectPath(); + } + + ~Agent() { unregisterAdaptor(); } + + private: + void Release() override; + + std::string RequestPinCode(const sdbus::ObjectPath& device) override; + void DisplayPinCode(const sdbus::ObjectPath& device, + const std::string& pincode) override; + + uint32_t RequestPasskey(const sdbus::ObjectPath& device) override; + void DisplayPasskey(const sdbus::ObjectPath& device, const uint32_t& passkey, + const uint16_t& entered) override; + + void RequestConfirmation(const sdbus::ObjectPath& device, + const uint32_t& passkey) override; + + void RequestAuthorization(const sdbus::ObjectPath& device) override; + + void AuthorizeService(const sdbus::ObjectPath& device, + const std::string& uuid) override; + + void Cancel() override; +}; + +class AgentManager final + : public sdbus::ProxyInterfaces { + public: + AgentManager(const AgentManager&) = delete; + AgentManager(AgentManager&&) = delete; + AgentManager& operator=(const AgentManager&) = delete; + AgentManager& operator=(AgentManager&&) = delete; + + explicit AgentManager(sdbus::IConnection& system_bus) + : ProxyInterfaces(system_bus, sdbus::ServiceName(bluez::SERVICE_DEST), + sdbus::ObjectPath("/org/bluez")) { + registerProxy(); + } + + ~AgentManager() { unregisterProxy(); } + + bool Register(std::optional capability, + const sdbus::ObjectPath& agent_object_path); + + bool AgentRegistered(absl::string_view agent_object_path); + + private: + absl::Mutex registered_agents_mutex_; + std::map> registered_agents_ + ABSL_GUARDED_BY(registered_agents_mutex_); +}; + +} // namespace nearby::linux diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc index af7a8364..3bab9bf7 100644 --- a/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_server.cc @@ -191,7 +191,10 @@ void GattCharacteristicServer::StopNotify() { if ((characteristic_.property | api::ble::GattCharacteristic::Property::kNotify) == api::ble::GattCharacteristic::Property::kNotify) { - if (notify_sessions_.fetch_sub(0) == 1) { + if (notify_sessions_.load() == 0) { + return; + } + if (notify_sessions_.fetch_sub(1) == 1) { if (server_cb_->characteristic_unsubscription_cb != nullptr) { server_cb_->characteristic_unsubscription_cb(characteristic_); } diff --git a/internal/platform/implementation/linux/bluez_gatt_manager.h b/internal/platform/implementation/linux/bluez_gatt_manager.h index 33116870..f47c0411 100644 --- a/internal/platform/implementation/linux/bluez_gatt_manager.h +++ b/internal/platform/implementation/linux/bluez_gatt_manager.h @@ -14,10 +14,15 @@ #ifndef PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_ #define PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_ + +#include + #include #include #include +#include "absl/functional/any_invocable.h" +#include "absl/synchronization/mutex.h" #include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h" namespace nearby { namespace linux { @@ -25,6 +30,8 @@ namespace bluez { class GattManager : public sdbus::ProxyInterfaces { public: + using ReplyCallback = absl::AnyInvocable)>; + GattManager(const GattManager &) = delete; GattManager(GattManager &&) = delete; GattManager &operator=(const GattManager &) = delete; @@ -37,6 +44,38 @@ class GattManager registerProxy(); } ~GattManager() { unregisterProxy(); } + + void SetRegisterApplicationReplyCallback(ReplyCallback callback) { + absl::MutexLock lock(&callbacks_mutex_); + register_application_reply_callback_ = std::move(callback); + } + + void SetUnregisterApplicationReplyCallback(ReplyCallback callback) { + absl::MutexLock lock(&callbacks_mutex_); + unregister_application_reply_callback_ = std::move(callback); + } + + protected: + void onRegisterApplicationReply(std::optional error) override { + absl::MutexLock lock(&callbacks_mutex_); + if (register_application_reply_callback_.has_value()) { + (*register_application_reply_callback_)(std::move(error)); + } + } + + void onUnregisterApplicationReply(std::optional error) override { + absl::MutexLock lock(&callbacks_mutex_); + if (unregister_application_reply_callback_.has_value()) { + (*unregister_application_reply_callback_)(std::move(error)); + } + } + + private: + absl::Mutex callbacks_mutex_; + std::optional register_application_reply_callback_ + ABSL_GUARDED_BY(callbacks_mutex_); + std::optional unregister_application_reply_callback_ + ABSL_GUARDED_BY(callbacks_mutex_); }; } // namespace bluez } // namespace linux diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/agent_server.h b/internal/platform/implementation/linux/generated/dbus/bluez/agent_server.h new file mode 100644 index 00000000..56d182e8 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/agent_server.h @@ -0,0 +1,65 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_agent_server_h__adaptor__H__ +#define __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_agent_server_h__adaptor__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class Agent1_adaptor +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.Agent1"; + +protected: + Agent1_adaptor(sdbus::IObject& object) + : m_object(object) + { + } + + Agent1_adaptor(const Agent1_adaptor&) = delete; + Agent1_adaptor& operator=(const Agent1_adaptor&) = delete; + Agent1_adaptor(Agent1_adaptor&&) = delete; + Agent1_adaptor& operator=(Agent1_adaptor&&) = delete; + + ~Agent1_adaptor() = default; + + void registerAdaptor() + { + m_object.addVTable( sdbus::registerMethod("Release").implementedAs([this](){ return this->Release(); }) + , sdbus::registerMethod("RequestPinCode").withInputParamNames("device").withOutputParamNames("pincode").implementedAs([this](const sdbus::ObjectPath& device){ return this->RequestPinCode(device); }) + , sdbus::registerMethod("DisplayPinCode").withInputParamNames("device", "pincode").implementedAs([this](const sdbus::ObjectPath& device, const std::string& pincode){ return this->DisplayPinCode(device, pincode); }) + , sdbus::registerMethod("RequestPasskey").withInputParamNames("device").withOutputParamNames("passkey").implementedAs([this](const sdbus::ObjectPath& device){ return this->RequestPasskey(device); }) + , sdbus::registerMethod("DisplayPasskey").withInputParamNames("device", "passkey", "entered").implementedAs([this](const sdbus::ObjectPath& device, const uint32_t& passkey, const uint16_t& entered){ return this->DisplayPasskey(device, passkey, entered); }) + , sdbus::registerMethod("RequestConfirmation").withInputParamNames("device", "passkey").implementedAs([this](const sdbus::ObjectPath& device, const uint32_t& passkey){ return this->RequestConfirmation(device, passkey); }) + , sdbus::registerMethod("RequestAuthorization").withInputParamNames("device").implementedAs([this](const sdbus::ObjectPath& device){ return this->RequestAuthorization(device); }) + , sdbus::registerMethod("AuthorizeService").withInputParamNames("device", "uuid").implementedAs([this](const sdbus::ObjectPath& device, const std::string& uuid){ return this->AuthorizeService(device, uuid); }) + , sdbus::registerMethod("Cancel").implementedAs([this](){ return this->Cancel(); }) + ).forInterface(INTERFACE_NAME); + } + +private: + virtual void Release() = 0; + virtual std::string RequestPinCode(const sdbus::ObjectPath& device) = 0; + virtual void DisplayPinCode(const sdbus::ObjectPath& device, const std::string& pincode) = 0; + virtual uint32_t RequestPasskey(const sdbus::ObjectPath& device) = 0; + virtual void DisplayPasskey(const sdbus::ObjectPath& device, const uint32_t& passkey, const uint16_t& entered) = 0; + virtual void RequestConfirmation(const sdbus::ObjectPath& device, const uint32_t& passkey) = 0; + virtual void RequestAuthorization(const sdbus::ObjectPath& device) = 0; + virtual void AuthorizeService(const sdbus::ObjectPath& device, const std::string& uuid) = 0; + virtual void Cancel() = 0; + +private: + sdbus::IObject& m_object; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/agentmanager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/agentmanager_client.h new file mode 100644 index 00000000..4f7f79b9 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/agentmanager_client.h @@ -0,0 +1,60 @@ + +/* + * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! + */ + +#ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_agentmanager_client_h__proxy__H__ +#define __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_agentmanager_client_h__proxy__H__ + +#include +#include +#include + +namespace org { +namespace bluez { + +class AgentManager1_proxy +{ +public: + static constexpr const char* INTERFACE_NAME = "org.bluez.AgentManager1"; + +protected: + AgentManager1_proxy(sdbus::IProxy& proxy) + : m_proxy(proxy) + { + } + + AgentManager1_proxy(const AgentManager1_proxy&) = delete; + AgentManager1_proxy& operator=(const AgentManager1_proxy&) = delete; + AgentManager1_proxy(AgentManager1_proxy&&) = delete; + AgentManager1_proxy& operator=(AgentManager1_proxy&&) = delete; + + ~AgentManager1_proxy() = default; + + void registerProxy() + { + } + +public: + void RegisterAgent(const sdbus::ObjectPath& agent, const std::string& capability) + { + m_proxy.callMethod("RegisterAgent").onInterface(INTERFACE_NAME).withArguments(agent, capability); + } + + void UnregisterAgent(const sdbus::ObjectPath& agent) + { + m_proxy.callMethod("UnregisterAgent").onInterface(INTERFACE_NAME).withArguments(agent); + } + + void RequestDefaultAgent(const sdbus::ObjectPath& agent) + { + m_proxy.callMethod("RequestDefaultAgent").onInterface(INTERFACE_NAME).withArguments(agent); + } + +private: + sdbus::IProxy& m_proxy; +}; + +}} // namespaces + +#endif diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h index cd30e950..bd59a013 100644 --- a/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h +++ b/internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h @@ -3,8 +3,8 @@ * This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! */ -#ifndef __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_gatt_manager_client_v2_h__proxy__H__ -#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_gatt_manager_client_v2_h__proxy__H__ +#ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_gatt_manager_client_h__proxy__H__ +#define __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_gatt_manager_client_h__proxy__H__ #include #include @@ -35,15 +35,18 @@ protected: { } + virtual void onRegisterApplicationReply(std::optional error) = 0; + virtual void onUnregisterApplicationReply(std::optional error) = 0; + public: - void RegisterApplication(const sdbus::ObjectPath& application, const std::map& options) + sdbus::PendingAsyncCall RegisterApplication(const sdbus::ObjectPath& application, const std::map& options) { - m_proxy.callMethod("RegisterApplication").onInterface(INTERFACE_NAME).withArguments(application, options); + return m_proxy.callMethodAsync("RegisterApplication").onInterface(INTERFACE_NAME).withArguments(application, options).uponReplyInvoke([this](std::optional error){ this->onRegisterApplicationReply(std::move(error)); }); } - void UnregisterApplication(const sdbus::ObjectPath& application) + sdbus::PendingAsyncCall UnregisterApplication(const sdbus::ObjectPath& application) { - m_proxy.callMethod("UnregisterApplication").onInterface(INTERFACE_NAME).withArguments(application); + return m_proxy.callMethodAsync("UnregisterApplication").onInterface(INTERFACE_NAME).withArguments(application).uponReplyInvoke([this](std::optional error){ this->onUnregisterApplicationReply(std::move(error)); }); } private: diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Agent1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Agent1.xml new file mode 100644 index 00000000..f5b62a11 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.Agent1.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AgentManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AgentManager1.xml new file mode 100644 index 00000000..b685e942 --- /dev/null +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.AgentManager1.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml index e353715b..a37bf7d4 100644 --- a/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml +++ b/internal/platform/implementation/linux/generated/dbus/bluez/org.bluez.GattManager1.xml @@ -3,10 +3,12 @@ + + From 19cf94faca0d8e918d9b53bccbaa4486c5d0f95d Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sun, 22 Mar 2026 08:40:33 +0530 Subject: [PATCH 12/26] added code to normalise transfer progress between incoming and outgoing transfers --- sharing/linux/nearby_sharing_api.cc | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sharing/linux/nearby_sharing_api.cc b/sharing/linux/nearby_sharing_api.cc index ef6acbec..bf511920 100644 --- a/sharing/linux/nearby_sharing_api.cc +++ b/sharing/linux/nearby_sharing_api.cc @@ -130,6 +130,16 @@ NearbySharingApi::TextAttachmentType ToFacadeTextAttachmentType( return FacadeType::kUnknown; } +float NormalizeFacadeProgress(float progress) { + if (progress <= 0.0f) { + return 0.0f; + } + if (progress >= 100.0f) { + return 1.0f; + } + return progress / 100.0f; +} + std::string GenerateQrCodeUrl() { auto ec_key = nearby::crypto::ECPrivateKey::Create(); if (!ec_key) { @@ -271,7 +281,7 @@ class NearbySharingApi::Impl : public nearby::sharing::ShareTargetDiscoveredCall info.device_name = share_target.device_name; info.is_incoming = share_target.is_incoming; info.status = ToFacadeTransferStatus(transfer_metadata.status()); - info.progress = transfer_metadata.progress(); + info.progress = NormalizeFacadeProgress(transfer_metadata.progress()); info.transferred_bytes = transfer_metadata.transferred_bytes(); info.total_attachments = transfer_metadata.total_attachments_count(); info.transferred_attachments = transfer_metadata.transferred_attachments_count(); From 7c3eae2db2243e21e951ba0b6b996794f6f298c9 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sun, 22 Mar 2026 09:14:30 +0530 Subject: [PATCH 13/26] added incoming transfer progress card. more blob eye candy --- .../qml_tray_app/components/AnimatedBlob.qml | 254 +++++++++++++++--- .../qml_tray_app/components/DeviceCard.qml | 2 +- .../linux/qml_tray_app/file_share_state.cc | 4 +- sharing/linux/qml_tray_app/file_share_state.h | 3 +- .../file_share_tray_controller.cc | 56 +++- .../qml_tray_app/file_share_tray_controller.h | 1 + .../qml_tray_app/file_share_tray_main.cpp | 1 + 7 files changed, 285 insertions(+), 36 deletions(-) diff --git a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml index 1b5387e3..31f67efe 100644 --- a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml +++ b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml @@ -8,7 +8,109 @@ Item { readonly property color textPrimary: "#111827" readonly property color textMuted: "#6b7280" + readonly property color textSoft: "#4b5563" + readonly property color cardSurface: "#ffffff" + readonly property color cardBorder: "#d1fae5" readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0 + readonly property var incomingTransfer: findIncomingTransfer() + readonly property var incomingTarget: findTargetForTransfer(incomingTransfer) + readonly property bool hasIncomingTransfer: incomingTransfer !== null + && incomingTarget !== null + readonly property bool isReceivingActive: hasIncomingTransfer + && String(incomingTransfer.status || "") !== "Complete" + readonly property real receivingIntensity: { + if (!isReceivingActive) + return 0 + var numeric = Number(incomingTransfer.progress) + if (!isFinite(numeric) || numeric < 0) + numeric = 0 + return Math.max(0.35, Math.min(1.0, numeric)) + } + property real blobChaos: receivingIntensity + property string dismissedTransferKey: "" + + Behavior on blobChaos { + NumberAnimation { + duration: 220 + easing.type: Easing.OutCubic + } + } + + function isIncomingTransferActive(status) { + return status === "InProgress" + || status === "Queued" + || status === "Connecting" + || status === "AwaitingLocalConfirmation" + || status === "AwaitingRemoteAcceptance" + || status === "Complete" + } + + function transferKey(transfer) { + if (!transfer) + return "" + return String(transfer.targetId || "") + + "|" + String(transfer.status || "") + + "|" + String(transfer.filePath || "") + + "|" + String(transfer.fileName || "") + } + + function findIncomingTransfer() { + var transfers = fileShareController.transfers + var latestCompleted = null + for (var i = transfers.length - 1; i >= 0; --i) { + var entry = transfers[i] + if (!entry) + continue + if (String(entry.direction || "") !== "incoming") + continue + var status = String(entry.status || "") + if (!isIncomingTransferActive(status)) + continue + if (status === "Complete") { + if (latestCompleted === null) + latestCompleted = entry + continue + } + return entry + } + return latestCompleted + } + + function findTargetForTransfer(transfer) { + if (!transfer) + return null + var targets = fileShareController.discoveredTargets + for (var i = 0; i < targets.length; ++i) { + var entry = targets[i] + if (entry && entry.id === transfer.targetId) + return entry + } + return { + id: transfer.targetId, + name: String(transfer.targetName || "Incoming device"), + isIncoming: true + } + } + + function incomingHeadline(status) { + if (status === "Complete") + return "Received" + if (status === "AwaitingLocalConfirmation") + return "Incoming transfer" + if (status === "AwaitingRemoteAcceptance") + return "Preparing transfer" + if (status === "Connecting") + return "Connecting" + return "Receiving" + } + + // The card only becomes actionable once the file has landed on disk and we + // have a local path to open. + function incomingClickReady() { + return hasIncomingTransfer + && String(incomingTransfer.status || "") === "Complete" + && String(incomingTransfer.filePath || "").length > 0 + } Label { x: 48; y: 48 @@ -27,15 +129,17 @@ Item { visible: !isSendMode property real t: 0 - property double startMs: Date.now() + property double lastMs: Date.now() Timer { interval: 16 running: true repeat: true onTriggered: { - // seconds -> radians scale (t grows forever, no loop jump) - blobCanvas3.t = (Date.now() - blobCanvas3.startMs) * 0.001 * (Math.PI * 2 / 8.0) + var now = Date.now() + var dt = Math.min(0.05, Math.max(0.0, (now - blobCanvas3.lastMs) * 0.001)) + blobCanvas3.lastMs = now + blobCanvas3.t += dt * (Math.PI * 2 / Math.max(4.6, 8.0 - blobChaos * 2.6)) blobCanvas3.requestPaint() } } @@ -44,18 +148,21 @@ Item { var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) - var cx = width / 2 - var cy = height / 2 + var chaos = blobChaos + var cx = width / 2 + Math.sin(t * 0.33) * chaos * 10 + var cy = height / 2 + Math.cos(t * 0.27) * chaos * 8 var n = 10 var pts = [] for (var i = 0; i < n; i++) { var a = (i / n) * Math.PI * 2 - Math.PI / 2 - var r = 150 - + Math.sin(a * 2 + t) * 11 - + Math.cos(a * 3 - t * 0.2) * 8 - + Math.sin(a * 1.5 + t * 0.7) * 6 - + Math.sin(a * 1.5 + t * 1) * 4 + var r = 150 + chaos * 22 + + Math.sin(a * 2 + t) * (11 + chaos * 14) + + Math.cos(a * 3 - t * 0.2) * (8 + chaos * 10) + + Math.sin(a * 1.5 + t * 0.7) * (6 + chaos * 8) + + Math.sin(a * 1.5 + t) * (4 + chaos * 6) + + Math.cos(a * 5 - t * 1.2) * (chaos * 9) + + Math.sin(a * 7 + t * 0.85) * (chaos * 6) pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) } @@ -89,15 +196,17 @@ Item { visible: !isSendMode property real t: 0 - property double startMs: Date.now() + property double lastMs: Date.now() Timer { interval: 16 running: true repeat: true onTriggered: { - // seconds -> radians scale (t grows forever, no loop jump) - blobCanvas2.t = (Date.now() - blobCanvas2.startMs) * 0.001 * (Math.PI * 2 / 8.0) + var now = Date.now() + var dt = Math.min(0.05, Math.max(0.0, (now - blobCanvas2.lastMs) * 0.001)) + blobCanvas2.lastMs = now + blobCanvas2.t += dt * (Math.PI * 2 / Math.max(4.9, 8.0 - blobChaos * 2.2)) blobCanvas2.requestPaint() } } @@ -106,18 +215,21 @@ Item { var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) - var cx = width / 2 - var cy = height / 2 + var chaos = blobChaos + var cx = width / 2 + Math.cos(t * 0.29) * chaos * 8 + var cy = height / 2 + Math.sin(t * 0.41) * chaos * 11 var n = 10 var pts = [] for (var i = 0; i < n; i++) { var a = (i / n) * Math.PI * 2 - Math.PI / 2 - var r = 140 - + Math.sin(a * 2 + t) * 11 - + Math.cos(a * 3 - t * 0.8) * 8 - + Math.sin(a * 1.5 + t * 0.23) * 6 - + Math.sin(a * 1.5 + t * 0.85) * 4 + var r = 140 + chaos * 18 + + Math.sin(a * 2 + t) * (11 + chaos * 12) + + Math.cos(a * 3 - t * 0.8) * (8 + chaos * 9) + + Math.sin(a * 1.5 + t * 0.23) * (6 + chaos * 7) + + Math.sin(a * 1.5 + t * 0.85) * (4 + chaos * 6) + + Math.cos(a * 4 + t * 1.1) * (chaos * 7) + + Math.sin(a * 6 - t * 0.95) * (chaos * 5) pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) } @@ -153,15 +265,17 @@ Item { visible: !isSendMode property real t: 0 - property double startMs: Date.now() + property double lastMs: Date.now() Timer { interval: 16 running: true repeat: true onTriggered: { - // seconds -> radians scale (t grows forever, no loop jump) - blobCanvas.t = (Date.now() - blobCanvas.startMs) * 0.001 * (Math.PI * 2 / 8.0) + var now = Date.now() + var dt = Math.min(0.05, Math.max(0.0, (now - blobCanvas.lastMs) * 0.001)) + blobCanvas.lastMs = now + blobCanvas.t += dt * (Math.PI * 2 / Math.max(4.4, 8.0 - blobChaos * 3.0)) blobCanvas.requestPaint() } } @@ -170,18 +284,21 @@ Item { var ctx = getContext("2d") ctx.clearRect(0, 0, width, height) - var cx = width / 2 - var cy = height / 2 + var chaos = blobChaos + var cx = width / 2 + Math.sin(t * 0.55) * chaos * 12 + var cy = height / 2 + Math.cos(t * 0.47) * chaos * 10 var n = 10 var pts = [] for (var i = 0; i < n; i++) { var a = (i / n) * Math.PI * 2 - Math.PI / 2 - var r = 130 - + Math.sin(a * 2 + t) * 11 - + Math.cos(a * 3 - t * 0.6) * 8 - + Math.sin(a * 1.5 + t * 0.35) * 6 - + Math.sin(a + t * 0.75) * 3 + var r = 130 + chaos * 15 + + Math.sin(a * 2 + t) * (11 + chaos * 15) + + Math.cos(a * 3 - t * 0.6) * (8 + chaos * 12) + + Math.sin(a * 1.5 + t * 0.35) * (6 + chaos * 9) + + Math.sin(a + t * 0.75) * (3 + chaos * 6) + + Math.cos(a * 5 - t * 1.35) * (chaos * 10) + + Math.sin(a * 8 + t * 0.92) * (chaos * 6) pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) } @@ -208,6 +325,83 @@ Item { } } + Rectangle { + id: incomingTransferCard + anchors.centerIn: parent + visible: !isSendMode && hasIncomingTransfer + && transferKey(incomingTransfer) !== dismissedTransferKey + width: 200 + height: 210 + radius: 34 + color: cardSurface + border.color: cardBorder + border.width: 1 + z: 10 + + Rectangle { + anchors.fill: parent + anchors.margins: 10 + radius: parent.radius - 10 + color: "#ffffff" + opacity: 0.84 + } + + Column { + anchors.fill: parent + anchors.margins: 24 + spacing: 10 + + Label { + anchors.horizontalCenter: parent.horizontalCenter + text: incomingHeadline(String(incomingTransfer.status || "")) + font.pixelSize: 12 + font.weight: Font.DemiBold + color: "#059669" + horizontalAlignment: Text.AlignHCenter + } + + DeviceCard { + anchors.horizontalCenter: parent.horizontalCenter + modelData: incomingTarget + } + + Label { + width: parent.width + visible: String(incomingTransfer.fileName || "").length > 0 + text: String(incomingTransfer.fileName || "") + font.pixelSize: 13 + font.weight: Font.StyleItalic + color: textPrimary + wrapMode: Text.Wrap + maximumLineCount: 2 + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + } + } + + MouseArea { + anchors.fill: parent + enabled: incomingClickReady() + cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { + dismissedTransferKey = transferKey(incomingTransfer) + fileShareController.openFileLocation(String(incomingTransfer.filePath || "")) + } + } + } + + onIncomingTransferChanged: { + if (!incomingTransfer) { + dismissedTransferKey = "" + return + } + + if (transferKey(incomingTransfer) !== dismissedTransferKey) + return + + if (String(incomingTransfer.status || "") !== "Complete") + dismissedTransferKey = "" + } Label { diff --git a/sharing/linux/qml_tray_app/components/DeviceCard.qml b/sharing/linux/qml_tray_app/components/DeviceCard.qml index 89e57991..90dd18e9 100644 --- a/sharing/linux/qml_tray_app/components/DeviceCard.qml +++ b/sharing/linux/qml_tray_app/components/DeviceCard.qml @@ -185,7 +185,7 @@ Item { horizontalAlignment: Text.AlignHCenter text: targetName font.pixelSize: 13 - font.weight: Font.Medium + font.weight: Font.Bold elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.Wrap diff --git a/sharing/linux/qml_tray_app/file_share_state.cc b/sharing/linux/qml_tray_app/file_share_state.cc index 968783f2..06cf4273 100644 --- a/sharing/linux/qml_tray_app/file_share_state.cc +++ b/sharing/linux/qml_tray_app/file_share_state.cc @@ -66,7 +66,7 @@ bool FileShareState::HasTarget(qlonglong id) const { void FileShareState::AddOrUpdateTransfer( qlonglong target_id, const QString& target_name, const QString& status, double progress, qulonglong transferred_bytes, const QString& direction, - const QString& file_name) { + const QString& file_name, const QString& file_path) { QVariantMap transfer{ {QStringLiteral("targetId"), target_id}, {QStringLiteral("targetName"), target_name}, @@ -75,8 +75,10 @@ void FileShareState::AddOrUpdateTransfer( {QStringLiteral("transferredBytes"), transferred_bytes}, {QStringLiteral("direction"), direction}, {QStringLiteral("fileName"), file_name}, + {QStringLiteral("filePath"), file_path}, }; + qInfo() << "qDiscovered targets" << discoveredTargets(); if (transfer_row_by_target_.contains(target_id)) { const int row_index = transfer_row_by_target_.value(target_id); if (row_index >= 0 && row_index < transfers_.size()) { diff --git a/sharing/linux/qml_tray_app/file_share_state.h b/sharing/linux/qml_tray_app/file_share_state.h index 05eaea5c..47b80b6e 100644 --- a/sharing/linux/qml_tray_app/file_share_state.h +++ b/sharing/linux/qml_tray_app/file_share_state.h @@ -72,7 +72,8 @@ class FileShareState { void AddOrUpdateTransfer(qlonglong target_id, const QString& target_name, const QString& status, double progress, qulonglong transferred_bytes, - const QString& direction, const QString& file_name); + const QString& direction, const QString& file_name, + const QString& file_path); void RemoveTransfer(qlonglong target_id); bool HasActiveTransferForTarget(qlonglong target_id) const; bool HasActiveTransfers() const; diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.cc b/sharing/linux/qml_tray_app/file_share_tray_controller.cc index 927e82e6..b42de0ca 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.cc +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.cc @@ -1,13 +1,17 @@ #include "file_share_tray_controller.h" +#include #include +#include #include +#include #include #include #include #include #include #include +#include #include "string_utils.h" #include "status_mapper.h" @@ -38,6 +42,10 @@ void FileShareTrayController::initializeService() { emit qrCodeUrlChanged(); emit qrCodeChanged(); attachServiceListeners(); + // service_ ->StartFastInitiationScanning([](auto a) + // { + // std::cout << "Probably fine"; + // }); } void FileShareTrayController::updateQrCodeData() { @@ -105,7 +113,8 @@ void FileShareTrayController::handleTransferUpdate( } state_.AddOrUpdateTransfer(update.share_target_id, name, status, update.progress, - update.transferred_bytes, direction, file_name); + update.transferred_bytes, direction, file_name, + StringUtils::FromStdString(update.first_file_path)); emit transfersChanged(); setStatus(QStringLiteral("%1 (%2)").arg(status, name)); @@ -429,7 +438,8 @@ void FileShareTrayController::sendPendingFileToTarget(qlonglong share_target_id) state_.SetPendingSendFile(file_path, file_info.fileName(), share_target_id); state_.AddOrUpdateTransfer(share_target_id, target_name, QStringLiteral("Queued"), 0.0, 0, - QStringLiteral("outgoing"), file_info.fileName()); + QStringLiteral("outgoing"), file_info.fileName(), + file_info.absoluteFilePath()); emit transfersChanged(); service_->SendFile( @@ -452,7 +462,8 @@ void FileShareTrayController::sendPendingFileToTarget(qlonglong share_target_id) state_.AddOrUpdateTransfer(share_target_id, target_name, QStringLiteral("Failed"), 0.0, 0, QStringLiteral("outgoing"), - state_.pendingSendFileName()); + state_.pendingSendFileName(), + state_.pendingSendFilePath()); emit transfersChanged(); state_.SetPendingSendFile("", "", 0); }, @@ -479,6 +490,45 @@ void FileShareTrayController::copyTextToClipboard(const QString& text) { QStringLiteral("Link copied to clipboard.")); } +void FileShareTrayController::openFileLocation(const QString& file_path) { + const QString trimmed = file_path.trimmed(); + if (trimmed.isEmpty()) { + emit requestTrayMessage(QStringLiteral("Open location failed"), + QStringLiteral("No received file location is available.")); + return; + } + + QFileInfo info(trimmed); + QString target_path; + if (info.exists() && info.isFile()) { + // Open the containing folder so the file is visible in the user's file + // manager regardless of the desktop environment. + target_path = info.absolutePath(); + } else if (info.exists() && info.isDir()) { + target_path = info.absoluteFilePath(); + } else { + // Some transfer updates can outlive the exact file entry we saw earlier; + // fall back to the parent directory when it still exists. + const QFileInfo parent_info(info.absolutePath()); + if (parent_info.exists() && parent_info.isDir()) { + target_path = parent_info.absoluteFilePath(); + } + } + + if (target_path.isEmpty()) { + emit requestTrayMessage(QStringLiteral("Open location failed"), + QStringLiteral("The file location is no longer available.")); + return; + } + + const bool opened = + QDesktopServices::openUrl(QUrl::fromLocalFile(target_path)); + if (!opened) { + emit requestTrayMessage(QStringLiteral("Open location failed"), + QStringLiteral("Could not open the file location.")); + } +} + void FileShareTrayController::clearTransfers() { state_.ClearAll(); emit discoveredTargetsChanged(); diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.h b/sharing/linux/qml_tray_app/file_share_tray_controller.h index 46fc21d8..99a0ff92 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.h +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.h @@ -55,6 +55,7 @@ class FileShareTrayController : public QObject { Q_INVOKABLE void switchToSendModeWithFile(const QString& file_path); Q_INVOKABLE void sendPendingFileToTarget(qlonglong share_target_id); Q_INVOKABLE void copyTextToClipboard(const QString& text); + Q_INVOKABLE void openFileLocation(const QString& file_path); Q_INVOKABLE void clearTransfers(); Q_INVOKABLE void hideToTray(); diff --git a/sharing/linux/qml_tray_app/file_share_tray_main.cpp b/sharing/linux/qml_tray_app/file_share_tray_main.cpp index 1ec24a63..4ad6492c 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_main.cpp +++ b/sharing/linux/qml_tray_app/file_share_tray_main.cpp @@ -233,6 +233,7 @@ int main(int argc, char* argv[]) { tray.show(); controller.start(); + //controller. controller.switchToReceiveMode(); return app.exec(); From 29e1aa0555eb9c00add2bfb49dc9b9715b85400a Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Thu, 26 Mar 2026 20:49:27 +0530 Subject: [PATCH 14/26] added sepereate cc_library for system clock --- internal/platform/implementation/linux/BUILD | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD index 895c8ece..a3779ab1 100644 --- a/internal/platform/implementation/linux/BUILD +++ b/internal/platform/implementation/linux/BUILD @@ -163,6 +163,22 @@ cc_library( ], ) +cc_library( + name = "system_clock", + srcs = [ + "system_clock.cc", + ], + alwayslink = True, + visibility = [ + "//sharing/linux:__pkg__", + ], + deps = [ + "//internal/platform:base", + "//internal/platform/implementation:types", + "@com_google_absl//absl/time", + ], +) + cc_library( name = "linux", srcs = [ @@ -200,7 +216,6 @@ cc_library( "scheduled_executor.cc", "stream.cc", "submittable_executor.cc", - "system_clock.cc", "thread_pool.cc", "utils.cc", # "wifi_direct.cc", @@ -225,6 +240,7 @@ cc_library( deps = [ ":comm", ":crypto", # build_cleaner: keep + ":system_clock", ":types", "//internal/flags:nearby_flags", "//internal/platform:base", From ae4a89fa7beb82be2fd09c88d4714ee627841fa7 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Thu, 26 Mar 2026 20:50:22 +0530 Subject: [PATCH 15/26] added nearby connections API for linux --- sharing/linux/BUILD | 67 +- sharing/linux/nearby_connections_api.cc | 649 +++++++++++++++++++ sharing/linux/nearby_connections_api.h | 268 ++++++++ sharing/linux/nearby_connections_api_test.cc | 44 ++ 4 files changed, 1014 insertions(+), 14 deletions(-) create mode 100644 sharing/linux/nearby_connections_api.cc create mode 100644 sharing/linux/nearby_connections_api.h create mode 100644 sharing/linux/nearby_connections_api_test.cc diff --git a/sharing/linux/BUILD b/sharing/linux/BUILD index 45ff2759..cba30b17 100644 --- a/sharing/linux/BUILD +++ b/sharing/linux/BUILD @@ -13,7 +13,8 @@ # limitations under the License. licenses(["notice"]) -load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") +load("@rules_cc//cc/private/rules_impl:cc_static_library.bzl", "cc_static_library") cc_library( name = "linux_sharing_platform", @@ -46,17 +47,6 @@ cc_library( ], ) -cc_library( - name = "system_clock_shim", - srcs = ["platform/system_clock_shim.cc"], - alwayslink = True, - visibility = ["//visibility:private"], - deps = [ - "//internal/platform:types", - "@com_google_absl//absl/time", - ], -) - cc_library( name = "nearby_sharing_api", srcs = ["nearby_sharing_api.cc"], @@ -65,8 +55,8 @@ cc_library( visibility = ["//visibility:public"], deps = [ ":linux_sharing_platform", - ":system_clock_shim", "//internal/base:file_path", + "//internal/platform/implementation/linux:system_clock", "//sharing:attachments", "//sharing:nearby_sharing_service", "//sharing/analytics", @@ -74,6 +64,25 @@ cc_library( ], ) +cc_library( + name = "nearby_connections_api", + srcs = ["nearby_connections_api.cc"], + hdrs = ["nearby_connections_api.h"], + alwayslink = True, + visibility = ["//visibility:public"], + deps = [ + ":linux_sharing_platform", + "//internal/base:file_path", + "//sharing/internal/public:nearby_context", + "//sharing:nearby_sharing_service", + ], +) + +cc_static_library( + name = "nearby_sharing_api_static", + deps = [":nearby_sharing_api"], +) + cc_binary( name = "nearby_sharing_api_shared", linkshared = True, @@ -87,13 +96,43 @@ cc_binary( ], deps = [ ":linux_sharing_platform", - ":system_clock_shim", "//internal/base:file_path", "//internal/platform/implementation:platform", "//internal/platform/implementation/linux", + "//internal/platform/implementation/linux:system_clock", "//sharing:attachments", "//sharing:nearby_sharing_service", "//sharing/analytics", "//sharing/local_device_data", ], ) + +cc_binary( + name = "nearby_connections_api_shared", + linkshared = True, + srcs = [ + "nearby_connections_api.cc", + "nearby_connections_api.h", + ], + visibility = ["//visibility:public"], + linkopts = [ + "-Wl,--exclude-libs,ALL", + ], + deps = [ + ":linux_sharing_platform", + "//internal/base:file_path", + "//internal/platform/implementation:platform", + "//internal/platform/implementation/linux", + "//sharing/internal/public:nearby_context", + "//sharing:nearby_sharing_service", + ], +) + +cc_test( + name = "nearby_connections_api_test", + srcs = ["nearby_connections_api_test.cc"], + deps = [ + ":nearby_connections_api", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/sharing/linux/nearby_connections_api.cc b/sharing/linux/nearby_connections_api.cc new file mode 100644 index 00000000..2ff2028a --- /dev/null +++ b/sharing/linux/nearby_connections_api.cc @@ -0,0 +1,649 @@ +#include "sharing/linux/nearby_connections_api.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/time/time.h" +#include "internal/base/file_path.h" +#include "internal/interop/authentication_status.h" +#include "sharing/internal/public/context_impl.h" +#include "sharing/linux/platform/linux_sharing_platform.h" +#include "sharing/nearby_connections_service.h" +#include "sharing/nearby_connections_service_impl.h" + +namespace nearby::sharing { + +namespace { + +using NativeService = nearby::sharing::NearbyConnectionsService; + +NearbyConnectionsApi::StatusCode ToFacadeStatus(Status status) { + switch (status) { + case Status::kSuccess: + return NearbyConnectionsApi::StatusCode::kSuccess; + case Status::kError: + return NearbyConnectionsApi::StatusCode::kError; + case Status::kOutOfOrderApiCall: + return NearbyConnectionsApi::StatusCode::kOutOfOrderApiCall; + case Status::kAlreadyHaveActiveStrategy: + return NearbyConnectionsApi::StatusCode::kAlreadyHaveActiveStrategy; + case Status::kAlreadyAdvertising: + return NearbyConnectionsApi::StatusCode::kAlreadyAdvertising; + case Status::kAlreadyDiscovering: + return NearbyConnectionsApi::StatusCode::kAlreadyDiscovering; + case Status::kAlreadyListening: + return NearbyConnectionsApi::StatusCode::kAlreadyListening; + case Status::kEndpointIOError: + return NearbyConnectionsApi::StatusCode::kEndpointIOError; + case Status::kEndpointUnknown: + return NearbyConnectionsApi::StatusCode::kEndpointUnknown; + case Status::kConnectionRejected: + return NearbyConnectionsApi::StatusCode::kConnectionRejected; + case Status::kAlreadyConnectedToEndpoint: + return NearbyConnectionsApi::StatusCode::kAlreadyConnectedToEndpoint; + case Status::kNotConnectedToEndpoint: + return NearbyConnectionsApi::StatusCode::kNotConnectedToEndpoint; + case Status::kBluetoothError: + return NearbyConnectionsApi::StatusCode::kBluetoothError; + case Status::kBleError: + return NearbyConnectionsApi::StatusCode::kBleError; + case Status::kWifiLanError: + return NearbyConnectionsApi::StatusCode::kWifiLanError; + case Status::kPayloadUnknown: + return NearbyConnectionsApi::StatusCode::kPayloadUnknown; + case Status::kReset: + return NearbyConnectionsApi::StatusCode::kReset; + case Status::kTimeout: + return NearbyConnectionsApi::StatusCode::kTimeout; + case Status::kUnknown: + return NearbyConnectionsApi::StatusCode::kUnknown; + case Status::kNextValue: + break; + } + return NearbyConnectionsApi::StatusCode::kUnknown; +} + +NearbyConnectionsApi::AuthenticationStatus ToFacadeAuthenticationStatus( + ::nearby::AuthenticationStatus status) { + switch (status) { + case ::nearby::AuthenticationStatus::kUnknown: + return NearbyConnectionsApi::AuthenticationStatus::kUnknown; + case ::nearby::AuthenticationStatus::kSuccess: + return NearbyConnectionsApi::AuthenticationStatus::kSuccess; + case ::nearby::AuthenticationStatus::kFailure: + return NearbyConnectionsApi::AuthenticationStatus::kFailure; + } + return NearbyConnectionsApi::AuthenticationStatus::kUnknown; +} + +NearbyConnectionsApi::DistanceInfo ToFacadeDistanceInfo( + nearby::sharing::DistanceInfo distance_info) { + switch (distance_info) { + case nearby::sharing::DistanceInfo::kUnknown: + return NearbyConnectionsApi::DistanceInfo::kUnknown; + case nearby::sharing::DistanceInfo::kVeryClose: + return NearbyConnectionsApi::DistanceInfo::kVeryClose; + case nearby::sharing::DistanceInfo::kClose: + return NearbyConnectionsApi::DistanceInfo::kClose; + case nearby::sharing::DistanceInfo::kFar: + return NearbyConnectionsApi::DistanceInfo::kFar; + } + return NearbyConnectionsApi::DistanceInfo::kUnknown; +} + +NearbyConnectionsApi::Medium ToFacadeMedium(nearby::sharing::Medium medium) { + switch (medium) { + case nearby::sharing::Medium::kUnknown: + return NearbyConnectionsApi::Medium::kUnknown; + case nearby::sharing::Medium::kMdns: + return NearbyConnectionsApi::Medium::kMdns; + case nearby::sharing::Medium::kBluetooth: + return NearbyConnectionsApi::Medium::kBluetooth; + case nearby::sharing::Medium::kWifiHotspot: + return NearbyConnectionsApi::Medium::kWifiHotspot; + case nearby::sharing::Medium::kBle: + return NearbyConnectionsApi::Medium::kBle; + case nearby::sharing::Medium::kWifiLan: + return NearbyConnectionsApi::Medium::kWifiLan; + case nearby::sharing::Medium::kWifiAware: + return NearbyConnectionsApi::Medium::kWifiAware; + case nearby::sharing::Medium::kNfc: + return NearbyConnectionsApi::Medium::kNfc; + case nearby::sharing::Medium::kWifiDirect: + return NearbyConnectionsApi::Medium::kWifiDirect; + case nearby::sharing::Medium::kWebRtc: + return NearbyConnectionsApi::Medium::kWebRtc; + case nearby::sharing::Medium::kBleL2Cap: + return NearbyConnectionsApi::Medium::kBleL2Cap; + } + return NearbyConnectionsApi::Medium::kUnknown; +} + +nearby::sharing::Strategy ToNativeStrategy( + NearbyConnectionsApi::Strategy strategy) { + switch (strategy) { + case NearbyConnectionsApi::Strategy::kP2pCluster: + return nearby::sharing::Strategy::kP2pCluster; + case NearbyConnectionsApi::Strategy::kP2pStar: + return nearby::sharing::Strategy::kP2pStar; + case NearbyConnectionsApi::Strategy::kP2pPointToPoint: + return nearby::sharing::Strategy::kP2pPointToPoint; + } + return nearby::sharing::Strategy::kP2pCluster; +} + +nearby::sharing::MediumSelection ToNativeMediumSelection( + const NearbyConnectionsApi::MediumSelection& selection) { + nearby::sharing::MediumSelection native_selection; + native_selection.bluetooth = selection.bluetooth; + native_selection.ble = selection.ble; + native_selection.web_rtc = selection.web_rtc; + native_selection.wifi_lan = selection.wifi_lan; + native_selection.wifi_hotspot = selection.wifi_hotspot; + return native_selection; +} + +nearby::sharing::AdvertisingOptions ToNativeAdvertisingOptions( + const NearbyConnectionsApi::AdvertisingOptions& options) { + nearby::sharing::AdvertisingOptions native_options; + native_options.strategy = ToNativeStrategy(options.strategy); + native_options.allowed_mediums = + ToNativeMediumSelection(options.allowed_mediums); + native_options.auto_upgrade_bandwidth = options.auto_upgrade_bandwidth; + native_options.enforce_topology_constraints = + options.enforce_topology_constraints; + native_options.enable_bluetooth_listening = + options.enable_bluetooth_listening; + native_options.enable_webrtc_listening = options.enable_webrtc_listening; + native_options.use_stable_endpoint_id = options.use_stable_endpoint_id; + native_options.force_new_endpoint_id = options.force_new_endpoint_id; + native_options.fast_advertisement_service_uuid = + nearby::sharing::Uuid(options.fast_advertisement_service_uuid); + return native_options; +} + +nearby::sharing::DiscoveryOptions ToNativeDiscoveryOptions( + const NearbyConnectionsApi::DiscoveryOptions& options) { + nearby::sharing::DiscoveryOptions native_options; + native_options.strategy = ToNativeStrategy(options.strategy); + native_options.allowed_mediums = + ToNativeMediumSelection(options.allowed_mediums); + if (options.has_fast_advertisement_service_uuid) { + native_options.fast_advertisement_service_uuid = + nearby::sharing::Uuid(options.fast_advertisement_service_uuid.uuid); + } + native_options.is_out_of_band_connection = + options.is_out_of_band_connection; + if (options.has_alternate_service_uuid) { + native_options.alternate_service_uuid = options.alternate_service_uuid; + } + return native_options; +} + +nearby::sharing::ConnectionOptions ToNativeConnectionOptions( + const NearbyConnectionsApi::ConnectionOptions& options) { + nearby::sharing::ConnectionOptions native_options; + native_options.allowed_mediums = + ToNativeMediumSelection(options.allowed_mediums); + if (!options.remote_bluetooth_mac_address.empty()) { + native_options.remote_bluetooth_mac_address = + options.remote_bluetooth_mac_address; + } + if (options.has_keep_alive_interval_millis && + options.keep_alive_interval_millis >= 0) { + native_options.keep_alive_interval = + absl::Milliseconds(options.keep_alive_interval_millis); + } + if (options.has_keep_alive_timeout_millis && + options.keep_alive_timeout_millis >= 0) { + native_options.keep_alive_timeout = + absl::Milliseconds(options.keep_alive_timeout_millis); + } + native_options.non_disruptive_hotspot_mode = + options.non_disruptive_hotspot_mode; + return native_options; +} + +NearbyConnectionsApi::ConnectionInfo ToFacadeConnectionInfo( + const nearby::sharing::ConnectionInfo& info) { + NearbyConnectionsApi::ConnectionInfo facade_info; + facade_info.authentication_token = info.authentication_token; + facade_info.raw_authentication_token = info.raw_authentication_token; + facade_info.endpoint_info = info.endpoint_info; + facade_info.is_incoming_connection = info.is_incoming_connection; + facade_info.connection_layer_status = + ToFacadeStatus(info.connection_layer_status); + facade_info.authentication_status = + ToFacadeAuthenticationStatus(info.authentication_status); + return facade_info; +} + +NearbyConnectionsApi::DiscoveredEndpointInfo ToFacadeDiscoveredEndpointInfo( + const nearby::sharing::DiscoveredEndpointInfo& info) { + NearbyConnectionsApi::DiscoveredEndpointInfo facade_info; + facade_info.endpoint_info = info.endpoint_info; + facade_info.service_id = info.service_id; + return facade_info; +} + +NearbyConnectionsApi::PayloadStatus ToFacadePayloadStatus( + nearby::sharing::PayloadStatus status) { + switch (status) { + case nearby::sharing::PayloadStatus::kSuccess: + return NearbyConnectionsApi::PayloadStatus::kSuccess; + case nearby::sharing::PayloadStatus::kFailure: + return NearbyConnectionsApi::PayloadStatus::kFailure; + case nearby::sharing::PayloadStatus::kInProgress: + return NearbyConnectionsApi::PayloadStatus::kInProgress; + case nearby::sharing::PayloadStatus::kCanceled: + return NearbyConnectionsApi::PayloadStatus::kCanceled; + } + return NearbyConnectionsApi::PayloadStatus::kFailure; +} + +NearbyConnectionsApi::Payload ToFacadePayload( + const nearby::sharing::Payload& payload) { + NearbyConnectionsApi::Payload facade_payload; + facade_payload.id = payload.id; + switch (payload.content.type) { + case nearby::sharing::PayloadContent::Type::kBytes: + facade_payload.type = NearbyConnectionsApi::PayloadType::kBytes; + facade_payload.bytes = payload.content.bytes_payload.bytes; + break; + case nearby::sharing::PayloadContent::Type::kFile: + facade_payload.type = NearbyConnectionsApi::PayloadType::kFile; + facade_payload.file_path = + payload.content.file_payload.file_path.ToString(); + facade_payload.parent_folder = payload.content.file_payload.parent_folder; + break; + case nearby::sharing::PayloadContent::Type::kUnknown: + case nearby::sharing::PayloadContent::Type::kStream: + facade_payload.type = NearbyConnectionsApi::PayloadType::kUnknown; + break; + } + return facade_payload; +} + +std::unique_ptr ToNativePayload( + NearbyConnectionsApi::Payload payload) { + switch (payload.type) { + case NearbyConnectionsApi::PayloadType::kBytes: + return std::make_unique( + payload.id, std::move(payload.bytes)); + case NearbyConnectionsApi::PayloadType::kFile: + return std::make_unique( + payload.id, FilePath(payload.file_path), payload.parent_folder); + case NearbyConnectionsApi::PayloadType::kUnknown: + return nullptr; + } + return nullptr; +} + +NearbyConnectionsApi::PayloadTransferUpdate ToFacadePayloadTransferUpdate( + const nearby::sharing::PayloadTransferUpdate& update) { + NearbyConnectionsApi::PayloadTransferUpdate facade_update; + facade_update.payload_id = update.payload_id; + facade_update.status = ToFacadePayloadStatus(update.status); + facade_update.total_bytes = update.total_bytes; + facade_update.bytes_transferred = update.bytes_transferred; + return facade_update; +} + +} // namespace + +class NearbyConnectionsApi::Impl { + public: + struct ListenerState { + std::mutex mutex; + NearbyConnectionsApi::Listener listener; + }; + + Impl() + : platform(), + context(platform), + service(std::make_unique( + context.GetConnectivityManager(), /*event_logger=*/nullptr)), + listener_state(std::make_shared()) {} + + static NearbyConnectionsApi::Listener CopyListener( + const std::shared_ptr& listener_state) { + std::scoped_lock lock(listener_state->mutex); + return listener_state->listener; + } + + static NativeService::ConnectionListener BuildConnectionListener( + const std::shared_ptr& listener_state) { + NativeService::ConnectionListener listener; + listener.initiated_cb = + [listener_state](const std::string& endpoint_id, + const nearby::sharing::ConnectionInfo& info) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (!listener_copy.connection_initiated_cb) { + return; + } + listener_copy.connection_initiated_cb(endpoint_id, + ToFacadeConnectionInfo(info)); + }; + listener.accepted_cb = [listener_state](const std::string& endpoint_id) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.connection_accepted_cb) { + listener_copy.connection_accepted_cb(endpoint_id); + } + }; + listener.rejected_cb = [listener_state](const std::string& endpoint_id, + Status status) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.connection_rejected_cb) { + listener_copy.connection_rejected_cb(endpoint_id, + ToFacadeStatus(status)); + } + }; + listener.disconnected_cb = [listener_state](const std::string& endpoint_id) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.disconnected_cb) { + listener_copy.disconnected_cb(endpoint_id); + } + }; + listener.bandwidth_changed_cb = + [listener_state](const std::string& endpoint_id, + nearby::sharing::Medium medium) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.bandwidth_changed_cb) { + listener_copy.bandwidth_changed_cb(endpoint_id, + ToFacadeMedium(medium)); + } + }; + return listener; + } + + static NativeService::DiscoveryListener BuildDiscoveryListener( + const std::shared_ptr& listener_state) { + NativeService::DiscoveryListener listener; + listener.endpoint_found_cb = + [listener_state](const std::string& endpoint_id, + const nearby::sharing::DiscoveredEndpointInfo& info) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.endpoint_found_cb) { + listener_copy.endpoint_found_cb( + endpoint_id, ToFacadeDiscoveredEndpointInfo(info)); + } + }; + listener.endpoint_lost_cb = [listener_state](const std::string& endpoint_id) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.endpoint_lost_cb) { + listener_copy.endpoint_lost_cb(endpoint_id); + } + }; + listener.endpoint_distance_changed_cb = + [listener_state](const std::string& endpoint_id, + nearby::sharing::DistanceInfo distance_info) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.endpoint_distance_changed_cb) { + listener_copy.endpoint_distance_changed_cb( + endpoint_id, ToFacadeDistanceInfo(distance_info)); + } + }; + return listener; + } + + static NativeService::PayloadListener BuildPayloadListener( + const std::shared_ptr& listener_state) { + NativeService::PayloadListener listener; + listener.payload_cb = [listener_state](absl::string_view endpoint_id, + nearby::sharing::Payload payload) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.payload_received_cb) { + listener_copy.payload_received_cb(std::string(endpoint_id), + ToFacadePayload(payload)); + } + }; + listener.payload_progress_cb = + [listener_state]( + absl::string_view endpoint_id, + const nearby::sharing::PayloadTransferUpdate& update) { + NearbyConnectionsApi::Listener listener_copy = + CopyListener(listener_state); + if (listener_copy.payload_transfer_update_cb) { + listener_copy.payload_transfer_update_cb( + std::string(endpoint_id), ToFacadePayloadTransferUpdate(update)); + } + }; + return listener; + } + + LinuxSharingPlatform platform; + ::nearby::ContextImpl context; + std::unique_ptr service; + std::shared_ptr listener_state; +}; + +NearbyConnectionsApi::NearbyConnectionsApi() : impl_(std::make_unique()) {} + +NearbyConnectionsApi::~NearbyConnectionsApi() = default; + +NearbyConnectionsApi::NearbyConnectionsApi(NearbyConnectionsApi&&) noexcept = + default; + +NearbyConnectionsApi& NearbyConnectionsApi::operator=( + NearbyConnectionsApi&&) noexcept = default; + +void NearbyConnectionsApi::SetListener(Listener listener) { + std::scoped_lock lock(impl_->listener_state->mutex); + impl_->listener_state->listener = std::move(listener); +} + +void NearbyConnectionsApi::StartAdvertising( + const std::string& service_id, const std::vector& endpoint_info, + const AdvertisingOptions& options, + std::function callback) { + impl_->service->StartAdvertising( + service_id, endpoint_info, ToNativeAdvertisingOptions(options), + Impl::BuildConnectionListener(impl_->listener_state), + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::StopAdvertising( + const std::string& service_id, std::function callback) { + impl_->service->StopAdvertising( + service_id, [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::StartDiscovery( + const std::string& service_id, const DiscoveryOptions& options, + std::function callback) { + impl_->service->StartDiscovery( + service_id, ToNativeDiscoveryOptions(options), + Impl::BuildDiscoveryListener(impl_->listener_state), + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::StopDiscovery( + const std::string& service_id, std::function callback) { + impl_->service->StopDiscovery( + service_id, [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::RequestConnection( + const std::string& service_id, const std::vector& endpoint_info, + const std::string& endpoint_id, const ConnectionOptions& options, + std::function callback) { + impl_->service->RequestConnection( + service_id, endpoint_info, endpoint_id, ToNativeConnectionOptions(options), + Impl::BuildConnectionListener(impl_->listener_state), + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::DisconnectFromEndpoint( + const std::string& service_id, const std::string& endpoint_id, + std::function callback) { + impl_->service->DisconnectFromEndpoint( + service_id, endpoint_id, + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::SendPayload( + const std::string& service_id, const std::vector& endpoint_ids, + Payload payload, std::function callback) { + std::unique_ptr native_payload = + ToNativePayload(std::move(payload)); + if (native_payload == nullptr) { + if (callback) { + callback(StatusCode::kError); + } + return; + } + + impl_->service->SendPayload( + service_id, endpoint_ids, std::move(native_payload), + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::CancelPayload( + const std::string& service_id, int64_t payload_id, + std::function callback) { + impl_->service->CancelPayload( + service_id, payload_id, + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::InitiateBandwidthUpgrade( + const std::string& service_id, const std::string& endpoint_id, + std::function callback) { + impl_->service->InitiateBandwidthUpgrade( + service_id, endpoint_id, + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::AcceptConnection( + const std::string& service_id, const std::string& endpoint_id, + std::function callback) { + impl_->service->AcceptConnection( + service_id, endpoint_id, Impl::BuildPayloadListener(impl_->listener_state), + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::StopAllEndpoints( + std::function callback) { + impl_->service->StopAllEndpoints( + [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::SetCustomSavePath( + const std::string& path, std::function callback) { + impl_->service->SetCustomSavePath( + path, [callback = std::move(callback)](Status status) mutable { + if (callback) { + callback(ToFacadeStatus(status)); + } + }); +} + +void NearbyConnectionsApi::OverrideSavePath(const std::string& endpoint_id, + const std::string& path) { + impl_->service->OverrideSavePath(endpoint_id, path); +} + +std::string NearbyConnectionsApi::Dump() const { return impl_->service->Dump(); } + +std::string NearbyConnectionsApi::StatusCodeToString(StatusCode status) { + switch (status) { + case StatusCode::kSuccess: + return "Success"; + case StatusCode::kError: + return "Error"; + case StatusCode::kOutOfOrderApiCall: + return "OutOfOrderApiCall"; + case StatusCode::kAlreadyHaveActiveStrategy: + return "AlreadyHaveActiveStrategy"; + case StatusCode::kAlreadyAdvertising: + return "AlreadyAdvertising"; + case StatusCode::kAlreadyDiscovering: + return "AlreadyDiscovering"; + case StatusCode::kAlreadyListening: + return "AlreadyListening"; + case StatusCode::kEndpointIOError: + return "EndpointIOError"; + case StatusCode::kEndpointUnknown: + return "EndpointUnknown"; + case StatusCode::kConnectionRejected: + return "ConnectionRejected"; + case StatusCode::kAlreadyConnectedToEndpoint: + return "AlreadyConnectedToEndpoint"; + case StatusCode::kNotConnectedToEndpoint: + return "NotConnectedToEndpoint"; + case StatusCode::kBluetoothError: + return "BluetoothError"; + case StatusCode::kBleError: + return "BleError"; + case StatusCode::kWifiLanError: + return "WifiLanError"; + case StatusCode::kPayloadUnknown: + return "PayloadUnknown"; + case StatusCode::kReset: + return "Reset"; + case StatusCode::kTimeout: + return "Timeout"; + case StatusCode::kUnknown: + return "Unknown"; + } + return "Unknown"; +} + +} // namespace nearby::sharing diff --git a/sharing/linux/nearby_connections_api.h b/sharing/linux/nearby_connections_api.h new file mode 100644 index 00000000..e81f6d97 --- /dev/null +++ b/sharing/linux/nearby_connections_api.h @@ -0,0 +1,268 @@ +// Copyright 2026 +// +// Thin app-facing API for NearbyConnectionsService on Linux that avoids +// exposing internal Nearby headers to external consumers. + +#ifndef SHARING_LINUX_NEARBY_CONNECTIONS_API_H_ +#define SHARING_LINUX_NEARBY_CONNECTIONS_API_H_ + +#include + +#include +#include +#include +#include + +namespace nearby { +namespace sharing { + +class __attribute__((visibility("default"))) NearbyConnectionsApi { + public: + enum class StatusCode { + kSuccess = 0, + kError = 1, + kOutOfOrderApiCall = 2, + kAlreadyHaveActiveStrategy = 3, + kAlreadyAdvertising = 4, + kAlreadyDiscovering = 5, + kAlreadyListening = 6, + kEndpointIOError = 7, + kEndpointUnknown = 8, + kConnectionRejected = 9, + kAlreadyConnectedToEndpoint = 10, + kNotConnectedToEndpoint = 11, + kBluetoothError = 12, + kBleError = 13, + kWifiLanError = 14, + kPayloadUnknown = 15, + kReset = 16, + kTimeout = 17, + kUnknown = 18, + }; + + enum class Strategy { + kP2pCluster = 0, + kP2pStar = 1, + kP2pPointToPoint = 2, + }; + + enum class Medium { + kUnknown = 0, + kMdns = 1, + kBluetooth = 2, + kWifiHotspot = 3, + kBle = 4, + kWifiLan = 5, + kWifiAware = 6, + kNfc = 7, + kWifiDirect = 8, + kWebRtc = 9, + kBleL2Cap = 10, + }; + + enum class DistanceInfo { + kUnknown = 1, + kVeryClose = 2, + kClose = 3, + kFar = 4, + }; + + enum class AuthenticationStatus { + kUnknown = 0, + kSuccess = 1, + kFailure = 2, + }; + + enum class PayloadType { + kUnknown = 0, + kBytes = 1, + kFile = 2, + }; + + enum class PayloadStatus { + kSuccess = 0, + kFailure = 1, + kInProgress = 2, + kCanceled = 3, + }; + + struct Uuid { + std::string uuid; + }; + + struct MediumSelection { + bool bluetooth = true; + bool ble = true; + bool web_rtc = true; + bool wifi_lan = true; + bool wifi_hotspot = true; + }; + + struct AdvertisingOptions { + Strategy strategy = Strategy::kP2pCluster; + MediumSelection allowed_mediums; + bool auto_upgrade_bandwidth = true; + bool enforce_topology_constraints = true; + bool enable_bluetooth_listening = false; + bool enable_webrtc_listening = false; + bool use_stable_endpoint_id = false; + bool force_new_endpoint_id = false; + std::string fast_advertisement_service_uuid; + }; + + struct DiscoveryOptions { + Strategy strategy = Strategy::kP2pCluster; + MediumSelection allowed_mediums; + bool has_fast_advertisement_service_uuid = false; + Uuid fast_advertisement_service_uuid; + bool is_out_of_band_connection = false; + bool has_alternate_service_uuid = false; + uint16_t alternate_service_uuid = 0; + }; + + struct ConnectionOptions { + MediumSelection allowed_mediums; + std::vector remote_bluetooth_mac_address; + bool has_keep_alive_interval_millis = false; + int64_t keep_alive_interval_millis = 0; + bool has_keep_alive_timeout_millis = false; + int64_t keep_alive_timeout_millis = 0; + bool non_disruptive_hotspot_mode = false; + }; + + struct ConnectionInfo { + std::string authentication_token; + std::vector raw_authentication_token; + std::vector endpoint_info; + bool is_incoming_connection = false; + StatusCode connection_layer_status = StatusCode::kUnknown; + AuthenticationStatus authentication_status = + AuthenticationStatus::kUnknown; + }; + + struct DiscoveredEndpointInfo { + std::vector endpoint_info; + std::string service_id; + }; + + struct PayloadTransferUpdate { + int64_t payload_id = 0; + PayloadStatus status = PayloadStatus::kInProgress; + uint64_t total_bytes = 0; + uint64_t bytes_transferred = 0; + }; + + struct Payload { + int64_t id = 0; + PayloadType type = PayloadType::kUnknown; + std::vector bytes; + std::string file_path; + std::string parent_folder; + + static Payload FromBytes(int64_t id, std::vector bytes) { + Payload payload; + payload.id = id; + payload.type = PayloadType::kBytes; + payload.bytes = std::move(bytes); + return payload; + } + + static Payload FromFile(int64_t id, std::string file_path, + std::string parent_folder = {}) { + Payload payload; + payload.id = id; + payload.type = PayloadType::kFile; + payload.file_path = std::move(file_path); + payload.parent_folder = std::move(parent_folder); + return payload; + } + }; + + struct Listener { + std::function + endpoint_found_cb; + std::function endpoint_lost_cb; + std::function + endpoint_distance_changed_cb; + + std::function + connection_initiated_cb; + std::function connection_accepted_cb; + std::function connection_rejected_cb; + std::function disconnected_cb; + std::function bandwidth_changed_cb; + + std::function + payload_received_cb; + std::function + payload_transfer_update_cb; + }; + + NearbyConnectionsApi(); + ~NearbyConnectionsApi(); + + NearbyConnectionsApi(const NearbyConnectionsApi&) = delete; + NearbyConnectionsApi& operator=(const NearbyConnectionsApi&) = delete; + NearbyConnectionsApi(NearbyConnectionsApi&&) noexcept; + NearbyConnectionsApi& operator=(NearbyConnectionsApi&&) noexcept; + + void SetListener(Listener listener); + + void StartAdvertising(const std::string& service_id, + const std::vector& endpoint_info, + const AdvertisingOptions& options, + std::function callback); + void StopAdvertising(const std::string& service_id, + std::function callback); + + void StartDiscovery(const std::string& service_id, + const DiscoveryOptions& options, + std::function callback); + void StopDiscovery(const std::string& service_id, + std::function callback); + + void RequestConnection(const std::string& service_id, + const std::vector& endpoint_info, + const std::string& endpoint_id, + const ConnectionOptions& options, + std::function callback); + + void DisconnectFromEndpoint(const std::string& service_id, + const std::string& endpoint_id, + std::function callback); + + void SendPayload(const std::string& service_id, + const std::vector& endpoint_ids, + Payload payload, + std::function callback); + void CancelPayload(const std::string& service_id, int64_t payload_id, + std::function callback); + + void InitiateBandwidthUpgrade(const std::string& service_id, + const std::string& endpoint_id, + std::function callback); + + void AcceptConnection(const std::string& service_id, + const std::string& endpoint_id, + std::function callback); + + void StopAllEndpoints(std::function callback); + + void SetCustomSavePath(const std::string& path, + std::function callback); + void OverrideSavePath(const std::string& endpoint_id, + const std::string& path); + + std::string Dump() const; + + static std::string StatusCodeToString(StatusCode status); + + private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace sharing +} // namespace nearby + +#endif // SHARING_LINUX_NEARBY_CONNECTIONS_API_H_ diff --git a/sharing/linux/nearby_connections_api_test.cc b/sharing/linux/nearby_connections_api_test.cc new file mode 100644 index 00000000..dfcfe6cc --- /dev/null +++ b/sharing/linux/nearby_connections_api_test.cc @@ -0,0 +1,44 @@ +#include "sharing/linux/nearby_connections_api.h" + +#include + +#include "gtest/gtest.h" + +namespace nearby::sharing { +namespace { + +TEST(NearbyConnectionsApiTest, PayloadFromBytesSetsFields) { + NearbyConnectionsApi::Payload payload = + NearbyConnectionsApi::Payload::FromBytes(42, {1, 2, 3}); + + EXPECT_EQ(payload.id, 42); + EXPECT_EQ(payload.type, NearbyConnectionsApi::PayloadType::kBytes); + EXPECT_EQ(payload.bytes, (std::vector{1, 2, 3})); + EXPECT_TRUE(payload.file_path.empty()); +} + +TEST(NearbyConnectionsApiTest, PayloadFromFileSetsFields) { + NearbyConnectionsApi::Payload payload = + NearbyConnectionsApi::Payload::FromFile(7, "/tmp/test.txt", "tmp"); + + EXPECT_EQ(payload.id, 7); + EXPECT_EQ(payload.type, NearbyConnectionsApi::PayloadType::kFile); + EXPECT_EQ(payload.file_path, "/tmp/test.txt"); + EXPECT_EQ(payload.parent_folder, "tmp"); + EXPECT_TRUE(payload.bytes.empty()); +} + +TEST(NearbyConnectionsApiTest, StatusCodeToStringCoversRepresentativeValues) { + EXPECT_EQ(NearbyConnectionsApi::StatusCodeToString( + NearbyConnectionsApi::StatusCode::kSuccess), + "Success"); + EXPECT_EQ(NearbyConnectionsApi::StatusCodeToString( + NearbyConnectionsApi::StatusCode::kConnectionRejected), + "ConnectionRejected"); + EXPECT_EQ(NearbyConnectionsApi::StatusCodeToString( + NearbyConnectionsApi::StatusCode::kUnknown), + "Unknown"); +} + +} // namespace +} // namespace nearby::sharing From 161ceedfac2e6b6bfb7c9cfc7b1fb24162f0edba Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Mon, 6 Apr 2026 22:26:40 +0530 Subject: [PATCH 16/26] re enabled GATT server. Android requires GATT characteristics to be preset to initiate l2cap connection --- internal/platform/implementation/linux/ble_v2_medium.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc index d50c3a22..6e0892bb 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.cc +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -470,9 +470,9 @@ bool BleV2Medium::StopScanning() { std::unique_ptr BleV2Medium::StartGattServer( api::ble::ServerGattConnectionCallback callback) { - (void)callback; - LOG(INFO) << __func__ << ": GATT is disabled on linux."; - return nullptr; + LOG(INFO) << __func__ << ": Starting Linux GATT server."; + return std::make_unique(*system_bus_, adapter_, devices_, + std::move(callback)); } std::unique_ptr BleV2Medium::ConnectToGattServer( From b85bb5f7127b6c6387c3bd695f223545d53dbb6a Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Mon, 6 Apr 2026 22:28:13 +0530 Subject: [PATCH 17/26] added stream payload type. --- sharing/BUILD | 1 + sharing/nearby_connections_service.cc | 67 +++++++++++++++++++++++++++ sharing/nearby_connections_types.h | 21 +++++++++ 3 files changed, 89 insertions(+) diff --git a/sharing/BUILD b/sharing/BUILD index 61c9d32d..83c10b5b 100644 --- a/sharing/BUILD +++ b/sharing/BUILD @@ -24,6 +24,7 @@ cc_library( "//internal/base:file_path", "//internal/base:files", "//internal/crypto_cros", # buildcleaner: keep + "//internal/platform:base", "//internal/interop:authentication_status", "@com_google_absl//absl/random", "@com_google_absl//absl/strings:string_view", diff --git a/sharing/nearby_connections_service.cc b/sharing/nearby_connections_service.cc index 3d8f5799..db617206 100644 --- a/sharing/nearby_connections_service.cc +++ b/sharing/nearby_connections_service.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,61 @@ using NcPayload = ::nearby::connections::Payload; using NcStatus = ::nearby::connections::Status; using NcStrategy = ::nearby::connections::Strategy; +class VectorInputStream final : public InputStream { + public: + explicit VectorInputStream(std::vector bytes) + : bytes_(std::move(bytes)) {} + + ExceptionOr Read(std::int64_t size) override { + if (closed_) { + return ExceptionOr(Exception::kIo); + } + if (offset_ >= bytes_.size() || size <= 0) { + return ExceptionOr(ByteArray()); + } + + const size_t remaining = bytes_.size() - offset_; + const size_t chunk_size = std::min(remaining, static_cast(size)); + ByteArray chunk(reinterpret_cast(bytes_.data() + offset_), + chunk_size); + offset_ += chunk_size; + return ExceptionOr(std::move(chunk)); + } + + Exception Close() override { + closed_ = true; + return {Exception::kSuccess}; + } + + private: + std::vector bytes_; + size_t offset_ = 0; + bool closed_ = false; +}; + +class SharedInputStream final : public InputStream { + public: + explicit SharedInputStream(std::shared_ptr stream) + : stream_(std::move(stream)) {} + + ExceptionOr Read(std::int64_t size) override { + if (stream_ == nullptr) { + return ExceptionOr(Exception::kIo); + } + return stream_->Read(size); + } + + Exception Close() override { + if (stream_ == nullptr) { + return {Exception::kIo}; + } + return stream_->Close(); + } + + private: + std::shared_ptr stream_; +}; + Status ConvertToStatus(NcStatus status) { return static_cast(status.value); } @@ -86,6 +142,17 @@ NcPayload ConvertToServicePayload(Payload payload) { return NcPayload(payload.id, ByteArray(std::string(bytes.begin(), bytes.end()))); } + case PayloadContent::Type::kStream: { + if (payload.content.stream_payload.input_stream != nullptr) { + return NcPayload( + payload.id, + std::make_unique( + std::move(payload.content.stream_payload.input_stream))); + } + auto stream = std::make_unique( + std::move(payload.content.stream_payload.bytes)); + return NcPayload(payload.id, std::move(stream)); + } default: return NcPayload(); } diff --git a/sharing/nearby_connections_types.h b/sharing/nearby_connections_types.h index 6112778e..6fade2f4 100644 --- a/sharing/nearby_connections_types.h +++ b/sharing/nearby_connections_types.h @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "absl/strings/string_view.h" #include "absl/time/time.h" #include "internal/base/file_path.h" +#include "internal/platform/input_stream.h" #include "internal/interop/authentication_status.h" namespace nearby { @@ -362,6 +364,14 @@ struct BytesPayload { std::vector bytes; }; +// A stream payload backed by an in-memory byte buffer on the sender side. +struct StreamPayload { + // The bytes to expose through the stream. + std::vector bytes; + // Optional live stream source for long-lived outgoing stream payloads. + std::shared_ptr input_stream; +}; + // A file payload representing a file. struct FilePayload { // The file to which this payload points to. When sending this payload, the @@ -375,6 +385,8 @@ struct FilePayload { struct PayloadContent { // A Payload consisting of a single byte array. BytesPayload bytes_payload; + // A Payload exposed to Nearby as a finite stream. + StreamPayload stream_payload; // A Payload representing a file on the device. FilePayload file_payload; enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 }; @@ -400,6 +412,9 @@ struct Payload { explicit Payload(std::vector bytes) : Payload(GenerateId(), std::move(bytes)) {} + explicit Payload(StreamPayload stream_payload) + : Payload(GenerateId(), std::move(stream_payload)) {} + explicit Payload(FilePath file_path, absl::string_view parent_folder = absl::string_view()) : Payload(std::hash()(file_path.ToString()), file_path, @@ -410,6 +425,12 @@ struct Payload { content.bytes_payload.bytes = std::move(bytes); } + Payload(int64_t id, StreamPayload stream_payload) : id(id) { + content.type = PayloadContent::Type::kStream; + content.stream_payload.bytes = std::move(stream_payload.bytes); + content.stream_payload.input_stream = std::move(stream_payload.input_stream); + } + Payload(int64_t id, FilePath file_path, absl::string_view parent_folder = absl::string_view()) : id(id) { From bfbab97c89e08bc054dd1ab31d754efd09c2aaee Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Mon, 6 Apr 2026 22:31:01 +0530 Subject: [PATCH 18/26] added stream payload type to nearby_connections_api --- sharing/linux/BUILD | 2 ++ sharing/linux/nearby_connections_api.cc | 12 ++++++- sharing/linux/nearby_connections_api.h | 22 +++++++++++++ sharing/linux/nearby_connections_api_test.cc | 31 +++++++++++++++++++ .../linux/qml_tray_app/file_share_state.cc | 1 - 5 files changed, 66 insertions(+), 2 deletions(-) diff --git a/sharing/linux/BUILD b/sharing/linux/BUILD index cba30b17..2ea5d434 100644 --- a/sharing/linux/BUILD +++ b/sharing/linux/BUILD @@ -71,6 +71,7 @@ cc_library( alwayslink = True, visibility = ["//visibility:public"], deps = [ + "//internal/platform:base", ":linux_sharing_platform", "//internal/base:file_path", "//sharing/internal/public:nearby_context", @@ -95,6 +96,7 @@ cc_binary( "-Wl,--exclude-libs,ALL", ], deps = [ + "//internal/platform:base", ":linux_sharing_platform", "//internal/base:file_path", "//internal/platform/implementation:platform", diff --git a/sharing/linux/nearby_connections_api.cc b/sharing/linux/nearby_connections_api.cc index 2ff2028a..4eaa04a4 100644 --- a/sharing/linux/nearby_connections_api.cc +++ b/sharing/linux/nearby_connections_api.cc @@ -254,6 +254,10 @@ NearbyConnectionsApi::Payload ToFacadePayload( facade_payload.type = NearbyConnectionsApi::PayloadType::kBytes; facade_payload.bytes = payload.content.bytes_payload.bytes; break; + case nearby::sharing::PayloadContent::Type::kStream: + facade_payload.type = NearbyConnectionsApi::PayloadType::kStream; + facade_payload.stream_bytes = payload.content.stream_payload.bytes; + break; case nearby::sharing::PayloadContent::Type::kFile: facade_payload.type = NearbyConnectionsApi::PayloadType::kFile; facade_payload.file_path = @@ -261,7 +265,6 @@ NearbyConnectionsApi::Payload ToFacadePayload( facade_payload.parent_folder = payload.content.file_payload.parent_folder; break; case nearby::sharing::PayloadContent::Type::kUnknown: - case nearby::sharing::PayloadContent::Type::kStream: facade_payload.type = NearbyConnectionsApi::PayloadType::kUnknown; break; } @@ -277,6 +280,13 @@ std::unique_ptr ToNativePayload( case NearbyConnectionsApi::PayloadType::kFile: return std::make_unique( payload.id, FilePath(payload.file_path), payload.parent_folder); + case NearbyConnectionsApi::PayloadType::kStream: { + nearby::sharing::StreamPayload stream_payload; + stream_payload.bytes = std::move(payload.stream_bytes); + stream_payload.input_stream = std::move(payload.stream_input); + return std::make_unique(payload.id, + std::move(stream_payload)); + } case NearbyConnectionsApi::PayloadType::kUnknown: return nullptr; } diff --git a/sharing/linux/nearby_connections_api.h b/sharing/linux/nearby_connections_api.h index e81f6d97..5be0975b 100644 --- a/sharing/linux/nearby_connections_api.h +++ b/sharing/linux/nearby_connections_api.h @@ -13,6 +13,8 @@ #include #include +#include "internal/platform/input_stream.h" + namespace nearby { namespace sharing { @@ -77,6 +79,7 @@ class __attribute__((visibility("default"))) NearbyConnectionsApi { kUnknown = 0, kBytes = 1, kFile = 2, + kStream = 3, }; enum class PayloadStatus { @@ -156,6 +159,8 @@ class __attribute__((visibility("default"))) NearbyConnectionsApi { int64_t id = 0; PayloadType type = PayloadType::kUnknown; std::vector bytes; + std::vector stream_bytes; + std::shared_ptr stream_input; std::string file_path; std::string parent_folder; @@ -176,6 +181,23 @@ class __attribute__((visibility("default"))) NearbyConnectionsApi { payload.parent_folder = std::move(parent_folder); return payload; } + + static Payload FromStream(int64_t id, std::vector stream_bytes) { + Payload payload; + payload.id = id; + payload.type = PayloadType::kStream; + payload.stream_bytes = std::move(stream_bytes); + return payload; + } + + static Payload FromInputStream(int64_t id, + std::shared_ptr stream_input) { + Payload payload; + payload.id = id; + payload.type = PayloadType::kStream; + payload.stream_input = std::move(stream_input); + return payload; + } }; struct Listener { diff --git a/sharing/linux/nearby_connections_api_test.cc b/sharing/linux/nearby_connections_api_test.cc index dfcfe6cc..c34eefaa 100644 --- a/sharing/linux/nearby_connections_api_test.cc +++ b/sharing/linux/nearby_connections_api_test.cc @@ -1,8 +1,10 @@ #include "sharing/linux/nearby_connections_api.h" +#include #include #include "gtest/gtest.h" +#include "internal/platform/pipe.h" namespace nearby::sharing { namespace { @@ -14,6 +16,7 @@ TEST(NearbyConnectionsApiTest, PayloadFromBytesSetsFields) { EXPECT_EQ(payload.id, 42); EXPECT_EQ(payload.type, NearbyConnectionsApi::PayloadType::kBytes); EXPECT_EQ(payload.bytes, (std::vector{1, 2, 3})); + EXPECT_TRUE(payload.stream_bytes.empty()); EXPECT_TRUE(payload.file_path.empty()); } @@ -26,6 +29,34 @@ TEST(NearbyConnectionsApiTest, PayloadFromFileSetsFields) { EXPECT_EQ(payload.file_path, "/tmp/test.txt"); EXPECT_EQ(payload.parent_folder, "tmp"); EXPECT_TRUE(payload.bytes.empty()); + EXPECT_TRUE(payload.stream_bytes.empty()); +} + +TEST(NearbyConnectionsApiTest, PayloadFromStreamSetsFields) { + NearbyConnectionsApi::Payload payload = + NearbyConnectionsApi::Payload::FromStream(9, {4, 5, 6}); + + EXPECT_EQ(payload.id, 9); + EXPECT_EQ(payload.type, NearbyConnectionsApi::PayloadType::kStream); + EXPECT_EQ(payload.stream_bytes, (std::vector{4, 5, 6})); + EXPECT_TRUE(payload.bytes.empty()); + EXPECT_TRUE(payload.file_path.empty()); +} + +TEST(NearbyConnectionsApiTest, PayloadFromInputStreamSetsFields) { + auto [input, output] = CreatePipe(); + (void)output; + + NearbyConnectionsApi::Payload payload = + NearbyConnectionsApi::Payload::FromInputStream( + 10, std::shared_ptr(std::move(input))); + + EXPECT_EQ(payload.id, 10); + EXPECT_EQ(payload.type, NearbyConnectionsApi::PayloadType::kStream); + EXPECT_TRUE(payload.stream_bytes.empty()); + EXPECT_NE(payload.stream_input, nullptr); + EXPECT_TRUE(payload.bytes.empty()); + EXPECT_TRUE(payload.file_path.empty()); } TEST(NearbyConnectionsApiTest, StatusCodeToStringCoversRepresentativeValues) { diff --git a/sharing/linux/qml_tray_app/file_share_state.cc b/sharing/linux/qml_tray_app/file_share_state.cc index 06cf4273..4e416652 100644 --- a/sharing/linux/qml_tray_app/file_share_state.cc +++ b/sharing/linux/qml_tray_app/file_share_state.cc @@ -78,7 +78,6 @@ void FileShareState::AddOrUpdateTransfer( {QStringLiteral("filePath"), file_path}, }; - qInfo() << "qDiscovered targets" << discoveredTargets(); if (transfer_row_by_target_.contains(target_id)) { const int row_index = transfer_row_by_target_.value(target_id); if (row_index >= 0 && row_index < transfers_.size()) { From f8b461b60a71557e81fe0f6663e807e5e0c47a27 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana <78136120+kidfromjupiter@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:36:11 +0530 Subject: [PATCH 19/26] updated readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 047bcb14..eedc7c54 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Google Nearby Sharing & Connections for Linux (Unofficial) -> P2P file sharing, discovery, and connectivity on Linux using Google Nearby protocols. +image + -![](cover.png) ## Demo From fbf6f9d0c9d76d9e9407237c056a0066558d07cc Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Mon, 6 Apr 2026 22:57:10 +0530 Subject: [PATCH 20/26] upddated readme --- README.md | 156 +++++++++--------------------------------------------- 1 file changed, 24 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index eedc7c54..104b4a53 100644 --- a/README.md +++ b/README.md @@ -9,142 +9,34 @@ https://github.com/user-attachments/assets/048afa1e-40a4-4351-a859-c81b642fc6e3 +## What +This repo consists of the entirety of google's open source nearby library. Currently, it is seperated into 3 sections. + - Sharing + - Connections + - Presence +Linux specific implementation and compatibility shims are provided for building **Sharing** and **Connections**. +Nearby presence may or may not build. I haven't tested it. + +## Why +This repo could've been a PR on the official repo. All I've done is implement the platform abstraction layer google has provided for a linux specific environment. +It's not like I haven't made a PR towards the official repo. I got tired of begging the official nearby maintainers for a PR review. So here we are. +Moreover, all I've wanted was seamless file sharing between my android devices and my linux workstation + laptop. With this repo and the example application provided here, +it accomplishes that goal perfectly. This repo wasn't created out of any altruistic goals or out of the goodness of my heart. I had a problem. I solved it. Simple as that. -This is an unofficial Linux-focused fork of Nearby. -## Purpose +## How -The primary goal of this repository is to provide reusable Linux libraries/APIs for Nearby. +### How to install +*coming soon* +### How to build +*coming soon* +### How to contribute +*coming soon* -Nearby Share sample applications are included and will continue to be supported, but they are examples first and library integration is the main long-term target. +## Special thanks -## Documentation - -Technical deep dives are being moved to the project wiki so this README can stay focused on setup and status. - -- Wiki: https://github.com/kidfromjupiter/nearby/wiki - -## Requirements - -To run this project on Linux, you need: -- BlueZ `5.86` or higher -- `NetworkManager` -- `Avahi` -- Qt (required for the tray UI app) - -## Installation (End Users) - -1. Download the latest release archive from the Releases page. -- https://github.com/kidfromjupiter/nearby/releases - -2. Extract it: - -```bash -tar -xzf nearby-file-share-linux-*.tar.gz -``` - -3. Install the application, shared library, and launcher: - (run this from the extracted bundle directory) - -```bash -./install_nearby_file_share.sh -``` - -4. Apply a temporary BlueZ service override (required for now). - Create/edit the `bluetooth.service` override: - -```bash -sudo systemctl edit bluetooth -``` - -Add: - -```ini -[Service] -ExecStart= -ExecStart=/usr/local/libexec/bluetooth/bluetoothd -E --debug=src/plugin.c --noplugin=bap,bass,mcp,vcp,micp,ccp,csip,tmap,asha,midi -``` - -Then reload and restart: - -```bash -sudo systemctl daemon-reload -sudo systemctl restart bluetooth -``` - -These plugins are currently disabled due to an ongoing likely upstream BlueZ bug: -`bap,bass,mcp,vcp,micp,ccp,csip,tmap,asha,midi`. -This workaround will be removed once the issue is fixed. - -This installs to `~/.local` by default. - -For system-wide installation: - -```bash -./install_nearby_file_share.sh --system -``` - -The installer also installs: -- `nearby_qml_file_tray_app` -- `libnearby_sharing_api_shared.so` -- `.desktop` launcher entry -- launcher icon - -## Build From Source - -Build and install the Linux Nearby Sharing library: - -```bash -./sharing/linux/install_nearby_sharing_service.sh -``` - -Build tray sample app: - -```bash -cd sharing/linux/qml_tray_app -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNEARBY_PREFIX=/usr/local -cmake --build build -j -./build/nearby_qml_file_tray_app -``` - -## Current Status and Compatibility - -The Linux implementation currently works with the reverse-engineered certificate manager used in this project. - -Compatibility can still break if Google changes certificate manager behavior or protocol details. That component is closed source, so upstream regressions may be hard to diagnose immediately. - -## Test Scope and Known Limits - -- Single-file sharing has been tested. -- Multi-transfer behavior in one app lifetime has not been fully validated. -- Current practical testing often restarts the app before a new transfer. - -In this project, a "session" means one application lifetime (start to exit). - -After one transfer, some endpoints/internal state can reset or change, and this is still being investigated. - -## Known Issues - -- Linux hotspot startup can be slow. -- Connecting to another device's hotspot can be slow on Linux. -- Android-initiated connection formation can be very slow. -- 5 GHz hotspot discovery and connection support is currently removed due to Intel Wi-Fi driver instability related to LAR behavior on Linux. There is no known fix yet; this may be re-enabled in a later release. - -A likely area to investigate is connection negotiation paths that Linux may not handle optimally yet. - -## Wi-Fi Direct - -Wi-Fi Direct is theoretically possible, but not implemented yet. - -Reason: NetworkManager does not natively support the Wi-Fi Direct Group Owner flow required here. - -## Contributing - -- General: `CONTRIBUTING.md` -- Linux-specific: `LINUX_CONTRIBUTING.md` - -## License - -Released under `LICENSE`. +https://github.com/proatgram and https://github.com/vibhavp +*they were the original authors of the linux platform support [PR](https://github.com/google/nearby/pull/2098) that I have based much of this codebase upon. I am standing on the shoulders +of giants* From ccdd43ec57d18e05c5d2921baa22ac7c701398f8 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana <78136120+kidfromjupiter@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:58:01 +0530 Subject: [PATCH 21/26] formatting --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 104b4a53..974ddceb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This repo consists of the entirety of google's open source nearby library. Curre - Sharing - Connections - Presence + + Linux specific implementation and compatibility shims are provided for building **Sharing** and **Connections**. Nearby presence may or may not build. I haven't tested it. @@ -38,5 +40,6 @@ it accomplishes that goal perfectly. This repo wasn't created out of any altruis ## Special thanks https://github.com/proatgram and https://github.com/vibhavp + *they were the original authors of the linux platform support [PR](https://github.com/google/nearby/pull/2098) that I have based much of this codebase upon. I am standing on the shoulders of giants* From 3fcdb17097d697f4d68dc5fb2d4a77350a3f4135 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana <78136120+kidfromjupiter@users.noreply.github.com> Date: Mon, 6 Apr 2026 22:59:11 +0530 Subject: [PATCH 22/26] formatting --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 974ddceb..1ba60265 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ https://github.com/user-attachments/assets/048afa1e-40a4-4351-a859-c81b642fc6e3 ## What This repo consists of the entirety of google's open source nearby library. Currently, it is seperated into 3 sections. - - Sharing - - Connections - - Presence +- Sharing +- Connections +- Presence Linux specific implementation and compatibility shims are provided for building **Sharing** and **Connections**. From 8536897335bd011742b3a4c51a16c9bde6e491a8 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana <78136120+kidfromjupiter@users.noreply.github.com> Date: Mon, 6 Apr 2026 23:10:29 +0530 Subject: [PATCH 23/26] more readme changes --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 1ba60265..4889492e 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,13 @@ it accomplishes that goal perfectly. This repo wasn't created out of any altruis ## How +### How to use +Proper documentation is coming I swear. University's getting pretty hectic so docs is on the backburner. The wiki has some more information but it's nowhere near a proper documentation. It has some good brief +overviews and where generally everything is located if you're thinking about contributing. + +If you want any clarification on anything, feel free to open an issue. I'll get back to you ASAP. + +As a consolation prize, I've indexed this project using [Deepwiki](https://deepwiki.com/kidfromjupiter/nearby). You might have strong feeling about AI use. But I feel like documenting very large codebases is a perfect usecase for such models. (They are called Large Language Models for a reason ) ### How to install *coming soon* @@ -37,6 +44,12 @@ it accomplishes that goal perfectly. This repo wasn't created out of any altruis ### How to contribute *coming soon* +## Apologies +I may have done things in *incredibly* stupid and overcomplicated ways. It doesn't certainly help that this was the way I decided to learn C++. Blessed be my naive soul. I also do not have much experience working with such +enormous codebases. + +If you see any such stupidities, feel free to berate me in the most shameless of manners in an issue. I look forward to learning how to do it the proper way and to improve my atrocious code quality. + ## Special thanks https://github.com/proatgram and https://github.com/vibhavp From b73f9744e908ed0223abd9d507ec70a150030c31 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Tue, 7 Apr 2026 07:36:19 +0530 Subject: [PATCH 24/26] upddated readme --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 1ba60265..a152b6b0 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,33 @@ it accomplishes that goal perfectly. This repo wasn't created out of any altruis ## How ### How to install +This repo provides prebuilt binaries of the Quick Share application. The only officially supported distro is Fedora 43 for now. The newest ubuntu images *should* work fine +although that needs to be tested. I want to support more distros so if you encounter issues installing on your distro, please let me know. + + +#### Prerequisites + +- `sdbus-cpp >= 2.0` +- `bluez >= 5.85` + +To install the prerequisites, run this command + +```bash +sudo dnf install -y \ + bluez bluez-libs bluez-libs-devel \ + sdbus-cpp sdbus-cpp-devel +``` +--- + +To install the Quick Share application, + +1. Go to [releases](https://github.com/kidfromjupiter/nearby/releases) +2. Download the latest nearby-file-share-linux-*.tar.gz +3. `mkdir -p nearby && tar -xf nearby-file-share-linux-*.tar.gz -C nearby` +4. `cd nearby` +5. `chmod +x install_nearby_file_share.sh` +6. `./install_nearby_file_share.sh` + *coming soon* ### How to build *coming soon* From 37207dbfdb9a1d3f4df2d84eb324c6a74ae713ae Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Tue, 7 Apr 2026 08:49:17 +0530 Subject: [PATCH 25/26] doc: readme --- README.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 787ea0d1..78844d3c 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ although that needs to be tested. I want to support more distros so if you encou - `sdbus-cpp >= 2.0` - `bluez >= 5.85` -To install the prerequisites, run this command +**To install the prerequisites, run this command** ```bash sudo dnf install -y \ @@ -56,20 +56,23 @@ sudo dnf install -y \ ``` --- -To install the Quick Share application, +**To install the Quick Share application,** 1. Go to [releases](https://github.com/kidfromjupiter/nearby/releases) -2. Download the latest nearby-file-share-linux-*.tar.gz +2. Download the latest `nearby-file-share-linux-*.tar.gz` 3. `mkdir -p nearby && tar -xf nearby-file-share-linux-*.tar.gz -C nearby` 4. `cd nearby` 5. `chmod +x install_nearby_file_share.sh` 6. `./install_nearby_file_share.sh` -*coming soon* +**To install the actual library and headers,** + +Currently there are no prebuilt shared library or headers. You'll have to build them yourself + ### How to build -*coming soon* +Check the [wiki](https://github.com/kidfromjupiter/nearby/wiki/Development-Environment-and-Building) ### How to contribute -*coming soon* +Check the [wiki](https://github.com/kidfromjupiter/nearby/wiki/Development-Environment-and-Building) ## Apologies I may have done things in *incredibly* stupid and overcomplicated ways. It doesn't certainly help that this was the way I decided to learn C++. Blessed be my naive soul. I also do not have much experience working with such From e9e2aaedaec754df1db68669837b7ddb634e49a1 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana <78136120+kidfromjupiter@users.noreply.github.com> Date: Tue, 7 Apr 2026 08:52:07 +0530 Subject: [PATCH 26/26] updated yaml --- .github/workflows/validate.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 48ead7d4..f1e4d0c7 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -27,7 +27,7 @@ jobs: image: ubuntu:25.04 steps: - name: Install git - run: | + run: | apt-get update apt-get install -y git ca-certificates - uses: actions/checkout@v6