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: jobs:
build-and-release-linux: build-and-release-linux:
name: Build and Release Linux Bundle name: Build and Release Linux Bundle
runs-on: ubuntu-latest
steps: steps:
- name: Install git
run: |
apt-get update
apt-get install -y git ca-certificates
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
@@ -42,13 +48,14 @@ jobs:
- name: Install Linux build dependencies - name: Install Linux build dependencies
run: | run: |
sudo apt-get update apt-get update
sudo apt-get install -y \ apt-get install -y \
build-essential \ build-essential \
clang \ clang \
cmake \ cmake \
ninja-build \ ninja-build \
pkg-config \ pkg-config \
libsdbus-c++-dev \
libssl-dev \ libssl-dev \
libsystemd-dev \ libsystemd-dev \
libcurlpp-dev \ libcurlpp-dev \
+6
View File
@@ -23,7 +23,13 @@ jobs:
build-linux: build-linux:
name: Build Linux name: Build Linux
runs-on: ubuntu-latest runs-on: ubuntu-latest
container:
image: ubuntu:25.04
steps: steps:
- name: Install git
run: |
apt-get update
apt-get install -y git ca-certificates
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Build Connections - 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 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) # 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 ## Demo
https://github.com/user-attachments/assets/048afa1e-40a4-4351-a859-c81b642fc6e3 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: **To install the prerequisites, run this command**
- BlueZ `5.86` or higher
- `NetworkManager`
- `Avahi`
- Qt (required for the tray UI app)
## Installation (End Users)
1. Download the latest release archive from the Releases page.
- https://github.com/kidfromjupiter/nearby/releases
2. Extract it:
```bash ```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: **To install the Quick Share application,**
(run this from the extracted bundle directory)
```bash 1. Go to [releases](https://github.com/kidfromjupiter/nearby/releases)
./install_nearby_file_share.sh 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). **To install the actual library and headers,**
Create/edit the `bluetooth.service` override:
```bash Currently there are no prebuilt shared library or headers. You'll have to build them yourself
sudo systemctl edit bluetooth
```
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 ## Apologies
[Service] 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
ExecStart= enormous codebases.
ExecStart=/usr/local/libexec/bluetooth/bluetoothd -E --debug=src/plugin.c --noplugin=bap,bass,mcp,vcp,micp,ccp,csip,tmap,asha,midi
```
Then reload and restart: 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 ## Special thanks
sudo systemctl daemon-reload
sudo systemctl restart bluetooth
```
These plugins are currently disabled due to an ongoing likely upstream BlueZ bug: https://github.com/proatgram and https://github.com/vibhavp
`bap,bass,mcp,vcp,micp,ccp,csip,tmap,asha,midi`.
This workaround will be removed once the issue is fixed.
This installs to `~/.local` by default. *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*
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`.
+19 -3
View File
@@ -92,8 +92,8 @@ cc_library(
"bluetooth_devices.h", "bluetooth_devices.h",
"bluetooth_pairing.h", "bluetooth_pairing.h",
"bluez.h", "bluez.h",
"bluez_agent.h",
"bluez_device.h", "bluez_device.h",
# "bluez_agent.h",
"bluez_advertisement_monitor.h", "bluez_advertisement_monitor.h",
"bluez_advertisement_monitor_manager.h", "bluez_advertisement_monitor_manager.h",
"bluez_gatt_characteristic_client.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( cc_library(
name = "linux", name = "linux",
srcs = [ srcs = [
@@ -184,7 +200,7 @@ cc_library(
"bluetooth_devices.cc", "bluetooth_devices.cc",
"bluetooth_pairing.cc", "bluetooth_pairing.cc",
"bluez.cc", "bluez.cc",
#"bluez_agent.cc", "bluez_agent.cc",
"bluez_advertisement_monitor.cc", "bluez_advertisement_monitor.cc",
"bluez_gatt_characteristic_client.cc", "bluez_gatt_characteristic_client.cc",
"bluez_gatt_characteristic_server.cc", "bluez_gatt_characteristic_server.cc",
@@ -200,7 +216,6 @@ cc_library(
"scheduled_executor.cc", "scheduled_executor.cc",
"stream.cc", "stream.cc",
"submittable_executor.cc", "submittable_executor.cc",
"system_clock.cc",
"thread_pool.cc", "thread_pool.cc",
"utils.cc", "utils.cc",
# "wifi_direct.cc", # "wifi_direct.cc",
@@ -225,6 +240,7 @@ cc_library(
deps = [ deps = [
":comm", ":comm",
":crypto", # build_cleaner: keep ":crypto", # build_cleaner: keep
":system_clock",
":types", ":types",
"//internal/flags:nearby_flags", "//internal/flags:nearby_flags",
"//internal/platform:base", "//internal/platform:base",
@@ -208,7 +208,6 @@ bool BluezGattDiscovery::InitializeKnownServices() {
return false; return false;
} }
absl::flat_hash_map<sdbus::ObjectPath, GattServiceClient> cached_services;
absl::MutexLock lock(&mutex_); absl::MutexLock lock(&mutex_);
auto chr_it = std::find_if( auto chr_it = std::find_if(
objects.cbegin(), objects.cend(), objects.cbegin(), objects.cend(),
@@ -219,29 +218,27 @@ bool BluezGattDiscovery::InitializeKnownServices() {
sdbus::InterfaceName(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)) == 1; sdbus::InterfaceName(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)) == 1;
}); });
for (; chr_it != objects.cend(); ++chr_it) { for (; chr_it != objects.cend(); ++chr_it) {
const auto& [path, ifaces] = *chr_it; const auto& [path, ifaces] = *chr_it;
auto iface_it = ifaces.find(sdbus::InterfaceName(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME)); auto iface_it = ifaces.find(sdbus::InterfaceName(
if (iface_it == ifaces.end()) { org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME));
// Not a GattCharacteristic1 object (or interfaces map incomplete) -> skip if (iface_it == ifaces.end()) {
continue; 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; return true;
} }
@@ -337,7 +334,7 @@ BluezGattDiscovery::GetSubscribedCharacteristic(
} }
return std::make_unique<bluez::SubscribedGattCharacteristicClient>( 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>> std::optional<std::tuple<Uuid, Uuid, sdbus::ObjectPath>>
@@ -396,7 +393,7 @@ try {
return std::nullopt; 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( void BluezGattDiscovery::onInterfacesAdded(
@@ -414,12 +411,12 @@ void BluezGattDiscovery::onInterfacesAdded(
absl::MutexLock lock(&mutex_); absl::MutexLock lock(&mutex_);
auto maybe_props = characteristicProperties(objectPath, properties); auto maybe_props = characteristicProperties(objectPath, properties);
if (!maybe_props.has_value()) return; 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( 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( 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( void BluezGattDiscovery::onInterfacesRemoved(
@@ -93,7 +93,7 @@ class BluezGattDiscovery final : public bluez::BluezObjectManager {
absl::Mutex mutex_; absl::Mutex mutex_;
absl::flat_hash_map<sdbus::ObjectPath, std::unique_ptr<GattServiceClient>> absl::flat_hash_map<sdbus::ObjectPath, std::unique_ptr<GattServiceClient>>
cached_services_ ABSL_GUARDED_BY(mutex_); 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>, absl::flat_hash_map<std::tuple<Uuid, Uuid, sdbus::ObjectPath>,
sdbus::ObjectPath> sdbus::ObjectPath>
discovered_characteristics_ ABSL_GUARDED_BY(mutex_); discovered_characteristics_ ABSL_GUARDED_BY(mutex_);
@@ -122,7 +122,7 @@ class GattClient : public api::ble::GattClient {
peripheral_object_path_(peripheral_object_path), peripheral_object_path_(peripheral_object_path),
gatt_discovery_(std::move(gatt_discovery)), gatt_discovery_(std::move(gatt_discovery)),
discovery_cancel_(false) { discovery_cancel_(false) {
disconnected_callback_it_ = gatt_discovery->AddPeripheralConnection( disconnected_callback_it_ = gatt_discovery_->AddPeripheralConnection(
peripheral_object_path_, std::move(disconnected_callback)); peripheral_object_path_, std::move(disconnected_callback));
} }
~GattClient() override { ~GattClient() override {
@@ -24,6 +24,19 @@
namespace nearby { namespace nearby {
namespace linux { 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> absl::optional<api::ble::GattCharacteristic>
GattServer::CreateCharacteristic( GattServer::CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid, const Uuid& service_uuid, const Uuid& characteristic_uuid,
@@ -60,23 +73,53 @@ GattServer::CreateCharacteristic(
if (service->AddCharacteristic(service_uuid, characteristic_uuid, permission, if (service->AddCharacteristic(service_uuid, characteristic_uuid, permission,
property)) { 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)}); 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{ api::ble::GattCharacteristic characteristic{
characteristic_uuid, service_uuid, permission, property}; characteristic_uuid, service_uuid, permission, property};
@@ -137,19 +180,86 @@ absl::Status GattServer::NotifyCharacteristicChanged(
return chr->NotifyChanged(confirm, new_value); 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() { void GattServer::Stop() {
bluez::GattManager manager(system_bus_, adapter_.GetObjectPath()); sdbus::ObjectPath app_path =
absl::MutexLock lock(&services_mutex_); gatt_service_root_object_manager->getObject().getObjectPath();
for (auto& [uuid, service] : services_) { bool should_unregister = false;
LOG(INFO) << __func__ << ": Unregistering service "
<< service->getObject().getObjectPath(); {
try { absl::MutexLock registration_lock(&registration_mutex_);
manager.UnregisterApplication(sdbus::ObjectPath("/")); if (register_application_call_.has_value() &&
} catch (const sdbus::Error& e) { register_application_call_->isPending()) {
DBUS_LOG_METHOD_CALL_ERROR(&manager, "UnregisterApplication", e); 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 } // namespace linux
@@ -16,6 +16,7 @@
#define PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_ #define PLATFORM_IMPL_LINUX_API_BLE_GATT_SERVER_H_
#include <memory> #include <memory>
#include <optional>
#include <regex> #include <regex>
#include <sdbus-c++/IConnection.h> #include <sdbus-c++/IConnection.h>
@@ -69,7 +70,16 @@ class GattServer : public api::ble::GattServer {
sdbus::ObjectPath("/com/google/nearby/medium/ble/gatt"))), sdbus::ObjectPath("/com/google/nearby/medium/ble/gatt"))),
gatt_manager_(std::make_unique<bluez::GattManager>(system_bus_, adapter_.GetObjectPath())), gatt_manager_(std::make_unique<bluez::GattManager>(system_bus_, adapter_.GetObjectPath())),
server_cb_(std::make_shared<api::ble::ServerGattConnectionCallback>( 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; ~GattServer() override = default;
absl::optional<api::ble::GattCharacteristic> CreateCharacteristic( absl::optional<api::ble::GattCharacteristic> CreateCharacteristic(
@@ -85,6 +95,9 @@ class GattServer : public api::ble::GattServer {
void Stop() override; void Stop() override;
private: private:
void OnRegisterApplicationReply(std::optional<sdbus::Error> error);
void OnUnregisterApplicationReply(std::optional<sdbus::Error> error);
sdbus::IConnection& system_bus_; sdbus::IConnection& system_bus_;
std::shared_ptr<BluetoothDevices> devices_; std::shared_ptr<BluetoothDevices> devices_;
BluetoothAdapter adapter_; BluetoothAdapter adapter_;
@@ -93,9 +106,20 @@ class GattServer : public api::ble::GattServer {
std::unique_ptr<RootObjectManager> gatt_service_root_object_manager; std::unique_ptr<RootObjectManager> gatt_service_root_object_manager;
absl::Mutex profiles_mutex_; absl::Mutex profiles_mutex_;
absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattProfile>> gatt_profiles_; 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::unique_ptr<bluez::GattManager> gatt_manager_;
std::shared_ptr<api::ble::ServerGattConnectionCallback> server_cb_; 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::Mutex services_mutex_;
absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattServiceServer>> services_ absl::flat_hash_map<Uuid, std::unique_ptr<bluez::GattServiceServer>> services_
ABSL_GUARDED_BY(services_mutex_); ABSL_GUARDED_BY(services_mutex_);
@@ -27,17 +27,14 @@
#include "absl/synchronization/mutex.h" #include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble.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 "internal/platform/implementation/linux/ble_v2_medium.h"
#include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include "absl/types/span.h" #include "absl/types/span.h"
#include "absl/time/time.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_server_socket.h"
#include "ble_l2cap_socket.h" #include "ble_l2cap_socket.h"
#include "ble_gatt_server.h"
#include "internal/base/observer_list.h" #include "internal/base/observer_list.h"
#include "internal/platform/implementation/linux/bluetooth_classic_device.h" #include "internal/platform/implementation/linux/bluetooth_classic_device.h"
#include "internal/platform/implementation/linux/bluetooth_devices.h" #include "internal/platform/implementation/linux/bluetooth_devices.h"
@@ -68,7 +65,6 @@ BleL2capSocket::ProtocolMode GetL2capProtocolMode() {
BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) BleV2Medium::BleV2Medium(BluetoothAdapter &adapter)
: system_bus_(adapter.GetConnection()), : system_bus_(adapter.GetConnection()),
adapter_(adapter), adapter_(adapter),
gatt_discovery_(std::make_shared<BluezGattDiscovery>(system_bus_)),
observers_(std::make_shared<ObserverList<api::BluetoothClassicMedium::Observer>>()), observers_(std::make_shared<ObserverList<api::BluetoothClassicMedium::Observer>>()),
devices_(std::make_unique<BluetoothDevices>( devices_(std::make_unique<BluetoothDevices>(
system_bus_, adapter_.GetObjectPath(), *observers_)), system_bus_, adapter_.GetObjectPath(), *observers_)),
@@ -89,16 +85,55 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter)
<< __func__ << __func__
<< ": Registering path /com/google/nearby/medium/ble/advertisement/monitor with AdvertisementMonitorManager at " << ": Registering path /com/google/nearby/medium/ble/advertisement/monitor with AdvertisementMonitorManager at "
<< adv_monitor_manager_->getProxy().getObjectPath(); << adv_monitor_manager_->getProxy().getObjectPath();
try { adv_monitor_manager_->SetRegisterMonitorReplyCallback(
adv_monitor_manager_->RegisterMonitor(root_object_manager_->getObject().getObjectPath()); [this](std::optional<sdbus::Error> error) {
} catch (const sdbus::Error &e) { OnRegisterMonitorReply(std::move(error));
DBUS_LOG_METHOD_CALL_ERROR(adv_monitor_manager_, "RegisterMonitor", e); });
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__ if (error.has_value() && error->isValid()) {
// << ": Could not initialize known GATT services"; 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 // sync api
@@ -240,9 +275,7 @@ bool BleV2Medium::StartScanning(const Uuid &service_uuid,
return false; return false;
} }
if (adv_monitor_manager_ == nullptr) { if (!WaitForAdvertisementMonitorManager()) {
LOG(WARNING) << __func__
<< ": Advertising monitor not supported by BlueZ";
// TODO: Implement manual monitoring. // TODO: Implement manual monitoring.
return false; return false;
} }
@@ -314,7 +347,7 @@ bool BleV2Medium::StopScanning() {
return false; return false;
} }
if (adv_monitor_manager_ == nullptr) { if (!WaitForAdvertisementMonitorManager()) {
// TODO: Implement manual monitoring. // TODO: Implement manual monitoring.
return false; return false;
} }
@@ -349,7 +382,7 @@ bool BleV2Medium::StopScanning() {
BleV2Medium::StartScanning(const Uuid &service_uuid, BleV2Medium::StartScanning(const Uuid &service_uuid,
api::ble::TxPowerLevel tx_power_level, api::ble::TxPowerLevel tx_power_level,
ScanningCallback callback) { ScanningCallback callback) {
if (adv_monitor_manager_ == nullptr) { if (!WaitForAdvertisementMonitorManager()) {
// TODO: Implement manual monitoring. // TODO: Implement manual monitoring.
return nullptr; return nullptr;
} }
@@ -437,12 +470,9 @@ bool BleV2Medium::StopScanning() {
std::unique_ptr<api::ble::GattServer> BleV2Medium::StartGattServer( std::unique_ptr<api::ble::GattServer> BleV2Medium::StartGattServer(
api::ble::ServerGattConnectionCallback callback) { api::ble::ServerGattConnectionCallback callback) {
// (void)callback; LOG(INFO) << __func__ << ": Starting Linux GATT server.";
// return nullptr; return std::make_unique<GattServer>(*system_bus_, adapter_, devices_,
std::move(callback));
return std::make_unique<GattServer>(
*system_bus_, adapter_, devices_,std::move(callback)
);
} }
std::unique_ptr<api::ble::GattClient> BleV2Medium::ConnectToGattServer( std::unique_ptr<api::ble::GattClient> BleV2Medium::ConnectToGattServer(
@@ -452,8 +482,7 @@ std::unique_ptr<api::ble::GattClient> BleV2Medium::ConnectToGattServer(
(void)peripheral_id; (void)peripheral_id;
(void)tx_power_level; (void)tx_power_level;
(void)callback; (void)callback;
LOG(WARNING) << __func__ LOG(INFO) << __func__ << ": GATT is disabled on linux.";
<< ": GATT client connection is not supported on Linux yet.";
return nullptr; return nullptr;
} }
@@ -17,8 +17,11 @@
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <string>
#include <vector> #include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/notification.h"
#include <sdbus-c++/IConnection.h> #include <sdbus-c++/IConnection.h>
#include "absl/base/attributes.h" #include "absl/base/attributes.h"
@@ -118,6 +121,8 @@ class BleV2Medium final : public api::ble::BleMedium {
private: private:
bool StartLEDiscovery(); bool StartLEDiscovery();
bool WaitForAdvertisementMonitorManager();
void OnRegisterMonitorReply(std::optional<sdbus::Error> error);
bool MonitorManagerSupportsOr() { bool MonitorManagerSupportsOr() {
std::vector<std::string> supported_types; 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<RootObjectManager> root_object_manager_;
std::unique_ptr<bluez::AdvertisementMonitorManager> adv_monitor_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::Mutex active_adv_monitors_mutex_;
absl::flat_hash_map< absl::flat_hash_map<
Uuid, Uuid,
@@ -21,34 +21,48 @@
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "internal/base/observer_list.h" #include "internal/base/observer_list.h"
#include "internal/platform/implementation/bluetooth_classic.h" #include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h"
#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" #include "internal/platform/implementation/linux/bluez_agent.h"
#include "internal/platform/implementation/linux/bluetooth_classic_device.h" #include "internal/platform/implementation/linux/bluetooth_bluez_profile.h"
#include "internal/platform/implementation/linux/bluetooth_classic_medium.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_server_socket.h"
#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" #include "internal/platform/implementation/linux/bluetooth_classic_socket.h"
#include "internal/platform/implementation/linux/bluetooth_pairing.h" #include "internal/platform/implementation/linux/bluetooth_pairing.h"
#include "internal/platform/logging.h" #include "internal/platform/logging.h"
namespace nearby { namespace nearby {
namespace linux { namespace linux {
BluetoothClassicMedium::BluetoothClassicMedium(BluetoothAdapter &adapter) namespace {
: system_bus_(adapter.GetConnection()),
adapter_(adapter), constexpr char kBluezAgentPath[] = "/com/google/nearby/bluetooth/agent";
observers_(nullptr),
devices_(nullptr), } // namespace
device_watcher_(nullptr),
// agent_manager_(std::make_unique<AgentManager>(*system_bus_)), BluetoothClassicMedium::BluetoothClassicMedium(BluetoothAdapter &adapter)
profile_manager_(nullptr) { : system_bus_(adapter.GetConnection()),
auto shared = adapter_(adapter),
GetSharedBluetoothDevices(system_bus_, adapter_.GetObjectPath()); observers_(nullptr),
observers_ = shared->observers; devices_(nullptr),
devices_ = shared->devices; device_watcher_(nullptr),
profile_manager_ = agent_manager_(std::make_unique<AgentManager>(*system_bus_)),
std::make_unique<ProfileManager>(*system_bus_, *devices_); 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( bool BluetoothClassicMedium::StartDiscovery(
DiscoveryCallback discovery_callback) { DiscoveryCallback discovery_callback) {
@@ -145,14 +159,12 @@ std::unique_ptr<api::BluetoothSocket> BluetoothClassicMedium::ConnectToService(
new BluetoothSocket(device, fd.value())); new BluetoothSocket(device, fd.value()));
} }
std::unique_ptr<api::BluetoothServerSocket> std::unique_ptr<api::BluetoothServerSocket>
BluetoothClassicMedium::ListenForService(const std::string &service_name, BluetoothClassicMedium::ListenForService(const std::string &service_name,
const std::string &service_uuid) { 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)) {
if (!profile_manager_->ProfileRegistered(service_uuid)) { LOG(ERROR) << __func__ << ": Could not register profile "
if (!profile_manager_->Register(service_name, service_uuid)) {
LOG(ERROR) << __func__ << ": Could not register profile "
<< service_name << " " << service_uuid << service_name << " " << service_uuid
<< " with Bluez"; << " with Bluez";
return nullptr; return nullptr;
@@ -26,7 +26,7 @@
#include <sdbus-c++/StandardInterfaces.h> #include <sdbus-c++/StandardInterfaces.h>
#include <sdbus-c++/Types.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/base/observer_list.h"
#include "internal/platform/implementation/bluetooth_classic.h" #include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h"
@@ -107,7 +107,7 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium {
std::shared_ptr<BluetoothDevices> devices_; std::shared_ptr<BluetoothDevices> devices_;
std::unique_ptr<DeviceWatcher> device_watcher_; std::unique_ptr<DeviceWatcher> device_watcher_;
// std::unique_ptr<AgentManager> agent_manager_; std::unique_ptr<AgentManager> agent_manager_;
std::unique_ptr<ProfileManager> profile_manager_; std::unique_ptr<ProfileManager> profile_manager_;
}; };
@@ -15,9 +15,13 @@
#ifndef PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_ #ifndef PLATFORM_IMPL_LINUX_BLUEZ_ADVERTISEMENT_MONITOR_MANAGER_H_
#define 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++/IConnection.h>
#include <sdbus-c++/ProxyInterfaces.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/bluetooth_adapter.h"
#include "internal/platform/implementation/linux/bluez.h" #include "internal/platform/implementation/linux/bluez.h"
#include "internal/platform/implementation/linux/dbus.h" #include "internal/platform/implementation/linux/dbus.h"
@@ -29,6 +33,9 @@ namespace bluez {
class AdvertisementMonitorManager final class AdvertisementMonitorManager final
: public sdbus::ProxyInterfaces< : public sdbus::ProxyInterfaces<
org::bluez::AdvertisementMonitorManager1_proxy> { org::bluez::AdvertisementMonitorManager1_proxy> {
public:
using ReplyCallback = absl::AnyInvocable<void(std::optional<sdbus::Error>)>;
private: private:
friend std::unique_ptr<AdvertisementMonitorManager> friend std::unique_ptr<AdvertisementMonitorManager>
std::make_unique<AdvertisementMonitorManager>( std::make_unique<AdvertisementMonitorManager>(
@@ -50,6 +57,16 @@ class AdvertisementMonitorManager final
delete; delete;
~AdvertisementMonitorManager() { unregisterProxy(); } ~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> static std::unique_ptr<AdvertisementMonitorManager>
DiscoverAdvertisementMonitorManager( DiscoverAdvertisementMonitorManager(
sdbus::IConnection &system_bus, sdbus::IConnection &system_bus,
@@ -82,6 +99,38 @@ class AdvertisementMonitorManager final
return std::make_unique<AdvertisementMonitorManager>(system_bus, adapter); 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 bluez
} // namespace linux } // 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 | if ((characteristic_.property |
api::ble::GattCharacteristic::Property::kNotify) == api::ble::GattCharacteristic::Property::kNotify) ==
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) { if (server_cb_->characteristic_unsubscription_cb != nullptr) {
server_cb_->characteristic_unsubscription_cb(characteristic_); server_cb_->characteristic_unsubscription_cb(characteristic_);
} }
@@ -14,10 +14,15 @@
#ifndef PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_ #ifndef PLATFORM_IMPL_LINUX_API_BLUEZ_GATT_MANAGER_H_
#define 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++/IConnection.h>
#include <sdbus-c++/ProxyInterfaces.h> #include <sdbus-c++/ProxyInterfaces.h>
#include <sdbus-c++/Types.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" #include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_manager_client.h"
namespace nearby { namespace nearby {
namespace linux { namespace linux {
@@ -25,6 +30,8 @@ namespace bluez {
class GattManager class GattManager
: public sdbus::ProxyInterfaces<org::bluez::GattManager1_proxy> { : public sdbus::ProxyInterfaces<org::bluez::GattManager1_proxy> {
public: public:
using ReplyCallback = absl::AnyInvocable<void(std::optional<sdbus::Error>)>;
GattManager(const GattManager &) = delete; GattManager(const GattManager &) = delete;
GattManager(GattManager &&) = delete; GattManager(GattManager &&) = delete;
GattManager &operator=(const GattManager &) = delete; GattManager &operator=(const GattManager &) = delete;
@@ -37,6 +44,38 @@ class GattManager
registerProxy(); registerProxy();
} }
~GattManager() { unregisterProxy(); } ~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 bluez
} // namespace linux } // namespace linux
@@ -3,8 +3,8 @@
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT! * 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__ #ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_advertisement_monitor_manager_client_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__ #define __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_advertisement_monitor_manager_client_h__proxy__H__
#include <sdbus-c++/sdbus-c++.h> #include <sdbus-c++/sdbus-c++.h>
#include <string> #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: 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: 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! * 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__ #ifndef __sdbuscpp__internal_platform_implementation_linux_generated_dbus_bluez_gatt_manager_client_h__proxy__H__
#define __sdbuscpp___home_lasan_Dev_nearby_latest_internal_platform_implementation_linux_generated_dbus_bluez_gatt_manager_client_v2_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 <sdbus-c++/sdbus-c++.h>
#include <string> #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: 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: private:
@@ -3,9 +3,11 @@
<node> <node>
<interface name="org.bluez.AdvertisementMonitorManager1"> <interface name="org.bluez.AdvertisementMonitorManager1">
<method name="RegisterMonitor"> <method name="RegisterMonitor">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" /> <arg name="application" type="o" direction="in" />
</method> </method>
<method name="UnregisterMonitor"> <method name="UnregisterMonitor">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" /> <arg name="application" type="o" direction="in" />
</method> </method>
<property name="SupportedMonitorTypes" type="as" access="read" /> <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> <node>
<interface name="org.bluez.GattManager1"> <interface name="org.bluez.GattManager1">
<method name="RegisterApplication"> <method name="RegisterApplication">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" /> <arg name="application" type="o" direction="in" />
<arg name="options" type="a{sv}" direction="in" /> <arg name="options" type="a{sv}" direction="in" />
</method> </method>
<method name="UnregisterApplication"> <method name="UnregisterApplication">
<annotation name="org.freedesktop.DBus.Method.Async" value="client" />
<arg name="application" type="o" direction="in" /> <arg name="application" type="o" direction="in" />
</method> </method>
</interface> </interface>
+1
View File
@@ -24,6 +24,7 @@ cc_library(
"//internal/base:file_path", "//internal/base:file_path",
"//internal/base:files", "//internal/base:files",
"//internal/crypto_cros", # buildcleaner: keep "//internal/crypto_cros", # buildcleaner: keep
"//internal/platform:base",
"//internal/interop:authentication_status", "//internal/interop:authentication_status",
"@com_google_absl//absl/random", "@com_google_absl//absl/random",
"@com_google_absl//absl/strings:string_view", "@com_google_absl//absl/strings:string_view",
+55 -14
View File
@@ -13,7 +13,8 @@
# limitations under the License. # limitations under the License.
licenses(["notice"]) 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( cc_library(
name = "linux_sharing_platform", 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( cc_library(
name = "nearby_sharing_api", name = "nearby_sharing_api",
srcs = ["nearby_sharing_api.cc"], srcs = ["nearby_sharing_api.cc"],
@@ -65,8 +55,8 @@ cc_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
":linux_sharing_platform", ":linux_sharing_platform",
":system_clock_shim",
"//internal/base:file_path", "//internal/base:file_path",
"//internal/platform/implementation/linux:system_clock",
"//sharing:attachments", "//sharing:attachments",
"//sharing:nearby_sharing_service", "//sharing:nearby_sharing_service",
"//sharing/analytics", "//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( cc_binary(
name = "nearby_sharing_api_shared", name = "nearby_sharing_api_shared",
linkshared = True, linkshared = True,
@@ -86,14 +96,45 @@ cc_binary(
"-Wl,--exclude-libs,ALL", "-Wl,--exclude-libs,ALL",
], ],
deps = [ deps = [
"//internal/platform:base",
":linux_sharing_platform", ":linux_sharing_platform",
":system_clock_shim",
"//internal/base:file_path", "//internal/base:file_path",
"//internal/platform/implementation:platform", "//internal/platform/implementation:platform",
"//internal/platform/implementation/linux", "//internal/platform/implementation/linux",
"//internal/platform/implementation/linux:system_clock",
"//sharing:attachments", "//sharing:attachments",
"//sharing:nearby_sharing_service", "//sharing:nearby_sharing_service",
"//sharing/analytics", "//sharing/analytics",
"//sharing/local_device_data", "//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/analytics/analytics_recorder.h"
#include "sharing/attachment_container.h" #include "sharing/attachment_container.h"
#include "sharing/file_attachment.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/linux/platform/linux_sharing_platform.h"
#include "sharing/local_device_data/nearby_share_local_device_data_manager.h" #include "sharing/local_device_data/nearby_share_local_device_data_manager.h"
#include "sharing/nearby_sharing_service_factory.h" #include "sharing/nearby_sharing_service_factory.h"
@@ -28,7 +29,7 @@
#include "sharing/transfer_metadata.h" #include "sharing/transfer_metadata.h"
#include "sharing/transfer_update_callback.h" #include "sharing/transfer_update_callback.h"
namespace nearby::sharing::linux { namespace nearby::sharing {
namespace { namespace {
@@ -39,6 +40,10 @@ void EnableBleL2capDefaults() {
nearby::connections::config_package_nearby::nearby_connections_feature:: nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableBleL2cap, kEnableBleL2cap,
true); true);
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::sharing::config_package_nearby::nearby_sharing_feature::
kEnableBleForTransfer,
true);
//nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( //nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
// nearby::connections::config_package_nearby::nearby_connections_feature:: // nearby::connections::config_package_nearby::nearby_connections_feature::
// kRefactorBleL2cap, // kRefactorBleL2cap,
@@ -125,6 +130,16 @@ NearbySharingApi::TextAttachmentType ToFacadeTextAttachmentType(
return FacadeType::kUnknown; 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() { std::string GenerateQrCodeUrl() {
auto ec_key = nearby::crypto::ECPrivateKey::Create(); auto ec_key = nearby::crypto::ECPrivateKey::Create();
if (!ec_key) { if (!ec_key) {
@@ -266,7 +281,7 @@ class NearbySharingApi::Impl : public nearby::sharing::ShareTargetDiscoveredCall
info.device_name = share_target.device_name; info.device_name = share_target.device_name;
info.is_incoming = share_target.is_incoming; info.is_incoming = share_target.is_incoming;
info.status = ToFacadeTransferStatus(transfer_metadata.status()); 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.transferred_bytes = transfer_metadata.transferred_bytes();
info.total_attachments = transfer_metadata.total_attachments_count(); info.total_attachments = transfer_metadata.total_attachments_count();
info.transferred_attachments = transfer_metadata.transferred_attachments_count(); info.transferred_attachments = transfer_metadata.transferred_attachments_count();
@@ -657,4 +672,4 @@ std::string NearbySharingApi::TransferStatusToString(TransferStatus status) {
return "Unknown"; return "Unknown";
} }
} // namespace nearby::sharing::linux } // namespace nearby::sharing
-8
View File
@@ -13,15 +13,8 @@
#include <string> #include <string>
#include <vector> #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 nearby {
namespace sharing { namespace sharing {
namespace linux {
class __attribute__((visibility("default"))) NearbySharingApi { class __attribute__((visibility("default"))) NearbySharingApi {
public: public:
@@ -131,7 +124,6 @@ class __attribute__((visibility("default"))) NearbySharingApi {
std::unique_ptr<Impl> impl_; std::unique_ptr<Impl> impl_;
}; };
} // namespace linux
} // namespace sharing } // namespace sharing
} // namespace nearby } // namespace nearby
@@ -60,7 +60,7 @@
#include "sharing/internal/public/pref_names.h" #include "sharing/internal/public/pref_names.h"
#include "sharing/proto/rpc_resources.pb.h" #include "sharing/proto/rpc_resources.pb.h"
namespace nearby::sharing::linux { namespace nearby::sharing {
namespace { namespace {
using Json = nlohmann::json; using Json = nlohmann::json;
@@ -15,7 +15,7 @@
#include "internal/platform/task_runner.h" #include "internal/platform/task_runner.h"
#include "sharing/internal/api/sharing_platform.h" #include "sharing/internal/api/sharing_platform.h"
namespace nearby::sharing::linux { namespace nearby::sharing {
class LinuxSharingPlatform final : public nearby::sharing::api::SharingPlatform { class LinuxSharingPlatform final : public nearby::sharing::api::SharingPlatform {
public: public:
+10
View File
@@ -26,6 +26,16 @@ qt_add_executable(nearby_qml_file_tray_app
file_share_tray_main.cpp file_share_tray_main.cpp
file_share_tray_controller.cc file_share_tray_controller.cc
file_share_tray_controller.h 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.cpp
notification_manager.h notification_manager.h
third_party/libqrencode/qrencode_compat.h third_party/libqrencode/qrencode_compat.h
+2 -5
View File
@@ -51,18 +51,16 @@ ApplicationWindow {
clip: true clip: true
readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0 readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0
readonly property bool isIdle: fileShareController.discoveredTargets.length === 0
&& fileShareController.transfers.length === 0
// ── Idle: animated blob ─────────────────────────────────── // ── Idle: animated blob ───────────────────────────────────
AnimatedBlob { visible: mainContent.isIdle } AnimatedBlob { visible: !mainContent.isSendMode }
// ── Non-idle: scrollable device + transfer cards ────────── // ── Non-idle: scrollable device + transfer cards ──────────
Flickable { Flickable {
id: mainFlickable id: mainFlickable
anchors.fill: parent anchors.fill: parent
clip: true clip: true
visible: !mainContent.isIdle visible: mainContent.isSendMode
contentWidth: width contentWidth: width
contentHeight: mainCol.implicitHeight + 96 contentHeight: mainCol.implicitHeight + 96
ScrollBar.vertical: ScrollBar {} ScrollBar.vertical: ScrollBar {}
@@ -77,7 +75,6 @@ ApplicationWindow {
SendUrlPanel { SendUrlPanel {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
width: Math.max(240, Math.min(mainCol.width, 420)) width: Math.max(240, Math.min(mainCol.width, 420))
visible: mainContent.isSendMode
} }
@@ -37,7 +37,7 @@ if(EXISTS "${_output_so}")
ERROR_QUIET ERROR_QUIET
) )
if(_nm_result EQUAL 0) 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::api::ImplementationPlatform::CreateScheduledExecutor()" _undef_platform_idx)
string(FIND "${_nm_output}" " U nearby::SystemClock::ElapsedRealtime()" _undef_clock_idx) string(FIND "${_nm_output}" " U nearby::SystemClock::ElapsedRealtime()" _undef_clock_idx)
string(FIND "${_nm_output}" " U nearby::Crypto::Sha256(" _undef_crypto_idx) string(FIND "${_nm_output}" " U nearby::Crypto::Sha256(" _undef_crypto_idx)
@@ -1,13 +1,116 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Effects
Item { Item {
anchors.fill: parent anchors.fill: parent
readonly property color textPrimary: "#111827" readonly property color textPrimary: "#111827"
readonly property color textMuted: "#6b7280" 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 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 { Label {
x: 48; y: 48 x: 48; y: 48
@@ -19,6 +122,141 @@ Item {
font.weight: Font.Medium font.weight: Font.Medium
color: textPrimary 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 { Canvas {
id: blobCanvas id: blobCanvas
@@ -27,15 +265,17 @@ Item {
visible: !isSendMode visible: !isSendMode
property real t: 0 property real t: 0
property double startMs: Date.now() property double lastMs: Date.now()
Timer { Timer {
interval: 16 interval: 16
running: true running: true
repeat: true repeat: true
onTriggered: { onTriggered: {
// seconds -> radians scale (t grows forever, no loop jump) var now = Date.now()
blobCanvas.t = (Date.now() - blobCanvas.startMs) * 0.001 * (Math.PI * 2 / 8.0) 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() blobCanvas.requestPaint()
} }
} }
@@ -44,17 +284,21 @@ Item {
var ctx = getContext("2d") var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height) ctx.clearRect(0, 0, width, height)
var cx = width / 2 var chaos = blobChaos
var cy = height / 2 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 n = 10
var pts = [] var pts = []
for (var i = 0; i < n; i++) { for (var i = 0; i < n; i++) {
var a = (i / n) * Math.PI * 2 - Math.PI / 2 var a = (i / n) * Math.PI * 2 - Math.PI / 2
var r = 130 var r = 130 + chaos * 15
+ Math.sin(a * 2 + t) * 11 + Math.sin(a * 2 + t) * (11 + chaos * 15)
+ Math.cos(a * 3 - t * 0.6) * 8 + Math.cos(a * 3 - t * 0.6) * (8 + chaos * 12)
+ Math.sin(a * 1.5 + t * 0.35) * 6 + 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 }) 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 anchors.centerIn: parent
width: parent.width - 96 visible: !isSendMode && hasIncomingTransfer
visible: isSendMode && 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 { Label {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@@ -185,7 +185,7 @@ Item {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: targetName text: targetName
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Bold
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 2 maximumLineCount: 2
wrapMode: Text.Wrap 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_ #define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_
#include <QObject> #include <QObject>
#include <QHash>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QVariantList>
#include <memory> #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 { class FileShareTrayController : public QObject {
Q_OBJECT Q_OBJECT
@@ -35,28 +29,24 @@ class FileShareTrayController : public QObject {
explicit FileShareTrayController(QObject* parent = nullptr); explicit FileShareTrayController(QObject* parent = nullptr);
~FileShareTrayController() override; ~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); 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); 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); void setLogPath(const QString& path);
Q_INVOKABLE void start(); Q_INVOKABLE void start();
@@ -65,6 +55,7 @@ class FileShareTrayController : public QObject {
Q_INVOKABLE void switchToSendModeWithFile(const QString& file_path); Q_INVOKABLE void switchToSendModeWithFile(const QString& file_path);
Q_INVOKABLE void sendPendingFileToTarget(qlonglong share_target_id); Q_INVOKABLE void sendPendingFileToTarget(qlonglong share_target_id);
Q_INVOKABLE void copyTextToClipboard(const QString& text); Q_INVOKABLE void copyTextToClipboard(const QString& text);
Q_INVOKABLE void openFileLocation(const QString& file_path);
Q_INVOKABLE void clearTransfers(); Q_INVOKABLE void clearTransfers();
Q_INVOKABLE void hideToTray(); Q_INVOKABLE void hideToTray();
@@ -84,65 +75,31 @@ class FileShareTrayController : public QObject {
void requestTrayMessage(const QString& title, const QString& body); void requestTrayMessage(const QString& title, const QString& body);
void requestCopyLinkTrayMessage(const QString& title, const QString& body, void requestCopyLinkTrayMessage(const QString& title, const QString& body,
const QString& link); const QString& link);
private: private:
void CreateService(); void initializeService();
void AttachServiceListeners(); void attachServiceListeners();
void loadSettings();
void saveSettings() const;
void updateQrCodeData();
void startSendMode(); void startSendMode();
void startReceiveMode(); void startReceiveMode();
void LoadSettings();
void SaveSettings() const; void updateTargetFromInfo(const NearbySharingApi::ShareTargetInfo& info);
void UpdateQrCodeData(); void handleTransferUpdate(const NearbySharingApi::TransferUpdateInfo& update);
void handleTransferComplete(const NearbySharingApi::TransferUpdateInfo& update);
void UpsertTarget(qlonglong share_target_id, const QString& name, void handleIncomingTransferComplete(const NearbySharingApi::TransferUpdateInfo& update,
bool is_incoming); const QString& name, bool success);
void RemoveTarget(qlonglong share_target_id); void handleOutgoingTransferComplete(const NearbySharingApi::TransferUpdateInfo& update,
QString TargetName(qlonglong share_target_id) const; const QString& name, bool success);
bool HasActiveTransferForTarget(qlonglong share_target_id) const;
void setStatus(const QString& status);
void UpsertTransfer(qlonglong share_target_id, const QString& target_name, void notifyStateChange(const QString& property);
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);
std::unique_ptr<NearbySharingApi> service_; std::unique_ptr<NearbySharingApi> service_;
FileShareState state_;
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_;
}; };
#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_ #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.setContextMenu(&tray_menu);
tray.show(); tray.show();
controller.start();
//controller.
controller.switchToReceiveMode(); controller.switchToReceiveMode();
return app.exec(); 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 <algorithm>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <memory>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
@@ -42,6 +43,61 @@ using NcPayload = ::nearby::connections::Payload;
using NcStatus = ::nearby::connections::Status; using NcStatus = ::nearby::connections::Status;
using NcStrategy = ::nearby::connections::Strategy; 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) { Status ConvertToStatus(NcStatus status) {
return static_cast<Status>(status.value); return static_cast<Status>(status.value);
} }
@@ -86,6 +142,17 @@ NcPayload ConvertToServicePayload(Payload payload) {
return NcPayload(payload.id, return NcPayload(payload.id,
ByteArray(std::string(bytes.begin(), bytes.end()))); 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: default:
return NcPayload(); return NcPayload();
} }
+21
View File
@@ -19,6 +19,7 @@
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <memory>
#include <optional> #include <optional>
#include <string> #include <string>
#include <utility> #include <utility>
@@ -28,6 +29,7 @@
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "absl/time/time.h" #include "absl/time/time.h"
#include "internal/base/file_path.h" #include "internal/base/file_path.h"
#include "internal/platform/input_stream.h"
#include "internal/interop/authentication_status.h" #include "internal/interop/authentication_status.h"
namespace nearby { namespace nearby {
@@ -362,6 +364,14 @@ struct BytesPayload {
std::vector<uint8_t> bytes; 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. // A file payload representing a file.
struct FilePayload { struct FilePayload {
// The file to which this payload points to. When sending this payload, the // The file to which this payload points to. When sending this payload, the
@@ -375,6 +385,8 @@ struct FilePayload {
struct PayloadContent { struct PayloadContent {
// A Payload consisting of a single byte array. // A Payload consisting of a single byte array.
BytesPayload bytes_payload; BytesPayload bytes_payload;
// A Payload exposed to Nearby as a finite stream.
StreamPayload stream_payload;
// A Payload representing a file on the device. // A Payload representing a file on the device.
FilePayload file_payload; FilePayload file_payload;
enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 }; enum class Type { kUnknown = 0, kBytes = 1, kStream = 2, kFile = 3 };
@@ -400,6 +412,9 @@ struct Payload {
explicit Payload(std::vector<uint8_t> bytes) explicit Payload(std::vector<uint8_t> bytes)
: Payload(GenerateId(), std::move(bytes)) {} : Payload(GenerateId(), std::move(bytes)) {}
explicit Payload(StreamPayload stream_payload)
: Payload(GenerateId(), std::move(stream_payload)) {}
explicit Payload(FilePath file_path, explicit Payload(FilePath file_path,
absl::string_view parent_folder = absl::string_view()) absl::string_view parent_folder = absl::string_view())
: Payload(std::hash<std::string>()(file_path.ToString()), file_path, : Payload(std::hash<std::string>()(file_path.ToString()), file_path,
@@ -410,6 +425,12 @@ struct Payload {
content.bytes_payload.bytes = std::move(bytes); 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, Payload(int64_t id, FilePath file_path,
absl::string_view parent_folder = absl::string_view()) absl::string_view parent_folder = absl::string_view())
: id(id) { : id(id) {