Merge remote-tracking branch 'origin/main'

# Conflicts:
#	.github/workflows/release-nearby-qml-tray.yaml
This commit is contained in:
lasan
2026-04-15 19:44:44 +05:30
54 changed files with 3342 additions and 930 deletions
@@ -15,8 +15,14 @@ permissions:
jobs:
build-and-release-linux:
name: Build and Release Linux Bundle
runs-on: ubuntu-latest
steps:
- name: Install git
run: |
apt-get update
apt-get install -y git ca-certificates
- name: Checkout
uses: actions/checkout@v6
with:
@@ -42,13 +48,14 @@ 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 \
ninja-build \
pkg-config \
libsdbus-c++-dev \
libssl-dev \
libsystemd-dev \
libcurlpp-dev \
+6
View File
@@ -23,7 +23,13 @@ jobs:
build-linux:
name: Build Linux
runs-on: ubuntu-latest
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
+55 -117
View File
@@ -1,150 +1,88 @@
# Google Nearby Sharing & Connections for Linux (Unofficial)
> P2P file sharing, discovery, and connectivity on Linux using Google Nearby protocols.
<img width="982" height="760" alt="image" src="https://github.com/user-attachments/assets/9533ea09-81d9-4162-b90f-e0bd8b714d1d" />
![](cover.png)
## Demo
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
### 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.
The primary goal of this repository is to provide reusable Linux libraries/APIs for Nearby.
If you want any clarification on anything, feel free to open an issue. I'll get back to you ASAP.
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.
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 )
## Documentation
### 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.
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
#### Prerequisites
## Requirements
- `sdbus-cpp >= 2.0`
- `bluez >= 5.85`
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:
**To install the prerequisites, run this command**
```bash
tar -xzf nearby-file-share-linux-*.tar.gz
sudo dnf install -y \
bluez bluez-libs bluez-libs-devel \
sdbus-cpp sdbus-cpp-devel
```
---
3. Install the application, shared library, and launcher:
(run this from the extracted bundle directory)
**To install the Quick Share application,**
```bash
./install_nearby_file_share.sh
```
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`
4. Apply a temporary BlueZ service override (required for now).
Create/edit the `bluetooth.service` override:
**To install the actual library and headers,**
```bash
sudo systemctl edit bluetooth
```
Currently there are no prebuilt shared library or headers. You'll have to build them yourself
Add:
### How to build
Check the [wiki](https://github.com/kidfromjupiter/nearby/wiki/Development-Environment-and-Building)
### How to contribute
Check the [wiki](https://github.com/kidfromjupiter/nearby/wiki/Development-Environment-and-Building)
```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
```
## 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.
Then reload and restart:
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.
```bash
sudo systemctl daemon-reload
sudo systemctl restart bluetooth
```
## Special thanks
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.
https://github.com/proatgram and https://github.com/vibhavp
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`.
*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*
+19 -3
View File
@@ -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",
@@ -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 = [
@@ -184,7 +200,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",
@@ -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",
@@ -208,7 +208,6 @@ bool BluezGattDiscovery::InitializeKnownServices() {
return false;
}
absl::flat_hash_map<sdbus::ObjectPath, GattServiceClient> 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<bluez::SubscribedGattCharacteristicClient>(
system_bus_, device_object_path, std::move(on_characteristic_changed_cb));
system_bus_, path_it->second, std::move(on_characteristic_changed_cb));
}
std::optional<std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
@@ -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(
@@ -93,7 +93,7 @@ class BluezGattDiscovery final : public bluez::BluezObjectManager {
absl::Mutex mutex_;
absl::flat_hash_map<sdbus::ObjectPath, std::unique_ptr<GattServiceClient>>
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<std::tuple<Uuid, Uuid, sdbus::ObjectPath>,
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 {
@@ -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<api::ble::GattCharacteristic>
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(&registration_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(&registration_mutex_);
register_application_call_ = std::move(call);
} catch (const sdbus::Error& e) {
{
absl::MutexLock registration_lock(&registration_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<sdbus::Error> error) {
{
absl::MutexLock lock(&registration_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<sdbus::Error> error) {
{
absl::MutexLock lock(&registration_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(&registration_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(&registration_mutex_);
unregister_application_call_ = std::move(call);
} catch (const sdbus::Error& e) {
{
absl::MutexLock registration_lock(&registration_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
@@ -16,6 +16,7 @@
#define PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_
#include <memory>
#include <optional>
#include <regex>
#include <sdbus-c++/IConnection.h>
@@ -69,7 +70,16 @@ class GattServer : public api::ble::GattServer {
sdbus::ObjectPath("/com/google/nearby/medium/ble/gatt"))),
gatt_manager_(std::make_unique<bluez::GattManager>(system_bus_, adapter_.GetObjectPath())),
server_cb_(std::make_shared<api::ble::ServerGattConnectionCallback>(
std::move(server_cb))) {}
std::move(server_cb))) {
gatt_manager_->SetRegisterApplicationReplyCallback(
[this](std::optional<sdbus::Error> error) {
OnRegisterApplicationReply(std::move(error));
});
gatt_manager_->SetUnregisterApplicationReplyCallback(
[this](std::optional<sdbus::Error> error) {
OnUnregisterApplicationReply(std::move(error));
});
}
~GattServer() override = default;
absl::optional<api::ble::GattCharacteristic> CreateCharacteristic(
@@ -85,6 +95,9 @@ class GattServer : public api::ble::GattServer {
void Stop() override;
private:
void OnRegisterApplicationReply(std::optional<sdbus::Error> error);
void OnUnregisterApplicationReply(std::optional<sdbus::Error> error);
sdbus::IConnection& system_bus_;
std::shared_ptr<BluetoothDevices> devices_;
BluetoothAdapter adapter_;
@@ -93,9 +106,20 @@ class GattServer : public api::ble::GattServer {
std::unique_ptr<RootObjectManager> gatt_service_root_object_manager;
absl::Mutex profiles_mutex_;
absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattProfile>> gatt_profiles_;
ABSL_GUARDED_BY(profiles_mutex_)
ABSL_GUARDED_BY(profiles_mutex_);
std::unique_ptr<bluez::GattManager> gatt_manager_;
std::shared_ptr<api::ble::ServerGattConnectionCallback> 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<sdbus::PendingAsyncCall> register_application_call_
ABSL_GUARDED_BY(registration_mutex_);
std::optional<sdbus::PendingAsyncCall> unregister_application_call_
ABSL_GUARDED_BY(registration_mutex_);
absl::Mutex services_mutex_;
absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattServiceServer>> services_
ABSL_GUARDED_BY(services_mutex_);
@@ -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<BluezGattDiscovery>(system_bus_)),
observers_(std::make_shared<ObserverList<api::BluetoothClassicMedium::Observer>>()),
devices_(std::make_unique<BluetoothDevices>(
system_bus_, adapter_.GetObjectPath(), *observers_)),
@@ -89,16 +85,55 @@ 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<sdbus::Error> error) {
OnRegisterMonitorReply(std::move(error));
});
adv_monitor_manager_->RegisterMonitor(
root_object_manager_->getObject().getObjectPath());
} else {
adv_monitor_manager_ready_notification_.Notify();
}
}
void BleV2Medium::OnRegisterMonitorReply(std::optional<sdbus::Error> 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 (gatt_discovery_->InitializeKnownServices()) {
// LOG(ERROR) << __func__
// << ": Could not initialize known GATT services";
// }
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
@@ -240,9 +275,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 +347,7 @@ bool BleV2Medium::StopScanning() {
return false;
}
if (adv_monitor_manager_ == nullptr) {
if (!WaitForAdvertisementMonitorManager()) {
// TODO: Implement manual monitoring.
return false;
}
@@ -349,7 +382,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;
}
@@ -437,12 +470,9 @@ bool BleV2Medium::StopScanning() {
std::unique_ptr<api::ble::GattServer> BleV2Medium::StartGattServer(
api::ble::ServerGattConnectionCallback callback) {
// (void)callback;
// return nullptr;
return std::make_unique<GattServer>(
*system_bus_, adapter_, devices_,std::move(callback)
);
LOG(INFO) << __func__ << ": Starting Linux GATT server.";
return std::make_unique<GattServer>(*system_bus_, adapter_, devices_,
std::move(callback));
}
std::unique_ptr<api::ble::GattClient> BleV2Medium::ConnectToGattServer(
@@ -452,8 +482,7 @@ std::unique_ptr<api::ble::GattClient> 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;
}
@@ -17,8 +17,11 @@
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/notification.h"
#include <sdbus-c++/IConnection.h>
#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<sdbus::Error> error);
bool MonitorManagerSupportsOr() {
std::vector<std::string> supported_types;
@@ -148,6 +153,14 @@ class BleV2Medium final : public api::ble::BleMedium {
std::unique_ptr<RootObjectManager> root_object_manager_;
std::unique_ptr<bluez::AdvertisementMonitorManager> 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,
@@ -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<AgentManager>(*system_bus_)),
profile_manager_(nullptr) {
auto shared =
GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath());
observers_ = shared->observers;
devices_ = shared->devices;
profile_manager_ =
std::make_unique<ProfileManager>(*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<AgentManager>(*system_bus_)),
profile_manager_(nullptr) {
auto shared =
GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath());
observers_ = shared->observers;
devices_ = shared->devices;
profile_manager_ =
std::make_unique<ProfileManager>(*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<api::BluetoothSocket> BluetoothClassicMedium::ConnectToService(
new BluetoothSocket(device, fd.value()));
}
std::unique_ptr<api::BluetoothServerSocket>
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<api::BluetoothServerSocket>
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;
@@ -26,7 +26,7 @@
#include <sdbus-c++/StandardInterfaces.h>
#include <sdbus-c++/Types.h>
// #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<BluetoothDevices> devices_;
std::unique_ptr<DeviceWatcher> device_watcher_;
// std::unique_ptr<AgentManager> agent_manager_;
std::unique_ptr<AgentManager> agent_manager_;
std::unique_ptr<ProfileManager> profile_manager_;
};
@@ -15,9 +15,13 @@
#ifndef PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_
#define PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_
#include <optional>
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/ProxyInterfaces.h>
#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<void(std::optional<sdbus::Error>)>;
private:
friend std::unique_ptr<AdvertisementMonitorManager>
std::make_unique<AdvertisementMonitorManager>(
@@ -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<AdvertisementMonitorManager>
DiscoverAdvertisementMonitorManager(
sdbus::IConnection &system_bus,
@@ -82,6 +99,38 @@ class AdvertisementMonitorManager final
return std::make_unique<AdvertisementMonitorManager>(system_bus, adapter);
}
protected:
void onRegisterMonitorReply(std::optional<sdbus::Error> error) override {
std::optional<ReplyCallback> 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<sdbus::Error> error) override {
std::optional<ReplyCallback> 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<ReplyCallback> register_monitor_reply_callback_
ABSL_GUARDED_BY(callbacks_mutex_);
std::optional<ReplyCallback> unregister_monitor_reply_callback_
ABSL_GUARDED_BY(callbacks_mutex_);
};
} // namespace bluez
} // namespace linux
@@ -0,0 +1,99 @@
#include "internal/platform/implementation/linux/bluez_agent.h"
#include <sdbus-c++/AdaptorInterfaces.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 {
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<absl::string_view> capability,
const sdbus::ObjectPath& agent_object_path) {
absl::MutexLock l(&registered_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<Agent>(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
@@ -0,0 +1,85 @@
#pragma once
#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <utility>
#include <sdbus-c++/sdbus-c++.h>
#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<org::bluez::Agent1_adaptor> {
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<org::bluez::AgentManager1_proxy> {
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<absl::string_view> capability,
const sdbus::ObjectPath& agent_object_path);
bool AgentRegistered(absl::string_view agent_object_path);
private:
absl::Mutex registered_agents_mutex_;
std::map<std::string, std::shared_ptr<Agent>> registered_agents_
ABSL_GUARDED_BY(registered_agents_mutex_);
};
} // namespace nearby::linux
@@ -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_);
}
@@ -14,10 +14,15 @@
#ifndef PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_
#define PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_
#include <optional>
#include <sdbus-c++/IConnection.h>
#include <sdbus-c++/ProxyInterfaces.h>
#include <sdbus-c++/Types.h>
#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<org::bluez::GattManager1_proxy> {
public:
using ReplyCallback = absl::AnyInvocable<void(std::optional<sdbus::Error>)>;
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<sdbus::Error> 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<sdbus::Error> 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<ReplyCallback> register_application_reply_callback_
ABSL_GUARDED_BY(callbacks_mutex_);
std::optional<ReplyCallback> unregister_application_reply_callback_
ABSL_GUARDED_BY(callbacks_mutex_);
};
} // namespace bluez
} // namespace linux
@@ -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 <sdbus-c++/sdbus-c++.h>
#include <string>
@@ -35,15 +35,18 @@ protected:
{
}
virtual void onRegisterMonitorReply(std::optional<sdbus::Error> error) = 0;
virtual void onUnregisterMonitorReply(std::optional<sdbus::Error> 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<sdbus::Error> 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<sdbus::Error> error){ this->onUnregisterMonitorReply(std::move(error)); });
}
public:
@@ -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 <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
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
@@ -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 <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
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
@@ -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 <sdbus-c++/sdbus-c++.h>
#include <string>
@@ -35,15 +35,18 @@ protected:
{
}
virtual void onRegisterApplicationReply(std::optional<sdbus::Error> error) = 0;
virtual void onUnregisterApplicationReply(std::optional<sdbus::Error> error) = 0;
public:
void RegisterApplication(const sdbus::ObjectPath& application, const std::map<std::string, sdbus::Variant>& options)
sdbus::PendingAsyncCall RegisterApplication(const sdbus::ObjectPath& application, const std::map<std::string, sdbus::Variant>& 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<sdbus::Error> 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<sdbus::Error> error){ this->onUnregisterApplicationReply(std::move(error)); });
}
private:
@@ -3,9 +3,11 @@
<node>
<interface name="org.bluez.AdvertisementMonitorManager1">
<method name="RegisterMonitor">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" />
</method>
<method name="UnregisterMonitor">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" />
</method>
<property name="SupportedMonitorTypes" type="as" access="read" />
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-Bus Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.bluez.Agent1">
<method name="Release"/>
<method name="RequestPinCode">
<arg name="device" type="o" direction="in"/>
<arg name="pincode" type="s" direction="out"/>
</method>
<method name="DisplayPinCode">
<arg name="device" type="o" direction="in"/>
<arg name="pincode" type="s" direction="in"/>
</method>
<method name="RequestPasskey">
<arg name="device" type="o" direction="in"/>
<arg name="passkey" type="u" direction="out"/>
</method>
<method name="DisplayPasskey">
<arg name="device" type="o" direction="in"/>
<arg name="passkey" type="u" direction="in"/>
<arg name="entered" type="q" direction="in"/>
</method>
<method name="RequestConfirmation">
<arg name="device" type="o" direction="in"/>
<arg name="passkey" type="u" direction="in"/>
</method>
<method name="RequestAuthorization">
<arg name="device" type="o" direction="in"/>
</method>
<method name="AuthorizeService">
<arg name="device" type="o" direction="in"/>
<arg name="uuid" type="s" direction="in"/>
</method>
<method name="Cancel"/>
</interface>
</node>
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-Bus Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.bluez.AgentManager1">
<method name="RegisterAgent">
<arg name="agent" type="o" direction="in"/>
<arg name="capability" type="s" direction="in"/>
</method>
<method name="UnregisterAgent">
<arg name="agent" type="o" direction="in"/>
</method>
<method name="RequestDefaultAgent">
<arg name="agent" type="o" direction="in"/>
</method>
</interface>
</node>
@@ -3,10 +3,12 @@
<node>
<interface name="org.bluez.GattManager1">
<method name="RegisterApplication">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" />
<arg name="options" type="a{sv}" direction="in" />
</method>
<method name="UnregisterApplication">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" />
</method>
</interface>
+1
View File
@@ -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",
+55 -14
View File
@@ -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,26 @@ cc_library(
],
)
cc_library(
name = "nearby_connections_api",
srcs = ["nearby_connections_api.cc"],
hdrs = ["nearby_connections_api.h"],
alwayslink = True,
visibility = ["//visibility:public"],
deps = [
"//internal/platform:base",
":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,
@@ -86,14 +96,45 @@ cc_binary(
"-Wl,--exclude-libs,ALL",
],
deps = [
"//internal/platform:base",
":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",
],
)
+659
View File
@@ -0,0 +1,659 @@
#include "sharing/linux/nearby_connections_api.h"
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#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::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 =
payload.content.file_payload.file_path.ToString();
facade_payload.parent_folder = payload.content.file_payload.parent_folder;
break;
case nearby::sharing::PayloadContent::Type::kUnknown:
facade_payload.type = NearbyConnectionsApi::PayloadType::kUnknown;
break;
}
return facade_payload;
}
std::unique_ptr<nearby::sharing::Payload> ToNativePayload(
NearbyConnectionsApi::Payload payload) {
switch (payload.type) {
case NearbyConnectionsApi::PayloadType::kBytes:
return std::make_unique<nearby::sharing::Payload>(
payload.id, std::move(payload.bytes));
case NearbyConnectionsApi::PayloadType::kFile:
return std::make_unique<nearby::sharing::Payload>(
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<nearby::sharing::Payload>(payload.id,
std::move(stream_payload));
}
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<NearbyConnectionsServiceImpl>(
context.GetConnectivityManager(), /*event_logger=*/nullptr)),
listener_state(std::make_shared<ListenerState>()) {}
static NearbyConnectionsApi::Listener CopyListener(
const std::shared_ptr<ListenerState>& listener_state) {
std::scoped_lock lock(listener_state->mutex);
return listener_state->listener;
}
static NativeService::ConnectionListener BuildConnectionListener(
const std::shared_ptr<ListenerState>& 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<ListenerState>& 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<ListenerState>& 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<NativeService> service;
std::shared_ptr<ListenerState> listener_state;
};
NearbyConnectionsApi::NearbyConnectionsApi() : impl_(std::make_unique<Impl>()) {}
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<uint8_t>& endpoint_info,
const AdvertisingOptions& options,
std::function<void(StatusCode)> 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<void(StatusCode)> 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<void(StatusCode)> 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<void(StatusCode)> 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<uint8_t>& endpoint_info,
const std::string& endpoint_id, const ConnectionOptions& options,
std::function<void(StatusCode)> 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<void(StatusCode)> 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<std::string>& endpoint_ids,
Payload payload, std::function<void(StatusCode)> callback) {
std::unique_ptr<nearby::sharing::Payload> 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<void(StatusCode)> 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<void(StatusCode)> 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<void(StatusCode)> 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<void(StatusCode)> 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<void(StatusCode)> 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
+290
View File
@@ -0,0 +1,290 @@
// 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 <stdint.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "internal/platform/input_stream.h"
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,
kStream = 3,
};
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<uint8_t> 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<uint8_t> raw_authentication_token;
std::vector<uint8_t> endpoint_info;
bool is_incoming_connection = false;
StatusCode connection_layer_status = StatusCode::kUnknown;
AuthenticationStatus authentication_status =
AuthenticationStatus::kUnknown;
};
struct DiscoveredEndpointInfo {
std::vector<uint8_t> 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<uint8_t> bytes;
std::vector<uint8_t> stream_bytes;
std::shared_ptr<nearby::InputStream> stream_input;
std::string file_path;
std::string parent_folder;
static Payload FromBytes(int64_t id, std::vector<uint8_t> 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;
}
static Payload FromStream(int64_t id, std::vector<uint8_t> 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<nearby::InputStream> stream_input) {
Payload payload;
payload.id = id;
payload.type = PayloadType::kStream;
payload.stream_input = std::move(stream_input);
return payload;
}
};
struct Listener {
std::function<void(const std::string&, const DiscoveredEndpointInfo&)>
endpoint_found_cb;
std::function<void(const std::string&)> endpoint_lost_cb;
std::function<void(const std::string&, DistanceInfo)>
endpoint_distance_changed_cb;
std::function<void(const std::string&, const ConnectionInfo&)>
connection_initiated_cb;
std::function<void(const std::string&)> connection_accepted_cb;
std::function<void(const std::string&, StatusCode)> connection_rejected_cb;
std::function<void(const std::string&)> disconnected_cb;
std::function<void(const std::string&, Medium)> bandwidth_changed_cb;
std::function<void(const std::string&, const Payload&)>
payload_received_cb;
std::function<void(const std::string&, const PayloadTransferUpdate&)>
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<uint8_t>& endpoint_info,
const AdvertisingOptions& options,
std::function<void(StatusCode)> callback);
void StopAdvertising(const std::string& service_id,
std::function<void(StatusCode)> callback);
void StartDiscovery(const std::string& service_id,
const DiscoveryOptions& options,
std::function<void(StatusCode)> callback);
void StopDiscovery(const std::string& service_id,
std::function<void(StatusCode)> callback);
void RequestConnection(const std::string& service_id,
const std::vector<uint8_t>& endpoint_info,
const std::string& endpoint_id,
const ConnectionOptions& options,
std::function<void(StatusCode)> callback);
void DisconnectFromEndpoint(const std::string& service_id,
const std::string& endpoint_id,
std::function<void(StatusCode)> callback);
void SendPayload(const std::string& service_id,
const std::vector<std::string>& endpoint_ids,
Payload payload,
std::function<void(StatusCode)> callback);
void CancelPayload(const std::string& service_id, int64_t payload_id,
std::function<void(StatusCode)> callback);
void InitiateBandwidthUpgrade(const std::string& service_id,
const std::string& endpoint_id,
std::function<void(StatusCode)> callback);
void AcceptConnection(const std::string& service_id,
const std::string& endpoint_id,
std::function<void(StatusCode)> callback);
void StopAllEndpoints(std::function<void(StatusCode)> callback);
void SetCustomSavePath(const std::string& path,
std::function<void(StatusCode)> 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> impl_;
};
} // namespace sharing
} // namespace nearby
#endif // SHARING_LINUX_NEARBY_CONNECTIONS_API_H_
@@ -0,0 +1,75 @@
#include "sharing/linux/nearby_connections_api.h"
#include <memory>
#include <vector>
#include "gtest/gtest.h"
#include "internal/platform/pipe.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<uint8_t>{1, 2, 3}));
EXPECT_TRUE(payload.stream_bytes.empty());
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());
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<uint8_t>{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<nearby::InputStream>(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) {
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
+18 -3
View File
@@ -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"
@@ -28,7 +29,7 @@
#include "sharing/transfer_metadata.h"
#include "sharing/transfer_update_callback.h"
namespace nearby::sharing::linux {
namespace nearby::sharing {
namespace {
@@ -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,
@@ -125,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) {
@@ -266,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();
@@ -657,4 +672,4 @@ std::string NearbySharingApi::TransferStatusToString(TransferStatus status) {
return "Unknown";
}
} // namespace nearby::sharing::linux
} // namespace nearby::sharing
-8
View File
@@ -13,15 +13,8 @@
#include <string>
#include <vector>
// 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> impl_;
};
} // namespace linux
} // namespace sharing
} // namespace nearby
@@ -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;
@@ -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:
+10
View File
@@ -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
+2 -5
View File
@@ -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
}
@@ -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)
@@ -1,13 +1,116 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Effects
Item {
anchors.fill: parent
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
@@ -19,6 +122,141 @@ 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 lastMs: Date.now()
Timer {
interval: 16
running: true
repeat: true
onTriggered: {
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()
}
}
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
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 + 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 })
}
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 lastMs: Date.now()
Timer {
interval: 16
running: true
repeat: true
onTriggered: {
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()
}
}
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
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 + 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 })
}
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
@@ -27,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()
}
}
@@ -44,17 +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
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 })
}
@@ -81,12 +325,85 @@ Item {
}
}
SendUrlPanel {
Rectangle {
id: incomingTransferCard
anchors.centerIn: parent
width: parent.width - 96
visible: isSendMode
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 {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
@@ -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
@@ -0,0 +1,157 @@
#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, const QString& file_path) {
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},
{QStringLiteral("filePath"), file_path},
};
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();
}
@@ -0,0 +1,119 @@
#ifndef SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_
#define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_
#include <QHash>
#include <QString>
#include <QStringList>
#include <QVariantList>
#include <QVariantMap>
#include <QSet>
#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,
const QString& file_path);
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<qlonglong, int> discovered_row_by_target_;
QHash<qlonglong, QString> target_names_;
// Transfers
QVariantList transfers_;
QHash<qlonglong, int> transfer_row_by_target_;
// Pending removals
QSet<qlonglong> pending_target_removals_;
};
#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_
File diff suppressed because it is too large Load Diff
@@ -2,18 +2,12 @@
#define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_
#include <QObject>
#include <QHash>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QVariantList>
#include <memory>
#include "sharing/linux/nearby_sharing_api.h"
#include "file_share_state.h"
#include <sharing/linux/nearby_sharing_api.h>
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();
@@ -65,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();
@@ -84,65 +75,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<NearbySharingApi> 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<qlonglong, int> discovered_row_by_target_;
QSet<qlonglong> pending_target_removals_;
QHash<qlonglong, QString> target_names_;
QVariantList transfers_;
QHash<qlonglong, int> transfer_row_by_target_;
FileShareState state_;
};
#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_
@@ -232,6 +232,8 @@ int main(int argc, char* argv[]) {
tray.setContextMenu(&tray_menu);
tray.show();
controller.start();
//controller.
controller.switchToReceiveMode();
return app.exec();
@@ -0,0 +1,48 @@
#include "qr_code_generator.h"
#include <QByteArray>
#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<const unsigned char*>(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
@@ -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 <QString>
#include <QStringList>
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_
@@ -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 <QString>
#include <QVariantMap>
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_
@@ -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
@@ -0,0 +1,21 @@
#ifndef SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_
#define SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_
#include <QString>
#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_
@@ -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
+17
View File
@@ -0,0 +1,17 @@
#ifndef SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_
#define SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_
#include <QString>
#include <string>
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_
@@ -0,0 +1,50 @@
#ifndef SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_
#define SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_
#include <QString>
#include <QVariantMap>
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<qulonglong>(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_
+67
View File
@@ -17,6 +17,7 @@
#include <algorithm>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
@@ -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<uint8_t> bytes)
: bytes_(std::move(bytes)) {}
ExceptionOr<ByteArray> Read(std::int64_t size) override {
if (closed_) {
return ExceptionOr<ByteArray>(Exception::kIo);
}
if (offset_ >= bytes_.size() || size <= 0) {
return ExceptionOr<ByteArray>(ByteArray());
}
const size_t remaining = bytes_.size() - offset_;
const size_t chunk_size = std::min<size_t>(remaining, static_cast<size_t>(size));
ByteArray chunk(reinterpret_cast<const char*>(bytes_.data() + offset_),
chunk_size);
offset_ += chunk_size;
return ExceptionOr<ByteArray>(std::move(chunk));
}
Exception Close() override {
closed_ = true;
return {Exception::kSuccess};
}
private:
std::vector<uint8_t> bytes_;
size_t offset_ = 0;
bool closed_ = false;
};
class SharedInputStream final : public InputStream {
public:
explicit SharedInputStream(std::shared_ptr<InputStream> stream)
: stream_(std::move(stream)) {}
ExceptionOr<ByteArray> Read(std::int64_t size) override {
if (stream_ == nullptr) {
return ExceptionOr<ByteArray>(Exception::kIo);
}
return stream_->Read(size);
}
Exception Close() override {
if (stream_ == nullptr) {
return {Exception::kIo};
}
return stream_->Close();
}
private:
std::shared_ptr<InputStream> stream_;
};
Status ConvertToStatus(NcStatus status) {
return static_cast<Status>(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<SharedInputStream>(
std::move(payload.content.stream_payload.input_stream)));
}
auto stream = std::make_unique<VectorInputStream>(
std::move(payload.content.stream_payload.bytes));
return NcPayload(payload.id, std::move(stream));
}
default:
return NcPayload();
}
+21
View File
@@ -19,6 +19,7 @@
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -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<uint8_t> 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<uint8_t> bytes;
// Optional live stream source for long-lived outgoing stream payloads.
std::shared_ptr<nearby::InputStream> 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<uint8_t> 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<std::string>()(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) {