refractored facade to switch from using nearby connections to linux nearby sharing service.

This commit is contained in:
Lasan Mahaliyana
2026-03-07 01:16:18 +05:30
parent 511f48c77f
commit 9eb1f1d411
21 changed files with 1362 additions and 1945 deletions
+34 -3
View File
@@ -72,6 +72,39 @@ cc_binary(
],
)
cc_library(
name = "nearby_sharing_api",
srcs = ["nearby_sharing_api.cc"],
hdrs = ["nearby_sharing_api.h"],
alwayslink = True,
visibility = ["//visibility:public"],
deps = [
":nearby_sharing_service_linux",
"//internal/base:file_path",
"//sharing:attachments",
],
)
cc_binary(
name = "nearby_sharing_api_shared",
linkshared = True,
srcs = [
"nearby_sharing_api.cc",
"nearby_sharing_api.h",
],
visibility = ["//visibility:public"],
linkopts = [
"-Wl,--exclude-libs,ALL",
],
deps = [
":nearby_sharing_service_linux",
"//internal/base:file_path",
"//internal/platform/implementation:platform",
"//internal/platform/implementation/linux",
"//sharing:attachments",
],
)
cc_binary(
name = "app",
@@ -135,7 +168,7 @@ cc_library(
"//sharing/local_device_data:nearby_share_local_device_data_manager.h",
],
visibility = ["//visibility:public"],
linkopts=[
linkopts = [
"-lssl",
"-lcrypto"
],
@@ -163,11 +196,9 @@ cc_binary(
deps = [
":nearby_sharing_service_linux",
"//internal/platform:base",
"//internal/platform/implementation:account_manager",
"//sharing:attachments",
"//sharing:types",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
],
)
+14
View File
@@ -28,6 +28,20 @@ The main service class that provides nearby sharing functionality:
- **Text Transfer**: Send and receive text messages
- **Connection Management**: Handle connection lifecycle (accept, reject, cancel)
### NearbySharingApi
For external/Linux app consumers (for example Qt/CMake apps), use
`nearby::sharing::linux::NearbySharingApi` from:
- `sharing/linux/nearby_sharing_api.h`
- `libnearby_sharing_api_shared.so`
Install artifacts with:
```bash
./sharing/linux/install_nearby_sharing_service.sh
```
### Key Concepts
#### 1. Send Surface
@@ -2,158 +2,17 @@
set -euo pipefail
TARGET="//sharing/linux:nearby_connections_service_linux_shared"
HEADER_SRC="sharing/linux/nearby_connections_qt_facade.h"
BAZEL_CMD="${BAZEL:-bazel}"
PREFIX="/usr/local"
LIBDIR=""
INCLUDEDIR=""
SKIP_BUILD=0
NEEDS_ELEVATION=0
INSTALL_PREFIX=()
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DELEGATE_SCRIPT="${SCRIPT_DIR}/install_nearby_sharing_service.sh"
usage() {
cat <<USAGE
Usage: $0 [options]
Builds and installs the Nearby Connections shared library and public header.
Options:
--prefix DIR Install prefix (default: /usr/local)
--libdir DIR Library directory (default: <prefix>/lib)
--includedir DIR Include root directory (default: <prefix>/include)
--bazel CMD Bazel command (default: bazel or env BAZEL)
--skip-build Skip bazel build step and only install from bazel-bin
-h, --help Show this help
Examples:
$0
sudo $0
sudo $0 --prefix /usr
sudo $0 --bazel /usr/bin/bazel
USAGE
}
run_bazel() {
# If invoked through sudo, run Bazel as the original user so Bazelisk reuses
# that user's cache and does not re-download Bazel as root.
if [[ "$(id -u)" -eq 0 && -n "${SUDO_USER:-}" ]]; then
local caller_home
caller_home="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
if [[ -z "$caller_home" ]]; then
echo "Failed to resolve home directory for SUDO_USER=$SUDO_USER" >&2
exit 1
fi
sudo -u "$SUDO_USER" -H env \
HOME="$caller_home" \
BAZELISK_HOME="${BAZELISK_HOME:-$caller_home/.cache/bazelisk}" \
"$BAZEL_CMD" "$@"
else
"$BAZEL_CMD" "$@"
fi
}
nearest_existing_parent() {
local p="$1"
while [[ ! -e "$p" ]]; do
p="$(dirname "$p")"
done
printf '%s\n' "$p"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--prefix)
PREFIX="$2"
shift 2
;;
--libdir)
LIBDIR="$2"
shift 2
;;
--includedir)
INCLUDEDIR="$2"
shift 2
;;
--bazel)
BAZEL_CMD="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$LIBDIR" ]]; then
LIBDIR="${PREFIX}/lib"
fi
if [[ -z "$INCLUDEDIR" ]]; then
INCLUDEDIR="${PREFIX}/include"
fi
LIB_PARENT="$(nearest_existing_parent "$LIBDIR")"
INCLUDE_PARENT="$(nearest_existing_parent "${INCLUDEDIR}/sharing/linux")"
if [[ ! -w "$LIB_PARENT" || ! -w "$INCLUDE_PARENT" ]]; then
NEEDS_ELEVATION=1
fi
if [[ "$NEEDS_ELEVATION" -eq 1 && "$(id -u)" -ne 0 ]]; then
if ! command -v sudo >/dev/null 2>&1; then
echo "Install requires elevated privileges, but sudo is not available." >&2
exit 1
fi
INSTALL_PREFIX=(sudo)
fi
if [[ ! -f "$HEADER_SRC" ]]; then
echo "Header not found: $HEADER_SRC" >&2
echo "Run this script from the workspace root." >&2
if [[ ! -x "${DELEGATE_SCRIPT}" ]]; then
echo "Missing installer: ${DELEGATE_SCRIPT}" >&2
exit 1
fi
if [[ "$SKIP_BUILD" -eq 0 ]]; then
echo "[1/4] Building $TARGET"
run_bazel build "$TARGET"
else
echo "[1/4] Skipping build (--skip-build)"
fi
cat <<'MSG'
install_nearby_connections_service.sh is deprecated.
Installing Nearby Sharing API artifacts instead.
MSG
echo "[2/4] Resolving bazel-bin path"
BAZEL_BIN="$(run_bazel info bazel-bin)"
LIB_SRC="${BAZEL_BIN}/sharing/linux/libnearby_connections_service_linux_shared.so"
if [[ ! -f "$LIB_SRC" ]]; then
echo "Shared library not found: $LIB_SRC" >&2
echo "Expected Bazel output for $TARGET is missing." >&2
exit 1
fi
echo "[3/4] Installing library and header"
"${INSTALL_PREFIX[@]}" install -d "$LIBDIR"
"${INSTALL_PREFIX[@]}" install -d "${INCLUDEDIR}/sharing/linux"
"${INSTALL_PREFIX[@]}" install -m 0755 "$LIB_SRC" "$LIBDIR/"
"${INSTALL_PREFIX[@]}" install -m 0644 "$HEADER_SRC" "${INCLUDEDIR}/sharing/linux/"
if command -v ldconfig >/dev/null 2>&1; then
echo "[4/4] Refreshing dynamic linker cache"
"${INSTALL_PREFIX[@]}" ldconfig
else
echo "[4/4] ldconfig not found; skipping linker cache refresh"
fi
echo "Installed:"
echo " library: $LIBDIR/$(basename "$LIB_SRC")"
echo " header : ${INCLUDEDIR}/sharing/linux/$(basename "$HEADER_SRC")"
exec "${DELEGATE_SCRIPT}" "$@"
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env bash
set -euo pipefail
TARGET="//sharing/linux:nearby_sharing_api_shared"
HEADER_SRC="sharing/linux/nearby_sharing_api.h"
BAZEL_CMD="${BAZEL:-bazel}"
PREFIX="/usr/local"
LIBDIR=""
INCLUDEDIR=""
SKIP_BUILD=0
NEEDS_ELEVATION=0
INSTALL_PREFIX=()
usage() {
cat <<USAGE
Usage: $0 [options]
Builds and installs the Nearby Sharing shared library and public header.
Options:
--prefix DIR Install prefix (default: /usr/local)
--libdir DIR Library directory (default: <prefix>/lib)
--includedir DIR Include root directory (default: <prefix>/include)
--bazel CMD Bazel command (default: bazel or env BAZEL)
--skip-build Skip bazel build step and only install from bazel-bin
-h, --help Show this help
Examples:
$0
sudo $0
sudo $0 --prefix /usr
sudo $0 --bazel /usr/bin/bazel
USAGE
}
run_bazel() {
if [[ "$(id -u)" -eq 0 && -n "${SUDO_USER:-}" ]]; then
local caller_home
caller_home="$(getent passwd "$SUDO_USER" | cut -d: -f6)"
if [[ -z "$caller_home" ]]; then
echo "Failed to resolve home directory for SUDO_USER=$SUDO_USER" >&2
exit 1
fi
sudo -u "$SUDO_USER" -H env \
HOME="$caller_home" \
BAZELISK_HOME="${BAZELISK_HOME:-$caller_home/.cache/bazelisk}" \
"$BAZEL_CMD" "$@"
else
"$BAZEL_CMD" "$@"
fi
}
nearest_existing_parent() {
local p="$1"
while [[ ! -e "$p" ]]; do
p="$(dirname "$p")"
done
printf '%s\n' "$p"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--prefix)
PREFIX="$2"
shift 2
;;
--libdir)
LIBDIR="$2"
shift 2
;;
--includedir)
INCLUDEDIR="$2"
shift 2
;;
--bazel)
BAZEL_CMD="$2"
shift 2
;;
--skip-build)
SKIP_BUILD=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage
exit 1
;;
esac
done
if [[ -z "$LIBDIR" ]]; then
LIBDIR="${PREFIX}/lib"
fi
if [[ -z "$INCLUDEDIR" ]]; then
INCLUDEDIR="${PREFIX}/include"
fi
LIB_PARENT="$(nearest_existing_parent "$LIBDIR")"
INCLUDE_PARENT="$(nearest_existing_parent "${INCLUDEDIR}/sharing/linux")"
if [[ ! -w "$LIB_PARENT" || ! -w "$INCLUDE_PARENT" ]]; then
NEEDS_ELEVATION=1
fi
if [[ "$NEEDS_ELEVATION" -eq 1 && "$(id -u)" -ne 0 ]]; then
if ! command -v sudo >/dev/null 2>&1; then
echo "Install requires elevated privileges, but sudo is not available." >&2
exit 1
fi
INSTALL_PREFIX=(sudo)
fi
if [[ ! -f "$HEADER_SRC" ]]; then
echo "Header not found: $HEADER_SRC" >&2
echo "Run this script from the workspace root." >&2
exit 1
fi
if [[ "$SKIP_BUILD" -eq 0 ]]; then
echo "[1/4] Building $TARGET"
run_bazel build "$TARGET"
else
echo "[1/4] Skipping build (--skip-build)"
fi
echo "[2/4] Resolving bazel-bin path"
BAZEL_BIN="$(run_bazel info bazel-bin)"
LIB_SRC="${BAZEL_BIN}/sharing/linux/libnearby_sharing_api_shared.so"
if [[ ! -f "$LIB_SRC" ]]; then
echo "Shared library not found: $LIB_SRC" >&2
echo "Expected Bazel output for $TARGET is missing." >&2
exit 1
fi
echo "[3/4] Installing library and header"
"${INSTALL_PREFIX[@]}" install -d "$LIBDIR"
"${INSTALL_PREFIX[@]}" install -d "${INCLUDEDIR}/sharing/linux"
"${INSTALL_PREFIX[@]}" install -m 0755 "$LIB_SRC" "$LIBDIR/"
"${INSTALL_PREFIX[@]}" install -m 0644 "$HEADER_SRC" "${INCLUDEDIR}/sharing/linux/"
if command -v ldconfig >/dev/null 2>&1; then
echo "[4/4] Refreshing dynamic linker cache"
"${INSTALL_PREFIX[@]}" ldconfig
else
echo "[4/4] ldconfig not found; skipping linker cache refresh"
fi
echo "Installed:"
echo " library: $LIBDIR/$(basename "$LIB_SRC")"
echo " header : ${INCLUDEDIR}/sharing/linux/$(basename "$HEADER_SRC")"
+19 -15
View File
@@ -7,6 +7,7 @@
#include "internal/base/file_path.h"
#include "internal/flags/nearby_flags.h"
#include "sharing/linux/nearby_connections_service_linux.h"
#include "sharing/nearby_connections_service.h"
#include "sharing/nearby_connections_types.h"
namespace nearby::sharing {
@@ -347,7 +348,9 @@ std::unique_ptr<nearby::sharing::Payload> ToNativePayload(Facade::Payload payloa
class NearbyConnectionsQtFacade::Impl {
public:
linux::NearbyConnectionsServiceLinux service;
Impl() : service(std::make_unique<linux::NearbyConnectionsServiceLinux>()) {}
std::unique_ptr<NativeService> service;
};
NearbyConnectionsQtFacade::NearbyConnectionsQtFacade()
@@ -387,7 +390,7 @@ void NearbyConnectionsQtFacade::StartAdvertising(
const AdvertisingOptions& advertising_options,
ConnectionListener advertising_listener,
std::function<void(Status)> callback) {
impl_->service.StartAdvertising(
impl_->service->StartAdvertising(
service_id, endpoint_info, ToNativeAdvertisingOptions(advertising_options),
ToNativeConnectionListener(std::move(advertising_listener)),
ToNativeStatusCallback(std::move(callback)));
@@ -395,14 +398,14 @@ void NearbyConnectionsQtFacade::StartAdvertising(
void NearbyConnectionsQtFacade::StopAdvertising(
const std::string& service_id, std::function<void(Status)> callback) {
impl_->service.StopAdvertising(service_id,
ToNativeStatusCallback(std::move(callback)));
impl_->service->StopAdvertising(service_id,
ToNativeStatusCallback(std::move(callback)));
}
void NearbyConnectionsQtFacade::StartDiscovery(
const std::string& service_id, const DiscoveryOptions& discovery_options,
DiscoveryListener discovery_listener, std::function<void(Status)> callback) {
impl_->service.StartDiscovery(
impl_->service->StartDiscovery(
service_id, ToNativeDiscoveryOptions(discovery_options),
ToNativeDiscoveryListener(std::move(discovery_listener)),
ToNativeStatusCallback(std::move(callback)));
@@ -410,15 +413,15 @@ void NearbyConnectionsQtFacade::StartDiscovery(
void NearbyConnectionsQtFacade::StopDiscovery(const std::string& service_id,
std::function<void(Status)> callback) {
impl_->service.StopDiscovery(service_id,
ToNativeStatusCallback(std::move(callback)));
impl_->service->StopDiscovery(service_id,
ToNativeStatusCallback(std::move(callback)));
}
void NearbyConnectionsQtFacade::RequestConnection(
const std::string& service_id, const std::vector<uint8_t>& endpoint_info,
const std::string& endpoint_id, const ConnectionOptions& connection_options,
ConnectionListener connection_listener, std::function<void(Status)> callback) {
impl_->service.RequestConnection(
impl_->service->RequestConnection(
service_id, endpoint_info, endpoint_id,
ToNativeConnectionOptions(connection_options),
ToNativeConnectionListener(std::move(connection_listener)),
@@ -428,7 +431,7 @@ void NearbyConnectionsQtFacade::RequestConnection(
void NearbyConnectionsQtFacade::DisconnectFromEndpoint(
const std::string& service_id, const std::string& endpoint_id,
std::function<void(Status)> callback) {
impl_->service.DisconnectFromEndpoint(
impl_->service->DisconnectFromEndpoint(
service_id, endpoint_id, ToNativeStatusCallback(std::move(callback)));
}
@@ -442,28 +445,29 @@ void NearbyConnectionsQtFacade::SendPayload(
}
return;
}
impl_->service.SendPayload(service_id, endpoint_ids, std::move(native_payload),
ToNativeStatusCallback(std::move(callback)));
impl_->service->SendPayload(service_id, endpoint_ids,
std::move(native_payload),
ToNativeStatusCallback(std::move(callback)));
}
void NearbyConnectionsQtFacade::InitiateBandwidthUpgrade(
const std::string& service_id, const std::string& endpoint_id,
std::function<void(Status)> callback) {
impl_->service.InitiateBandwidthUpgrade(
impl_->service->InitiateBandwidthUpgrade(
service_id, endpoint_id, ToNativeStatusCallback(std::move(callback)));
}
void NearbyConnectionsQtFacade::AcceptConnection(
const std::string& service_id, const std::string& endpoint_id,
PayloadListener payload_listener, std::function<void(Status)> callback) {
impl_->service.AcceptConnection(
impl_->service->AcceptConnection(
service_id, endpoint_id, ToNativePayloadListener(std::move(payload_listener)),
ToNativeStatusCallback(std::move(callback)));
}
void NearbyConnectionsQtFacade::StopAllEndpoints(
std::function<void(Status)> callback) {
impl_->service.StopAllEndpoints(ToNativeStatusCallback(std::move(callback)));
impl_->service->StopAllEndpoints(ToNativeStatusCallback(std::move(callback)));
}
} // namespace nearby::sharing::linux
} // namespace nearby::sharing
@@ -187,7 +187,7 @@ void NearbyConnectionsServiceLinux::StartAdvertising(
advertising_options.enable_bluetooth_listening;
options.enable_webrtc_listening = advertising_options.enable_webrtc_listening;
options.use_stable_endpoint_id = advertising_options.use_stable_endpoint_id;
options.force_new_endpoint_id = advertising_options.force_new_endpoint_id;
options.force_new_endpoint_id = false;
options.fast_advertisement_service_uuid =
advertising_options.fast_advertisement_service_uuid.uuid;
+444
View File
@@ -0,0 +1,444 @@
#include "sharing/linux/nearby_sharing_api.h"
#include <mutex>
#include <utility>
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include "internal/base/file_path.h"
#include "internal/flags/nearby_flags.h"
#include "sharing/attachment_container.h"
#include "sharing/file_attachment.h"
#include "sharing/linux/nearby_sharing_service_linux.h"
#include "sharing/share_target_discovered_callback.h"
#include "sharing/transfer_metadata.h"
#include "sharing/transfer_update_callback.h"
namespace nearby::sharing::linux {
namespace {
using NativeService = nearby::sharing::linux::NearbySharingServiceLinux;
void EnableBleL2capDefaults() {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableBleL2cap,
true);
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap,
false);
}
NearbySharingApi::StatusCode ToFacadeStatus(
nearby::sharing::NearbySharingService::StatusCodes status) {
switch (status) {
case nearby::sharing::NearbySharingService::StatusCodes::kOk:
return NearbySharingApi::StatusCode::kOk;
case nearby::sharing::NearbySharingService::StatusCodes::kError:
return NearbySharingApi::StatusCode::kError;
case nearby::sharing::NearbySharingService::StatusCodes::kOutOfOrderApiCall:
return NearbySharingApi::StatusCode::kOutOfOrderApiCall;
case nearby::sharing::NearbySharingService::StatusCodes::kStatusAlreadyStopped:
return NearbySharingApi::StatusCode::kStatusAlreadyStopped;
case nearby::sharing::NearbySharingService::StatusCodes::kTransferAlreadyInProgress:
return NearbySharingApi::StatusCode::kTransferAlreadyInProgress;
case nearby::sharing::NearbySharingService::StatusCodes::kNoAvailableConnectionMedium:
return NearbySharingApi::StatusCode::kNoAvailableConnectionMedium;
case nearby::sharing::NearbySharingService::StatusCodes::kIrrecoverableHardwareError:
return NearbySharingApi::StatusCode::kIrrecoverableHardwareError;
case nearby::sharing::NearbySharingService::StatusCodes::kInvalidArgument:
return NearbySharingApi::StatusCode::kInvalidArgument;
}
return NearbySharingApi::StatusCode::kError;
}
NearbySharingApi::TransferStatus ToFacadeTransferStatus(
nearby::sharing::TransferMetadata::Status status) {
using NativeStatus = nearby::sharing::TransferMetadata::Status;
using FacadeStatus = NearbySharingApi::TransferStatus;
switch (status) {
case NativeStatus::kUnknown:
return FacadeStatus::kUnknown;
case NativeStatus::kConnecting:
return FacadeStatus::kConnecting;
case NativeStatus::kAwaitingLocalConfirmation:
return FacadeStatus::kAwaitingLocalConfirmation;
case NativeStatus::kAwaitingRemoteAcceptance:
return FacadeStatus::kAwaitingRemoteAcceptance;
case NativeStatus::kInProgress:
return FacadeStatus::kInProgress;
case NativeStatus::kComplete:
return FacadeStatus::kComplete;
case NativeStatus::kFailed:
return FacadeStatus::kFailed;
case NativeStatus::kRejected:
return FacadeStatus::kRejected;
case NativeStatus::kCancelled:
return FacadeStatus::kCancelled;
case NativeStatus::kTimedOut:
return FacadeStatus::kTimedOut;
case NativeStatus::kMediaUnavailable:
return FacadeStatus::kMediaUnavailable;
case NativeStatus::kNotEnoughSpace:
return FacadeStatus::kNotEnoughSpace;
case NativeStatus::kUnsupportedAttachmentType:
return FacadeStatus::kUnsupportedAttachmentType;
case NativeStatus::kDeviceAuthenticationFailed:
return FacadeStatus::kDeviceAuthenticationFailed;
case NativeStatus::kIncompletePayloads:
return FacadeStatus::kIncompletePayloads;
}
return FacadeStatus::kUnknown;
}
} // namespace
class NearbySharingApi::Impl : public nearby::sharing::ShareTargetDiscoveredCallback,
public nearby::sharing::TransferUpdateCallback {
public:
Impl() : service() {}
explicit Impl(std::string device_name_override)
: service(std::move(device_name_override)) {}
void OnShareTargetDiscovered(const nearby::sharing::ShareTarget& share_target)
override {
Listener listener_copy;
{
std::scoped_lock lock(listener_mutex);
listener_copy = listener;
}
if (!listener_copy.target_discovered_cb) {
return;
}
listener_copy.target_discovered_cb(ToShareTargetInfo(share_target));
}
void OnShareTargetLost(const nearby::sharing::ShareTarget& share_target)
override {
Listener listener_copy;
{
std::scoped_lock lock(listener_mutex);
listener_copy = listener;
}
if (!listener_copy.target_lost_cb) {
return;
}
listener_copy.target_lost_cb(share_target.id);
}
void OnShareTargetUpdated(const nearby::sharing::ShareTarget& share_target)
override {
Listener listener_copy;
{
std::scoped_lock lock(listener_mutex);
listener_copy = listener;
}
if (!listener_copy.target_updated_cb) {
return;
}
listener_copy.target_updated_cb(ToShareTargetInfo(share_target));
}
void OnTransferUpdate(
const nearby::sharing::ShareTarget& share_target,
const nearby::sharing::AttachmentContainer& attachment_container,
const nearby::sharing::TransferMetadata& transfer_metadata) override {
Listener listener_copy;
{
std::scoped_lock lock(listener_mutex);
listener_copy = listener;
}
if (!listener_copy.transfer_update_cb) {
return;
}
NearbySharingApi::TransferUpdateInfo info;
info.share_target_id = share_target.id;
info.device_name = share_target.device_name;
info.is_incoming = share_target.is_incoming;
info.status = ToFacadeTransferStatus(transfer_metadata.status());
info.progress = transfer_metadata.progress();
info.transferred_bytes = transfer_metadata.transferred_bytes();
info.total_attachments = transfer_metadata.total_attachments_count();
info.transferred_attachments = transfer_metadata.transferred_attachments_count();
if (!attachment_container.GetFileAttachments().empty()) {
const nearby::sharing::FileAttachment& file =
attachment_container.GetFileAttachments().front();
info.first_file_name = std::string(file.file_name());
if (file.file_path().has_value()) {
info.first_file_path = file.file_path()->ToString();
}
}
listener_copy.transfer_update_cb(info);
}
NearbySharingApi::ShareTargetInfo ToShareTargetInfo(
const nearby::sharing::ShareTarget& share_target) {
NearbySharingApi::ShareTargetInfo info;
info.id = share_target.id;
info.device_name = share_target.device_name;
info.is_incoming = share_target.is_incoming;
info.device_type = static_cast<int>(share_target.type);
return info;
}
NativeService service;
bool send_mode_started = false;
bool receive_mode_started = false;
std::mutex listener_mutex;
NearbySharingApi::Listener listener;
};
NearbySharingApi::NearbySharingApi() {
EnableBleL2capDefaults();
impl_ = std::make_unique<Impl>();
}
NearbySharingApi::NearbySharingApi(std::string device_name_override)
: impl_(nullptr) {
EnableBleL2capDefaults();
impl_ = std::make_unique<Impl>(std::move(device_name_override));
}
NearbySharingApi::~NearbySharingApi() = default;
NearbySharingApi::NearbySharingApi(NearbySharingApi&&) noexcept = default;
NearbySharingApi& NearbySharingApi::operator=(NearbySharingApi&&) noexcept = default;
void NearbySharingApi::SetListener(Listener listener) {
std::scoped_lock lock(impl_->listener_mutex);
impl_->listener = std::move(listener);
}
void NearbySharingApi::StartSendMode(std::function<void(StatusCode)> callback) {
if (impl_->send_mode_started) {
if (callback) {
callback(StatusCode::kOk);
}
return;
}
impl_->service.RegisterSendSurface(
impl_.get(), impl_.get(),
nearby::sharing::NearbySharingService::SendSurfaceState::kForeground,
nearby::sharing::Advertisement::BlockedVendorId::kNone,
/*disable_wifi_hotspot=*/false,
[this, cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (status == nearby::sharing::NearbySharingService::StatusCodes::kOk) {
impl_->send_mode_started = true;
}
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::StopSendMode(std::function<void(StatusCode)> callback) {
if (!impl_->send_mode_started) {
if (callback) {
callback(StatusCode::kStatusAlreadyStopped);
}
return;
}
impl_->service.UnregisterSendSurface(
impl_.get(),
[this, cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (status == nearby::sharing::NearbySharingService::StatusCodes::kOk) {
impl_->send_mode_started = false;
}
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::StartReceiveMode(std::function<void(StatusCode)> callback) {
if (impl_->receive_mode_started) {
if (callback) {
callback(StatusCode::kOk);
}
return;
}
impl_->service.RegisterReceiveSurface(
impl_.get(),
nearby::sharing::NearbySharingService::ReceiveSurfaceState::kForeground,
nearby::sharing::Advertisement::BlockedVendorId::kNone,
[this, cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (status == nearby::sharing::NearbySharingService::StatusCodes::kOk) {
impl_->receive_mode_started = true;
}
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::StopReceiveMode(std::function<void(StatusCode)> callback) {
if (!impl_->receive_mode_started) {
if (callback) {
callback(StatusCode::kStatusAlreadyStopped);
}
return;
}
impl_->service.UnregisterReceiveSurface(
impl_.get(),
[this, cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (status == nearby::sharing::NearbySharingService::StatusCodes::kOk) {
impl_->receive_mode_started = false;
}
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::SendFile(int64_t share_target_id,
const std::string& file_path,
std::function<void(StatusCode)> callback) {
if (file_path.empty()) {
if (callback) {
callback(StatusCode::kInvalidArgument);
}
return;
}
nearby::sharing::AttachmentContainer::Builder builder;
builder.AddFileAttachment(
nearby::sharing::FileAttachment(FilePath(file_path)));
std::unique_ptr<nearby::sharing::AttachmentContainer> attachments =
builder.Build();
if (!attachments || !attachments->HasAttachments()) {
if (callback) {
callback(StatusCode::kInvalidArgument);
}
return;
}
impl_->service.SendAttachments(
share_target_id, std::move(attachments),
[cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::Accept(int64_t share_target_id,
std::function<void(StatusCode)> callback) {
impl_->service.Accept(
share_target_id,
[cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::Reject(int64_t share_target_id,
std::function<void(StatusCode)> callback) {
impl_->service.Reject(
share_target_id,
[cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::Cancel(int64_t share_target_id,
std::function<void(StatusCode)> callback) {
impl_->service.Cancel(
share_target_id,
[cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
void NearbySharingApi::Shutdown(std::function<void(StatusCode)> callback) {
impl_->service.Shutdown(
[this, cb = std::move(callback)](
nearby::sharing::NearbySharingService::StatusCodes status) mutable {
if (status == nearby::sharing::NearbySharingService::StatusCodes::kOk) {
impl_->send_mode_started = false;
impl_->receive_mode_started = false;
}
if (cb) {
cb(ToFacadeStatus(status));
}
});
}
std::string NearbySharingApi::GetQrCodeUrl() const {
return impl_->service.GetQrCodeUrl();
}
std::string NearbySharingApi::StatusCodeToString(StatusCode status) {
switch (status) {
case StatusCode::kOk:
return "Ok";
case StatusCode::kError:
return "Error";
case StatusCode::kOutOfOrderApiCall:
return "OutOfOrderApiCall";
case StatusCode::kStatusAlreadyStopped:
return "StatusAlreadyStopped";
case StatusCode::kTransferAlreadyInProgress:
return "TransferAlreadyInProgress";
case StatusCode::kNoAvailableConnectionMedium:
return "NoAvailableConnectionMedium";
case StatusCode::kIrrecoverableHardwareError:
return "IrrecoverableHardwareError";
case StatusCode::kInvalidArgument:
return "InvalidArgument";
}
return "Error";
}
std::string NearbySharingApi::TransferStatusToString(TransferStatus status) {
switch (status) {
case TransferStatus::kUnknown:
return "Unknown";
case TransferStatus::kConnecting:
return "Connecting";
case TransferStatus::kAwaitingLocalConfirmation:
return "AwaitingLocalConfirmation";
case TransferStatus::kAwaitingRemoteAcceptance:
return "AwaitingRemoteAcceptance";
case TransferStatus::kInProgress:
return "InProgress";
case TransferStatus::kComplete:
return "Complete";
case TransferStatus::kFailed:
return "Failed";
case TransferStatus::kRejected:
return "Rejected";
case TransferStatus::kCancelled:
return "Cancelled";
case TransferStatus::kTimedOut:
return "TimedOut";
case TransferStatus::kMediaUnavailable:
return "MediaUnavailable";
case TransferStatus::kNotEnoughSpace:
return "NotEnoughSpace";
case TransferStatus::kUnsupportedAttachmentType:
return "UnsupportedAttachmentType";
case TransferStatus::kDeviceAuthenticationFailed:
return "DeviceAuthenticationFailed";
case TransferStatus::kIncompletePayloads:
return "IncompletePayloads";
}
return "Unknown";
}
} // namespace nearby::sharing::linux
+121
View File
@@ -0,0 +1,121 @@
// Copyright 2026
//
// Thin app-facing API for NearbySharingServiceLinux that avoids exposing
// internal Nearby headers to external consumers.
#ifndef SHARING_LINUX_NEARBY_SHARING_API_H_
#define SHARING_LINUX_NEARBY_SHARING_API_H_
#include <stdint.h>
#include <functional>
#include <memory>
#include <string>
// Some toolchains define `linux` as a macro (e.g. `#define linux 1`), which
// breaks namespace tokens like `nearby::sharing::linux`.
#ifdef linux
#undef linux
#endif
namespace nearby {
namespace sharing {
namespace linux {
class __attribute__((visibility("default"))) NearbySharingApi {
public:
enum class StatusCode {
kOk = 0,
kError = 1,
kOutOfOrderApiCall = 2,
kStatusAlreadyStopped = 3,
kTransferAlreadyInProgress = 4,
kNoAvailableConnectionMedium = 5,
kIrrecoverableHardwareError = 6,
kInvalidArgument = 7,
};
enum class TransferStatus {
kUnknown = 0,
kConnecting = 1,
kAwaitingLocalConfirmation = 2,
kAwaitingRemoteAcceptance = 3,
kInProgress = 4,
kComplete = 5,
kFailed = 6,
kRejected = 7,
kCancelled = 8,
kTimedOut = 9,
kMediaUnavailable = 10,
kNotEnoughSpace = 11,
kUnsupportedAttachmentType = 12,
kDeviceAuthenticationFailed = 13,
kIncompletePayloads = 14,
};
struct ShareTargetInfo {
int64_t id = 0;
std::string device_name;
bool is_incoming = false;
int device_type = 0;
};
struct TransferUpdateInfo {
int64_t share_target_id = 0;
std::string device_name;
bool is_incoming = false;
TransferStatus status = TransferStatus::kUnknown;
float progress = 0.0f;
uint64_t transferred_bytes = 0;
int total_attachments = 0;
int transferred_attachments = 0;
std::string first_file_name;
std::string first_file_path;
};
struct Listener {
std::function<void(const ShareTargetInfo&)> target_discovered_cb;
std::function<void(const ShareTargetInfo&)> target_updated_cb;
std::function<void(int64_t)> target_lost_cb;
std::function<void(const TransferUpdateInfo&)> transfer_update_cb;
};
NearbySharingApi();
explicit NearbySharingApi(std::string device_name_override);
~NearbySharingApi();
NearbySharingApi(const NearbySharingApi&) = delete;
NearbySharingApi& operator=(const NearbySharingApi&) = delete;
NearbySharingApi(NearbySharingApi&&) noexcept;
NearbySharingApi& operator=(NearbySharingApi&&) noexcept;
void SetListener(Listener listener);
void StartSendMode(std::function<void(StatusCode)> callback);
void StopSendMode(std::function<void(StatusCode)> callback);
void StartReceiveMode(std::function<void(StatusCode)> callback);
void StopReceiveMode(std::function<void(StatusCode)> callback);
void SendFile(int64_t share_target_id, const std::string& file_path,
std::function<void(StatusCode)> callback);
void Accept(int64_t share_target_id, std::function<void(StatusCode)> callback);
void Reject(int64_t share_target_id, std::function<void(StatusCode)> callback);
void Cancel(int64_t share_target_id, std::function<void(StatusCode)> callback);
void Shutdown(std::function<void(StatusCode)> callback);
std::string GetQrCodeUrl() const;
static std::string StatusCodeToString(StatusCode status);
static std::string TransferStatusToString(TransferStatus status);
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace linux
} // namespace sharing
} // namespace nearby
#endif // SHARING_LINUX_NEARBY_SHARING_API_H_
+13 -156
View File
@@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include <chrono>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <chrono>
#include <vector>
#include "absl/time/time.h"
#include "internal/platform/implementation/account_manager.h"
#include "sharing/linux/nearby_sharing_service_linux.h"
#include "sharing/attachment_container.h"
#include "sharing/file_attachment.h"
@@ -31,6 +31,7 @@
#include "sharing/transfer_update_callback.h"
#include "internal/base/file_path.h"
#include "internal/base/files.h"
#include "internal/flags/nearby_flags.h"
using namespace nearby::sharing;
using namespace nearby::sharing::linux;
@@ -96,9 +97,6 @@ class MyTransferUpdateCallback : public TransferUpdateCallback {
std::cout << "║ 3. List devices │ 8. Cancel transfer ║" << std::endl;
std::cout << "║ 4. Send file │ 9. Print status ║" << std::endl;
std::cout << "║ 5. Send text │ 0. Exit ║" << std::endl;
std::cout << "║10. Sync credentials │11. Credential status ║" << std::endl;
std::cout << "║12. Visibility: Everyone │13. Visibility: Contacts ║" << std::endl;
std::cout << "║14. Visibility: Hidden │ ║" << std::endl;
std::cout << "╚═════════════════════════════════════════════════════════╝" << std::endl;
std::cout << "Choice: ";
}
@@ -160,9 +158,6 @@ class MyShareTargetDiscoveredCallback : public ShareTargetDiscoveredCallback {
std::cout << "║ 3. List devices │ 8. Cancel transfer ║" << std::endl;
std::cout << "║ 4. Send file │ 9. Print status ║" << std::endl;
std::cout << "║ 5. Send text │ 0. Exit ║" << std::endl;
std::cout << "║10. Sync credentials │11. Credential status ║" << std::endl;
std::cout << "║12. Visibility: Everyone │13. Visibility: Contacts ║" << std::endl;
std::cout << "║14. Visibility: Hidden │ ║" << std::endl;
std::cout << "╚═════════════════════════════════════════════════════════╝" << std::endl;
std::cout << "Choice: ";
}
@@ -183,8 +178,6 @@ class NearbySharingApp {
}
void StartAsReceiver() {
PrepareCredentialFlow(/*for_receiver=*/true);
std::cout << "\n=== Starting as Receiver (Foreground) ===" << std::endl;
service_->RegisterReceiveSurface(
@@ -193,8 +186,6 @@ class NearbySharingApp {
Advertisement::BlockedVendorId::kNone,
[this](NearbySharingService::StatusCodes status) {
if (status == NearbySharingService::StatusCodes::kOk) {
receive_surface_state_ =
NearbySharingService::ReceiveSurfaceState::kForeground;
std::cout << "Successfully registered as receiver!" << std::endl;
// Display QR code URL
@@ -212,7 +203,6 @@ class NearbySharingApp {
std::cout << "│ Scan this with your phone to connect! │" << std::endl;
std::cout << "└──────────────────────────────────────────────────────────────┘" << std::endl;
}
ForceCredentialSync("receiver surface registration");
} else {
std::cout << "Failed to register as receiver: "
<< NearbySharingService::StatusCodeToString(status) << std::endl;
@@ -223,8 +213,6 @@ class NearbySharingApp {
}
void StartAsSender() {
PrepareCredentialFlow(/*for_receiver=*/false);
std::cout << "\n=== Starting as Sender (Foreground) ===" << std::endl;
service_->RegisterSendSurface(
@@ -235,7 +223,6 @@ class NearbySharingApp {
false, // disable_wifi_hotspot
[this](NearbySharingService::StatusCodes status) {
if (status == NearbySharingService::StatusCodes::kOk) {
send_surface_state_ = NearbySharingService::SendSurfaceState::kForeground;
std::cout << "Successfully registered as sender!" << std::endl;
// Display QR code URL
@@ -253,7 +240,6 @@ class NearbySharingApp {
std::cout << "│ Scan this with your phone to connect! │" << std::endl;
std::cout << "└──────────────────────────────────────────────────────────────┘" << std::endl;
}
ForceCredentialSync("sender surface registration");
} else {
std::cout << "Failed to register as sender: "
<< NearbySharingService::StatusCodeToString(status) << std::endl;
@@ -428,46 +414,6 @@ class NearbySharingApp {
std::cout << "======================" << std::endl;
}
void SyncCredentialsNow() { ForceCredentialSync("manual menu request"); }
void PrintCredentialStatus() {
std::cout << "\n=== Credential Flow Status ===" << std::endl;
auto* account_manager = service_->GetAccountManager();
if (account_manager == nullptr) {
std::cout << "Account manager: unavailable on this service implementation."
<< std::endl;
} else if (account_manager->GetCurrentAccount().has_value()) {
const auto& account = *account_manager->GetCurrentAccount();
std::cout << "Signed in account: " << account.email << std::endl;
std::cout << "Account id: " << account.id << std::endl;
} else {
std::cout << "Signed in account: none" << std::endl;
}
std::cout << "Certificate manager pointer: "
<< (service_->GetCertificateManager() != nullptr ? "available"
: "null")
<< std::endl;
std::cout << "Service dump:\n" << service_->Dump() << std::endl;
std::cout << "==============================" << std::endl;
}
void SetVisibilityEveryone() {
ApplyVisibility(proto::DeviceVisibility::DEVICE_VISIBILITY_EVERYONE,
absl::Minutes(15), "EVERYONE");
}
void SetVisibilityContacts() {
ApplyVisibility(proto::DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS,
absl::ZeroDuration(), "ALL_CONTACTS");
}
void SetVisibilityHidden() {
ApplyVisibility(proto::DeviceVisibility::DEVICE_VISIBILITY_HIDDEN,
absl::ZeroDuration(), "HIDDEN");
}
void Shutdown() {
std::cout << "\n=== Shutting Down ===" << std::endl;
service_->Shutdown([](NearbySharingService::StatusCodes status) {
@@ -477,84 +423,9 @@ class NearbySharingApp {
}
private:
void ApplyVisibility(proto::DeviceVisibility visibility,
absl::Duration expiration,
const std::string& label) {
service_->SetVisibility(
visibility, expiration,
[label](NearbySharingService::StatusCodes status) mutable {
if (status == NearbySharingService::StatusCodes::kOk) {
std::cout << "Visibility updated to " << label << std::endl;
} else {
std::cout << "Failed to set visibility to " << label << ": "
<< NearbySharingService::StatusCodeToString(status)
<< std::endl;
}
});
}
void PrepareCredentialFlow(bool for_receiver) {
auto* account_manager = service_->GetAccountManager();
bool has_account = account_manager != nullptr &&
account_manager->GetCurrentAccount().has_value();
// Match NearbySharingServiceImpl behavior: outgoing paths are contacts-based
// when account data is available; receiver mode generally uses everyone.
if (for_receiver) {
SetVisibilityEveryone();
} else if (has_account) {
SetVisibilityContacts();
} else {
SetVisibilityEveryone();
}
}
void ForceCredentialSync(const std::string& reason) {
std::cout << "[Credential flow] Requesting sync via service hooks ("
<< reason << ")." << std::endl;
// Keep visibility explicitly valid for cert-backed advertising.
SetVisibilityEveryone();
// NearbySharingServiceImpl forces private cert upload from
// RegisterReceiveSurface when visibility is not hidden. Re-registering
// the current receive surface is enough to trigger that path.
auto state = receive_surface_state_.value_or(
NearbySharingService::ReceiveSurfaceState::kBackground);
service_->RegisterReceiveSurface(
transfer_callback_.get(), state, Advertisement::BlockedVendorId::kNone,
[](NearbySharingService::StatusCodes status) {
if (status == NearbySharingService::StatusCodes::kOk) {
std::cout << "[Credential flow] Receive surface refreshed for sync."
<< std::endl;
} else {
std::cout << "[Credential flow] Failed to refresh receive surface: "
<< NearbySharingService::StatusCodeToString(status)
<< std::endl;
}
});
// Discovery-triggered public cert download is internal to the full service.
if (send_surface_state_.has_value()) {
service_->RegisterSendSurface(
transfer_callback_.get(), discovery_callback_.get(),
*send_surface_state_, Advertisement::BlockedVendorId::kNone,
/*disable_wifi_hotspot=*/false,
[](NearbySharingService::StatusCodes status) {
if (status == NearbySharingService::StatusCodes::kOk) {
std::cout << "[Credential flow] Send surface refreshed for scan "
"side sync."
<< std::endl;
}
});
}
}
std::unique_ptr<NearbySharingServiceLinux> service_;
std::unique_ptr<MyTransferUpdateCallback> transfer_callback_;
std::unique_ptr<MyShareTargetDiscoveredCallback> discovery_callback_;
std::optional<NearbySharingService::ReceiveSurfaceState> receive_surface_state_;
std::optional<NearbySharingService::SendSurfaceState> send_surface_state_;
};
void PrintMenu() {
@@ -566,14 +437,20 @@ void PrintMenu() {
std::cout << "║ 3. List devices │ 8. Cancel transfer ║" << std::endl;
std::cout << "║ 4. Send file │ 9. Print status ║" << std::endl;
std::cout << "║ 5. Send text │ 0. Exit ║" << std::endl;
std::cout << "║10. Sync credentials │11. Credential status ║" << std::endl;
std::cout << "║12. Visibility: Everyone │13. Visibility: Contacts ║" << std::endl;
std::cout << "║14. Visibility: Hidden │ ║" << std::endl;
std::cout << "╚═════════════════════════════════════════════════════════╝" << std::endl;
std::cout << "Choice: ";
}
int main(int argc, char* argv[]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableBleL2cap,
true);
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap,
true);
std::string device_name = "MyLinuxDevice";
if (argc > 1) {
@@ -657,26 +534,6 @@ int main(int argc, char* argv[]) {
case 9:
app.PrintStatus();
break;
case 10:
app.SyncCredentialsNow();
break;
case 11:
app.PrintCredentialStatus();
break;
case 12:
app.SetVisibilityEveryone();
break;
case 13:
app.SetVisibilityContacts();
break;
case 14:
app.SetVisibilityHidden();
break;
case 0:
running = false;
+5 -5
View File
@@ -11,10 +11,10 @@ find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Qml Quick QuickControls2)
# Where the Bazel-built Nearby .so and header were installed
set(NEARBY_PREFIX "/usr/local" CACHE PATH "Install prefix for the Nearby shared library")
find_library(NEARBY_LIB nearby_connections_service_linux_shared
find_library(NEARBY_SHARING_LIB nearby_sharing_api_shared
HINTS "${NEARBY_PREFIX}/lib" REQUIRED)
find_path(NEARBY_INCLUDE sharing/linux/nearby_connections_qt_facade.h
find_path(NEARBY_SHARING_INCLUDE sharing/linux/nearby_sharing_api.h
HINTS "${NEARBY_PREFIX}/include" REQUIRED)
qt_add_executable(nearby_qml_file_tray_app
@@ -24,11 +24,11 @@ qt_add_executable(nearby_qml_file_tray_app
resources_file_share.qrc
)
target_include_directories(nearby_qml_file_tray_app PRIVATE "${NEARBY_INCLUDE}")
target_include_directories(nearby_qml_file_tray_app PRIVATE "${NEARBY_SHARING_INCLUDE}")
target_link_libraries(nearby_qml_file_tray_app PRIVATE
Qt6::Core Qt6::Gui Qt6::Widgets Qt6::Qml Qt6::Quick Qt6::QuickControls2
"${NEARBY_LIB}"
"${NEARBY_SHARING_LIB}"
)
set_target_properties(nearby_qml_file_tray_app PROPERTIES
@@ -38,4 +38,4 @@ set_target_properties(nearby_qml_file_tray_app PROPERTIES
include(GNUInstallDirs)
install(TARGETS nearby_qml_file_tray_app RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
install(FILES "${NEARBY_LIB}" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
install(FILES "${NEARBY_SHARING_LIB}" DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+2 -2
View File
@@ -50,7 +50,7 @@ ApplicationWindow {
topLeftRadius: 48
clip: true
readonly property bool isIdle: fileShareController.discoveredDevices.length === 0
readonly property bool isIdle: fileShareController.discoveredTargets.length === 0
&& fileShareController.transfers.length === 0
// ── Idle: animated blob ───────────────────────────────────
@@ -81,7 +81,7 @@ ApplicationWindow {
}
Repeater {
model: fileShareController.discoveredDevices
model: fileShareController.discoveredTargets
delegate: DeviceCard {}
}
+19 -19
View File
@@ -1,19 +1,19 @@
# Nearby File Share Tray App
This folder contains the Qt/QML **FileShareTray** application — a system tray app for file sharing via Nearby Connections, wired to:
This folder contains the Qt/QML **FileShareTray** application — a system tray
app for file sharing via Nearby Sharing, wired to:
- `nearby::sharing::linux::NearbyConnectionsServiceLinux`
- Send mode (discovery + connect)
- `nearby::sharing::linux::NearbySharingApi`
- Send mode (discover nearby share targets + send file)
- Receive mode (incoming requests + accept/reject)
- Transfer status list (progress + payload status)
- Connected medium display per endpoint
- Transfer status list (progress + transfer status)
- Persistent tray behavior (window close hides app to tray)
- File logging to `/tmp/nearby_qml_tray.log`
- File logging to `/tmp/nearby_qml_file_tray.log`
## Files
- `file_share_tray_main.cpp`: Qt app bootstrap + system tray behavior.
- `file_share_tray_controller.h/.cc`: QML-facing backend wrapper around Nearby Connections.
- `file_share_tray_controller.h/.cc`: QML-facing backend wrapper around Nearby Sharing.
- `FileShareTray.qml`: Top-level UI for the file share tray app.
- `components/`: Shared QML UI components used by `FileShareTray.qml`.
- `resources_file_share.qrc`: Embeds `FileShareTray.qml` and components.
@@ -24,36 +24,36 @@ This folder contains the Qt/QML **FileShareTray** application — a system tray
- Use tray icon menu to show/hide/quit.
- Mode `Send`:
- Starts discovery.
- Shows discovered endpoints.
- Lets you connect and send text payloads.
- Shows discovered share targets.
- Sends the selected file to a chosen target.
- Mode `Receive`:
- Starts advertising.
- Shows pending incoming connection requests.
- Shows pending incoming transfer requests.
- Lets you accept/reject incoming requests.
- Transfers are shown with endpoint, direction, status, progress, and medium.
- Logs are appended to `/tmp/nearby_qml_tray.log`.
- Transfers are shown with target, direction, status, and progress.
- Logs are appended to `/tmp/nearby_qml_file_tray.log`.
## Building
This CMake app links against the installed Nearby shared library and header:
- `libnearby_connections_service_linux_shared.so`
- `sharing/linux/nearby_connections_qt_facade.h`
- `libnearby_sharing_api_shared.so`
- `sharing/linux/nearby_sharing_api.h`
Install them first (repo root):
```bash
./sharing/linux/install_nearby_connections_service.sh
./sharing/linux/install_nearby_sharing_service.sh
```
Then build the app (from `sharing/linux/qml_tray_app`):
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNEARBY_INSTALL_PREFIX=/usr/local
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNEARBY_PREFIX=/usr/local
cmake --build build -j
```
## Bundle `libnearby_connections_service_linux_shared.so` with the app
## Bundle `libnearby_sharing_api_shared.so` with the app
From `sharing/linux/qml_tray_app`:
@@ -66,7 +66,7 @@ cmake --install build
Bundle output:
- `dist/bin/nearby_qml_file_tray_app`
- `dist/bin/libnearby_connections_service_linux_shared.so`
- `dist/bin/libnearby_sharing_api_shared.so`
The app is installed with `INSTALL_RPATH=$ORIGIN`, so it resolves the Nearby
shared library from the same folder in the bundle.
@@ -88,5 +88,5 @@ Output:
This zip is created from the CMake install tree and includes:
- `nearby_qml_file_tray_app`
- `libnearby_connections_service_linux_shared.so`
- `libnearby_sharing_api_shared.so`
- Qt runtime libs/plugins/QML imports discovered by Qt deploy tooling
@@ -26,7 +26,7 @@ if(EXISTS "${_output_so}")
endforeach()
if(NOT _needs_rebuild)
# Reuse an existing .so when it already exports the Qt facade symbols.
# Reuse an existing .so when it already exports the NearbySharingApi symbols.
# This avoids stale-cache link failures after facade changes.
find_program(_nm_program nm)
if(_nm_program)
@@ -37,11 +37,11 @@ if(EXISTS "${_output_so}")
ERROR_QUIET
)
if(_nm_result EQUAL 0)
string(FIND "${_nm_output}" "nearby::sharing::linux::NearbyConnectionsQtFacade::NearbyConnectionsQtFacade()" _facade_ctor_idx)
string(FIND "${_nm_output}" "nearby::sharing::linux::NearbySharingApi::NearbySharingApi()" _api_ctor_idx)
string(FIND "${_nm_output}" " U nearby::api::ImplementationPlatform::CreateScheduledExecutor()" _undef_platform_idx)
string(FIND "${_nm_output}" " U nearby::SystemClock::ElapsedRealtime()" _undef_clock_idx)
string(FIND "${_nm_output}" " U nearby::Crypto::Sha256(" _undef_crypto_idx)
if(NOT _facade_ctor_idx EQUAL -1
if(NOT _api_ctor_idx EQUAL -1
AND _undef_platform_idx EQUAL -1
AND _undef_clock_idx EQUAL -1
AND _undef_crypto_idx EQUAL -1)
@@ -6,11 +6,14 @@ Item {
readonly property color textPrimary: "#111827"
readonly property color textMuted: "#6b7280"
readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0
Label {
x: 48; y: 48
visible: fileShareController.running
text: "Ready to receive" + (fileShareController.pendingSendFilePath.length > 0 ? " / send" : "")
text: isSendMode
? "Ready to send"
: "Ready to receive"
font.pixelSize: 20
font.weight: Font.Medium
color: textPrimary
@@ -20,6 +23,7 @@ Item {
id: blobCanvas
width: 380; height: 380
anchors.centerIn: parent
visible: !isSendMode
property real t: 0
property double startMs: Date.now()
@@ -76,10 +80,55 @@ Item {
}
}
// QR code URL panel shown when a file is pending to send
Column {
anchors.centerIn: parent
visible: isSendMode
spacing: 16
width: parent.width - 96
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: "Scan to connect"
font.pixelSize: 16
font.weight: Font.Medium
color: textPrimary
}
Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: urlLabel.implicitHeight + 24
radius: 12
color: "#f0fdf4"
border.color: "#bbf7d0"
border.width: 1
Label {
id: urlLabel
anchors.centerIn: parent
width: parent.width - 32
text: fileShareController.qrCodeUrl
font.pixelSize: 12
font.family: "monospace"
color: "#166534"
wrapMode: Text.WrapAnywhere
}
}
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: "Sending: " + fileShareController.pendingSendFileName
font.pixelSize: 13
color: textMuted
}
}
Label {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 48
visible: !isSendMode
text: fileShareController.statusMessage
font.pixelSize: 13
color: textMuted
@@ -3,7 +3,7 @@ import QtQuick.Controls
import QtQuick.Layouts
Rectangle {
required property string modelData
required property var modelData
Layout.fillWidth: true
height: 76
@@ -15,10 +15,12 @@ Rectangle {
readonly property color textPrimary: "#111827"
readonly property color textMuted: "#6b7280"
function endpointLabel(endpointId) {
var label = fileShareController.peerNameForEndpoint(endpointId)
if (!label || label.length === 0 || label === "Unknown device") return "Unknown device"
return label
readonly property string targetName: modelData.name && modelData.name.length > 0
? modelData.name : "Unknown device"
function initialLetter(label) {
if (!label || label.length === 0) return "?"
return label.charAt(0).toUpperCase()
}
RowLayout {
@@ -33,7 +35,7 @@ Rectangle {
Label {
anchors.centerIn: parent
text: endpointLabel(modelData).charAt(0).toUpperCase()
text: initialLetter(targetName)
font.pixelSize: 18
font.weight: Font.Medium
color: textPrimary
@@ -46,7 +48,7 @@ Rectangle {
Label {
Layout.fillWidth: true
text: endpointLabel(modelData)
text: targetName
font.weight: Font.Medium
elide: Text.ElideRight
color: textPrimary
@@ -54,7 +56,7 @@ Rectangle {
Label {
Layout.fillWidth: true
text: modelData
text: "#" + modelData.id
font.pixelSize: 11
color: textMuted
elide: Text.ElideRight
@@ -83,7 +85,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor
enabled: fileShareController.mode === "Send"
&& fileShareController.pendingSendFilePath.length > 0
onClicked: fileShareController.sendPendingFileToEndpoint(modelData)
onClicked: fileShareController.sendPendingFileToTarget(modelData.id)
}
}
}
@@ -11,9 +11,7 @@ Drawer {
implicitHeight: parent ? parent.height : 0
readonly property color bg: "#f0fdf4"
readonly property color surface: "#ffffff"
readonly property color accent: "#38aa62"
readonly property color accentLight: "#dcfce7"
readonly property color cardBg: "#ffffff"
readonly property color borderColor: "#bbf7d0"
readonly property color textPrimary: "#111827"
readonly property color textMuted: "#6b7280"
@@ -25,12 +23,10 @@ Drawer {
height: root.height
spacing: 0
// ── Header ────────────────────────────────────────────────────────
Rectangle {
Layout.fillWidth: true
height: 64
color: "transparent"
// border.color: root.borderColor
RowLayout {
anchors.fill: parent
@@ -46,448 +42,114 @@ Drawer {
Item { Layout.fillWidth: true }
Rectangle {
width: 32
height: 32
radius: 8
// color: closeArea.containsMouse ? "#f3f4f6" : "transparent"
Label {
anchors.centerIn: parent
text: "✕"
font.pixelSize: 14
color: root.textMuted
}
MouseArea {
id: closeArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.close()
}
ToolButton {
text: "✕"
onClicked: root.close()
}
}
}
// ── Scrollable content ────────────────────────────────────────────
Flickable {
id: flick
ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: false
contentWidth: width
contentHeight: settingsCol.height + 32
ScrollBar.vertical: ScrollBar {}
clip: true
Column {
id: settingsCol
x: 20
y: 20
width: flick.width - 40
spacing: 20
ColumnLayout {
width: root.width - 40
anchors.horizontalCenter: parent.horizontalCenter
spacing: 16
// ── Device ───────────────────────────────────────────────
SectionLabel { text: "DEVICE" }
GroupBox {
title: "Device"
Layout.fillWidth: true
SectionCard {
width: settingsCol.width
background: Rectangle {
color: root.cardBg
radius: 10
border.color: root.borderColor
}
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 16
spacing: 14
anchors.fill: parent
anchors.margins: 12
spacing: 8
RowLayout {
Label {
text: "Device name"
color: root.textMuted
}
TextField {
Layout.fillWidth: true
spacing: 10
Label {
text: "Device name"
font.pixelSize: 13
color: root.textMuted
Layout.preferredWidth: 110
}
ThemedField {
text: fileShareController.deviceName
onEditingFinished: fileShareController.deviceName = text
}
text: fileShareController.deviceName
onEditingFinished: fileShareController.deviceName = text
}
}
}
// ── Connection ────────────────────────────────────────────
SectionLabel { text: "CONNECTION" }
GroupBox {
title: "Sharing"
Layout.fillWidth: true
SectionCard {
width: settingsCol.width
background: Rectangle {
color: root.cardBg
radius: 10
border.color: root.borderColor
}
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 16
spacing: 14
anchors.fill: parent
anchors.margins: 12
spacing: 8
// ── Advanced Settings ─────────────────────────────
ColumnLayout {
Label {
Layout.fillWidth: true
spacing: 0
// Header row (always visible)
RowLayout {
Layout.fillWidth: true
spacing: 6
Canvas {
id: advChevron
width: 14; height: 14
property bool open: false
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
ctx.strokeStyle = root.accent
ctx.lineWidth = 1.8
ctx.lineCap = "round"
ctx.lineJoin = "round"
ctx.beginPath()
if (open) {
ctx.moveTo(2, 5); ctx.lineTo(7, 10); ctx.lineTo(12, 5)
} else {
ctx.moveTo(5, 2); ctx.lineTo(10, 7); ctx.lineTo(5, 12)
}
ctx.stroke()
}
}
Label {
text: "Advanced Settings"
font.pixelSize: 12
font.weight: Font.Medium
color: root.accent
}
Item { Layout.fillWidth: true }
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
advChevron.open = !advChevron.open
advChevron.requestPaint()
}
}
}
// Warning + Strategy (shown when expanded)
ColumnLayout {
visible: advChevron.open
Layout.fillWidth: true
spacing: 10
Label {
Layout.fillWidth: true
text: "⚠️ Only mess with these if you know what you're doing."
font.pixelSize: 11
color: "#b45309"
wrapMode: Text.WordWrap
topPadding: 6
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Label {
text: "Strategy"
font.pixelSize: 13
color: root.textMuted
Layout.preferredWidth: 110
}
ThemedCombo {
id: strategyCombo
model: ["P2pPointToPoint", "P2pStar", "P2pCluster"]
currentIndex: {
var s = fileShareController.connectionStrategy
if (s === "P2pStar") return 1
if (s === "P2pCluster") return 2
return 0
}
onActivated: fileShareController.connectionStrategy = currentText
Connections {
target: fileShareController
function onConnectionStrategyChanged() {
var s = fileShareController.connectionStrategy
strategyCombo.currentIndex = s === "P2pStar" ? 1 : s === "P2pCluster" ? 2 : 0
}
}
}
}
RowLayout {
Layout.fillWidth: true
spacing: 10
Label {
text: "Service ID"
font.pixelSize: 13
color: root.textMuted
Layout.preferredWidth: 110
}
ThemedField {
text: fileShareController.serviceId
onEditingFinished: fileShareController.serviceId = text
}
}
}
}
Rectangle {
Layout.fillWidth: true
height: 1
color: root.borderColor
wrapMode: Text.WordWrap
color: root.textMuted
text: "Nearby Sharing uses built-in transport and discovery settings."
}
RowLayout {
Layout.fillWidth: true
Label {
text: "Auto-accept incoming"
font.pixelSize: 13
color: root.textPrimary
Layout.fillWidth: true
color: root.textPrimary
text: "Auto-accept incoming"
}
Switch {
checked: fileShareController.autoAcceptIncoming
palette.highlight: root.accent
onCheckedChanged: fileShareController.autoAcceptIncoming = checked
onToggled: fileShareController.autoAcceptIncoming = checked
}
}
}
}
// ── Transport mediums ─────────────────────────────────────
SectionLabel { text: "TRANSPORT MEDIUMS" }
GroupBox {
title: "Logging"
Layout.fillWidth: true
SectionCard {
width: settingsCol.width
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 16
spacing: 4
MediumRow { label: "Bluetooth"; checked: fileShareController.bluetoothEnabled; onToggled: (v) => fileShareController.bluetoothEnabled = v }
MediumRow { label: "BLE"; checked: fileShareController.bleEnabled; onToggled: (v) => fileShareController.bleEnabled = v }
MediumRow { label: "WiFi LAN"; checked: fileShareController.wifiLanEnabled; onToggled: (v) => fileShareController.wifiLanEnabled = v }
MediumRow { label: "WiFi Hotspot";checked: fileShareController.wifiHotspotEnabled; onToggled: (v) => fileShareController.wifiHotspotEnabled = v }
MediumRow { label: "WebRTC"; checked: fileShareController.webRtcEnabled; onToggled: (v) => fileShareController.webRtcEnabled = v }
background: Rectangle {
color: root.cardBg
radius: 10
border.color: root.borderColor
}
}
// ── Logging ───────────────────────────────────────────────
SectionLabel { text: "LOGGING" }
SectionCard {
width: settingsCol.width
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 16
spacing: 14
anchors.fill: parent
anchors.margins: 12
spacing: 8
RowLayout {
Label {
text: "Log path"
color: root.textMuted
}
TextField {
Layout.fillWidth: true
spacing: 10
Label {
text: "Log path"
font.pixelSize: 13
color: root.textMuted
Layout.preferredWidth: 110
}
ThemedField {
font.pixelSize: 11
text: fileShareController.logPath
onEditingFinished: fileShareController.logPath = text
}
text: fileShareController.logPath
onEditingFinished: fileShareController.logPath = text
}
}
}
}
}
}
// ── Section heading label ─────────────────────────────────────────────
component SectionLabel: Label {
font.pixelSize: 11
font.weight: Font.DemiBold
font.letterSpacing: 0.8
color: root.accent
}
// ── Rounded card that sizes itself to its content ─────────────────────
component SectionCard: Rectangle {
radius: 12
color: root.surface
border.color: root.borderColor
// height wraps the first ColumnLayout child placed inside via anchors.top
height: (children.length > 0 ? children[0].implicitHeight : 0) + 32
}
// ── Themed text field ─────────────────────────────────────────────────
component ThemedField: TextField {
Layout.fillWidth: true
implicitHeight: 38
font.pixelSize: 13
leftPadding: 12
rightPadding: 12
topPadding: 0
bottomPadding: 0
verticalAlignment: TextInput.AlignVCenter
color: root.textPrimary
background: Rectangle {
radius: 8
color: "#f9fafb"
border.color: parent.activeFocus ? root.accent : root.borderColor
border.width: parent.activeFocus ? 2 : 1
}
}
// ── Themed combo box ──────────────────────────────────────────────────
component ThemedCombo: ComboBox {
id: theComboBox
Layout.fillWidth: true
implicitHeight: 38
font.pixelSize: 13
leftPadding: 12
rightPadding: 36 // leave room for the indicator
topPadding: 0
bottomPadding: 0
background: Rectangle {
radius: 8
color: "#f9fafb"
border.color: theComboBox.down || theComboBox.hovered ? root.accent : root.borderColor
border.width: theComboBox.down ? 2 : 1
}
contentItem: Label {
text: theComboBox.displayText
font: theComboBox.font
color: root.textPrimary
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
indicator: Item {
x: theComboBox.width - width - 10
y: (theComboBox.height - height) / 2
width: 16
height: 16
Canvas {
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)
ctx.strokeStyle = root.accent
ctx.lineWidth = 2
ctx.lineCap = "round"
ctx.lineJoin = "round"
ctx.beginPath()
ctx.moveTo(2, 5)
ctx.lineTo(8, 11)
ctx.lineTo(14, 5)
ctx.stroke()
}
}
}
popup: Popup {
y: theComboBox.height + 4
width: theComboBox.width
padding: 4
background: Rectangle {
radius: 10
color: root.surface
border.color: root.borderColor
}
contentItem: ListView {
clip: true
implicitHeight: contentHeight
model: theComboBox.delegateModel
currentIndex: theComboBox.highlightedIndex
ScrollIndicator.vertical: ScrollIndicator {}
}
}
delegate: ItemDelegate {
width: ListView.view ? ListView.view.width : 0
highlighted: theComboBox.highlightedIndex === index
contentItem: Label {
leftPadding: 8
text: modelData
font.pixelSize: 13
color: root.textPrimary
verticalAlignment: Text.AlignVCenter
}
background: Rectangle {
radius: 7
color: parent.highlighted ? root.accentLight : "transparent"
}
}
}
// ── One toggle row for a transport medium ─────────────────────────────
component MediumRow: RowLayout {
id: mrow
required property string label
required property bool checked
signal toggled(bool value)
Layout.fillWidth: true
implicitHeight: 38
spacing: 12
Rectangle {
width: 20
height: 20
radius: 5
color: mrow.checked ? root.accentLight : root.surface
border.color: mrow.checked ? root.accent : "#d1d5db"
Label {
anchors.centerIn: parent
text: "✓"
font.pixelSize: 11
font.weight: Font.Bold
color: root.accent
visible: mrow.checked
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: mrow.toggled(!mrow.checked)
}
}
Label {
text: mrow.label
font.pixelSize: 13
color: root.textPrimary
Layout.fillWidth: true
TapHandler {
cursorShape: Qt.PointingHandCursor
onTapped: mrow.toggled(!mrow.checked)
}
}
}
}
@@ -51,12 +51,6 @@ Item {
font.weight: Font.Medium
color: textPrimary
}
Label {
text: ""
font.pixelSize: 22
color: textMuted
}
}
}
@@ -15,15 +15,13 @@ Rectangle {
readonly property color textPrimary: "#111827"
readonly property color textMuted: "#6b7280"
readonly property bool isActive: modelData.status === "InProgress" || modelData.status === "Queued"
readonly property bool isActive: modelData.status === "InProgress"
|| modelData.status === "Queued"
|| modelData.status === "Connecting"
|| modelData.status === "AwaitingLocalConfirmation"
|| modelData.status === "AwaitingRemoteAcceptance"
readonly property bool isTerminal: !isActive
function endpointLabel(endpointId) {
var label = fileShareController.peerNameForEndpoint(endpointId)
if (!label || label.length === 0 || label === "Unknown device") return "Unknown device"
return label
}
function formatBytes(bytes) {
if (bytes === undefined || bytes === null) return "0 B"
var value = Number(bytes)
@@ -52,7 +50,8 @@ Rectangle {
Label {
anchors.centerIn: parent
text: endpointLabel(modelData.endpointId).charAt(0).toUpperCase()
text: modelData.targetName && modelData.targetName.length > 0
? modelData.targetName.charAt(0).toUpperCase() : "?"
font.pixelSize: 18
font.weight: Font.Medium
color: textPrimary
@@ -65,7 +64,7 @@ Rectangle {
Label {
Layout.fillWidth: true
text: endpointLabel(modelData.endpointId)
text: modelData.targetName
font.weight: Font.Medium
elide: Text.ElideRight
color: textPrimary
@@ -76,27 +75,12 @@ Rectangle {
color: textMuted
text: {
if (isActive)
return modelData.direction === "Send" ? "Sending..." : "Receiving..."
return modelData.status
return modelData.direction === "outgoing" ? "Sending..." : "Receiving..."
return modelData.status + (modelData.fileName && modelData.fileName.length > 0
? " • " + modelData.fileName : "")
}
}
}
Rectangle {
height: 22
width: Math.max(68, medLbl.implicitWidth + 16)
radius: 11
color: "#e2e8f0"
border.color: "#cbd5e1"
Label {
id: medLbl
anchors.centerIn: parent
text: modelData.medium
font.pixelSize: 11
color: "#334155"
}
}
}
ProgressBar {
@@ -110,7 +94,8 @@ Rectangle {
Layout.fillWidth: true
visible: isActive
horizontalAlignment: Text.AlignRight
text: formatBytes(modelData.bytesTransferred) + " / " + formatBytes(modelData.totalBytes)
text: Math.round((modelData.progress || 0) * 100) + "% • " +
formatBytes(modelData.transferredBytes)
font.pixelSize: 12
color: textMuted
}
File diff suppressed because it is too large Load Diff
@@ -5,19 +5,15 @@
#include <QFile>
#include <QHash>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QTimer>
#include <QVariantList>
#include <QVariantMap>
#include <string>
#include <vector>
#include <memory>
#include "sharing/linux/nearby_connections_qt_facade.h"
#include "sharing/linux/nearby_sharing_api.h"
using NearbyConnectionsQtFacade = nearby::sharing::NearbyConnectionsQtFacade;
using NearbySharingApi = nearby::sharing::linux::NearbySharingApi;
class FileShareTrayController : public QObject {
Q_OBJECT
@@ -27,18 +23,10 @@ class FileShareTrayController : public QObject {
Q_PROPERTY(bool running READ running NOTIFY runningChanged)
Q_PROPERTY(QString pendingSendFileName READ pendingSendFileName NOTIFY pendingSendFileNameChanged)
Q_PROPERTY(QString pendingSendFilePath READ pendingSendFilePath NOTIFY pendingSendFilePathChanged)
Q_PROPERTY(QStringList discoveredDevices READ discoveredDevices NOTIFY discoveredDevicesChanged)
Q_PROPERTY(QStringList connectedDevices READ connectedDevices NOTIFY connectedDevicesChanged)
Q_PROPERTY(QVariantMap endpointMediums READ endpointMediums NOTIFY endpointMediumsChanged)
Q_PROPERTY(QVariantList discoveredTargets READ discoveredTargets NOTIFY discoveredTargetsChanged)
Q_PROPERTY(QVariantList transfers READ transfers NOTIFY transfersChanged)
Q_PROPERTY(bool autoAcceptIncoming READ autoAcceptIncoming WRITE setAutoAcceptIncoming NOTIFY autoAcceptIncomingChanged)
Q_PROPERTY(bool bluetoothEnabled READ bluetoothEnabled WRITE setBluetoothEnabled NOTIFY bluetoothEnabledChanged)
Q_PROPERTY(bool bleEnabled READ bleEnabled WRITE setBleEnabled NOTIFY bleEnabledChanged)
Q_PROPERTY(bool wifiLanEnabled READ wifiLanEnabled WRITE setWifiLanEnabled NOTIFY wifiLanEnabledChanged)
Q_PROPERTY(bool wifiHotspotEnabled READ wifiHotspotEnabled WRITE setWifiHotspotEnabled NOTIFY wifiHotspotEnabledChanged)
Q_PROPERTY(bool webRtcEnabled READ webRtcEnabled WRITE setWebRtcEnabled NOTIFY webRtcEnabledChanged)
Q_PROPERTY(QString connectionStrategy READ connectionStrategy WRITE setConnectionStrategy NOTIFY connectionStrategyChanged)
Q_PROPERTY(QString serviceId READ serviceId WRITE setServiceId NOTIFY serviceIdChanged)
Q_PROPERTY(QString qrCodeUrl READ qrCodeUrl NOTIFY qrCodeUrlChanged)
Q_PROPERTY(QString logPath READ logPath WRITE setLogPath NOTIFY logPathChanged)
public:
@@ -56,34 +44,13 @@ class FileShareTrayController : public QObject {
QString pendingSendFileName() const { return pending_send_file_name_; }
QString pendingSendFilePath() const { return pending_send_file_path_; }
QStringList discoveredDevices() const { return discovered_devices_; }
QStringList connectedDevices() const { return connected_devices_; }
QVariantMap endpointMediums() const { return endpoint_mediums_; }
QVariantList discoveredTargets() const { return discovered_targets_; }
QVariantList transfers() const { return transfers_; }
bool autoAcceptIncoming() const { return auto_accept_incoming_; }
void setAutoAcceptIncoming(bool enabled);
bool bluetoothEnabled() const { return bluetooth_enabled_; }
void setBluetoothEnabled(bool enabled);
bool bleEnabled() const { return ble_enabled_; }
void setBleEnabled(bool enabled);
bool wifiLanEnabled() const { return wifi_lan_enabled_; }
void setWifiLanEnabled(bool enabled);
bool wifiHotspotEnabled() const { return wifi_hotspot_enabled_; }
void setWifiHotspotEnabled(bool enabled);
bool webRtcEnabled() const { return web_rtc_enabled_; }
void setWebRtcEnabled(bool enabled);
QString connectionStrategy() const { return connection_strategy_; }
void setConnectionStrategy(const QString& strategy);
QString serviceId() const { return QString::fromStdString(service_id_); }
void setServiceId(const QString& service_id);
QString qrCodeUrl() const { return qr_code_url_; }
QString logPath() const { return log_path_; }
void setLogPath(const QString& path);
@@ -92,9 +59,7 @@ class FileShareTrayController : public QObject {
Q_INVOKABLE void stop();
Q_INVOKABLE void switchToReceiveMode();
Q_INVOKABLE void switchToSendModeWithFile(const QString& file_path);
Q_INVOKABLE void sendPendingFileToEndpoint(const QString& endpoint_id);
Q_INVOKABLE QString mediumForEndpoint(const QString& endpoint_id) const;
Q_INVOKABLE QString peerNameForEndpoint(const QString& endpoint_id) const;
Q_INVOKABLE void sendPendingFileToTarget(qlonglong share_target_id);
Q_INVOKABLE void clearTransfers();
Q_INVOKABLE void hideToTray();
@@ -105,117 +70,63 @@ class FileShareTrayController : public QObject {
void runningChanged();
void pendingSendFileNameChanged();
void pendingSendFilePathChanged();
void discoveredDevicesChanged();
void connectedDevicesChanged();
void endpointMediumsChanged();
void discoveredTargetsChanged();
void transfersChanged();
void autoAcceptIncomingChanged();
void bluetoothEnabledChanged();
void bleEnabledChanged();
void wifiLanEnabledChanged();
void wifiHotspotEnabledChanged();
void webRtcEnabledChanged();
void connectionStrategyChanged();
void serviceIdChanged();
void qrCodeUrlChanged();
void logPathChanged();
void requestTrayMessage(const QString& title, const QString& body);
private:
void CreateService();
void AttachServiceListeners();
void startSendMode();
void startReceiveMode();
void LoadSettings();
void SaveSettings() const;
std::vector<uint8_t> BuildEndpointInfo() const;
void UpsertTarget(qlonglong share_target_id, const QString& name,
bool is_incoming);
void RemoveTarget(qlonglong share_target_id);
QString TargetName(qlonglong share_target_id) const;
NearbyConnectionsQtFacade::ConnectionListener BuildConnectionListener();
NearbyConnectionsQtFacade::DiscoveryListener BuildDiscoveryListener();
NearbyConnectionsQtFacade::PayloadListener BuildPayloadListener();
NearbyConnectionsQtFacade::AdvertisingOptions BuildAdvertisingOptions() const;
NearbyConnectionsQtFacade::DiscoveryOptions BuildDiscoveryOptions() const;
NearbyConnectionsQtFacade::ConnectionOptions BuildConnectionOptions() const;
NearbyConnectionsQtFacade::MediumSelection BuildMediumSelection() const;
void acceptIncomingInternal(const QString& endpoint_id);
void requestConnectionForSend(const QString& endpoint_id);
void sendPendingFile(const QString& endpoint_id);
void disconnectDevice(const QString& endpoint_id);
void AddDiscoveredDevice(const QString& endpoint_id);
void RemoveDiscoveredDevice(const QString& endpoint_id);
void AddConnectedDevice(const QString& endpoint_id);
void RemoveConnectedDevice(const QString& endpoint_id);
void SetPeerNameForEndpoint(const QString& endpoint_id,
const QString& peer_name);
QString PeerLabelForEndpoint(const QString& endpoint_id) const;
QString FinalizeReceivedFilePath(const QString& received_path,
const QString& received_file_name,
qlonglong payload_id) const;
void UpsertTransfer(const QString& endpoint_id, qlonglong payload_id,
const QString& status, qulonglong bytes_transferred,
qulonglong total_bytes, const QString& direction);
void UpdateTransferMediumForEndpoint(const QString& endpoint_id,
const QString& medium);
void UpsertTransfer(qlonglong share_target_id, const QString& target_name,
const QString& status, double progress,
qulonglong transferred_bytes, const QString& direction,
const QString& file_name);
void SetStatus(const QString& status);
bool HasActiveTransfers() const;
void LogLine(const QString& line);
void ReopenLogFile();
static QString StatusToString(NearbyConnectionsQtFacade::Status status);
static QString PayloadStatusToString(
NearbyConnectionsQtFacade::PayloadStatus status);
static QString MediumToString(NearbyConnectionsQtFacade::Medium medium);
static QString StatusToString(NearbySharingApi::StatusCode status);
static QString TransferStatusToString(NearbySharingApi::TransferStatus status);
static bool IsFinalTransferStatus(NearbySharingApi::TransferStatus status);
NearbyConnectionsQtFacade service_;
std::unique_ptr<NearbySharingApi> service_;
QString mode_ = QStringLiteral("Receive");
QString device_name_ = QStringLiteral("NearbyQtFile");
std::string service_id_ = "com.nearby.qml.tray";
QString device_name_ = QStringLiteral("NearbyLinux");
QString status_message_ = QStringLiteral("Idle");
bool running_ = false;
bool auto_accept_incoming_ = true;
bool bluetooth_enabled_ = true;
bool ble_enabled_ = true;
bool wifi_lan_enabled_ = true;
bool wifi_hotspot_enabled_ = true;
bool web_rtc_enabled_ = false;
QString connection_strategy_ = QStringLiteral("P2pPointToPoint");
QString qr_code_url_;
QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log");
QString pending_send_file_path_;
QString pending_send_file_name_;
QString target_endpoint_for_send_;
qlonglong pending_send_target_id_ = 0;
QStringList discovered_devices_;
QStringList connected_devices_;
QHash<QString, QString> endpoint_peer_names_;
QVariantMap endpoint_mediums_;
QVariantList discovered_targets_;
QHash<qlonglong, int> discovered_row_by_target_;
QHash<qlonglong, QString> target_names_;
QVariantList transfers_;
QHash<qlonglong, int> transfer_row_for_payload_;
QHash<QString, QString> pending_file_names_;
QHash<qlonglong, QString> incoming_file_paths_;
QHash<qlonglong, QString> incoming_file_names_;
QHash<qlonglong, QString> incoming_file_endpoints_;
QHash<qlonglong, QString> outgoing_file_payload_to_endpoint_;
QHash<qlonglong, QString> outgoing_file_payload_to_name_;
QSet<qlonglong> send_terminal_notified_;
struct PendingOutgoingCompletion {
QString file_name;
QString peer;
QString endpoint;
bool success = false;
};
QHash<QString, PendingOutgoingCompletion> pending_outgoing_completions_;
QHash<QString, QTimer*> outgoing_disconnect_timers_;
QHash<qlonglong, int> transfer_row_by_target_;
QFile log_file_;
};
@@ -46,9 +46,6 @@ QIcon BuildTintedSymbolicIcon(const QString& source, const QColor& color) {
} // namespace
int main(int argc, char* argv[]) {
nearby::sharing::NearbyConnectionsQtFacade::SetBleL2capFlagOverrides(
true, false);
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);