diff --git a/sharing/linux/qml_tray_app/CMakeLists.txt b/sharing/linux/qml_tray_app/CMakeLists.txt deleted file mode 100644 index 2f87b894..00000000 --- a/sharing/linux/qml_tray_app/CMakeLists.txt +++ /dev/null @@ -1,60 +0,0 @@ -cmake_minimum_required(VERSION 3.21) -project(nearby_qml_tray_app LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) - -find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Qml Quick QuickControls2 DBus) - -# 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_SHARING_LIB nearby_sharing_api_shared - HINTS "${NEARBY_PREFIX}/lib" REQUIRED) - -find_path(NEARBY_SHARING_INCLUDE sharing/linux/nearby_sharing_api.h - HINTS "${NEARBY_PREFIX}/include" REQUIRED) - -find_library(QRENCODE_LIB - NAMES qrencode libqrencode.so.4 - HINTS /usr/lib64 /usr/lib /usr/local/lib - REQUIRED) - -qt_add_executable(nearby_qml_file_tray_app - file_share_tray_main.cpp - file_share_tray_controller.cc - file_share_tray_controller.h - file_share_state.cc - file_share_state.h - share_target_model.h - transfer_model.h - string_utils.cc - string_utils.h - status_mapper.cc - status_mapper.h - qr_code_generator.cc - qr_code_generator.h - notification_manager.cpp - notification_manager.h - third_party/libqrencode/qrencode_compat.h - resources_file_share.qrc -) - -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 Qt6::DBus - "${QRENCODE_LIB}" - "${NEARBY_SHARING_LIB}" -) - -set_target_properties(nearby_qml_file_tray_app PROPERTIES - INSTALL_RPATH "$ORIGIN/../lib" -) - -include(GNUInstallDirs) - -install(TARGETS nearby_qml_file_tray_app RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") -install(FILES "${NEARBY_SHARING_LIB}" DESTINATION "${CMAKE_INSTALL_LIBDIR}") diff --git a/sharing/linux/qml_tray_app/FileShareTray.qml b/sharing/linux/qml_tray_app/FileShareTray.qml deleted file mode 100644 index 9ef509d9..00000000 --- a/sharing/linux/qml_tray_app/FileShareTray.qml +++ /dev/null @@ -1,110 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import "components" - -ApplicationWindow { - id: root - width: 980 - height: 760 - minimumWidth: 820 - minimumHeight: 620 - visible: true - title: "Quick Share" - - background: Rectangle { color: "#f0fdf4" } - - onClosing: function(close) { - close.accepted = false - root.hide() - fileShareController.hideToTray() - } - - SettingsPanel { - id: settingsPanel - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - AppHeader { - onSettingsRequested: settingsPanel.open() - } - - // ── Body ───────────────────────────────────────────────────────── - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: true - spacing: 0 - - SideBar {} - - // ── Main content (white panel) ──────────────────────────────── - Rectangle { - id: mainContent - Layout.fillWidth: true - Layout.fillHeight: true - color: "#ffffff" - topLeftRadius: 48 - clip: true - - readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0 - - // ── Idle: animated blob ─────────────────────────────────── - AnimatedBlob { visible: !mainContent.isSendMode } - - // ── Non-idle: scrollable device + transfer cards ────────── - Flickable { - id: mainFlickable - anchors.fill: parent - clip: true - visible: mainContent.isSendMode - contentWidth: width - contentHeight: mainCol.implicitHeight + 96 - ScrollBar.vertical: ScrollBar {} - - ColumnLayout { - id: mainCol - x: 48 - y: 48 - width: mainFlickable.width - 96 - spacing: 16 - - SendUrlPanel { - Layout.alignment: Qt.AlignHCenter - width: Math.max(240, Math.min(mainCol.width, 420)) - } - - - Label { - text: "Nearby devices" - font.pixelSize: 20 - font.weight: Font.Medium - color: "#111827" - } - - Item { - Layout.fillWidth: true - implicitHeight: deviceFlow.childrenRect.height - visible: fileShareController.discoveredTargets.length > 0 - - Flow { - id: deviceFlow - width: parent.width - spacing: 20 - - Repeater { - model: fileShareController.discoveredTargets - delegate: DeviceCard {} - } - } - } - - } - } - } - } - } -} diff --git a/sharing/linux/qml_tray_app/README.md b/sharing/linux/qml_tray_app/README.md deleted file mode 100644 index df4b5ce1..00000000 --- a/sharing/linux/qml_tray_app/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# Nearby File Share Tray App - -This folder contains the Qt/QML **FileShareTray** application — a system tray -app for file sharing via Nearby Sharing, wired to: - -- `nearby::sharing::linux::NearbySharingApi` -- Send mode (discover nearby share targets + send file) -- Receive mode (incoming requests + accept/reject) -- Transfer status list (progress + transfer status) -- Persistent tray behavior (window close hides app to tray) -- Process log redirection to file (`stdout`/`stderr`) - -## Files - -- `file_share_tray_main.cpp`: Qt app bootstrap + system tray behavior. -- `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. - -## Runtime behavior - -- Close button does **not** terminate the process; it hides to tray. -- Use tray icon menu to show/hide/quit. -- Mode `Send`: - - Starts discovery. - - Shows discovered share targets. - - Sends the selected file to a chosen target. -- Mode `Receive`: - - Starts advertising. - - Shows pending incoming transfer requests. - - Lets you accept/reject incoming requests. -- Transfers are shown with target, direction, status, and progress. -- `stdout` and `stderr` are redirected at startup to the configured `logPath` - setting. -- Default log path is `/tmp/nearby_qml_file_tray.log` when `logPath` is unset. -- If `logPath` is changed from Settings, restart the app to apply redirection. - -## Building - -This CMake app links against the installed Nearby shared library and header: - -- `libnearby_sharing_api_shared.so` -- `sharing/linux/nearby_sharing_api.h` - -Install them first (repo root): - -```bash -./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_PREFIX=/usr/local -cmake --build build -j -``` - -## Bundle `libnearby_sharing_api_shared.so` with the app - -From `sharing/linux/qml_tray_app`: - -```bash -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$PWD/dist" -cmake --build build -j -cmake --install build -``` - -Bundle output: - -- `dist/bin/nearby_qml_file_tray_app` -- `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. - -## Build a distributable `.zip` (includes runtime dependencies) - -From `sharing/linux/qml_tray_app`: - -```bash -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -cmake --build build -j -cpack --config build/CPackConfig.cmake -G ZIP -``` - -Output: - -- `build/nearby_qml_tray_app-Linux-x86_64.zip` - -This zip is created from the CMake install tree and includes: - -- `nearby_qml_file_tray_app` -- `libnearby_sharing_api_shared.so` -- Qt runtime libs/plugins/QML imports discovered by Qt deploy tooling diff --git a/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake b/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake deleted file mode 100644 index 693c780c..00000000 --- a/sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake +++ /dev/null @@ -1,80 +0,0 @@ -set(_output_so "${OUTPUT_SO}") -set(_bazel_executable "${BAZEL_EXECUTABLE}") -set(_bazel_target "${BAZEL_TARGET}") -set(_bazel_build_options "${BAZEL_BUILD_OPTIONS}") -set(_repo_root "${REPO_ROOT}") -set(_rebuild_inputs "${REBUILD_INPUTS}") - -# Values passed via -D can arrive wrapped in literal quotes when emitted from -# a custom command. Strip one outer quote pair if present. -foreach(_var IN ITEMS _output_so _bazel_executable _bazel_target _bazel_build_options _repo_root) - string(REGEX REPLACE "^\"(.*)\"$" "\\1" ${_var} "${${_var}}") -endforeach() - -set(_needs_rebuild TRUE) -if(EXISTS "${_output_so}") - # Rebuild if any tracked input file is newer than the output. - set(_needs_rebuild FALSE) - foreach(_input IN LISTS _rebuild_inputs) - if(EXISTS "${_input}") - if("${_input}" IS_NEWER_THAN "${_output_so}") - set(_needs_rebuild TRUE) - message(STATUS "Input changed since last Bazel build: ${_input}") - break() - endif() - endif() - endforeach() - - if(NOT _needs_rebuild) - # 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) - execute_process( - COMMAND "${_nm_program}" -D -C "${_output_so}" - RESULT_VARIABLE _nm_result - OUTPUT_VARIABLE _nm_output - ERROR_QUIET - ) - if(_nm_result EQUAL 0) - string(FIND "${_nm_output}" "nearby::sharing::NearbySharingApi::NearbySharingApi()" _api_ctor_idx) - string(FIND "${_nm_output}" " U nearby::api::ImplementationPlatform::CreateScheduledExecutor()" _undef_platform_idx) - string(FIND "${_nm_output}" " U nearby::SystemClock::ElapsedRealtime()" _undef_clock_idx) - string(FIND "${_nm_output}" " U nearby::Crypto::Sha256(" _undef_crypto_idx) - 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) - set(_needs_rebuild FALSE) - else() - set(_needs_rebuild TRUE) - endif() - else() - set(_needs_rebuild TRUE) - endif() - endif() - endif() - - if(NOT _needs_rebuild) - message(STATUS "Using existing Bazel library: ${_output_so}") - return() - endif() - message(STATUS "Existing Bazel library is stale/incompatible, rebuilding: ${_output_so}") -endif() - -separate_arguments(_bazel_build_options_list NATIVE_COMMAND "${_bazel_build_options}") - -message(STATUS "Bazel library not found, building ${_bazel_target}") -execute_process( - COMMAND "${_bazel_executable}" build ${_bazel_build_options_list} "${_bazel_target}" - WORKING_DIRECTORY "${_repo_root}" - RESULT_VARIABLE BAZEL_BUILD_RESULT -) - -if(NOT BAZEL_BUILD_RESULT EQUAL 0) - message(FATAL_ERROR "Bazel build failed for ${_bazel_target} (exit ${BAZEL_BUILD_RESULT})") -endif() - -if(NOT EXISTS "${_output_so}") - message(FATAL_ERROR "Bazel build completed but expected output is missing: ${_output_so}") -endif() diff --git a/sharing/linux/qml_tray_app/cmake/BuildNearbyStaticArchive.cmake b/sharing/linux/qml_tray_app/cmake/BuildNearbyStaticArchive.cmake deleted file mode 100644 index 2a0bb575..00000000 --- a/sharing/linux/qml_tray_app/cmake/BuildNearbyStaticArchive.cmake +++ /dev/null @@ -1,41 +0,0 @@ -set(_output_static_lib "${OUTPUT_STATIC_LIB}") -set(_output_linkopts_file "${OUTPUT_LINKOPTS_FILE}") -set(_bazel_executable "${BAZEL_EXECUTABLE}") -set(_bazel_target "${BAZEL_TARGET}") -set(_bazel_build_options "${BAZEL_BUILD_OPTIONS}") -set(_bazel_env_cc "${BAZEL_ENV_CC}") -set(_bazel_env_cxx "${BAZEL_ENV_CXX}") -set(_repo_root "${REPO_ROOT}") - -foreach(_var IN ITEMS _output_static_lib _output_linkopts_file _bazel_executable _bazel_target _bazel_build_options _bazel_env_cc _bazel_env_cxx _repo_root) - string(REGEX REPLACE "^\"(.*)\"$" "\\1" ${_var} "${${_var}}") -endforeach() - -if(EXISTS "${_output_static_lib}" AND EXISTS "${_output_linkopts_file}") - message(STATUS "Using existing Bazel static archive: ${_output_static_lib}") - return() -endif() - -separate_arguments(_bazel_build_options_list NATIVE_COMMAND "${_bazel_build_options}") - -message(STATUS "Building Bazel static archive ${_bazel_target}") -execute_process( - COMMAND "${CMAKE_COMMAND}" -E env - "CC=${_bazel_env_cc}" - "CXX=${_bazel_env_cxx}" - "${_bazel_executable}" build ${_bazel_build_options_list} "${_bazel_target}" - WORKING_DIRECTORY "${_repo_root}" - RESULT_VARIABLE _bazel_build_result -) - -if(NOT _bazel_build_result EQUAL 0) - message(FATAL_ERROR "Bazel build failed for ${_bazel_target} (exit ${_bazel_build_result})") -endif() - -if(NOT EXISTS "${_output_static_lib}") - message(FATAL_ERROR "Expected static archive is missing: ${_output_static_lib}") -endif() - -if(NOT EXISTS "${_output_linkopts_file}") - message(FATAL_ERROR "Expected Bazel link options file is missing: ${_output_linkopts_file}") -endif() diff --git a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml b/sharing/linux/qml_tray_app/components/AnimatedBlob.qml deleted file mode 100644 index 31f67efe..00000000 --- a/sharing/linux/qml_tray_app/components/AnimatedBlob.qml +++ /dev/null @@ -1,416 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Effects - -Item { - anchors.fill: parent - - readonly property color textPrimary: "#111827" - readonly property color textMuted: "#6b7280" - readonly property color textSoft: "#4b5563" - readonly property color cardSurface: "#ffffff" - readonly property color cardBorder: "#d1fae5" - readonly property bool isSendMode: fileShareController.pendingSendFilePath.length > 0 - readonly property var incomingTransfer: findIncomingTransfer() - readonly property var incomingTarget: findTargetForTransfer(incomingTransfer) - readonly property bool hasIncomingTransfer: incomingTransfer !== null - && incomingTarget !== null - readonly property bool isReceivingActive: hasIncomingTransfer - && String(incomingTransfer.status || "") !== "Complete" - readonly property real receivingIntensity: { - if (!isReceivingActive) - return 0 - var numeric = Number(incomingTransfer.progress) - if (!isFinite(numeric) || numeric < 0) - numeric = 0 - return Math.max(0.35, Math.min(1.0, numeric)) - } - property real blobChaos: receivingIntensity - property string dismissedTransferKey: "" - - Behavior on blobChaos { - NumberAnimation { - duration: 220 - easing.type: Easing.OutCubic - } - } - - function isIncomingTransferActive(status) { - return status === "InProgress" - || status === "Queued" - || status === "Connecting" - || status === "AwaitingLocalConfirmation" - || status === "AwaitingRemoteAcceptance" - || status === "Complete" - } - - function transferKey(transfer) { - if (!transfer) - return "" - return String(transfer.targetId || "") - + "|" + String(transfer.status || "") - + "|" + String(transfer.filePath || "") - + "|" + String(transfer.fileName || "") - } - - function findIncomingTransfer() { - var transfers = fileShareController.transfers - var latestCompleted = null - for (var i = transfers.length - 1; i >= 0; --i) { - var entry = transfers[i] - if (!entry) - continue - if (String(entry.direction || "") !== "incoming") - continue - var status = String(entry.status || "") - if (!isIncomingTransferActive(status)) - continue - if (status === "Complete") { - if (latestCompleted === null) - latestCompleted = entry - continue - } - return entry - } - return latestCompleted - } - - function findTargetForTransfer(transfer) { - if (!transfer) - return null - var targets = fileShareController.discoveredTargets - for (var i = 0; i < targets.length; ++i) { - var entry = targets[i] - if (entry && entry.id === transfer.targetId) - return entry - } - return { - id: transfer.targetId, - name: String(transfer.targetName || "Incoming device"), - isIncoming: true - } - } - - function incomingHeadline(status) { - if (status === "Complete") - return "Received" - if (status === "AwaitingLocalConfirmation") - return "Incoming transfer" - if (status === "AwaitingRemoteAcceptance") - return "Preparing transfer" - if (status === "Connecting") - return "Connecting" - return "Receiving" - } - - // The card only becomes actionable once the file has landed on disk and we - // have a local path to open. - function incomingClickReady() { - return hasIncomingTransfer - && String(incomingTransfer.status || "") === "Complete" - && String(incomingTransfer.filePath || "").length > 0 - } - - Label { - x: 48; y: 48 - visible: fileShareController.running - text: isSendMode - ? "Ready to send" - : "Ready to receive" - font.pixelSize: 20 - font.weight: Font.Medium - color: textPrimary - } - Canvas { - id: blobCanvas3 - width: 380; height: 380 - anchors.centerIn: parent - visible: !isSendMode - - property real t: 0 - property double lastMs: Date.now() - - Timer { - interval: 16 - running: true - repeat: true - onTriggered: { - var now = Date.now() - var dt = Math.min(0.05, Math.max(0.0, (now - blobCanvas3.lastMs) * 0.001)) - blobCanvas3.lastMs = now - blobCanvas3.t += dt * (Math.PI * 2 / Math.max(4.6, 8.0 - blobChaos * 2.6)) - blobCanvas3.requestPaint() - } - } - - onPaint: { - var ctx = getContext("2d") - ctx.clearRect(0, 0, width, height) - - var chaos = blobChaos - var cx = width / 2 + Math.sin(t * 0.33) * chaos * 10 - var cy = height / 2 + Math.cos(t * 0.27) * chaos * 8 - var n = 10 - var pts = [] - - for (var i = 0; i < n; i++) { - var a = (i / n) * Math.PI * 2 - Math.PI / 2 - var r = 150 + chaos * 22 - + Math.sin(a * 2 + t) * (11 + chaos * 14) - + Math.cos(a * 3 - t * 0.2) * (8 + chaos * 10) - + Math.sin(a * 1.5 + t * 0.7) * (6 + chaos * 8) - + Math.sin(a * 1.5 + t) * (4 + chaos * 6) - + Math.cos(a * 5 - t * 1.2) * (chaos * 9) - + Math.sin(a * 7 + t * 0.85) * (chaos * 6) - pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) - } - - var len = pts.length - ctx.beginPath() - for (var j = 0; j < len; j++) { - var p0 = pts[(j - 1 + len) % len] - var p1 = pts[j] - var p2 = pts[(j + 1) % len] - var p3 = pts[(j + 2) % len] - var cp1x = p1.x + (p2.x - p0.x) / 6 - var cp1y = p1.y + (p2.y - p0.y) / 6 - var cp2x = p2.x - (p3.x - p1.x) / 6 - var cp2y = p2.y - (p3.y - p1.y) / 6 - if (j === 0) ctx.moveTo(p1.x, p1.y) - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y) - } - ctx.closePath() - - var grad = ctx.createRadialGradient(cx - 40, cy - 40, 0, cx, cy, 150) - grad.addColorStop(0, "#e7faed") - ctx.fillStyle = grad - ctx.fill() - } - } - - Canvas { - id: blobCanvas2 - width: 380; height: 380 - anchors.centerIn: parent - visible: !isSendMode - - property real t: 0 - property double lastMs: Date.now() - - Timer { - interval: 16 - running: true - repeat: true - onTriggered: { - var now = Date.now() - var dt = Math.min(0.05, Math.max(0.0, (now - blobCanvas2.lastMs) * 0.001)) - blobCanvas2.lastMs = now - blobCanvas2.t += dt * (Math.PI * 2 / Math.max(4.9, 8.0 - blobChaos * 2.2)) - blobCanvas2.requestPaint() - } - } - - onPaint: { - var ctx = getContext("2d") - ctx.clearRect(0, 0, width, height) - - var chaos = blobChaos - var cx = width / 2 + Math.cos(t * 0.29) * chaos * 8 - var cy = height / 2 + Math.sin(t * 0.41) * chaos * 11 - var n = 10 - var pts = [] - - for (var i = 0; i < n; i++) { - var a = (i / n) * Math.PI * 2 - Math.PI / 2 - var r = 140 + chaos * 18 - + Math.sin(a * 2 + t) * (11 + chaos * 12) - + Math.cos(a * 3 - t * 0.8) * (8 + chaos * 9) - + Math.sin(a * 1.5 + t * 0.23) * (6 + chaos * 7) - + Math.sin(a * 1.5 + t * 0.85) * (4 + chaos * 6) - + Math.cos(a * 4 + t * 1.1) * (chaos * 7) - + Math.sin(a * 6 - t * 0.95) * (chaos * 5) - pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) - } - - var len = pts.length - ctx.beginPath() - for (var j = 0; j < len; j++) { - var p0 = pts[(j - 1 + len) % len] - var p1 = pts[j] - var p2 = pts[(j + 1) % len] - var p3 = pts[(j + 2) % len] - var cp1x = p1.x + (p2.x - p0.x) / 6 - var cp1y = p1.y + (p2.y - p0.y) / 6 - var cp2x = p2.x - (p3.x - p1.x) / 6 - var cp2y = p2.y - (p3.y - p1.y) / 6 - if (j === 0) ctx.moveTo(p1.x, p1.y) - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y) - } - ctx.closePath() - - var grad = ctx.createRadialGradient(cx - 40, cy - 40, 0, cx, cy, 150) - grad.addColorStop(0, "#caeada") - ctx.fillStyle = grad - ctx.fill() - } - } - - - - Canvas { - id: blobCanvas - width: 380; height: 380 - anchors.centerIn: parent - visible: !isSendMode - - property real t: 0 - property double lastMs: Date.now() - - Timer { - interval: 16 - running: true - repeat: true - onTriggered: { - var now = Date.now() - var dt = Math.min(0.05, Math.max(0.0, (now - blobCanvas.lastMs) * 0.001)) - blobCanvas.lastMs = now - blobCanvas.t += dt * (Math.PI * 2 / Math.max(4.4, 8.0 - blobChaos * 3.0)) - blobCanvas.requestPaint() - } - } - - onPaint: { - var ctx = getContext("2d") - ctx.clearRect(0, 0, width, height) - - var chaos = blobChaos - var cx = width / 2 + Math.sin(t * 0.55) * chaos * 12 - var cy = height / 2 + Math.cos(t * 0.47) * chaos * 10 - var n = 10 - var pts = [] - - for (var i = 0; i < n; i++) { - var a = (i / n) * Math.PI * 2 - Math.PI / 2 - var r = 130 + chaos * 15 - + Math.sin(a * 2 + t) * (11 + chaos * 15) - + Math.cos(a * 3 - t * 0.6) * (8 + chaos * 12) - + Math.sin(a * 1.5 + t * 0.35) * (6 + chaos * 9) - + Math.sin(a + t * 0.75) * (3 + chaos * 6) - + Math.cos(a * 5 - t * 1.35) * (chaos * 10) - + Math.sin(a * 8 + t * 0.92) * (chaos * 6) - pts.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r }) - } - - var len = pts.length - ctx.beginPath() - for (var j = 0; j < len; j++) { - var p0 = pts[(j - 1 + len) % len] - var p1 = pts[j] - var p2 = pts[(j + 1) % len] - var p3 = pts[(j + 2) % len] - var cp1x = p1.x + (p2.x - p0.x) / 6 - var cp1y = p1.y + (p2.y - p0.y) / 6 - var cp2x = p2.x - (p3.x - p1.x) / 6 - var cp2y = p2.y - (p3.y - p1.y) / 6 - if (j === 0) ctx.moveTo(p1.x, p1.y) - ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y) - } - ctx.closePath() - - var grad = ctx.createRadialGradient(cx - 40, cy - 40, 0, cx, cy, 150) - grad.addColorStop(0, "#acdac4") - ctx.fillStyle = grad - ctx.fill() - } - } - - Rectangle { - id: incomingTransferCard - anchors.centerIn: parent - visible: !isSendMode && hasIncomingTransfer - && transferKey(incomingTransfer) !== dismissedTransferKey - width: 200 - height: 210 - radius: 34 - color: cardSurface - border.color: cardBorder - border.width: 1 - z: 10 - - Rectangle { - anchors.fill: parent - anchors.margins: 10 - radius: parent.radius - 10 - color: "#ffffff" - opacity: 0.84 - } - - Column { - anchors.fill: parent - anchors.margins: 24 - spacing: 10 - - Label { - anchors.horizontalCenter: parent.horizontalCenter - text: incomingHeadline(String(incomingTransfer.status || "")) - font.pixelSize: 12 - font.weight: Font.DemiBold - color: "#059669" - horizontalAlignment: Text.AlignHCenter - } - - DeviceCard { - anchors.horizontalCenter: parent.horizontalCenter - modelData: incomingTarget - } - - Label { - width: parent.width - visible: String(incomingTransfer.fileName || "").length > 0 - text: String(incomingTransfer.fileName || "") - font.pixelSize: 13 - font.weight: Font.StyleItalic - color: textPrimary - wrapMode: Text.Wrap - maximumLineCount: 2 - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - } - } - - MouseArea { - anchors.fill: parent - enabled: incomingClickReady() - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - onClicked: { - dismissedTransferKey = transferKey(incomingTransfer) - fileShareController.openFileLocation(String(incomingTransfer.filePath || "")) - } - } - } - - onIncomingTransferChanged: { - if (!incomingTransfer) { - dismissedTransferKey = "" - return - } - - if (transferKey(incomingTransfer) !== dismissedTransferKey) - return - - if (String(incomingTransfer.status || "") !== "Complete") - dismissedTransferKey = "" - } - - - Label { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 48 - visible: !isSendMode - text: fileShareController.statusMessage - font.pixelSize: 13 - color: textMuted - } -} diff --git a/sharing/linux/qml_tray_app/components/AppHeader.qml b/sharing/linux/qml_tray_app/components/AppHeader.qml deleted file mode 100644 index cbf4f338..00000000 --- a/sharing/linux/qml_tray_app/components/AppHeader.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Item { - Layout.fillWidth: true - height: 80 - - signal settingsRequested() - - readonly property color textPrimary: "#111827" - readonly property color textMuted: "#6b7280" - readonly property color accent: "#16a34a" - - RowLayout { - anchors.fill: parent - anchors.leftMargin: 24 - anchors.rightMargin: 24 - - ColumnLayout { - spacing: 2 - Label { - text: "Device name" - font.pixelSize: 12 - color: textMuted - } - Label { - text: fileShareController.deviceName - font.pixelSize: 22 - font.weight: Font.Medium - color: textPrimary - } - } - - Item { Layout.fillWidth: true } - - - - Rectangle { - width: 40 - height: 40 - radius: 12 - color: settingsBtn.containsMouse ? "#dcfce7" : "transparent" - border.color: settingsBtn.containsMouse ? "#86efac" : "transparent" - - Label { - anchors.centerIn: parent - text: "⚙" - font.pixelSize: 18 - color: settingsBtn.containsMouse ? accent : textMuted - } - - MouseArea { - id: settingsBtn - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: settingsRequested() - } - } - } -} diff --git a/sharing/linux/qml_tray_app/components/DeviceCard.qml b/sharing/linux/qml_tray_app/components/DeviceCard.qml deleted file mode 100644 index 90dd18e9..00000000 --- a/sharing/linux/qml_tray_app/components/DeviceCard.qml +++ /dev/null @@ -1,224 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Item { - id: root - required property var modelData - - width: 116 - height: deviceColumn.implicitHeight - - readonly property color surface: "#ffffff" - readonly property color textPrimary: "#111827" - readonly property color textMuted: "#6b7280" - readonly property color avatarFill: "#dcfce7" - readonly property color avatarBorder: "#bbf7d0" - readonly property color ringBase: "#d1fae5" - readonly property color ringActive: "#10b981" - readonly property color ringComplete: "#16a34a" - readonly property color ringFailed: "#ef4444" - readonly property bool canSend: fileShareController.mode === "Send" - && fileShareController.pendingSendFilePath.length > 0 - - readonly property string targetName: modelData.name && modelData.name.length > 0 - ? modelData.name : "Unknown device" - readonly property var transferData: transferForTarget() - readonly property string transferStatus: transferData ? String(transferData.status || "") : "" - readonly property bool hasTransfer: transferData !== null - readonly property bool isTransferActive: transferStatus === "InProgress" - || transferStatus === "Queued" - || transferStatus === "Connecting" - || transferStatus === "AwaitingLocalConfirmation" - || transferStatus === "AwaitingRemoteAcceptance" - readonly property bool isTransferComplete: transferStatus === "Complete" - readonly property bool isTransferFailed: hasTransfer && !isTransferActive && !isTransferComplete - readonly property bool isConnecting: transferStatus === "Connecting" - property bool showCompletionTick: false - property string previousTransferStatus: "" - readonly property real transferProgress: { - if (!hasTransfer) - return 0 - if (isTransferComplete || isTransferFailed) - return 1 - var numeric = Number(transferData.progress) - if (!isFinite(numeric) || numeric < 0) - numeric = 0 - if (isTransferActive && numeric === 0) - return 0.08 - return Math.max(0, Math.min(1, numeric)) - } - readonly property color ringColor: isTransferComplete ? ringComplete - : isTransferFailed ? ringFailed - : ringActive - - function initialLetter(label) { - if (!label || label.length === 0) return "?" - return label.charAt(0).toUpperCase() - } - - function transferForTarget() { - var transfers = fileShareController.transfers - for (var i = 0; i < transfers.length; ++i) { - var entry = transfers[i] - if (entry && entry.targetId === modelData.id) - return entry - } - return null - } - - Column { - id: deviceColumn - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - spacing: 10 - - Rectangle { - anchors.horizontalCenter: parent.horizontalCenter - width: 84 - height: 84 - radius: 42 - color: surface - opacity: canSend ? 1.0 : 0.5 - - Canvas { - id: progressRing - anchors.fill: parent - antialiasing: true - transformOrigin: Item.Center - onWidthChanged: requestPaint() - onHeightChanged: requestPaint() - - onPaint: { - var ctx = getContext("2d") - ctx.clearRect(0, 0, width, height) - - var lineWidth = 5 - var radius = (Math.min(width, height) - lineWidth) / 2 - var center = width / 2 - - ctx.lineWidth = lineWidth - ctx.lineCap = "round" - - if (!root.hasTransfer) - return - - ctx.beginPath() - ctx.strokeStyle = root.ringColor - ctx.arc(center, center, radius, -Math.PI / 2, - -Math.PI / 2 + Math.PI * 2 * root.transferProgress, false) - ctx.stroke() - } - } - - NumberAnimation { - id: connectingSpin - target: progressRing - property: "rotation" - from: 0 - to: 360 - duration: 1100 - easing.type: Easing.Linear - loops: Animation.Infinite - running: root.isConnecting - } - - Rectangle { - anchors.fill: parent - anchors.margins: 9 - radius: width / 2 - color: avatarFill - - Label { - anchors.centerIn: parent - text: initialLetter(targetName) - font.pixelSize: 28 - font.weight: Font.DemiBold - color: textPrimary - } - } - - Rectangle { - anchors.fill: parent - anchors.margins: 9 - radius: width / 2 - color: "#16a34a" - opacity: showCompletionTick ? 0.94 : 0.0 - visible: opacity > 0 - - Behavior on opacity { - NumberAnimation { duration: 160; easing.type: Easing.OutCubic } - } - - Canvas { - anchors.centerIn: parent - width: 28 - height: 28 - - onPaint: { - var ctx = getContext("2d") - ctx.clearRect(0, 0, width, height) - ctx.strokeStyle = "#ffffff" - ctx.lineWidth = 4 - ctx.lineCap = "round" - ctx.lineJoin = "round" - ctx.beginPath() - ctx.moveTo(width * 0.18, height * 0.56) - ctx.lineTo(width * 0.42, height * 0.8) - ctx.lineTo(width * 0.84, height * 0.24) - ctx.stroke() - } - } - } - - MouseArea { - anchors.fill: parent - cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor - enabled: canSend - onClicked: fileShareController.sendPendingFileToTarget(modelData.id) - } - } - - Label { - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - horizontalAlignment: Text.AlignHCenter - text: targetName - font.pixelSize: 13 - font.weight: Font.Bold - elide: Text.ElideRight - maximumLineCount: 2 - wrapMode: Text.Wrap - color: textPrimary - } - } - - Timer { - id: completionTickTimer - interval: 1200 - repeat: false - onTriggered: root.showCompletionTick = false - } - - onTransferDataChanged: progressRing.requestPaint() - onTransferProgressChanged: progressRing.requestPaint() - onRingColorChanged: progressRing.requestPaint() - onIsConnectingChanged: { - if (!isConnecting) - progressRing.rotation = 0 - } - onTransferStatusChanged: { - if (transferStatus === "Complete" && previousTransferStatus.length > 0 - && previousTransferStatus !== "Complete") { - showCompletionTick = true - completionTickTimer.restart() - } else if (transferStatus !== "Complete") { - showCompletionTick = false - completionTickTimer.stop() - } - - previousTransferStatus = transferStatus - } - - Component.onCompleted: previousTransferStatus = transferStatus -} diff --git a/sharing/linux/qml_tray_app/components/SendUrlPanel.qml b/sharing/linux/qml_tray_app/components/SendUrlPanel.qml deleted file mode 100644 index b730f737..00000000 --- a/sharing/linux/qml_tray_app/components/SendUrlPanel.qml +++ /dev/null @@ -1,131 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -ColumnLayout { - id: root - - property string urlText: fileShareController.qrCodeUrl - property var qrRows: fileShareController.qrCodeRows - property int qrSize: fileShareController.qrCodeSize - property string fileName: fileShareController.pendingSendFileName - readonly property color panelTint: "#ecfdf3" - readonly property color panelBorder: "#a7f3d0" - readonly property color qrPaper: "#fffdf7" - readonly property color qrInk: "#14532d" - readonly property color accentSoft: "#d1fae5" - readonly property color accentStrong: "#34d399" - readonly property bool compact: width < 360 - readonly property real qrFrameSize: 360 - readonly property real qrInnerSize: qrFrameSize - (compact ? 34 : 42) - - spacing: compact ? 14 : 18 - implicitWidth: 420 - - Label { - Layout.alignment: Qt.AlignHCenter - text: "Scan to connect" - font.pixelSize: compact ? 16 : 18 - font.weight: Font.DemiBold - color: "#111827" - } - - Rectangle { - Layout.alignment: Qt.AlignHCenter - width: root.qrFrameSize - height: width - radius: compact ? 24 : 32 - gradient: Gradient { - GradientStop { position: 0.0; color: "#f7fff9" } - GradientStop { position: 1.0; color: root.panelTint } - } - border.color: root.panelBorder - border.width: 1 - - Rectangle { - width: parent.width * 0.52 - height: width - radius: width / 2 - x: parent.width - width * 0.72 - y: -width * 0.22 - color: "#ffffff" - opacity: 0.35 - } - - - Rectangle { - anchors.centerIn: parent - width: root.qrInnerSize - height: width - radius: compact ? 18 : 24 - color: root.qrPaper - border.color: "#dcfce7" - border.width: 1 - - Canvas { - id: qrCanvas - anchors.fill: parent - anchors.margins: compact ? 16 : 22 - antialiasing: true - onWidthChanged: requestPaint() - onHeightChanged: requestPaint() - - onPaint: { - var ctx = getContext("2d") - ctx.clearRect(0, 0, width, height) - ctx.fillStyle = root.qrPaper - ctx.fillRect(0, 0, width, height) - - if (root.qrSize <= 0 || !root.qrRows || root.qrRows.length !== root.qrSize) - return - - var quietZone = 4 - var totalModules = root.qrSize + quietZone * 2 - - var moduleSize = Math.min(width, height) / totalModules - var drawSize = moduleSize * totalModules - var offsetX = (width - drawSize) / 2 - var offsetY = (height - drawSize) / 2 - var dotInset = moduleSize * 0.18 - var dotSize = Math.max(1, moduleSize - dotInset * 2) - var dotRadius = dotSize/1.2 - - ctx.fillStyle = root.qrInk - for (var row = 0; row < root.qrSize; ++row) { - var rowData = root.qrRows[row] - for (var col = 0; col < root.qrSize; ++col) { - if (rowData.charAt(col) !== "1") - continue - - var dotX = offsetX + (col + quietZone) * moduleSize + dotInset - var dotY = offsetY + (row + quietZone) * moduleSize + dotInset - - ctx.beginPath() - ctx.arc(dotX + dotRadius, dotY + dotRadius, dotRadius, 0, Math.PI * 2) - ctx.fill() - } - } - } - } - - Label { - anchors.centerIn: parent - visible: root.qrSize <= 0 - text: "Preparing QR code..." - font.pixelSize: compact ? 12 : 13 - color: "#6b7280" - } - } - } - - Label { - Layout.alignment: Qt.AlignHCenter - text: root.fileName.length > 0 ? "Sending: " + root.fileName : "" - font.pixelSize: compact ? 12 : 13 - color: "#6b7280" - visible: text.length > 0 - } - - onQrRowsChanged: qrCanvas.requestPaint() - onQrSizeChanged: qrCanvas.requestPaint() -} diff --git a/sharing/linux/qml_tray_app/components/SettingsPanel.qml b/sharing/linux/qml_tray_app/components/SettingsPanel.qml deleted file mode 100644 index 8677681a..00000000 --- a/sharing/linux/qml_tray_app/components/SettingsPanel.qml +++ /dev/null @@ -1,234 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Drawer { - id: root - edge: Qt.RightEdge - width: 380 - height: parent ? parent.height : 0 - implicitWidth: 380 - 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 borderColor: "#bbf7d0" - readonly property color textPrimary: "#111827" - readonly property color textMuted: "#6b7280" - - background: Rectangle { color: root.bg } - - ColumnLayout { - width: root.width - height: root.height - spacing: 0 - - // Header - Rectangle { - Layout.fillWidth: true - height: 64 - color: "transparent" - - RowLayout { - anchors.fill: parent - anchors.leftMargin: 20 - anchors.rightMargin: 20 - - Label { - text: "Settings" - font.pixelSize: 20 - font.weight: Font.Bold - color: root.textPrimary - } - - 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() - } - } - } - } - - Flickable { - id: flick - Layout.fillWidth: true - Layout.fillHeight: true - clip: false - contentWidth: width - contentHeight: settingsCol.height + 32 - ScrollBar.vertical: ScrollBar {} - - Column { - id: settingsCol - x: 20 - y: 20 - width: flick.width - 40 - spacing: 20 - - SectionLabel { text: "DEVICE" } - SectionCard { - width: settingsCol.width - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: 12 - spacing: 12 - - RowLayout { - 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 - } - } - } - } - - SectionLabel { text: "SHARING" } - SectionCard { - width: settingsCol.width - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: 12 - spacing: 12 - - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 12 - color: root.textMuted - text: "Nearby Sharing uses built-in transport and discovery settings." - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 - Label { - Layout.fillWidth: true - color: root.textPrimary - font.pixelSize: 13 - text: "Auto-accept incoming" - } - ThemedToggle { - checked: fileShareController.autoAcceptIncoming - onToggled: fileShareController.autoAcceptIncoming = checked - } - } - - RowLayout { - Layout.fillWidth: true - spacing: 10 - Label { - Layout.fillWidth: true - color: root.textPrimary - font.pixelSize: 13 - text: "Enable 5 GHz hotspot" - } - ThemedToggle { - checked: fileShareController.enable5GhzHotspot - onToggled: fileShareController.enable5GhzHotspot = checked - } - } - } - } - - SectionLabel { text: "LOGGING" } - SectionCard { - width: settingsCol.width - - ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: 12 - spacing: 12 - - RowLayout { - 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 - } - } - } - } - } - } - } - - component SectionLabel: Label { - font.pixelSize: 11 - font.weight: Font.DemiBold - font.letterSpacing: 0.8 - color: root.accent - } - - component SectionCard: Rectangle { - radius: 12 - color: root.surface - border.color: root.borderColor - height: (children.length > 0 ? children[0].implicitHeight : 0) + 24 - } - - 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 - } - } - - component ThemedToggle: Switch { - palette.highlight: root.accent - } -} diff --git a/sharing/linux/qml_tray_app/components/SideBar.qml b/sharing/linux/qml_tray_app/components/SideBar.qml deleted file mode 100644 index 653de941..00000000 --- a/sharing/linux/qml_tray_app/components/SideBar.qml +++ /dev/null @@ -1,153 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Item { - Layout.preferredWidth: 280 - Layout.fillHeight: true - - readonly property color surface: "#ffffff" - readonly property color cardBorder: "#bbf7d0" - readonly property color textPrimary: "#111827" - readonly property color textMuted: "#6b7280" - - ColumnLayout { - anchors.fill: parent - anchors.margins: 12 - spacing: 0 - - // Receive mode: visibility info - ColumnLayout { - visible: fileShareController.pendingSendFilePath.length === 0 - Layout.fillWidth: true - spacing: 0 - - Label { - Layout.leftMargin: 12 - Layout.topMargin: 16 - Layout.bottomMargin: 8 - text: "Visibility state" - color: textMuted - font.pixelSize: 13 - } - - Rectangle { - Layout.fillWidth: true - height: 52 - radius: 12 - color: "#e8faf0" - border.color: cardBorder - - RowLayout { - anchors.fill: parent - anchors.leftMargin: 12 - anchors.rightMargin: 12 - - Label { - Layout.fillWidth: true - text: !fileShareController.running - ? "Inactive" - : fileShareController.mode === "Send" ? "Discovering" : "Always visible" - font.weight: Font.Medium - color: textPrimary - } - } - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: 12 - Layout.topMargin: 8 - Layout.rightMargin: 12 - text: !fileShareController.running - ? "The service is not running. Start it to discover or receive files." - : fileShareController.mode === "Send" - ? "Discovering nearby devices. Select a device below to send your file." - : "Nearby devices can share files with you. You'll be notified and must approve each transfer." - wrapMode: Text.WordWrap - font.pixelSize: 12 - color: textMuted - } - } - - // Send mode: outbound file info - ColumnLayout { - visible: fileShareController.pendingSendFilePath.length > 0 - Layout.fillWidth: true - spacing: 0 - - Label { - Layout.leftMargin: 12 - Layout.topMargin: 16 - Layout.bottomMargin: 8 - text: "Sharing 1 file" - font.weight: Font.Medium - color: textPrimary - } - - Rectangle { - Layout.leftMargin: 12 - width: 72 - height: 72 - radius: 12 - color: surface - - Label { - anchors.centerIn: parent - text: "📄" - font.pixelSize: 28 - } - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: 12 - Layout.topMargin: 8 - Layout.rightMargin: 12 - text: fileShareController.pendingSendFileName - elide: Text.ElideRight - font.pixelSize: 13 - color: textMuted - } - - Label { - Layout.fillWidth: true - Layout.leftMargin: 12 - Layout.topMargin: 12 - Layout.rightMargin: 12 - text: "Make sure both devices are unlocked, close together, and have Bluetooth turned on." - wrapMode: Text.WordWrap - font.pixelSize: 12 - color: textMuted - } - } - - Item { Layout.fillHeight: true } - - // Cancel (only visible in send mode) - Rectangle { - visible: fileShareController.pendingSendFilePath.length > 0 - Layout.leftMargin: 12 - Layout.bottomMargin: 12 - height: 40 - width: cancelLbl.implicitWidth + 24 - radius: 12 - color: "#f3f4f6" - border.color: "#d1d5db" - - Label { - id: cancelLbl - anchors.centerIn: parent - text: "Cancel" - font.weight: Font.Medium - color: textPrimary - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: fileShareController.switchToReceiveMode() - } - } - } -} diff --git a/sharing/linux/qml_tray_app/file_share_state.cc b/sharing/linux/qml_tray_app/file_share_state.cc deleted file mode 100644 index 4e416652..00000000 --- a/sharing/linux/qml_tray_app/file_share_state.cc +++ /dev/null @@ -1,157 +0,0 @@ -#include "file_share_state.h" -#include "status_mapper.h" - -FileShareState::FileShareState() = default; - -void FileShareState::AddOrUpdateTarget(qlonglong id, const QString& name, - bool is_incoming) { - target_names_[id] = name; - - if (discovered_row_by_target_.contains(id)) { - const int row_index = discovered_row_by_target_.value(id); - if (row_index >= 0 && row_index < discovered_targets_.size()) { - QVariantMap target; - target[QStringLiteral("id")] = id; - target[QStringLiteral("name")] = name; - target[QStringLiteral("isIncoming")] = is_incoming; - discovered_targets_[row_index] = target; - return; - } - } - - QVariantMap target; - target[QStringLiteral("id")] = id; - target[QStringLiteral("name")] = name; - target[QStringLiteral("isIncoming")] = is_incoming; - discovered_row_by_target_[id] = discovered_targets_.size(); - discovered_targets_.append(target); -} - -void FileShareState::RemoveTarget(qlonglong id) { - if (HasActiveTransferForTarget(id)) { - AddPendingTargetRemoval(id); - return; - } - - RemovePendingTargetRemoval(id); - target_names_.remove(id); - - if (!discovered_row_by_target_.contains(id)) { - return; - } - - const int removed_index = discovered_row_by_target_.take(id); - if (removed_index < 0 || removed_index >= discovered_targets_.size()) { - return; - } - - discovered_targets_.removeAt(removed_index); - for (auto it = discovered_row_by_target_.begin(); - it != discovered_row_by_target_.end(); ++it) { - if (it.value() > removed_index) { - it.value() = it.value() - 1; - } - } -} - -QString FileShareState::GetTargetName(qlonglong id) const { - const QString name = target_names_.value(id).trimmed(); - return name.isEmpty() ? QStringLiteral("Unknown device") : name; -} - -bool FileShareState::HasTarget(qlonglong id) const { - return discovered_row_by_target_.contains(id); -} - -void FileShareState::AddOrUpdateTransfer( - qlonglong target_id, const QString& target_name, const QString& status, - double progress, qulonglong transferred_bytes, const QString& direction, - const QString& file_name, const QString& file_path) { - QVariantMap transfer{ - {QStringLiteral("targetId"), target_id}, - {QStringLiteral("targetName"), target_name}, - {QStringLiteral("status"), status}, - {QStringLiteral("progress"), progress}, - {QStringLiteral("transferredBytes"), transferred_bytes}, - {QStringLiteral("direction"), direction}, - {QStringLiteral("fileName"), file_name}, - {QStringLiteral("filePath"), file_path}, - }; - - if (transfer_row_by_target_.contains(target_id)) { - const int row_index = transfer_row_by_target_.value(target_id); - if (row_index >= 0 && row_index < transfers_.size()) { - transfers_[row_index] = transfer; - return; - } - } - - transfer_row_by_target_.insert(target_id, transfers_.size()); - transfers_.append(transfer); -} - -void FileShareState::RemoveTransfer(qlonglong target_id) { - if (!transfer_row_by_target_.contains(target_id)) { - return; - } - - const int removed_index = transfer_row_by_target_.take(target_id); - if (removed_index < 0 || removed_index >= transfers_.size()) { - return; - } - - transfers_.removeAt(removed_index); - for (auto it = transfer_row_by_target_.begin(); - it != transfer_row_by_target_.end(); ++it) { - if (it.value() > removed_index) { - it.value() = it.value() - 1; - } - } -} - -bool FileShareState::HasActiveTransferForTarget(qlonglong target_id) const { - for (const QVariant& row_value : transfers_) { - const QVariantMap row = row_value.toMap(); - if (row.value(QStringLiteral("targetId")).toLongLong() != target_id) { - continue; - } - - const QString status = row.value(QStringLiteral("status")).toString(); - if (StatusMapper::IsActiveTransferStatus(status)) { - return true; - } - } - return false; -} - -bool FileShareState::HasActiveTransfers() const { - for (const QVariant& row_value : transfers_) { - const QVariantMap row = row_value.toMap(); - const QString status = row.value(QStringLiteral("status")).toString(); - if (StatusMapper::IsActiveTransferStatus(status)) { - return true; - } - } - return false; -} - -void FileShareState::AddPendingTargetRemoval(qlonglong id) { - pending_target_removals_.insert(id); -} - -void FileShareState::RemovePendingTargetRemoval(qlonglong id) { - pending_target_removals_.remove(id); -} - -bool FileShareState::IsPendingTargetRemoval(qlonglong id) const { - return pending_target_removals_.contains(id); -} - -void FileShareState::ClearAll() { - discovered_targets_.clear(); - discovered_row_by_target_.clear(); - target_names_.clear(); - transfers_.clear(); - transfer_row_by_target_.clear(); - pending_target_removals_.clear(); -} diff --git a/sharing/linux/qml_tray_app/file_share_state.h b/sharing/linux/qml_tray_app/file_share_state.h deleted file mode 100644 index d95f190e..00000000 --- a/sharing/linux/qml_tray_app/file_share_state.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_ -#define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_ - -#include -#include -#include -#include -#include -#include -#include "share_target_model.h" -#include "transfer_model.h" - -class FileShareState { - public: - FileShareState(); - - // Getters - QString mode() const { return mode_; } - QString deviceName() const { return device_name_; } - QString statusMessage() const { return status_message_; } - bool running() const { return running_; } - bool autoAcceptIncoming() const { return auto_accept_incoming_; } - bool enable5GhzHotspot() const { return enable_5ghz_hotspot_; } - - QString pendingSendFileName() const { return pending_send_file_name_; } - QString pendingSendFilePath() const { return pending_send_file_path_; } - qlonglong pendingSendTargetId() const { return pending_send_target_id_; } - - QVariantList discoveredTargets() const { return discovered_targets_; } - QVariantList transfers() const { return transfers_; } - - QString qrCodeUrl() const { return qr_code_url_; } - QStringList qrCodeRows() const { return qr_code_rows_; } - int qrCodeSize() const { return qr_code_size_; } - - QString logPath() const { return log_path_; } - - // Setters - void SetMode(const QString& mode) { mode_ = mode; } - void SetDeviceName(const QString& name) { device_name_ = name; } - void SetStatusMessage(const QString& message) { status_message_ = message; } - void SetRunning(bool running) { running_ = running; } - void SetAutoAcceptIncoming(bool enabled) { auto_accept_incoming_ = enabled; } - void SetEnable5GhzHotspot(bool enabled) { enable_5ghz_hotspot_ = enabled; } - - void SetPendingSendFile(const QString& file_path, const QString& file_name, - qlonglong target_id) { - pending_send_file_path_ = file_path; - pending_send_file_name_ = file_name; - pending_send_target_id_ = target_id; - } - - void ClearPendingSendFile() { - pending_send_file_path_.clear(); - pending_send_file_name_.clear(); - pending_send_target_id_ = 0; - } - - void SetQrCodeData(const QString& url, const QStringList& rows, int size) { - qr_code_url_ = url; - qr_code_rows_ = rows; - qr_code_size_ = size; - } - - void SetLogPath(const QString& path) { log_path_ = path; } - - // Target management - void AddOrUpdateTarget(qlonglong id, const QString& name, bool is_incoming); - void RemoveTarget(qlonglong id); - QString GetTargetName(qlonglong id) const; - bool HasTarget(qlonglong id) const; - - // Transfer management - void AddOrUpdateTransfer(qlonglong target_id, const QString& target_name, - const QString& status, double progress, - qulonglong transferred_bytes, - const QString& direction, const QString& file_name, - const QString& file_path); - void RemoveTransfer(qlonglong target_id); - bool HasActiveTransferForTarget(qlonglong target_id) const; - bool HasActiveTransfers() const; - - // Pending target removal management - void AddPendingTargetRemoval(qlonglong id); - void RemovePendingTargetRemoval(qlonglong id); - bool IsPendingTargetRemoval(qlonglong id) const; - - void ClearAll(); - - private: - QString mode_ = QStringLiteral("Receive"); - QString device_name_ = QStringLiteral("NearbyLinux"); - QString status_message_ = QStringLiteral("Idle"); - bool running_ = false; - bool auto_accept_incoming_ = true; - bool enable_5ghz_hotspot_ = true; - - // QR Code - QString qr_code_url_; - QStringList qr_code_rows_; - int qr_code_size_ = 0; - QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log"); - - // Pending send state - QString pending_send_file_path_; - QString pending_send_file_name_; - qlonglong pending_send_target_id_ = 0; - - // Discovered targets - QVariantList discovered_targets_; - QHash discovered_row_by_target_; - QHash target_names_; - - // Transfers - QVariantList transfers_; - QHash transfer_row_by_target_; - - // Pending removals - QSet pending_target_removals_; -}; - -#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_STATE_H_ diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.cc b/sharing/linux/qml_tray_app/file_share_tray_controller.cc deleted file mode 100644 index 7f692bb1..00000000 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.cc +++ /dev/null @@ -1,587 +0,0 @@ -#include "file_share_tray_controller.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "string_utils.h" -#include "status_mapper.h" -#include "qr_code_generator.h" - -FileShareTrayController::FileShareTrayController(QObject* parent) - : QObject(parent) { - const QString host = QSysInfo::machineHostName().trimmed(); - if (!host.isEmpty()) { - state_.SetDeviceName(host); - } - - loadSettings(); - initializeService(); -} - -FileShareTrayController::~FileShareTrayController() { - stop(); - if (service_) { - service_->Shutdown([](NearbySharingApi::StatusCode) {}); - } -} - -void FileShareTrayController::initializeService() { - service_ = std::make_unique(state_.deviceName().toStdString()); - service_->Set5GhzHotspotEnabled(state_.enable5GhzHotspot()); - state_.SetQrCodeData(QString::fromStdString(service_->GetQrCodeUrl()), {}, 0); - updateQrCodeData(); - emit qrCodeUrlChanged(); - emit qrCodeChanged(); - attachServiceListeners(); - // service_ ->StartFastInitiationScanning([](auto a) - // { - // std::cout << "Probably fine"; - // }); -} - -void FileShareTrayController::updateQrCodeData() { - const auto qr_data = QrCodeGenerator::GenerateQrCode(state_.qrCodeUrl()); - state_.SetQrCodeData(state_.qrCodeUrl(), qr_data.rows, qr_data.size); - emit qrCodeChanged(); -} - -void FileShareTrayController::attachServiceListeners() { - NearbySharingApi::Listener listener; - - listener.target_discovered_cb = [this](const NearbySharingApi::ShareTargetInfo& info) { - QMetaObject::invokeMethod(this, [this, info]() { updateTargetFromInfo(info); }, - Qt::QueuedConnection); - }; - - listener.target_updated_cb = [this](const NearbySharingApi::ShareTargetInfo& info) { - QMetaObject::invokeMethod(this, [this, info]() { updateTargetFromInfo(info); }, - Qt::QueuedConnection); - }; - - listener.target_lost_cb = [this](int64_t share_target_id) { - QMetaObject::invokeMethod( - this, [this, share_target_id]() { - state_.RemoveTarget(share_target_id); - emit discoveredTargetsChanged(); - }, - Qt::QueuedConnection); - }; - - listener.transfer_update_cb = [this](const NearbySharingApi::TransferUpdateInfo& update) { - QMetaObject::invokeMethod(this, [this, update]() { handleTransferUpdate(update); }, - Qt::QueuedConnection); - }; - - service_->SetListener(std::move(listener)); -} - -void FileShareTrayController::updateTargetFromInfo( - const NearbySharingApi::ShareTargetInfo& info) { - const QString name = StringUtils::TrimmedOrFallback( - StringUtils::FromStdString(info.device_name), - QStringLiteral("Unknown device")); - state_.AddOrUpdateTarget(info.id, name, info.is_incoming); - emit discoveredTargetsChanged(); -} - -void FileShareTrayController::handleTransferUpdate( - const NearbySharingApi::TransferUpdateInfo& update) { - const QString target_name = StringUtils::TrimmedFromStdString(update.device_name); - if (!target_name.isEmpty()) { - state_.AddOrUpdateTarget(update.share_target_id, target_name, update.is_incoming); - } - - const QString name = state_.GetTargetName(update.share_target_id); - const QString status = StatusMapper::TransferStatusToString(update.status); - const QString direction = - update.is_incoming ? QStringLiteral("incoming") : QStringLiteral("outgoing"); - - QString file_name = StringUtils::FromStdString(update.first_file_name); - if (file_name.isEmpty() && !update.is_incoming && - state_.pendingSendTargetId() == update.share_target_id && - !state_.pendingSendFileName().isEmpty()) { - file_name = state_.pendingSendFileName(); - } - - state_.AddOrUpdateTransfer(update.share_target_id, name, status, update.progress, - update.transferred_bytes, direction, file_name, - StringUtils::FromStdString(update.first_file_path)); - emit transfersChanged(); - - setStatus(QStringLiteral("%1 (%2)").arg(status, name)); - - // Auto-accept incoming transfers if enabled - if (update.status == NearbySharingApi::TransferStatus::kAwaitingLocalConfirmation && - state_.autoAcceptIncoming()) { - service_->Accept(update.share_target_id, [](NearbySharingApi::StatusCode) {}); - } - - // Handle final transfer status - if (StatusMapper::IsFinalTransferStatus(update.status)) { - handleTransferComplete(update); - } -} - -void FileShareTrayController::handleTransferComplete( - const NearbySharingApi::TransferUpdateInfo& update) { - const bool success = update.status == NearbySharingApi::TransferStatus::kComplete; - const QString name = state_.GetTargetName(update.share_target_id); - - if (update.is_incoming) { - handleIncomingTransferComplete(update, name, success); - } else { - handleOutgoingTransferComplete(update, name, success); - } - - // Cleanup pending send state - if (state_.pendingSendTargetId() == update.share_target_id) { - state_.ClearPendingSendFile(); - emit pendingSendFilePathChanged(); - emit pendingSendFileNameChanged(); - - // Auto-switch to receive mode after successful send - if (!update.is_incoming && success) { - switchToReceiveMode(); - } - } - - // Deferred target removal - if (state_.IsPendingTargetRemoval(update.share_target_id)) { - QTimer::singleShot(1400, this, [this, target_id = update.share_target_id]() { - if (state_.IsPendingTargetRemoval(target_id)) { - state_.RemoveTarget(target_id); - emit discoveredTargetsChanged(); - } - }); - } -} - -void FileShareTrayController::handleIncomingTransferComplete( - const NearbySharingApi::TransferUpdateInfo& update, const QString& name, - bool success) { - if (!success) { - emit requestTrayMessage( - QStringLiteral("Receive failed"), - QStringLiteral("Transfer from %1 failed").arg(name)); - return; - } - - const QString file_name = - StringUtils::FromStdString(update.first_file_name).isEmpty() - ? QStringLiteral("file") - : StringUtils::FromStdString(update.first_file_name); - - // Check for received URL - for (const auto& text : update.text_attachments) { - if (text.type == NearbySharingApi::TextAttachmentType::kUrl) { - const QString link = StringUtils::TrimmedFromStdString(text.text_body); - if (!link.isEmpty()) { - emit requestCopyLinkTrayMessage(QStringLiteral("Link received"), - QStringLiteral("%1 from %2").arg(link, name), - link); - return; - } - } - } - - // Check for received text - if (!update.text_attachments.empty()) { - const QString text_summary = [&]() { - for (const auto& text : update.text_attachments) { - const QString title = StringUtils::TrimmedFromStdString(text.text_title); - if (!title.isEmpty()) return title; - const QString body = StringUtils::TrimmedFromStdString(text.text_body); - if (!body.isEmpty()) return body; - } - return QStringLiteral("Text"); - }(); - emit requestTrayMessage(QStringLiteral("Text received"), - QStringLiteral("%1 from %2").arg(text_summary, name)); - return; - } - - emit requestTrayMessage(QStringLiteral("File received"), - QStringLiteral("%1 from %2").arg(file_name, name)); -} - -void FileShareTrayController::handleOutgoingTransferComplete( - const NearbySharingApi::TransferUpdateInfo& update, const QString& name, - bool success) { - const QString file_name = - StringUtils::FromStdString(update.first_file_name).isEmpty() - ? QStringLiteral("file") - : StringUtils::FromStdString(update.first_file_name); - - if (success) { - emit requestTrayMessage(QStringLiteral("Send complete"), - QStringLiteral("%1 sent to %2").arg(file_name, name)); - } else { - emit requestTrayMessage(QStringLiteral("Send failed"), - QStringLiteral("%1 failed to send to %2").arg(file_name, name)); - } -} - -void FileShareTrayController::loadSettings() { - QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); - - const QString stored_device_name = - settings.value(QStringLiteral("deviceName"), state_.deviceName()) - .toString() - .trimmed(); - if (!stored_device_name.isEmpty()) { - state_.SetDeviceName(stored_device_name); - } - - const bool stored_auto_accept = - settings.value(QStringLiteral("autoAcceptIncoming"), true).toBool(); - state_.SetAutoAcceptIncoming(stored_auto_accept); - - const bool stored_enable_5ghz_hotspot = - settings.value(QStringLiteral("enable5GhzHotspot"), true).toBool(); - state_.SetEnable5GhzHotspot(stored_enable_5ghz_hotspot); - - const QString stored_log_path = - settings.value(QStringLiteral("logPath"), QStringLiteral("/tmp/nearby_qml_file_tray.log")) - .toString() - .trimmed(); - if (!stored_log_path.isEmpty()) { - state_.SetLogPath(stored_log_path); - } -} - -void FileShareTrayController::saveSettings() const { - QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); - settings.setValue(QStringLiteral("deviceName"), state_.deviceName()); - settings.setValue(QStringLiteral("autoAcceptIncoming"), state_.autoAcceptIncoming()); - settings.setValue(QStringLiteral("enable5GhzHotspot"), - state_.enable5GhzHotspot()); - settings.setValue(QStringLiteral("logPath"), state_.logPath()); -} - -void FileShareTrayController::setDeviceName(const QString& device_name) { - const QString trimmed = device_name.trimmed(); - if (trimmed.isEmpty() || trimmed == state_.deviceName()) { - return; - } - - state_.SetDeviceName(trimmed); - saveSettings(); - emit deviceNameChanged(); - - if (state_.running()) { - stop(); - initializeService(); - start(); - } -} - -void FileShareTrayController::setAutoAcceptIncoming(bool enabled) { - if (enabled == state_.autoAcceptIncoming()) { - return; - } - state_.SetAutoAcceptIncoming(enabled); - saveSettings(); - emit autoAcceptIncomingChanged(); -} - -void FileShareTrayController::setEnable5GhzHotspot(bool enabled) { - if (enabled == state_.enable5GhzHotspot()) { - return; - } - state_.SetEnable5GhzHotspot(enabled); - if (service_) { - service_->Set5GhzHotspotEnabled(enabled); - } - saveSettings(); - emit enable5GhzHotspotChanged(); -} - -void FileShareTrayController::setLogPath(const QString& path) { - const QString trimmed = path.trimmed(); - if (trimmed.isEmpty() || trimmed == state_.logPath()) { - return; - } - state_.SetLogPath(trimmed); - saveSettings(); - emit logPathChanged(); -} - -void FileShareTrayController::start() { - if (state_.running()) { - return; - } - - state_.SetRunning(true); - emit runningChanged(); - -} - -void FileShareTrayController::stop() { - if (!state_.running()) { - return; - } - - state_.SetRunning(false); - emit runningChanged(); - - service_->StopSendMode([](NearbySharingApi::StatusCode) {}); - service_->StopReceiveMode([](NearbySharingApi::StatusCode) {}); - - state_.ClearAll(); - emit discoveredTargetsChanged(); - emit transfersChanged(); - - setStatus(QStringLiteral("Stopped")); -} - -void FileShareTrayController::startSendMode() { - service_->StopReceiveMode([this](NearbySharingApi::StatusCode status) { - if (status == NearbySharingApi::StatusCode::kOk || - status == NearbySharingApi::StatusCode::kStatusAlreadyStopped) { - service_->StartSendMode([this](NearbySharingApi::StatusCode status) { - QMetaObject::invokeMethod( - this, - [this, status]() { - setStatus(QStringLiteral("StartSendMode: %1") - .arg(StatusMapper::ApiStatusToString(status))); - if (status != NearbySharingApi::StatusCode::kOk) { - state_.SetRunning(false); - emit runningChanged(); - } - }, - Qt::QueuedConnection); - }); - } - }); -} - -void FileShareTrayController::startReceiveMode() { - service_->StopSendMode([this](NearbySharingApi::StatusCode status) { - if (status == NearbySharingApi::StatusCode::kOk || - status == NearbySharingApi::StatusCode::kStatusAlreadyStopped) { - service_->StartReceiveMode([this](NearbySharingApi::StatusCode status) { - QMetaObject::invokeMethod( - this, - [this, status]() { - setStatus(QStringLiteral("StartReceiveMode: %1") - .arg(StatusMapper::ApiStatusToString(status))); - if (status != NearbySharingApi::StatusCode::kOk) { - state_.SetRunning(false); - emit runningChanged(); - } - }, - Qt::QueuedConnection); - }); - } - }); -} - -void FileShareTrayController::switchToReceiveMode() { - if (state_.running() && state_.HasActiveTransfers()) { - setStatus(QStringLiteral("Cannot switch mode while transfer is active")); - emit requestTrayMessage( - QStringLiteral("Transfer in progress"), - QStringLiteral("Wait for the current transfer to complete.")); - return; - } - if (state_.running()) - { - startReceiveMode(); - state_.SetMode(QStringLiteral("Receive")); - emit modeChanged(); - } -} - -void FileShareTrayController::switchToSendModeWithFile(const QString& file_path) { - const QString trimmed_path = file_path.trimmed(); - QFileInfo info(trimmed_path); - - if (trimmed_path.isEmpty() || !info.exists() || !info.isFile()) { - setStatus(QStringLiteral("Selected file is not valid")); - emit requestTrayMessage(QStringLiteral("Send canceled"), - QStringLiteral("Please choose a valid file.")); - return; - } - - state_.SetPendingSendFile(info.absoluteFilePath(), info.fileName(), 0); - emit pendingSendFilePathChanged(); - emit pendingSendFileNameChanged(); - - if (state_.running() && state_.HasActiveTransfers()) { - setStatus(QStringLiteral("Cannot switch mode while transfer is active")); - emit requestTrayMessage( - QStringLiteral("Transfer in progress"), - QStringLiteral("Wait for the current transfer to complete.")); - return; - } - if (state_.running()) - { - startSendMode(); - state_.SetMode(QStringLiteral("Send")); - emit modeChanged(); - } - - setStatus(QStringLiteral("Discovery started. Choose a nearby device.")); - emit requestTrayMessage( - QStringLiteral("Send mode"), - QStringLiteral("Selected %1. Choose a nearby device to send.") - .arg(info.fileName())); -} - -void FileShareTrayController::sendPendingFileToTarget(qlonglong share_target_id) { - if (share_target_id <= 0) { - return; - } - - const QString file_path = state_.pendingSendFilePath(); - QFileInfo file_info(file_path); - - if (file_path.isEmpty() || !file_info.exists() || !file_info.isFile()) { - setStatus(QStringLiteral("Selected file is not available")); - emit requestTrayMessage(QStringLiteral("Send failed"), - QStringLiteral("Selected file is not available.")); - return; - } - - const QString target_name = state_.GetTargetName(share_target_id); - state_.SetPendingSendFile(file_path, file_info.fileName(), share_target_id); - - state_.AddOrUpdateTransfer(share_target_id, target_name, QStringLiteral("Queued"), 0.0, 0, - QStringLiteral("outgoing"), file_info.fileName(), - file_info.absoluteFilePath()); - emit transfersChanged(); - - service_->SendFile( - share_target_id, file_info.absoluteFilePath().toStdString(), - [this, share_target_id](NearbySharingApi::StatusCode status) { - QMetaObject::invokeMethod( - this, - [this, share_target_id, status]() { - const QString target_name = state_.GetTargetName(share_target_id); - if (status == NearbySharingApi::StatusCode::kOk) { - setStatus(QStringLiteral("Sending %1 to %2") - .arg(state_.pendingSendFileName(), target_name)); - return; - } - - emit requestTrayMessage( - QStringLiteral("Send failed"), - QStringLiteral("Could not send to %1").arg(target_name)); - - state_.AddOrUpdateTransfer(share_target_id, target_name, - QStringLiteral("Failed"), 0.0, 0, - QStringLiteral("outgoing"), - state_.pendingSendFileName(), - state_.pendingSendFilePath()); - emit transfersChanged(); - state_.SetPendingSendFile("", "", 0); - }, - Qt::QueuedConnection); - }); -} - -void FileShareTrayController::copyTextToClipboard(const QString& text) { - const QString trimmed = text.trimmed(); - if (trimmed.isEmpty()) { - return; - } - - QClipboard* clipboard = QGuiApplication::clipboard(); - if (clipboard == nullptr) { - emit requestTrayMessage(QStringLiteral("Copy failed"), - QStringLiteral("Clipboard is not available.")); - return; - } - - clipboard->setText(trimmed, QClipboard::Clipboard); - setStatus(QStringLiteral("Connection URL copied to clipboard")); - emit requestTrayMessage(QStringLiteral("URL copied"), - QStringLiteral("Link copied to clipboard.")); -} - -void FileShareTrayController::openFileLocation(const QString& file_path) { - const QString trimmed = file_path.trimmed(); - if (trimmed.isEmpty()) { - emit requestTrayMessage(QStringLiteral("Open location failed"), - QStringLiteral("No received file location is available.")); - return; - } - - QFileInfo info(trimmed); - QString target_path; - if (info.exists() && info.isFile()) { - // Open the containing folder so the file is visible in the user's file - // manager regardless of the desktop environment. - target_path = info.absolutePath(); - } else if (info.exists() && info.isDir()) { - target_path = info.absoluteFilePath(); - } else { - // Some transfer updates can outlive the exact file entry we saw earlier; - // fall back to the parent directory when it still exists. - const QFileInfo parent_info(info.absolutePath()); - if (parent_info.exists() && parent_info.isDir()) { - target_path = parent_info.absoluteFilePath(); - } - } - - if (target_path.isEmpty()) { - emit requestTrayMessage(QStringLiteral("Open location failed"), - QStringLiteral("The file location is no longer available.")); - return; - } - - const bool opened = - QDesktopServices::openUrl(QUrl::fromLocalFile(target_path)); - if (!opened) { - emit requestTrayMessage(QStringLiteral("Open location failed"), - QStringLiteral("Could not open the file location.")); - } -} - -void FileShareTrayController::clearTransfers() { - state_.ClearAll(); - emit discoveredTargetsChanged(); - emit transfersChanged(); -} - -void FileShareTrayController::hideToTray() { - // This is handled by the main window, but can be extended here if needed -} - -void FileShareTrayController::setStatus(const QString& status) { - if (status == state_.statusMessage()) { - return; - } - state_.SetStatusMessage(status); - emit statusMessageChanged(); -} - -void FileShareTrayController::notifyStateChange(const QString& property) { - if (property == QStringLiteral("mode")) { - emit modeChanged(); - } else if (property == QStringLiteral("deviceName")) { - emit deviceNameChanged(); - } else if (property == QStringLiteral("statusMessage")) { - emit statusMessageChanged(); - } else if (property == QStringLiteral("running")) { - emit runningChanged(); - } else if (property == QStringLiteral("autoAcceptIncoming")) { - emit autoAcceptIncomingChanged(); - } else if (property == QStringLiteral("enable5GhzHotspot")) { - emit enable5GhzHotspotChanged(); - } else if (property == QStringLiteral("discoveredTargets")) { - emit discoveredTargetsChanged(); - } else if (property == QStringLiteral("transfers")) { - emit transfersChanged(); - } -} diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.h b/sharing/linux/qml_tray_app/file_share_tray_controller.h deleted file mode 100644 index 05f88dbe..00000000 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.h +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_ -#define SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_ - -#include -#include - -#include "file_share_state.h" -#include - -using NearbySharingApi = nearby::sharing::NearbySharingApi; - -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(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 enable5GhzHotspot READ enable5GhzHotspot WRITE setEnable5GhzHotspot NOTIFY enable5GhzHotspotChanged) - Q_PROPERTY(QString qrCodeUrl READ qrCodeUrl NOTIFY qrCodeUrlChanged) - Q_PROPERTY(QStringList qrCodeRows READ qrCodeRows NOTIFY qrCodeChanged) - Q_PROPERTY(int qrCodeSize READ qrCodeSize NOTIFY qrCodeChanged) - Q_PROPERTY(QString logPath READ logPath WRITE setLogPath NOTIFY logPathChanged) - - public: - explicit FileShareTrayController(QObject* parent = nullptr); - ~FileShareTrayController() override; - - // Property accessors - QString mode() const { return state_.mode(); } - QString deviceName() const { return state_.deviceName(); } - QString statusMessage() const { return state_.statusMessage(); } - bool running() const { return state_.running(); } - QString pendingSendFileName() const { return state_.pendingSendFileName(); } - QString pendingSendFilePath() const { return state_.pendingSendFilePath(); } - QVariantList discoveredTargets() const { return state_.discoveredTargets(); } - QVariantList transfers() const { return state_.transfers(); } - bool autoAcceptIncoming() const { return state_.autoAcceptIncoming(); } - bool enable5GhzHotspot() const { return state_.enable5GhzHotspot(); } - QString qrCodeUrl() const { return state_.qrCodeUrl(); } - QStringList qrCodeRows() const { return state_.qrCodeRows(); } - int qrCodeSize() const { return state_.qrCodeSize(); } - QString logPath() const { return state_.logPath(); } - - // Public methods - void setDeviceName(const QString& device_name); - void setAutoAcceptIncoming(bool enabled); - void setEnable5GhzHotspot(bool enabled); - void setLogPath(const QString& path); - - Q_INVOKABLE void start(); - Q_INVOKABLE void stop(); - Q_INVOKABLE void switchToReceiveMode(); - Q_INVOKABLE void switchToSendModeWithFile(const QString& file_path); - Q_INVOKABLE void sendPendingFileToTarget(qlonglong share_target_id); - Q_INVOKABLE void copyTextToClipboard(const QString& text); - Q_INVOKABLE void openFileLocation(const QString& file_path); - Q_INVOKABLE void clearTransfers(); - Q_INVOKABLE void hideToTray(); - - signals: - void modeChanged(); - void deviceNameChanged(); - void statusMessageChanged(); - void runningChanged(); - void pendingSendFileNameChanged(); - void pendingSendFilePathChanged(); - void discoveredTargetsChanged(); - void transfersChanged(); - void autoAcceptIncomingChanged(); - void enable5GhzHotspotChanged(); - void qrCodeUrlChanged(); - void qrCodeChanged(); - void logPathChanged(); - - void requestTrayMessage(const QString& title, const QString& body); - void requestCopyLinkTrayMessage(const QString& title, const QString& body, - const QString& link); - - private: - void initializeService(); - void attachServiceListeners(); - void loadSettings(); - void saveSettings() const; - void updateQrCodeData(); - - void startSendMode(); - void startReceiveMode(); - - void updateTargetFromInfo(const NearbySharingApi::ShareTargetInfo& info); - void handleTransferUpdate(const NearbySharingApi::TransferUpdateInfo& update); - void handleTransferComplete(const NearbySharingApi::TransferUpdateInfo& update); - void handleIncomingTransferComplete(const NearbySharingApi::TransferUpdateInfo& update, - const QString& name, bool success); - void handleOutgoingTransferComplete(const NearbySharingApi::TransferUpdateInfo& update, - const QString& name, bool success); - - void setStatus(const QString& status); - void notifyStateChange(const QString& property); - - std::unique_ptr service_; - FileShareState state_; -}; - -#endif // SHARING_LINUX_QML_TRAY_APP_FILE_SHARE_TRAY_CONTROLLER_H_ diff --git a/sharing/linux/qml_tray_app/file_share_tray_main.cpp b/sharing/linux/qml_tray_app/file_share_tray_main.cpp deleted file mode 100644 index 355ef007..00000000 --- a/sharing/linux/qml_tray_app/file_share_tray_main.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "file_share_tray_controller.h" -#include "notification_manager.h" - -namespace { - -constexpr char kDefaultLogPath[] = "/tmp/nearby_qml_file_tray.log"; - -bool EnsureLogDirectory(const QString& file_path) { - const QFileInfo file_info(file_path); - QDir directory = file_info.absoluteDir(); - if (directory.exists()) { - return true; - } - return directory.mkpath(QStringLiteral(".")); -} - -bool RedirectStdStreamsToFile(const QString& file_path) { - const QByteArray encoded_path = QFile::encodeName(file_path); - const int fd = ::open(encoded_path.constData(), O_CREAT | O_APPEND | O_WRONLY, 0644); - if (fd < 0) { - return false; - } - - const bool redirected_stdout = ::dup2(fd, STDOUT_FILENO) >= 0; - const bool redirected_stderr = ::dup2(fd, STDERR_FILENO) >= 0; - ::close(fd); - return redirected_stdout && redirected_stderr; -} - -QString ResolveConfiguredLogPath() { - QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); - const QString configured_path = - settings.value(QStringLiteral("logPath"), - QString::fromLatin1(kDefaultLogPath)) - .toString() - .trimmed(); - if (configured_path.isEmpty()) { - return QString::fromLatin1(kDefaultLogPath); - } - return configured_path; -} - -void RedirectProcessLogsToConfiguredPath() { - QString log_path = ResolveConfiguredLogPath(); - if (EnsureLogDirectory(log_path) && RedirectStdStreamsToFile(log_path)) { - return; - } - - const QString fallback_path = QString::fromLatin1(kDefaultLogPath); - if (log_path == fallback_path) { - return; - } - if (!EnsureLogDirectory(fallback_path)) { - return; - } - RedirectStdStreamsToFile(fallback_path); -} - -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[]) { - RedirectProcessLogsToConfiguredPath(); - - 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(engine.rootObjects().first()); - if (window == nullptr) { - return 1; - } - - const auto resolve_tray_icon = [&app]() { - const QColor white = "white"; - QIcon tray_icon = BuildTintedSymbolicIcon( - QStringLiteral(":/icons/tray_icon-symbolic.svg"), white); - 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")); - NotificationManager notification_manager(&tray, &app); - - 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, - ¬ification_manager, &NotificationManager::ShowNotification); - QObject::connect(&controller, - &FileShareTrayController::requestCopyLinkTrayMessage, - ¬ification_manager, - [¬ification_manager](const QString& title, - const QString& body, - const QString& link) { - notification_manager.ShowCopyableNotification( - title, body, link, QStringLiteral("Copy link")); - }); - - QObject::connect(&app, &QCoreApplication::aboutToQuit, &controller, - [&controller]() { controller.stop(); }); -#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) - QObject::connect(app.styleHints(), &QStyleHints::colorSchemeChanged, &tray, - [&tray, &resolve_tray_icon](Qt::ColorScheme) { - tray.setIcon(resolve_tray_icon()); - }); -#endif - - tray.setContextMenu(&tray_menu); - tray.show(); - - controller.start(); - //controller. - controller.switchToReceiveMode(); - - return app.exec(); -} diff --git a/sharing/linux/qml_tray_app/icons/README.txt b/sharing/linux/qml_tray_app/icons/README.txt deleted file mode 100644 index a59c988d..00000000 --- a/sharing/linux/qml_tray_app/icons/README.txt +++ /dev/null @@ -1,21 +0,0 @@ -Converted with ❤️ by ConvertICO.com -Tool used: convertico.com/icon-maker/ -Free, no account needed - bookmark us for next time! - -Conversion details: - Original file : nearby-linux-desktop.png - Converted on : 2026-03-22 - Sizes included : 16x16px, 32x32px, 48x48px, 64x64px, 128x128px, 256x256px - Shape : Rounded - Background : transparent - Format : PNG - -Files in this archive: - *.png - Individual PNG icons at each selected size - *.ico - Multi-resolution ICO file (if ICO format selected) - -How to use your ICO as a favicon: - - -Need help? convertico.com/contact/ -Convert more images at ConvertICO.com \ No newline at end of file diff --git a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_128x128.png b/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_128x128.png deleted file mode 100644 index bff55a1c..00000000 Binary files a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_128x128.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_16x16.png b/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_16x16.png deleted file mode 100644 index 9e4daeef..00000000 Binary files a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_16x16.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_256x256.png b/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_256x256.png deleted file mode 100644 index 32242740..00000000 Binary files a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_256x256.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_32x32.png b/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_32x32.png deleted file mode 100644 index 0ec887ba..00000000 Binary files a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_32x32.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_48x48.png b/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_48x48.png deleted file mode 100644 index 6e615b64..00000000 Binary files a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_48x48.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_64x64.png b/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_64x64.png deleted file mode 100644 index 030ea29e..00000000 Binary files a/sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_64x64.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/install_nearby_file_share.sh b/sharing/linux/qml_tray_app/install_nearby_file_share.sh deleted file mode 100755 index 91fa408c..00000000 --- a/sharing/linux/qml_tray_app/install_nearby_file_share.sh +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -MODE="user" -PREFIX="" - -usage() { - cat <<'USAGE' -Usage: ./install_nearby_file_share.sh [options] - -Install Nearby File Share app artifacts from an extracted release bundle. - -Options: - --user Install under $HOME/.local (default) - --system Install under /usr/local (uses sudo when needed) - --prefix DIR Custom install prefix (overrides --user/--system default) - -h, --help Show this help - -Examples: - ./install_nearby_file_share.sh - ./install_nearby_file_share.sh --system - ./install_nearby_file_share.sh --prefix "$HOME/.local" -USAGE -} - -nearest_existing_parent() { - local path="$1" - while [[ ! -e "$path" ]]; do - path="$(dirname "$path")" - done - printf '%s\n' "$path" -} - -while [[ $# -gt 0 ]]; do - case "$1" in - --user) - MODE="user" - shift - ;; - --system) - MODE="system" - shift - ;; - --prefix) - PREFIX="$2" - shift 2 - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "Unknown argument: $1" >&2 - usage - exit 1 - ;; - esac -done - -if [[ -z "$PREFIX" ]]; then - if [[ "$MODE" == "system" ]]; then - PREFIX="/usr/local" - else - PREFIX="$HOME/.local" - fi -fi - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -BIN_SRC="$SCRIPT_DIR/bin/nearby_qml_file_tray_app" -LIB_SRC="$SCRIPT_DIR/lib/libnearby_sharing_api_shared.so" -DESKTOP_SRC="$SCRIPT_DIR/share/applications/nearby-file-share.desktop" -ICON_SRC_STAGED="$SCRIPT_DIR/share/icons/hicolor/256x256/apps/nearby-file-share.png" -ICON_SRC_FALLBACK="$SCRIPT_DIR/nearby-linux-desktop.png" -ICON_SRC="$ICON_SRC_STAGED" -if [[ ! -f "$ICON_SRC" && -f "$ICON_SRC_FALLBACK" ]]; then - ICON_SRC="$ICON_SRC_FALLBACK" -fi -HEADER_SRC="$SCRIPT_DIR/include/sharing/linux/nearby_sharing_api.h" - -for required in "$BIN_SRC" "$LIB_SRC" "$DESKTOP_SRC" "$ICON_SRC"; do - if [[ ! -f "$required" ]]; then - echo "Missing required bundle artifact: $required" >&2 - echo "Run this installer from the extracted release bundle root." >&2 - exit 1 - fi -done - -BINDIR="$PREFIX/bin" -LIBDIR="$PREFIX/lib" -INCLUDEDIR="$PREFIX/include/sharing/linux" -DESKTOP_DIR="$PREFIX/share/applications" -ICON_DIR="$PREFIX/share/icons/hicolor/256x256/apps" - -NEEDS_ELEVATION=0 -for path in "$BINDIR" "$LIBDIR" "$DESKTOP_DIR" "$ICON_DIR"; do - parent="$(nearest_existing_parent "$path")" - if [[ ! -w "$parent" ]]; then - NEEDS_ELEVATION=1 - break - fi -done - -INSTALL_PREFIX=() -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 - -TMP_DESKTOP="$(mktemp)" -trap 'rm -f "$TMP_DESKTOP"' EXIT - -sed \ - -e "s|^Exec=.*|Exec=${BINDIR}/nearby_qml_file_tray_app|" \ - -e "s|^Icon=.*|Icon=${ICON_DIR}/nearby-file-share.png|" \ - "$DESKTOP_SRC" > "$TMP_DESKTOP" - -echo "[1/5] Installing application binary" -"${INSTALL_PREFIX[@]}" install -d "$BINDIR" -"${INSTALL_PREFIX[@]}" install -m 0755 "$BIN_SRC" "$BINDIR/" - -echo "[2/5] Installing shared library" -"${INSTALL_PREFIX[@]}" install -d "$LIBDIR" -"${INSTALL_PREFIX[@]}" install -m 0755 "$LIB_SRC" "$LIBDIR/" - -if [[ -f "$HEADER_SRC" ]]; then - echo "[3/5] Installing public header" - "${INSTALL_PREFIX[@]}" install -d "$INCLUDEDIR" - "${INSTALL_PREFIX[@]}" install -m 0644 "$HEADER_SRC" "$INCLUDEDIR/" -else - echo "[3/5] Header not present in bundle; skipping" -fi - -echo "[4/5] Installing desktop entry and icon" -"${INSTALL_PREFIX[@]}" install -d "$DESKTOP_DIR" -"${INSTALL_PREFIX[@]}" install -d "$ICON_DIR" -"${INSTALL_PREFIX[@]}" install -m 0644 "$TMP_DESKTOP" "$DESKTOP_DIR/nearby-file-share.desktop" -"${INSTALL_PREFIX[@]}" install -m 0644 "$ICON_SRC" "$ICON_DIR/nearby-file-share.png" - -echo "[5/5] Refreshing desktop/system caches" -if command -v update-desktop-database >/dev/null 2>&1; then - "${INSTALL_PREFIX[@]}" update-desktop-database "$DESKTOP_DIR" || true -fi -if command -v ldconfig >/dev/null 2>&1 && [[ "$PREFIX" == "/usr" || "$PREFIX" == "/usr/local" ]]; then - "${INSTALL_PREFIX[@]}" ldconfig || true -fi - -echo "Installed Nearby File Share:" -echo " binary : $BINDIR/nearby_qml_file_tray_app" -echo " library: $LIBDIR/libnearby_sharing_api_shared.so" -echo " desktop: $DESKTOP_DIR/nearby-file-share.desktop" -echo " icon : $ICON_DIR/nearby-file-share.png" -if [[ -f "$HEADER_SRC" ]]; then - echo " header : $INCLUDEDIR/nearby_sharing_api.h" -fi diff --git a/sharing/linux/qml_tray_app/nearby-file-share.desktop b/sharing/linux/qml_tray_app/nearby-file-share.desktop deleted file mode 100644 index 948b022d..00000000 --- a/sharing/linux/qml_tray_app/nearby-file-share.desktop +++ /dev/null @@ -1,10 +0,0 @@ -[Desktop Entry] -Type=Application -Name=Nearby File Share -Comment=Share files with nearby devices using Nearby Connections -Exec=nearby_qml_file_tray_app -Icon=nearby-file-share -Categories=Utility;Network;FileTransfer; -Keywords=share;file;nearby;transfer; -StartupNotify=false -Terminal=false diff --git a/sharing/linux/qml_tray_app/nearby-linux-desktop.png b/sharing/linux/qml_tray_app/nearby-linux-desktop.png deleted file mode 100644 index 2273e633..00000000 Binary files a/sharing/linux/qml_tray_app/nearby-linux-desktop.png and /dev/null differ diff --git a/sharing/linux/qml_tray_app/notification_manager.cpp b/sharing/linux/qml_tray_app/notification_manager.cpp deleted file mode 100644 index cdf19cda..00000000 --- a/sharing/linux/qml_tray_app/notification_manager.cpp +++ /dev/null @@ -1,210 +0,0 @@ -#include "notification_manager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace { - -constexpr char kNotificationsService[] = "org.freedesktop.Notifications"; -constexpr char kNotificationsPath[] = "/org/freedesktop/Notifications"; -constexpr char kNotificationsInterface[] = "org.freedesktop.Notifications"; -constexpr char kCopyActionId[] = "copy_value"; -constexpr char kDesktopEntryId[] = "nearby-file-share"; - -} // namespace - -NotificationManager::NotificationManager(QSystemTrayIcon* tray_icon, - QObject* parent) - : QObject(parent), tray_icon_(tray_icon) { - QDBusConnection session_bus = QDBusConnection::sessionBus(); - if (!session_bus.isConnected()) { - return; - } - - session_bus.connect(QString::fromLatin1(kNotificationsService), - QString::fromLatin1(kNotificationsPath), - QString::fromLatin1(kNotificationsInterface), - QStringLiteral("ActionInvoked"), this, - SLOT(OnActionInvoked(uint,QString))); - session_bus.connect(QString::fromLatin1(kNotificationsService), - QString::fromLatin1(kNotificationsPath), - QString::fromLatin1(kNotificationsInterface), - QStringLiteral("NotificationClosed"), this, - SLOT(OnNotificationClosed(uint,uint))); - - QDBusInterface notification_interface( - QString::fromLatin1(kNotificationsService), - QString::fromLatin1(kNotificationsPath), - QString::fromLatin1(kNotificationsInterface), session_bus); - QDBusReply capabilities_reply = - notification_interface.call(QStringLiteral("GetCapabilities")); - if (capabilities_reply.isValid()) { - supports_actions_ = - capabilities_reply.value().contains(QStringLiteral("actions")); - } -} - -void NotificationManager::ShowNotification(const QString& title, - const QString& body) { - if (tray_icon_ != nullptr) { - tray_icon_->showMessage(title, body, QSystemTrayIcon::Information, 4000); - } -} - -void NotificationManager::ShowCopyableNotification( - const QString& title, const QString& body, const QString& text_to_copy, - const QString& action_label) { - const QString trimmed_text = text_to_copy.trimmed(); - const QString trimmed_action_label = action_label.trimmed().isEmpty() - ? QStringLiteral("Copy") - : action_label.trimmed(); - if (trimmed_text.isEmpty()) { - ShowNotification(title, body); - return; - } - - if (supports_actions_) { - QDBusInterface notification_interface( - QString::fromLatin1(kNotificationsService), - QString::fromLatin1(kNotificationsPath), - QString::fromLatin1(kNotificationsInterface), - QDBusConnection::sessionBus()); - const QString application_name = QCoreApplication::applicationName(); - const QString notification_icon = EnsureNotificationIconPath(); - QVariantMap hints{{QStringLiteral("desktop-entry"), - QString::fromLatin1(kDesktopEntryId)}}; - if (!notification_icon.isEmpty()) { - hints.insert(QStringLiteral("image-path"), notification_icon); - } - QDBusReply reply = notification_interface.call( - QStringLiteral("Notify"), application_name, - static_cast(0), - notification_icon.isEmpty() ? QString::fromLatin1(kDesktopEntryId) - : notification_icon, - title, body, - QStringList{QString::fromLatin1(kCopyActionId), trimmed_action_label}, - hints, 8000); - if (reply.isValid()) { - copy_actions_.insert(reply.value(), CopyActionState{trimmed_text}); - return; - } - } - - ShowFallbackDialog(title, body, trimmed_text, trimmed_text, - trimmed_action_label); -} - -void NotificationManager::OnActionInvoked(uint notification_id, - const QString& action_key) { - if (action_key != QString::fromLatin1(kCopyActionId)) { - return; - } - - auto it = copy_actions_.find(notification_id); - if (it == copy_actions_.end()) { - return; - } - - CopyTextToClipboard(it->text_to_copy, QStringLiteral("Copied"), - QStringLiteral("Copied to clipboard.")); - copy_actions_.erase(it); -} - -void NotificationManager::OnNotificationClosed(uint notification_id, - uint reason) { - Q_UNUSED(reason); - copy_actions_.remove(notification_id); -} - -void NotificationManager::CopyTextToClipboard( - const QString& text_to_copy, const QString& confirmation_title, - const QString& confirmation_body) const { - QClipboard* clipboard = QGuiApplication::clipboard(); - if (clipboard != nullptr) { - clipboard->setText(text_to_copy); - } - - if (tray_icon_ != nullptr) { - tray_icon_->showMessage(confirmation_title, confirmation_body, - QSystemTrayIcon::Information, 2500); - } -} - -QString NotificationManager::EnsureNotificationIconPath() { - if (!notification_icon_path_.isEmpty() && - QFileInfo::exists(notification_icon_path_)) { - return notification_icon_path_; - } - - if (tray_icon_ == nullptr || tray_icon_->icon().isNull()) { - return {}; - } - - QString cache_dir = - QStandardPaths::writableLocation(QStandardPaths::CacheLocation); - if (cache_dir.isEmpty()) { - cache_dir = QDir::tempPath() + QStringLiteral("/nearby-file-share"); - } - - QDir dir(cache_dir); - if (!dir.exists() && !dir.mkpath(QStringLiteral("."))) { - return {}; - } - - const QString icon_path = dir.filePath(QStringLiteral("notification-icon.png")); - const QPixmap icon_pixmap = tray_icon_->icon().pixmap(128, 128); - if (icon_pixmap.isNull() || !icon_pixmap.save(icon_path, "PNG")) { - return {}; - } - - notification_icon_path_ = icon_path; - return notification_icon_path_; -} - -void NotificationManager::ShowFallbackDialog(const QString& title, - const QString& body, - const QString& informative_text, - const QString& text_to_copy, - const QString& action_label) { - auto* message_box = - new QMessageBox(QMessageBox::Information, title, body, QMessageBox::NoButton); - message_box->setAttribute(Qt::WA_DeleteOnClose); - message_box->setTextFormat(Qt::PlainText); - message_box->setInformativeText(informative_text); - message_box->setWindowFlag(Qt::WindowStaysOnTopHint); - if (tray_icon_ != nullptr && !tray_icon_->icon().isNull()) { - message_box->setWindowIcon(tray_icon_->icon()); - } - - QAbstractButton* copy_button = - message_box->addButton(action_label, QMessageBox::ActionRole); - message_box->addButton(QMessageBox::Close); - - QObject::connect(message_box, &QMessageBox::buttonClicked, message_box, - [this, message_box, copy_button, text_to_copy]( - QAbstractButton* button) { - if (button == copy_button) { - CopyTextToClipboard(text_to_copy, QStringLiteral("Copied"), - QStringLiteral("Copied to clipboard.")); - } - message_box->close(); - }); - - message_box->show(); - message_box->raise(); - message_box->activateWindow(); -} diff --git a/sharing/linux/qml_tray_app/notification_manager.h b/sharing/linux/qml_tray_app/notification_manager.h deleted file mode 100644 index b90dcdf0..00000000 --- a/sharing/linux/qml_tray_app/notification_manager.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_NOTIFICATION_MANAGER_H_ -#define SHARING_LINUX_QML_TRAY_APP_NOTIFICATION_MANAGER_H_ - -#include - -#include -#include - -class QSystemTrayIcon; - -class NotificationManager : public QObject { - Q_OBJECT - - public: - explicit NotificationManager(QSystemTrayIcon* tray_icon, - QObject* parent = nullptr); - - void ShowNotification(const QString& title, const QString& body); - void ShowCopyableNotification(const QString& title, const QString& body, - const QString& text_to_copy, - const QString& action_label); - - private slots: - void OnActionInvoked(uint notification_id, const QString& action_key); - void OnNotificationClosed(uint notification_id, uint reason); - - private: - struct CopyActionState { - QString text_to_copy; - }; - - void CopyTextToClipboard(const QString& text_to_copy, - const QString& confirmation_title, - const QString& confirmation_body) const; - QString EnsureNotificationIconPath(); - void ShowFallbackDialog(const QString& title, const QString& body, - const QString& informative_text, - const QString& text_to_copy, - const QString& action_label); - - bool supports_actions_ = false; - QHash copy_actions_; - QString notification_icon_path_; - QSystemTrayIcon* tray_icon_ = nullptr; -}; - -#endif // SHARING_LINUX_QML_TRAY_APP_NOTIFICATION_MANAGER_H_ diff --git a/sharing/linux/qml_tray_app/qr_code_generator.cc b/sharing/linux/qml_tray_app/qr_code_generator.cc deleted file mode 100644 index f105b7ec..00000000 --- a/sharing/linux/qml_tray_app/qr_code_generator.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "qr_code_generator.h" -#include -#include "third_party/libqrencode/qrencode_compat.h" - -namespace QrCodeGenerator { - -QrCodeData GenerateQrCode(const QString& url) { - QrCodeData result; - result.size = 0; - - const QByteArray encoded_url = url.trimmed().toUtf8(); - if (encoded_url.isEmpty()) { - return result; - } - - QRcode* qr_code = QRcode_encodeData( - encoded_url.size(), - reinterpret_cast(encoded_url.constData()), 0, - QR_ECLEVEL_M); - - if (qr_code == nullptr || qr_code->data == nullptr || qr_code->width <= 0) { - if (qr_code != nullptr) { - QRcode_free(qr_code); - } - return result; - } - - result.size = qr_code->width; - result.rows.reserve(result.size); - - for (int row = 0; row < result.size; ++row) { - QString row_data; - row_data.reserve(result.size); - - for (int col = 0; col < result.size; ++col) { - const unsigned char module = - qr_code->data[row * result.size + col] & 0x1; - row_data.append(module ? QLatin1Char('1') : QLatin1Char('0')); - } - - result.rows.append(row_data); - } - - QRcode_free(qr_code); - return result; -} - -} // namespace QrCodeGenerator diff --git a/sharing/linux/qml_tray_app/qr_code_generator.h b/sharing/linux/qml_tray_app/qr_code_generator.h deleted file mode 100644 index 8506cfad..00000000 --- a/sharing/linux/qml_tray_app/qr_code_generator.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_QR_CODE_GENERATOR_H_ -#define SHARING_LINUX_QML_TRAY_APP_QR_CODE_GENERATOR_H_ - -#include -#include - -namespace QrCodeGenerator { - -struct QrCodeData { - QStringList rows; - int size; -}; - -QrCodeData GenerateQrCode(const QString& url); - -} // namespace QrCodeGenerator - -#endif // SHARING_LINUX_QML_TRAY_APP_QR_CODE_GENERATOR_H_ diff --git a/sharing/linux/qml_tray_app/resources_file_share.qrc b/sharing/linux/qml_tray_app/resources_file_share.qrc deleted file mode 100644 index 14d75129..00000000 --- a/sharing/linux/qml_tray_app/resources_file_share.qrc +++ /dev/null @@ -1,15 +0,0 @@ - - - FileShareTray.qml - components/AppHeader.qml - components/SideBar.qml - components/AnimatedBlob.qml - components/SendUrlPanel.qml - components/DeviceCard.qml - components/SettingsPanel.qml - - - tray_icon.png - tray_icon-symbolic.svg - - diff --git a/sharing/linux/qml_tray_app/share_target_model.h b/sharing/linux/qml_tray_app/share_target_model.h deleted file mode 100644 index 33e3a3df..00000000 --- a/sharing/linux/qml_tray_app/share_target_model.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_SHARE_TARGET_MODEL_H_ -#define SHARING_LINUX_QML_TRAY_APP_SHARE_TARGET_MODEL_H_ - -#include -#include - -class ShareTarget { - public: - explicit ShareTarget(qlonglong id, const QString& name, bool is_incoming) - : id_(id), name_(name), is_incoming_(is_incoming) {} - - qlonglong id() const { return id_; } - QString name() const { return name_; } - bool isIncoming() const { return is_incoming_; } - - QVariantMap toVariantMap() const { - QVariantMap map; - map[QStringLiteral("id")] = id_; - map[QStringLiteral("name")] = name_; - map[QStringLiteral("isIncoming")] = is_incoming_; - return map; - } - - private: - qlonglong id_; - QString name_; - bool is_incoming_; -}; - -#endif // SHARING_LINUX_QML_TRAY_APP_SHARE_TARGET_MODEL_H_ diff --git a/sharing/linux/qml_tray_app/status_mapper.cc b/sharing/linux/qml_tray_app/status_mapper.cc deleted file mode 100644 index 13dc677d..00000000 --- a/sharing/linux/qml_tray_app/status_mapper.cc +++ /dev/null @@ -1,44 +0,0 @@ -#include "status_mapper.h" - -namespace StatusMapper { - -QString TransferStatusToString(NearbySharingApi::TransferStatus status) { - return QString::fromStdString(NearbySharingApi::TransferStatusToString(status)); -} - -QString ApiStatusToString(NearbySharingApi::StatusCode status) { - return QString::fromStdString(NearbySharingApi::StatusCodeToString(status)); -} - -bool IsActiveTransferStatus(const QString& status) { - return status == QStringLiteral("Queued") || - status == QStringLiteral("Connecting") || - status == QStringLiteral("AwaitingLocalConfirmation") || - status == QStringLiteral("AwaitingRemoteAcceptance") || - status == QStringLiteral("InProgress"); -} - -bool IsFinalTransferStatus(NearbySharingApi::TransferStatus status) { - switch (status) { - case NearbySharingApi::TransferStatus::kComplete: - case NearbySharingApi::TransferStatus::kFailed: - case NearbySharingApi::TransferStatus::kRejected: - case NearbySharingApi::TransferStatus::kCancelled: - case NearbySharingApi::TransferStatus::kTimedOut: - case NearbySharingApi::TransferStatus::kMediaUnavailable: - case NearbySharingApi::TransferStatus::kNotEnoughSpace: - case NearbySharingApi::TransferStatus::kUnsupportedAttachmentType: - case NearbySharingApi::TransferStatus::kDeviceAuthenticationFailed: - case NearbySharingApi::TransferStatus::kIncompletePayloads: - return true; - case NearbySharingApi::TransferStatus::kUnknown: - case NearbySharingApi::TransferStatus::kConnecting: - case NearbySharingApi::TransferStatus::kAwaitingLocalConfirmation: - case NearbySharingApi::TransferStatus::kAwaitingRemoteAcceptance: - case NearbySharingApi::TransferStatus::kInProgress: - return false; - } - return false; -} - -} // namespace StatusMapper diff --git a/sharing/linux/qml_tray_app/status_mapper.h b/sharing/linux/qml_tray_app/status_mapper.h deleted file mode 100644 index fc3efd8f..00000000 --- a/sharing/linux/qml_tray_app/status_mapper.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_ -#define SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_ - -#include -#include "sharing/linux/nearby_sharing_api.h" - -using NearbySharingApi = nearby::sharing::NearbySharingApi; - -namespace StatusMapper { - -QString TransferStatusToString(NearbySharingApi::TransferStatus status); - -QString ApiStatusToString(NearbySharingApi::StatusCode status); - -bool IsActiveTransferStatus(const QString& status); - -bool IsFinalTransferStatus(NearbySharingApi::TransferStatus status); - -} // namespace StatusMapper - -#endif // SHARING_LINUX_QML_TRAY_APP_STATUS_MAPPER_H_ diff --git a/sharing/linux/qml_tray_app/string_utils.cc b/sharing/linux/qml_tray_app/string_utils.cc deleted file mode 100644 index 9ef97e7a..00000000 --- a/sharing/linux/qml_tray_app/string_utils.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include "string_utils.h" - -namespace StringUtils { - -QString TrimmedOrFallback(const QString& value, const QString& fallback) { - const QString trimmed = value.trimmed(); - return trimmed.isEmpty() ? fallback : trimmed; -} - -QString FromStdString(const std::string& value) { - return QString::fromStdString(value); -} - -QString TrimmedFromStdString(const std::string& value) { - return QString::fromStdString(value).trimmed(); -} - -} // namespace StringUtils diff --git a/sharing/linux/qml_tray_app/string_utils.h b/sharing/linux/qml_tray_app/string_utils.h deleted file mode 100644 index ebba9354..00000000 --- a/sharing/linux/qml_tray_app/string_utils.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_ -#define SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_ - -#include -#include - -namespace StringUtils { - -QString TrimmedOrFallback(const QString& value, const QString& fallback); - -QString FromStdString(const std::string& value); - -QString TrimmedFromStdString(const std::string& value); - -} // namespace StringUtils - -#endif // SHARING_LINUX_QML_TRAY_APP_STRING_UTILS_H_ diff --git a/sharing/linux/qml_tray_app/third_party/libqrencode/qrencode_compat.h b/sharing/linux/qml_tray_app/third_party/libqrencode/qrencode_compat.h deleted file mode 100644 index d52cec3c..00000000 --- a/sharing/linux/qml_tray_app/third_party/libqrencode/qrencode_compat.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_THIRD_PARTY_LIBQRENCODE_QRENCODE_COMPAT_H_ -#define SHARING_LINUX_QML_TRAY_APP_THIRD_PARTY_LIBQRENCODE_QRENCODE_COMPAT_H_ - -// Minimal libqrencode ABI used by this app when the runtime shared library is -// available but the development headers are not installed. - -#if defined(__cplusplus) -extern "C" { -#endif - -typedef enum { - QR_ECLEVEL_L = 0, - QR_ECLEVEL_M, - QR_ECLEVEL_Q, - QR_ECLEVEL_H -} QRecLevel; - -typedef struct { - int version; - int width; - unsigned char* data; -} QRcode; - -QRcode* QRcode_encodeData(int size, const unsigned char* data, int version, - QRecLevel level); -void QRcode_free(QRcode* qrcode); - -#if defined(__cplusplus) -} // extern "C" -#endif - -#endif // SHARING_LINUX_QML_TRAY_APP_THIRD_PARTY_LIBQRENCODE_QRENCODE_COMPAT_H_ diff --git a/sharing/linux/qml_tray_app/transfer_model.h b/sharing/linux/qml_tray_app/transfer_model.h deleted file mode 100644 index 9241c3a5..00000000 --- a/sharing/linux/qml_tray_app/transfer_model.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_ -#define SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_ - -#include -#include - -class Transfer { - public: - Transfer(qlonglong target_id, const QString& target_name, - const QString& status, double progress, qulonglong transferred_bytes, - const QString& direction, const QString& file_name) - : target_id_(target_id), - target_name_(target_name), - status_(status), - progress_(progress), - transferred_bytes_(transferred_bytes), - direction_(direction), - file_name_(file_name) {} - - qlonglong targetId() const { return target_id_; } - QString targetName() const { return target_name_; } - QString status() const { return status_; } - double progress() const { return progress_; } - qulonglong transferredBytes() const { return transferred_bytes_; } - QString direction() const { return direction_; } - QString fileName() const { return file_name_; } - - QVariantMap toVariantMap() const { - QVariantMap map; - map[QStringLiteral("targetId")] = target_id_; - map[QStringLiteral("targetName")] = target_name_; - map[QStringLiteral("status")] = status_; - map[QStringLiteral("progress")] = progress_; - map[QStringLiteral("transferredBytes")] = static_cast(transferred_bytes_); - map[QStringLiteral("direction")] = direction_; - map[QStringLiteral("fileName")] = file_name_; - return map; - } - - private: - qlonglong target_id_; - QString target_name_; - QString status_; - double progress_; - qulonglong transferred_bytes_; - QString direction_; - QString file_name_; -}; - -#endif // SHARING_LINUX_QML_TRAY_APP_TRANSFER_MODEL_H_ diff --git a/sharing/linux/qml_tray_app/tray_icon-symbolic.svg b/sharing/linux/qml_tray_app/tray_icon-symbolic.svg deleted file mode 100644 index 645e746a..00000000 --- a/sharing/linux/qml_tray_app/tray_icon-symbolic.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - diff --git a/sharing/linux/qml_tray_app/tray_icon.png b/sharing/linux/qml_tray_app/tray_icon.png deleted file mode 100644 index e9e17f54..00000000 Binary files a/sharing/linux/qml_tray_app/tray_icon.png and /dev/null differ