added file share qml tray app. made improvements to qml tray controller

This commit is contained in:
Lasan Mahaliyana
2026-02-22 09:00:59 +05:30
parent 2539024306
commit 1c1ab1d457
17 changed files with 2303 additions and 207 deletions
+159
View File
@@ -0,0 +1,159 @@
#!/usr/bin/env bash
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=()
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
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_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")"
+16 -2
View File
@@ -3,11 +3,13 @@
#include <atomic>
#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/linux/nearby_connections_service_linux.h"
#include "sharing/nearby_connections_types.h"
namespace nearby::sharing::linux {
namespace nearby::sharing {
namespace {
@@ -345,7 +347,7 @@ std::unique_ptr<nearby::sharing::Payload> ToNativePayload(Facade::Payload payloa
class NearbyConnectionsQtFacade::Impl {
public:
NearbyConnectionsServiceLinux service;
linux::NearbyConnectionsServiceLinux service;
};
NearbyConnectionsQtFacade::NearbyConnectionsQtFacade()
@@ -359,6 +361,18 @@ NearbyConnectionsQtFacade::NearbyConnectionsQtFacade(
NearbyConnectionsQtFacade& NearbyConnectionsQtFacade::operator=(
NearbyConnectionsQtFacade&&) noexcept = default;
void NearbyConnectionsQtFacade::SetBleL2capFlagOverrides(
bool enable_ble_l2cap, bool refactor_ble_l2cap) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableBleL2cap,
enable_ble_l2cap);
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap,
refactor_ble_l2cap);
}
NearbyConnectionsQtFacade::Payload NearbyConnectionsQtFacade::CreateBytesPayload(
std::vector<uint8_t> bytes) const {
Payload payload;
+47 -43
View File
@@ -19,10 +19,10 @@
#undef linux
#endif
namespace nearby::sharing::linux {
namespace nearby {
namespace sharing {
class NearbyConnectionsQtFacade {
public:
public:
enum class Status {
kSuccess = 0,
kError = 1,
@@ -141,84 +141,88 @@ class NearbyConnectionsQtFacade {
};
struct ConnectionListener {
std::function<void(const std::string&, const ConnectionInfo&)> initiated_cb;
std::function<void(const std::string&)> accepted_cb;
std::function<void(const std::string&, Status)> rejected_cb;
std::function<void(const std::string&)> disconnected_cb;
std::function<void(const std::string&, Medium)> bandwidth_changed_cb;
std::function<void(const std::string &, const ConnectionInfo &)>
initiated_cb;
std::function<void(const std::string &)> accepted_cb;
std::function<void(const std::string &, Status)> rejected_cb;
std::function<void(const std::string &)> disconnected_cb;
std::function<void(const std::string &, Medium)> bandwidth_changed_cb;
};
struct DiscoveryListener {
std::function<void(const std::string&, const DiscoveredEndpointInfo&)>
std::function<void(const std::string &, const DiscoveredEndpointInfo &)>
endpoint_found_cb;
std::function<void(const std::string&)> endpoint_lost_cb;
std::function<void(const std::string&, DistanceInfo)>
std::function<void(const std::string &)> endpoint_lost_cb;
std::function<void(const std::string &, DistanceInfo)>
endpoint_distance_changed_cb;
};
struct PayloadListener {
std::function<void(const std::string&, Payload)> payload_cb;
std::function<void(const std::string&, const PayloadTransferUpdate&)>
std::function<void(const std::string &, Payload)> payload_cb;
std::function<void(const std::string &, const PayloadTransferUpdate &)>
payload_progress_cb;
};
NearbyConnectionsQtFacade();
~NearbyConnectionsQtFacade();
NearbyConnectionsQtFacade(const NearbyConnectionsQtFacade&) = delete;
NearbyConnectionsQtFacade& operator=(const NearbyConnectionsQtFacade&) =
delete;
NearbyConnectionsQtFacade(NearbyConnectionsQtFacade&&) noexcept;
NearbyConnectionsQtFacade& operator=(
NearbyConnectionsQtFacade&&) noexcept;
NearbyConnectionsQtFacade(const NearbyConnectionsQtFacade &) = delete;
NearbyConnectionsQtFacade &
operator=(const NearbyConnectionsQtFacade &) = delete;
NearbyConnectionsQtFacade(NearbyConnectionsQtFacade &&) noexcept;
NearbyConnectionsQtFacade &operator=(NearbyConnectionsQtFacade &&) noexcept;
// Sets global Nearby flag overrides for BLE L2CAP.
static void SetBleL2capFlagOverrides(bool enable_ble_l2cap,
bool refactor_ble_l2cap);
Payload CreateBytesPayload(std::vector<uint8_t> bytes) const;
void StartAdvertising(const std::string& service_id,
const std::vector<uint8_t>& endpoint_info,
const AdvertisingOptions& advertising_options,
void StartAdvertising(const std::string &service_id,
const std::vector<uint8_t> &endpoint_info,
const AdvertisingOptions &advertising_options,
ConnectionListener advertising_listener,
std::function<void(Status)> callback);
void StopAdvertising(const std::string& service_id,
void StopAdvertising(const std::string &service_id,
std::function<void(Status)> callback);
void StartDiscovery(const std::string& service_id,
const DiscoveryOptions& discovery_options,
void StartDiscovery(const std::string &service_id,
const DiscoveryOptions &discovery_options,
DiscoveryListener discovery_listener,
std::function<void(Status)> callback);
void StopDiscovery(const std::string& service_id,
void StopDiscovery(const std::string &service_id,
std::function<void(Status)> callback);
void RequestConnection(const std::string& service_id,
const std::vector<uint8_t>& endpoint_info,
const std::string& endpoint_id,
const ConnectionOptions& connection_options,
void 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);
void DisconnectFromEndpoint(const std::string& service_id,
const std::string& endpoint_id,
void DisconnectFromEndpoint(const std::string &service_id,
const std::string &endpoint_id,
std::function<void(Status)> callback);
void SendPayload(const std::string& service_id,
const std::vector<std::string>& endpoint_ids, Payload payload,
std::function<void(Status)> callback);
void InitiateBandwidthUpgrade(const std::string& service_id,
const std::string& endpoint_id,
void SendPayload(const std::string &service_id,
const std::vector<std::string> &endpoint_ids,
Payload payload, std::function<void(Status)> callback);
void InitiateBandwidthUpgrade(const std::string &service_id,
const std::string &endpoint_id,
std::function<void(Status)> callback);
void AcceptConnection(const std::string& service_id,
const std::string& endpoint_id,
void AcceptConnection(const std::string &service_id,
const std::string &endpoint_id,
PayloadListener payload_listener,
std::function<void(Status)> callback);
void StopAllEndpoints(std::function<void(Status)> callback);
private:
private:
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace sharing
} // namespace nearby
} // namespace nearby::sharing::linux
#endif // SHARING_LINUX_NEARBY_CONNECTIONS_QT_FACADE_H_
#endif // SHARING_LINUX_NEARBY_CONNECTIONS_QT_FACADE_H_
@@ -17,6 +17,7 @@
#include <stdint.h>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <memory>
#include <string>
@@ -127,6 +128,17 @@ NcStrategy ConvertStrategy(Strategy strategy) {
return NcStrategy::kP2pPointToPoint;
}
void SetUpgradeMediumEnv(const nearby::sharing::MediumSelection& mediums) {
setenv("NEARBY_ALLOW_UPGRADE_BLUETOOTH", mediums.bluetooth ? "1" : "0", 1);
setenv("NEARBY_ALLOW_UPGRADE_BLE", mediums.ble ? "1" : "0", 1);
setenv("NEARBY_ALLOW_UPGRADE_WEB_RTC", mediums.web_rtc ? "1" : "0", 1);
setenv("NEARBY_ALLOW_UPGRADE_WIFI_LAN", mediums.wifi_lan ? "1" : "0", 1);
setenv("NEARBY_ALLOW_UPGRADE_WIFI_HOTSPOT",
mediums.wifi_hotspot ? "1" : "0", 1);
setenv("NEARBY_ALLOW_UPGRADE_WIFI_DIRECT", "0", 1);
setenv("NEARBY_ALLOW_UPGRADE_AWDL", "0", 1);
}
ConnectionInfo ConvertConnectionInfo(const ConnectionResponseInfo& info) {
ConnectionInfo connection_info;
connection_info.authentication_token = info.authentication_token;
@@ -158,9 +170,11 @@ void NearbyConnectionsServiceLinux::StartAdvertising(
ConnectionListener advertising_listener,
std::function<void(Status status)> callback) {
advertising_listener_ = std::move(advertising_listener);
SetUpgradeMediumEnv(advertising_options.allowed_mediums);
NcAdvertisingOptions options{};
options.strategy = ConvertStrategy(advertising_options.strategy);
options.allowed.SetAll(false);
options.allowed.ble = advertising_options.allowed_mediums.ble;
options.allowed.bluetooth = advertising_options.allowed_mediums.bluetooth;
options.allowed.web_rtc = advertising_options.allowed_mediums.web_rtc;
@@ -217,6 +231,7 @@ void NearbyConnectionsServiceLinux::StartDiscovery(
DiscoveryListener discovery_listener,
std::function<void(Status status)> callback) {
discovery_listener_ = std::move(discovery_listener);
SetUpgradeMediumEnv(discovery_options.allowed_mediums);
NcDiscoveryOptions options{};
options.strategy = ConvertStrategy(discovery_options.strategy);
@@ -274,8 +289,10 @@ void NearbyConnectionsServiceLinux::RequestConnection(
std::function<void(Status status)> callback) {
static_cast<void>(service_id);
connection_listener_ = std::move(connection_listener);
SetUpgradeMediumEnv(connection_options.allowed_mediums);
NcConnectionOptions options{};
options.allowed.SetAll(false);
options.allowed.ble = connection_options.allowed_mediums.ble;
options.allowed.bluetooth = connection_options.allowed_mediums.bluetooth;
options.allowed.web_rtc = connection_options.allowed_mediums.web_rtc;
+75 -91
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.21)
project(nearby_qml_tray_app LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
@@ -11,100 +11,43 @@ set(CMAKE_AUTOUIC OFF)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Qml Quick QuickControls2)
include(GNUInstallDirs)
set(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../..")
get_filename_component(REPO_ROOT "${REPO_ROOT}" ABSOLUTE)
set(NEARBY_INCLUDE_ROOT "${CMAKE_CURRENT_BINARY_DIR}/nearby_include_root")
file(MAKE_DIRECTORY "${NEARBY_INCLUDE_ROOT}")
set(NEARBY_INSTALL_PREFIX "/usr/local" CACHE PATH "Prefix where Nearby facade header and shared library are installed")
set(NEARBY_INCLUDE_ROOT "${NEARBY_INSTALL_PREFIX}/include" CACHE PATH "Nearby include root")
set(NEARBY_LIBRARY_DIR "${NEARBY_INSTALL_PREFIX}/lib" CACHE PATH "Nearby shared library directory")
# Avoid exposing the full repo root as an include directory (which can make IDEs
# index unrelated trees like the Bazel 'external' symlink). We create a narrow
# include root that only links directories needed by headers used in this app.
set(NEARBY_INCLUDE_DIRS
sharing
)
foreach(dir_name IN LISTS NEARBY_INCLUDE_DIRS)
if(EXISTS "${REPO_ROOT}/${dir_name}")
execute_process(
COMMAND "${CMAKE_COMMAND}" -E create_symlink
"${REPO_ROOT}/${dir_name}"
"${NEARBY_INCLUDE_ROOT}/${dir_name}"
RESULT_VARIABLE LINK_RESULT
ERROR_VARIABLE LINK_ERROR
)
if(NOT LINK_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to create include symlink for ${dir_name}: ${LINK_ERROR}")
endif()
endif()
endforeach()
set(BAZEL_EXECUTABLE "bazel" CACHE STRING "Path to bazel executable")
set(
BAZEL_NEARBY_TARGET
"//sharing/linux:nearby_connections_service_linux_shared"
CACHE STRING
"Bazel target that produces a shared library for nearby_connections_service_linux"
)
set(BAZEL_BUILD_OPTIONS
-s
--check_visibility=false
--spawn_strategy=standalone
--verbose_failures
--cxxopt=-std=c++20
--host_cxxopt=-std=c++20
)
string(JOIN " " BAZEL_BUILD_OPTIONS_STRING ${BAZEL_BUILD_OPTIONS})
execute_process(
COMMAND "${BAZEL_EXECUTABLE}" info bazel-bin
WORKING_DIRECTORY "${REPO_ROOT}"
RESULT_VARIABLE BAZEL_INFO_RESULT
OUTPUT_VARIABLE BAZEL_BIN_DIR
ERROR_VARIABLE BAZEL_INFO_ERROR
OUTPUT_STRIP_TRAILING_WHITESPACE
find_path(
NEARBY_FACADE_INCLUDE_ROOT
NAMES sharing/linux/nearby_connections_qt_facade.h
HINTS "${NEARBY_INCLUDE_ROOT}"
)
if(NOT BAZEL_INFO_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to query bazel-bin with '${BAZEL_EXECUTABLE} info bazel-bin': ${BAZEL_INFO_ERROR}")
find_library(
NEARBY_CONNECTIONS_SHARED_LIB
NAMES nearby_connections_service_linux_shared
HINTS "${NEARBY_LIBRARY_DIR}"
)
if(NOT NEARBY_FACADE_INCLUDE_ROOT)
message(FATAL_ERROR
"Could not find sharing/linux/nearby_connections_qt_facade.h. "
"Install it first (for example with sharing/linux/install_nearby_connections_service.sh) "
"or set -DNEARBY_INCLUDE_ROOT=<prefix>/include.")
endif()
set(BAZEL_NEARBY_SO "${BAZEL_BIN_DIR}/sharing/linux/libnearby_connections_service_linux_shared.so")
set(
BAZEL_BUILD_IF_MISSING_SCRIPT
"${CMAKE_CURRENT_LIST_DIR}/cmake/BuildNearbySoIfMissing.cmake"
)
set(BAZEL_REBUILD_INPUTS
"${REPO_ROOT}/sharing/linux/BUILD"
"${REPO_ROOT}/sharing/linux/nearby_connections_qt_facade.h"
"${REPO_ROOT}/sharing/linux/nearby_connections_qt_facade.cc"
"${REPO_ROOT}/sharing/linux/nearby_connections_service_linux.h"
"${REPO_ROOT}/sharing/linux/nearby_connections_service_linux.cc"
"${REPO_ROOT}/internal/platform/implementation/linux/crypto.cc"
"${REPO_ROOT}/internal/platform/uuid.cc"
)
if(NOT NEARBY_CONNECTIONS_SHARED_LIB)
message(FATAL_ERROR
"Could not find libnearby_connections_service_linux_shared.so. "
"Install it first (for example with sharing/linux/install_nearby_connections_service.sh) "
"or set -DNEARBY_LIBRARY_DIR=<prefix>/lib.")
endif()
add_custom_target(
bazel_nearby_connections_service_linux
COMMAND "${CMAKE_COMMAND}"
-DOUTPUT_SO=${BAZEL_NEARBY_SO}
-DBAZEL_EXECUTABLE=${BAZEL_EXECUTABLE}
-DBAZEL_TARGET=${BAZEL_NEARBY_TARGET}
-DBAZEL_BUILD_OPTIONS=${BAZEL_BUILD_OPTIONS_STRING}
-DREPO_ROOT=${REPO_ROOT}
-DREBUILD_INPUTS=${BAZEL_REBUILD_INPUTS}
-P "${BAZEL_BUILD_IF_MISSING_SCRIPT}"
BYPRODUCTS "${BAZEL_NEARBY_SO}"
COMMENT "Checking/building ${BAZEL_NEARBY_TARGET} with Bazel"
VERBATIM
)
add_library(nearby_connections_service_linux_bazel SHARED IMPORTED GLOBAL)
add_library(nearby_connections_service_linux_installed SHARED IMPORTED GLOBAL)
set_target_properties(
nearby_connections_service_linux_bazel
nearby_connections_service_linux_installed
PROPERTIES
IMPORTED_LOCATION "${BAZEL_NEARBY_SO}"
INTERFACE_INCLUDE_DIRECTORIES "${NEARBY_INCLUDE_ROOT}"
IMPORTED_LOCATION "${NEARBY_CONNECTIONS_SHARED_LIB}"
INTERFACE_INCLUDE_DIRECTORIES "${NEARBY_FACADE_INCLUDE_ROOT}"
)
add_dependencies(nearby_connections_service_linux_bazel bazel_nearby_connections_service_linux)
qt_add_executable(nearby_qml_tray_app
main.cpp
@@ -113,8 +56,16 @@ qt_add_executable(nearby_qml_tray_app
resources.qrc
)
qt_add_executable(nearby_qml_file_tray_app
file_share_tray_main.cpp
file_share_tray_controller.cc
file_share_tray_controller.h
resources_file_share.qrc
)
target_include_directories(nearby_qml_tray_app PRIVATE
"${NEARBY_INCLUDE_ROOT}"
"${CMAKE_CURRENT_LIST_DIR}"
"${NEARBY_FACADE_INCLUDE_ROOT}"
)
target_link_libraries(nearby_qml_tray_app PRIVATE
Qt6::Core
@@ -123,20 +74,42 @@ target_link_libraries(nearby_qml_tray_app PRIVATE
Qt6::Qml
Qt6::Quick
Qt6::QuickControls2
nearby_connections_service_linux_bazel
nearby_connections_service_linux_installed
)
add_dependencies(nearby_qml_tray_app bazel_nearby_connections_service_linux)
target_include_directories(nearby_qml_file_tray_app PRIVATE
"${CMAKE_CURRENT_LIST_DIR}"
"${NEARBY_FACADE_INCLUDE_ROOT}"
)
target_link_libraries(nearby_qml_file_tray_app PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Qml
Qt6::Quick
Qt6::QuickControls2
nearby_connections_service_linux_installed
)
get_filename_component(NEARBY_CONNECTIONS_SHARED_LIB_DIR "${NEARBY_CONNECTIONS_SHARED_LIB}" DIRECTORY)
set_target_properties(nearby_qml_tray_app PROPERTIES
BUILD_RPATH "${BAZEL_BIN_DIR}/sharing/linux"
BUILD_RPATH "${NEARBY_CONNECTIONS_SHARED_LIB_DIR}"
INSTALL_RPATH "$ORIGIN"
)
set_target_properties(nearby_qml_file_tray_app PROPERTIES
BUILD_RPATH "${NEARBY_CONNECTIONS_SHARED_LIB_DIR}"
INSTALL_RPATH "$ORIGIN"
)
install(TARGETS nearby_qml_tray_app
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
install(TARGETS nearby_qml_file_tray_app
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
install(FILES "${BAZEL_NEARBY_SO}"
install(FILES "${NEARBY_CONNECTIONS_SHARED_LIB}"
DESTINATION "${CMAKE_INSTALL_BINDIR}"
)
@@ -146,6 +119,17 @@ qt_generate_deploy_qml_app_script(
)
install(SCRIPT "${nearby_qml_tray_app_deploy_script}")
qt_generate_deploy_qml_app_script(
TARGET nearby_qml_file_tray_app
OUTPUT_SCRIPT nearby_qml_file_tray_app_deploy_script
)
install(SCRIPT "${nearby_qml_file_tray_app_deploy_script}")
# Convenience build target for the file tray app.
add_custom_target(build_file_tray_app
DEPENDS nearby_qml_file_tray_app
)
set(CPACK_GENERATOR "ZIP")
set(CPACK_PACKAGE_NAME "nearby_qml_tray_app")
set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
@@ -0,0 +1,441 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ApplicationWindow {
id: root
width: 980
height: 760
minimumWidth: 820
minimumHeight: 620
visible: true
title: "Nearby File Tray"
readonly property color appBg: "#f5f6f8"
readonly property color surface: "#ffffff"
readonly property color border: "#d7dbe0"
readonly property color textPrimary: "#1f2328"
readonly property color textMuted: "#59636e"
palette.window: appBg
palette.base: surface
palette.button: "#f0f3f7"
palette.text: textPrimary
palette.windowText: textPrimary
palette.buttonText: textPrimary
palette.placeholderText: textMuted
palette.highlight: "#2f6feb"
palette.highlightedText: "#ffffff"
background: Rectangle {
color: root.appBg
}
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)
if (!isFinite(value) || value < 0) {
return "0 B"
}
var units = ["B", "KB", "MB", "GB", "TB"]
var unit = 0
while (value >= 1024 && unit < units.length - 1) {
value /= 1024
unit += 1
}
return (value >= 10 || unit === 0 ? value.toFixed(0) : value.toFixed(1)) + " " + units[unit]
}
onClosing: function(close) {
close.accepted = false
root.hide()
fileShareController.hideToTray()
}
header: ToolBar {
height: 52
RowLayout {
anchors.fill: parent
anchors.leftMargin: 12
anchors.rightMargin: 12
spacing: 10
Label {
text: "Nearby File Share"
font.bold: true
font.pixelSize: 18
color: root.textPrimary
}
Rectangle {
radius: 12
color: fileShareController.mode === "Send" ? "#dbeafe" : "#dcfce7"
border.color: fileShareController.mode === "Send" ? "#60a5fa" : "#4ade80"
Layout.preferredHeight: 24
Layout.preferredWidth: 86
Label {
anchors.centerIn: parent
text: fileShareController.mode
font.bold: true
color: "#1f2328"
}
}
Item { Layout.fillWidth: true }
Label {
text: fileShareController.running ? "Running" : "Stopped"
color: fileShareController.running ? "#1f7a1f" : "#a33"
font.bold: true
}
Button {
text: fileShareController.running ? "Stop" : "Start"
onClicked: {
if (fileShareController.running) {
fileShareController.stop()
} else {
fileShareController.start()
}
}
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 10
Frame {
Layout.fillWidth: true
padding: 10
background: Rectangle {
color: root.surface
border.color: root.border
radius: 6
}
GridLayout {
anchors.fill: parent
columns: 4
columnSpacing: 10
rowSpacing: 8
Label { text: "Device name" }
TextField {
Layout.fillWidth: true
text: fileShareController.deviceName
onEditingFinished: fileShareController.deviceName = text
}
Label { text: "Selected file" }
Label {
Layout.fillWidth: true
text: fileShareController.pendingSendFileName.length > 0
? fileShareController.pendingSendFileName
: "None"
elide: Text.ElideRight
color: root.textMuted
}
Label { text: "Status" }
Label {
Layout.columnSpan: 3
Layout.fillWidth: true
text: fileShareController.statusMessage
elide: Text.ElideRight
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
Frame {
Layout.preferredWidth: 420
Layout.fillHeight: true
padding: 10
background: Rectangle {
color: root.surface
border.color: root.border
radius: 6
}
ColumnLayout {
anchors.fill: parent
spacing: 8
RowLayout {
Layout.fillWidth: true
Label {
text: "Nearby Devices"
font.bold: true
}
Item { Layout.fillWidth: true }
Rectangle {
radius: 10
color: fileShareController.mode === "Send" ? "#dbeafe" : "#f1f5f9"
border.color: fileShareController.mode === "Send" ? "#60a5fa" : "#cbd5e1"
Layout.preferredHeight: 22
Layout.preferredWidth: 74
Label {
anchors.centerIn: parent
text: fileShareController.mode === "Send" ? "Discovering" : "Paused"
font.pixelSize: 11
}
}
}
Label {
Layout.fillWidth: true
text: fileShareController.mode === "Send"
? "Select a nearby device to send your selected file."
: "Use tray menu Send to choose a file and start discovery."
wrapMode: Text.WordWrap
color: root.textMuted
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 6
model: fileShareController.discoveredDevices
delegate: Rectangle {
required property string modelData
width: ListView.view.width
height: 70
color: root.surface
border.color: root.border
radius: 6
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 8
ColumnLayout {
Layout.fillWidth: true
spacing: 2
Label {
Layout.fillWidth: true
text: root.endpointLabel(modelData)
font.bold: true
elide: Text.ElideRight
}
Label {
Layout.fillWidth: true
text: modelData
color: root.textMuted
font.pixelSize: 11
elide: Text.ElideRight
}
}
Button {
text: "Send"
enabled: fileShareController.mode === "Send"
&& fileShareController.pendingSendFilePath.length > 0
onClicked: fileShareController.sendPendingFileToEndpoint(modelData)
}
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 10
Frame {
Layout.fillWidth: true
Layout.preferredHeight: 220
padding: 10
background: Rectangle {
color: root.surface
border.color: root.border
radius: 6
}
ColumnLayout {
anchors.fill: parent
spacing: 6
Label {
text: "Connected Devices"
font.bold: true
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 6
model: fileShareController.connectedDevices
delegate: Rectangle {
required property string modelData
width: ListView.view.width
height: 46
color: root.surface
border.color: root.border
radius: 6
RowLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
Label {
Layout.fillWidth: true
text: root.endpointLabel(modelData)
font.bold: true
elide: Text.ElideRight
}
Rectangle {
radius: 10
color: "#e2e8f0"
border.color: "#cbd5e1"
Layout.preferredHeight: 22
Layout.preferredWidth: Math.max(68, mediumLabel.implicitWidth + 16)
Label {
id: mediumLabel
anchors.centerIn: parent
text: fileShareController.mediumForEndpoint(modelData)
font.pixelSize: 11
color: "#334155"
}
}
}
}
}
}
}
Frame {
Layout.fillWidth: true
Layout.fillHeight: true
padding: 10
background: Rectangle {
color: root.surface
border.color: root.border
radius: 6
}
ColumnLayout {
anchors.fill: parent
spacing: 6
RowLayout {
Layout.fillWidth: true
Label {
text: "Transfers"
font.bold: true
}
Item { Layout.fillWidth: true }
Button {
text: "Clear"
onClicked: fileShareController.clearTransfers()
}
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 6
model: fileShareController.transfers
delegate: Rectangle {
required property var modelData
width: ListView.view.width
height: 86
color: root.surface
border.color: root.border
radius: 6
ColumnLayout {
anchors.fill: parent
anchors.leftMargin: 10
anchors.rightMargin: 10
spacing: 4
RowLayout {
Layout.fillWidth: true
Label {
Layout.fillWidth: true
text: modelData.direction + " | " + root.endpointLabel(modelData.endpointId)
font.bold: true
elide: Text.ElideRight
}
Label { text: modelData.status }
Rectangle {
radius: 10
color: "#e2e8f0"
border.color: "#cbd5e1"
Layout.preferredHeight: 22
Layout.preferredWidth: Math.max(68, transferMediumLabel.implicitWidth + 16)
Label {
id: transferMediumLabel
anchors.centerIn: parent
text: modelData.medium
font.pixelSize: 11
color: "#334155"
}
}
}
ProgressBar {
Layout.fillWidth: true
from: 0
to: 1
value: modelData.progress
}
Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
text: root.formatBytes(modelData.bytesTransferred)
+ " / " + root.formatBytes(modelData.totalBytes)
color: root.textMuted
}
}
}
}
}
}
}
}
}
}
+8 -1
View File
@@ -243,7 +243,14 @@ ApplicationWindow {
id: strategyCombo
Layout.preferredWidth: 170
model: ["P2pCluster", "P2pStar", "P2pPointToPoint"]
currentIndex: Math.max(0, find(nearbyController.connectionStrategy))
currentIndex: {
var strategy = String(nearbyController.connectionStrategy)
if (strategy === "P2pStar")
return 1
if (strategy === "P2pPointToPoint")
return 2
return 0
}
onActivated: nearbyController.connectionStrategy = currentText
}
+9 -30
View File
@@ -32,45 +32,24 @@ This folder contains a Qt/QML tray application backend and UI wired to:
- Transfers are shown with endpoint, direction, status, progress, and medium.
- Logs are appended to `/tmp/nearby_qml_tray.log`.
## Building (host stays clean)
## Building
This app now has a Bazel target:
This CMake app links against the installed Nearby shared library and header:
- `//sharing/linux/qml_tray_app:nearby_qml_tray_app`
- `libnearby_connections_service_linux_shared.so`
- `sharing/linux/nearby_connections_qt_facade.h`
Qt compiler and linker flags are resolved through `pkg-config` at build time.
### Recommended: build in container
From repo root:
Install them first (repo root):
```bash
./sharing/linux/qml_tray_app/build_in_container.sh
./sharing/linux/install_nearby_connections_service.sh
```
This command:
- builds an image from `sharing/linux/qml_tray_app/container/Dockerfile`
- installs Bazel + Qt 6 dev packages inside that image
- runs the Bazel build in the container
- keeps Bazel cache in a Docker volume (`nearby_qml_tray_bazel_cache`)
Useful overrides:
Then build the app (from `sharing/linux/qml_tray_app`):
```bash
# Use podman instead of docker.
CONTAINER_RUNTIME=podman ./sharing/linux/qml_tray_app/build_in_container.sh
# Pin a custom image tag.
QML_TRAY_IMAGE_TAG=nearby-qml-tray-builder:v1 ./sharing/linux/qml_tray_app/build_in_container.sh
```
### Build in an already-provisioned environment
If you already have Bazel + Qt 6 dev dependencies installed:
```bash
./sharing/linux/qml_tray_app/build.sh
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNEARBY_INSTALL_PREFIX=/usr/local
cmake --build build -j
```
## Bundle `libnearby_connections_service_linux_shared.so` with the app
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,160 @@
#ifndef SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_
#define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_
#include <QObject>
#include <QFile>
#include <QHash>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QVariantList>
#include <QVariantMap>
#include <string>
#include <vector>
#include "sharing/linux/nearby_connections_qt_facade.h"
using NearbyConnectionsQtFacade = nearby::sharing::NearbyConnectionsQtFacade;
class FileShareTrayController : public QObject {
Q_OBJECT
Q_PROPERTY(QString mode READ mode NOTIFY modeChanged)
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
Q_PROPERTY(QString statusMessage READ statusMessage NOTIFY statusMessageChanged)
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 transfers READ transfers NOTIFY transfersChanged)
public:
explicit FileShareTrayController(QObject* parent = nullptr);
~FileShareTrayController() override;
QString mode() const { return mode_; }
QString deviceName() const { return device_name_; }
void setDeviceName(const QString& device_name);
QString statusMessage() const { return status_message_; }
bool running() const { return running_; }
QString pendingSendFileName() const { return pending_send_file_name_; }
QString pendingSendFilePath() const { return pending_send_file_path_; }
QStringList discoveredDevices() const { return discovered_devices_; }
QStringList connectedDevices() const { return connected_devices_; }
QVariantMap endpointMediums() const { return endpoint_mediums_; }
QVariantList transfers() const { return transfers_; }
Q_INVOKABLE void start();
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 clearTransfers();
Q_INVOKABLE void hideToTray();
signals:
void modeChanged();
void deviceNameChanged();
void statusMessageChanged();
void runningChanged();
void pendingSendFileNameChanged();
void pendingSendFilePathChanged();
void discoveredDevicesChanged();
void connectedDevicesChanged();
void endpointMediumsChanged();
void transfersChanged();
void requestTrayMessage(const QString& title, const QString& body);
private:
void startSendMode();
void startReceiveMode();
void LoadSettings();
void SaveSettings() const;
std::vector<uint8_t> BuildEndpointInfo() 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 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);
NearbyConnectionsQtFacade service_;
QString mode_ = QStringLiteral("Receive");
QString device_name_ = QStringLiteral("NearbyQtFile");
const std::string service_id_ = "com.nearby.qml.tray";
QString status_message_ = QStringLiteral("Idle");
bool running_ = false;
QString pending_send_file_path_;
QString pending_send_file_name_;
QString target_endpoint_for_send_;
QStringList discovered_devices_;
QStringList connected_devices_;
QHash<QString, QString> endpoint_peer_names_;
QVariantMap endpoint_mediums_;
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_;
QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log");
QFile log_file_;
};
#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_
@@ -0,0 +1,169 @@
#include <QAction>
#include <QApplication>
#include <QFileDialog>
#include <QIcon>
#include <QMenu>
#include <QPainter>
#include <QPalette>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QStyleHints>
#include <QSystemTrayIcon>
#include "file_share_tray_controller.h"
namespace {
QIcon BuildTintedSymbolicIcon(const QString& source, const QColor& color) {
QIcon source_icon(source);
if (source_icon.isNull()) {
return QIcon();
}
QIcon tinted_icon;
for (int size : {16, 18, 20, 22, 24, 32}) {
QPixmap pixmap = source_icon.pixmap(size, size);
if (pixmap.isNull()) {
continue;
}
QPixmap tinted(pixmap.size());
tinted.fill(Qt::transparent);
QPainter painter(&tinted);
painter.drawPixmap(0, 0, pixmap);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.fillRect(tinted.rect(), color);
painter.end();
tinted_icon.addPixmap(tinted);
}
return tinted_icon;
}
} // namespace
int main(int argc, char* argv[]) {
nearby::sharing::NearbyConnectionsQtFacade::SetBleL2capFlagOverrides(
true, false);
QApplication app(argc, argv);
app.setQuitOnLastWindowClosed(false);
FileShareTrayController controller;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("fileShareController", &controller);
engine.load(QUrl(QStringLiteral("qrc:/qml/FileShareTray.qml")));
if (engine.rootObjects().isEmpty()) {
return 1;
}
auto* window = qobject_cast<QQuickWindow*>(engine.rootObjects().first());
if (window == nullptr) {
return 1;
}
const auto resolve_tray_icon = [&app]() {
const QColor symbolic_color = app.palette().color(QPalette::WindowText);
QIcon tray_icon = BuildTintedSymbolicIcon(
QStringLiteral(":/icons/tray_icon-symbolic.svg"), symbolic_color);
if (tray_icon.isNull()) {
tray_icon = QIcon::fromTheme(QStringLiteral("network-wireless-symbolic"));
}
if (tray_icon.isNull()) {
tray_icon = QIcon(QStringLiteral(":/icons/tray_icon.png"));
}
if (tray_icon.isNull()) {
tray_icon = app.windowIcon();
}
return tray_icon;
};
QSystemTrayIcon tray(resolve_tray_icon());
tray.setToolTip(QStringLiteral("Nearby File Tray"));
QMenu tray_menu;
QAction* send_action = tray_menu.addAction(QStringLiteral("Send"));
QAction* receive_action = tray_menu.addAction(QStringLiteral("Receive"));
tray_menu.addSeparator();
QAction* show_action = tray_menu.addAction(QStringLiteral("Show"));
QAction* hide_action = tray_menu.addAction(QStringLiteral("Hide"));
tray_menu.addSeparator();
QAction* quit_action = tray_menu.addAction(QStringLiteral("Quit"));
QObject::connect(send_action, &QAction::triggered, window,
[&controller, window]() {
const QString file = QFileDialog::getOpenFileName(
nullptr, QStringLiteral("Select file to send"));
if (file.isEmpty()) {
return;
}
controller.switchToSendModeWithFile(file);
window->show();
window->raise();
window->requestActivate();
});
QObject::connect(receive_action, &QAction::triggered,
[&controller, window]() {
controller.switchToReceiveMode();
window->show();
window->raise();
window->requestActivate();
});
QObject::connect(show_action, &QAction::triggered, window, [window]() {
window->show();
window->raise();
window->requestActivate();
});
QObject::connect(hide_action, &QAction::triggered, window, [window]() {
window->hide();
});
QObject::connect(quit_action, &QAction::triggered, &app,
[&controller, &app]() {
controller.stop();
app.quit();
});
QObject::connect(&tray, &QSystemTrayIcon::activated, window,
[window](QSystemTrayIcon::ActivationReason reason) {
if (reason != QSystemTrayIcon::Trigger &&
reason != QSystemTrayIcon::DoubleClick) {
return;
}
if (window->isVisible()) {
window->hide();
} else {
window->show();
window->raise();
window->requestActivate();
}
});
QObject::connect(&controller, &FileShareTrayController::requestTrayMessage,
&tray, [&tray](const QString& title, const QString& body) {
tray.showMessage(title, body, QSystemTrayIcon::Information,
4000);
});
QObject::connect(&app, &QCoreApplication::aboutToQuit, &controller,
[&controller]() { controller.stop(); });
QObject::connect(app.styleHints(), &QStyleHints::colorSchemeChanged, &tray,
[&tray, &resolve_tray_icon](Qt::ColorScheme) {
tray.setIcon(resolve_tray_icon());
});
tray.setContextMenu(&tray_menu);
tray.show();
controller.switchToReceiveMode();
return app.exec();
}
+64 -18
View File
@@ -2,12 +2,47 @@
#include <QApplication>
#include <QIcon>
#include <QMenu>
#include <QPainter>
#include <QPalette>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QStyleHints>
#include <QSystemTrayIcon>
#include "sharing/linux/qml_tray_app/nearby_tray_controller.h"
#include "nearby_tray_controller.h"
namespace {
QIcon BuildTintedSymbolicIcon(const QString& source, const QColor& color) {
QIcon source_icon(source);
if (source_icon.isNull()) {
return QIcon();
}
QIcon tinted_icon;
for (int size : {16, 18, 20, 22, 24, 32}) {
QPixmap pixmap = source_icon.pixmap(size, size);
if (pixmap.isNull()) {
continue;
}
QPixmap tinted(pixmap.size());
tinted.fill(Qt::transparent);
QPainter painter(&tinted);
painter.drawPixmap(0, 0, pixmap);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.fillRect(tinted.rect(), color);
painter.end();
tinted_icon.addPixmap(tinted);
}
return tinted_icon;
}
} // namespace
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
@@ -27,23 +62,30 @@ int main(int argc, char* argv[]) {
return 1;
}
// Try custom symbolic icon first (will auto-theme if available)
QIcon tray_icon = QIcon(QStringLiteral(":/icons/tray_icon-symbolic.svg"));
// Fallback to system themed icon
if (tray_icon.isNull()) {
tray_icon = QIcon::fromTheme(QStringLiteral("network-wireless-symbolic"));
}
// Fallback to custom PNG icon
if (tray_icon.isNull()) {
tray_icon = QIcon(QStringLiteral(":/icons/tray_icon.png"));
}
// Final fallback
if (tray_icon.isNull()) {
tray_icon = app.windowIcon();
}
const auto resolve_tray_icon = [&app]() {
// Render the bundled symbolic icon with palette text color so it adapts to light/dark themes.
const QColor symbolic_color = app.palette().color(QPalette::WindowText);
QIcon tray_icon = BuildTintedSymbolicIcon(
QStringLiteral(":/icons/tray_icon-symbolic.svg"), symbolic_color);
// Fallback to system themed icon.
if (tray_icon.isNull()) {
tray_icon = QIcon::fromTheme(QStringLiteral("network-wireless-symbolic"));
}
// Fallback to custom PNG icon.
if (tray_icon.isNull()) {
tray_icon = QIcon(QStringLiteral(":/icons/tray_icon.png"));
}
// Final fallback.
if (tray_icon.isNull()) {
tray_icon = app.windowIcon();
}
return tray_icon;
};
QIcon tray_icon = resolve_tray_icon();
QSystemTrayIcon tray(tray_icon);
tray.setToolTip(QStringLiteral("Nearby QML Tray"));
@@ -89,6 +131,10 @@ int main(int argc, char* argv[]) {
QObject::connect(&app, &QCoreApplication::aboutToQuit, &controller,
[&controller]() { controller.stop(); });
QObject::connect(app.styleHints(), &QStyleHints::colorSchemeChanged, &tray,
[&tray, &resolve_tray_icon](Qt::ColorScheme) {
tray.setIcon(resolve_tray_icon());
});
tray.setContextMenu(&tray_menu);
tray.show();
@@ -1,4 +1,4 @@
#include "sharing/linux/qml_tray_app/nearby_tray_controller.h"
#include "nearby_tray_controller.h"
#include <QDateTime>
#include <QDir>
@@ -13,7 +13,7 @@
namespace {
using NearbyConnectionsQtFacade =
nearby::sharing::linux::NearbyConnectionsQtFacade;
nearby::sharing::NearbyConnectionsQtFacade;
QString NormalizeMediumsMode(QString mode) {
mode = mode.trimmed().toLower();
@@ -15,6 +15,8 @@
#include "sharing/linux/nearby_connections_qt_facade.h"
using NearbyConnectionsQtFacade = nearby::sharing::NearbyConnectionsQtFacade;
class NearbyTrayController : public QObject {
Q_OBJECT
Q_PROPERTY(QString mode READ mode WRITE setMode NOTIFY modeChanged)
@@ -132,21 +134,14 @@ class NearbyTrayController : public QObject {
std::vector<uint8_t> BuildEndpointInfo() const;
nearby::sharing::linux::NearbyConnectionsQtFacade::ConnectionListener
BuildConnectionListener();
nearby::sharing::linux::NearbyConnectionsQtFacade::DiscoveryListener
BuildDiscoveryListener();
nearby::sharing::linux::NearbyConnectionsQtFacade::PayloadListener
BuildPayloadListener();
NearbyConnectionsQtFacade::ConnectionListener BuildConnectionListener();
NearbyConnectionsQtFacade::DiscoveryListener BuildDiscoveryListener();
NearbyConnectionsQtFacade::PayloadListener BuildPayloadListener();
nearby::sharing::linux::NearbyConnectionsQtFacade::AdvertisingOptions
BuildAdvertisingOptions() const;
nearby::sharing::linux::NearbyConnectionsQtFacade::DiscoveryOptions
BuildDiscoveryOptions() const;
nearby::sharing::linux::NearbyConnectionsQtFacade::ConnectionOptions
BuildConnectionOptions() const;
nearby::sharing::linux::NearbyConnectionsQtFacade::MediumSelection
BuildMediumSelection() const;
NearbyConnectionsQtFacade::AdvertisingOptions BuildAdvertisingOptions() const;
NearbyConnectionsQtFacade::DiscoveryOptions BuildDiscoveryOptions() const;
NearbyConnectionsQtFacade::ConnectionOptions BuildConnectionOptions() const;
NearbyConnectionsQtFacade::MediumSelection BuildMediumSelection() const;
void AddDiscoveredDevice(const QString& endpoint_id);
void RemoveDiscoveredDevice(const QString& endpoint_id);
@@ -171,14 +166,12 @@ class NearbyTrayController : public QObject {
void LogLine(const QString& line);
void ReopenLogFile();
static QString StatusToString(
nearby::sharing::linux::NearbyConnectionsQtFacade::Status status);
static QString StatusToString(NearbyConnectionsQtFacade::Status status);
static QString PayloadStatusToString(
nearby::sharing::linux::NearbyConnectionsQtFacade::PayloadStatus status);
static QString MediumToString(
nearby::sharing::linux::NearbyConnectionsQtFacade::Medium medium);
NearbyConnectionsQtFacade::PayloadStatus status);
static QString MediumToString(NearbyConnectionsQtFacade::Medium medium);
nearby::sharing::linux::NearbyConnectionsQtFacade service_;
NearbyConnectionsQtFacade service_;
QString mode_ = QStringLiteral("Receive");
QString device_name_ = QStringLiteral("NearbyQt");
@@ -0,0 +1,9 @@
<RCC>
<qresource prefix="/qml">
<file>FileShareTray.qml</file>
</qresource>
<qresource prefix="/icons">
<file>tray_icon.png</file>
<file alias="tray_icon-symbolic.svg">tray_icon-symbolic.svg</file>
</qresource>
</RCC>
@@ -0,0 +1,14 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" />
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" />
</svg>

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB