mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
refractored facade to switch from using nearby connections to linux nearby sharing service.
This commit is contained in:
+34
-3
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -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
@@ -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")"
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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 {}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user