From b970cf4826efa9602c45b82fc22be387623bcc60 Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Fri, 19 Jun 2026 20:09:23 +0530 Subject: [PATCH] removed qml tray app --- sharing/linux/qml_tray_app/CMakeLists.txt | 60 -- sharing/linux/qml_tray_app/FileShareTray.qml | 110 ---- sharing/linux/qml_tray_app/README.md | 95 --- .../cmake/BuildNearbySoIfMissing.cmake | 80 --- .../cmake/BuildNearbyStaticArchive.cmake | 41 -- .../qml_tray_app/components/AnimatedBlob.qml | 416 ------------- .../qml_tray_app/components/AppHeader.qml | 62 -- .../qml_tray_app/components/DeviceCard.qml | 224 ------- .../qml_tray_app/components/SendUrlPanel.qml | 131 ---- .../qml_tray_app/components/SettingsPanel.qml | 234 ------- .../linux/qml_tray_app/components/SideBar.qml | 153 ----- .../linux/qml_tray_app/file_share_state.cc | 157 ----- sharing/linux/qml_tray_app/file_share_state.h | 122 ---- .../file_share_tray_controller.cc | 587 ------------------ .../qml_tray_app/file_share_tray_controller.h | 109 ---- .../qml_tray_app/file_share_tray_main.cpp | 240 ------- sharing/linux/qml_tray_app/icons/README.txt | 21 - ...onvertico-nearby-linux-desktop_128x128.png | Bin 6518 -> 0 bytes .../convertico-nearby-linux-desktop_16x16.png | Bin 643 -> 0 bytes ...onvertico-nearby-linux-desktop_256x256.png | Bin 18657 -> 0 bytes .../convertico-nearby-linux-desktop_32x32.png | Bin 1323 -> 0 bytes .../convertico-nearby-linux-desktop_48x48.png | Bin 1922 -> 0 bytes .../convertico-nearby-linux-desktop_64x64.png | Bin 2738 -> 0 bytes .../qml_tray_app/install_nearby_file_share.sh | 159 ----- .../qml_tray_app/nearby-file-share.desktop | 10 - .../qml_tray_app/nearby-linux-desktop.png | Bin 46351 -> 0 bytes .../qml_tray_app/notification_manager.cpp | 210 ------- .../linux/qml_tray_app/notification_manager.h | 47 -- .../linux/qml_tray_app/qr_code_generator.cc | 48 -- .../linux/qml_tray_app/qr_code_generator.h | 18 - .../qml_tray_app/resources_file_share.qrc | 15 - .../linux/qml_tray_app/share_target_model.h | 30 - sharing/linux/qml_tray_app/status_mapper.cc | 44 -- sharing/linux/qml_tray_app/status_mapper.h | 21 - sharing/linux/qml_tray_app/string_utils.cc | 18 - sharing/linux/qml_tray_app/string_utils.h | 17 - .../third_party/libqrencode/qrencode_compat.h | 32 - sharing/linux/qml_tray_app/transfer_model.h | 50 -- .../linux/qml_tray_app/tray_icon-symbolic.svg | 14 - sharing/linux/qml_tray_app/tray_icon.png | Bin 2092 -> 0 bytes 40 files changed, 3575 deletions(-) delete mode 100644 sharing/linux/qml_tray_app/CMakeLists.txt delete mode 100644 sharing/linux/qml_tray_app/FileShareTray.qml delete mode 100644 sharing/linux/qml_tray_app/README.md delete mode 100644 sharing/linux/qml_tray_app/cmake/BuildNearbySoIfMissing.cmake delete mode 100644 sharing/linux/qml_tray_app/cmake/BuildNearbyStaticArchive.cmake delete mode 100644 sharing/linux/qml_tray_app/components/AnimatedBlob.qml delete mode 100644 sharing/linux/qml_tray_app/components/AppHeader.qml delete mode 100644 sharing/linux/qml_tray_app/components/DeviceCard.qml delete mode 100644 sharing/linux/qml_tray_app/components/SendUrlPanel.qml delete mode 100644 sharing/linux/qml_tray_app/components/SettingsPanel.qml delete mode 100644 sharing/linux/qml_tray_app/components/SideBar.qml delete mode 100644 sharing/linux/qml_tray_app/file_share_state.cc delete mode 100644 sharing/linux/qml_tray_app/file_share_state.h delete mode 100644 sharing/linux/qml_tray_app/file_share_tray_controller.cc delete mode 100644 sharing/linux/qml_tray_app/file_share_tray_controller.h delete mode 100644 sharing/linux/qml_tray_app/file_share_tray_main.cpp delete mode 100644 sharing/linux/qml_tray_app/icons/README.txt delete mode 100644 sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_128x128.png delete mode 100644 sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_16x16.png delete mode 100644 sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_256x256.png delete mode 100644 sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_32x32.png delete mode 100644 sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_48x48.png delete mode 100644 sharing/linux/qml_tray_app/icons/convertico-nearby-linux-desktop_64x64.png delete mode 100755 sharing/linux/qml_tray_app/install_nearby_file_share.sh delete mode 100644 sharing/linux/qml_tray_app/nearby-file-share.desktop delete mode 100644 sharing/linux/qml_tray_app/nearby-linux-desktop.png delete mode 100644 sharing/linux/qml_tray_app/notification_manager.cpp delete mode 100644 sharing/linux/qml_tray_app/notification_manager.h delete mode 100644 sharing/linux/qml_tray_app/qr_code_generator.cc delete mode 100644 sharing/linux/qml_tray_app/qr_code_generator.h delete mode 100644 sharing/linux/qml_tray_app/resources_file_share.qrc delete mode 100644 sharing/linux/qml_tray_app/share_target_model.h delete mode 100644 sharing/linux/qml_tray_app/status_mapper.cc delete mode 100644 sharing/linux/qml_tray_app/status_mapper.h delete mode 100644 sharing/linux/qml_tray_app/string_utils.cc delete mode 100644 sharing/linux/qml_tray_app/string_utils.h delete mode 100644 sharing/linux/qml_tray_app/third_party/libqrencode/qrencode_compat.h delete mode 100644 sharing/linux/qml_tray_app/transfer_model.h delete mode 100644 sharing/linux/qml_tray_app/tray_icon-symbolic.svg delete mode 100644 sharing/linux/qml_tray_app/tray_icon.png 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 bff55a1c84404111a43be2d9c3157e8a042ceba9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6518 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 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 9e4daeeff0a0d00adc9db996f1152e453b73c744..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643 zcmV-}0(||6P)-+Ni{`~y@{l@s%$@tpL_}tI<-qQHs)cNAq`Q+O9 z=H2<|;Q8vq_}1U~>f-wB<@)aE`taEJ<=Ob=-TCS1`tjKLOfbs^Z61!H=`$&G!HK>iY5T z{PpSn@W$`igVe3Z?%MzV|E}Z7cFv^n{Pyer@u1+qO}>eI(WrmXs^RzSP```9>(=-G z`tAGk^8EI=>d;NUiQo6?fzqpd(5O$pimKtq{r~>#`t$Pq_w)Ssbp>{84vkS^xk53v^OWQvd(}D6_LDD6_LDpE?~T0002mNkl1e*#l$5f*%@H+QqnR&zAOg=gfAy0ub`+TDXXl) z0FhT!Q`gYcl2lgFW&q3U=<4Yk7#bOyXqz&C_^M{+7M51lHdZF4whSQkcJ>aAPR=f_ zZtk`o3_$bcJ-xhreEs|b0&RnW83cu-6+%L-!onjW18qHmqZovQqhn&@;u8{+OhEi( z2B7Gcv*aECvyv`?7PajNKvpTn1L4dzDR0V0=ynHjsO@O@Zcv_<39mjO-jB dK2)BI2>_UxFL)5m-!=dM002ovPDHLkV1nOsmZSgx 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 32242740582fbb553f4dcfb8ed0b748b7df841e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18657 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 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 0ec887ba8e848f7ef55f39428028d5ceedcf947a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1323 zcmZ9KdrZ?;6vqpV=wxmtD5$Uj3me;1MCXJpj8|YNGGu}4@@5k)}88wKSdl!q7j1ui{O6Ddv=HHw??<+yNYRjL+<4FFc`MYQ4ukrt>|zYI?|4g%CIE`rd!1H%4Hcy ziX>@@q)CdVI?=H%^wl#UoW<00m}UXfS}Sr)XYEvB1~raYg;NAhk;FgJ;7B(R^?>mg zV4@dH_JgSbF#QtD3<0qKNJfBk6wZ#pIU$@E!G#GZn}qTysF;C^VyKit)oWg_4fe02 z1gd7C+G?c1jnhyu1r!pXnu8kaR9f69!Bl$MtRQqUe91%uHGwQZ?GkS415*oxR*I=k z^xbr>KIZr*XK(9;LA}I4TI5hSg(|kyWvnPam?fyn7e0KyhHHN(QDBhjNwbbHPs7D= zRHmko(B^Y%Dh`RvGK?kwjhSiq!Gn~D){8hzEJD4WFl%Wer19sv$#JtxOB!c^MvAHn zM2{W$90OsQ1oAmtEhm<)%`_)=7x9Lx2J|xsh0X&FP18BUcb)GZo5wXi4OxuRn9mw` zF4e~wx1tKi>$QY&#-g;l9e=W~#JU=UuzXaT>cBrTXb|ftlXXp5Lsi~&r^Oc4dOq9h zQHG8%Ap~k9(5;Ws*YVjq9%r(;i!C%1=%o%-M>f}f7>%VvSvNTHX<8@YX@{Ml|31i*sB-gh1~+>DoZw73b7)-O7OCYJ~kpm0dx*fMF$u;n&>C9~4} zPWt}HXlwlcIHn7Q>5d5r3I8R+07qYMEAO^@LV{v^``IiOXKq$jR9qAn6|+C~>jTju z!Ptm!_|>S7)M+gzIcY_o{l-m6MLxT>f9jBYl-=Q(!gcQ&>^QeiJQ98-&*?!+{RXD% zp)<~@c3Y5!)8C)rt+d;DCM?V|fql&BlepTJf^*Ky>n%0S7pkr{7H*3W7Y22@hWfjC zf9UJ6bBkZsruTHubC0yMS)7>kh?C!D2bZr1@xNqqFy~bGiTJE9lVeXuau4~3wB_1N zWRqRxbC*XRAp`m0J}Y^(+3T6XZGTlZ)U=g7>@9DqsNd>R-hMaV#$8e)8J>7GGdkrY z4oI5w*nE-T*gb*ThUm)38~$why_ShI`R4;^%>JRn9gMVgp0q1;m`S-kP8Yk~MA*e42y*a;teq<_(=v(wsu)ef$#< zWOtl!nM?({li_eB`*}jXi_2QXbHg77bv80~bF?Ua@a7kplu* zct4iQ$4dQ<28QSCs?~>G>nWyh7E!*z3JYTAbt6>vl1YI=_xt=gq}S+kcg_SV`-H ugaNJxN{Wi|iVG5BBJ8ShkBTS0O*S=6sm^cMZ}D1YWlv~iNKG*7$G-s&Pfzy% 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 6e615b64658bf4d3adbd7abc7fe0412fb4e3fac2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1922 zcmV-|2YvX7P)~lXQ=+;Of|HiSkysAk~#vd23wO9Iv`0Ufs(Z3v3YDZcX#hS{;>}hl9=7FKr@-i zoZLD0+|N1pcYfz&&&sh#GXhBZCtWTQs1}h;AXNh8W7L_rECRXIPnJPC1!)lBpdj^v z431g9KGJ;sMV}zs1a>AKo+CIv_~iZ9T(1>mk06n&Dv*XqOJ*bU1!)!FcHz^%W!yRo z1ZhjPtv*b6CdMO3D=+m4rs@u`}vfKL(~5Rx%JRq&~IwG9#E?~yY_0s~VF z4F4^ZkU;N`a0+0%TO|Ms*)JPJzTDG2iT z{7VA<4@kkaSlq+5kG#anrW_R;po^Jdt1^yuW9<1Su z6)RCF6hcS1n52d}W}R2^<#33XLl z*if;GJ}XSGHAJ^H$Q*x=eV?ddTCkFrT90#npc9|zMo$p)*JmY_Z@ktT%Z@YKU&^x& z)^M=;Db|)R=883dOPRQofm@k`qQg`cOyl!&mlF;5@J{FwUgbukv&N5`g(ib{v_qlG zi&wd^oH%IB)`jbc5~s@wVJhRAIiiwbGR;#2+94db((#`ye#|U1nF*^&q{^o53%v5~ z8?^Lx5VhmXE1J&w%KKP1cO@|=o=lgSc?QmeE`jN$+!w$hF^RLqXLR7kQf0th!1Je% zu=C9WFdPDJ5h#N6I(mPoj9{R0se6D6i3_tMP+av%ggRy z*p8099*l#RL>*;d&5DvccwojtHa9$n62*ueW4fn=zjd8qL;YUD;U0dt`U$?aU@f6Z;02Ht zt9`tqV;uzrLH1XFk5A3Io2VV<$&&Ui9ID3@$g>M7*FyKk`j)P4#J%c!f`!rEDj zxH2-pG;c98yd`vXx3T`%9=0xdgjLfPpwTp3KFu>NuM&;)v3=E7*?G@n^u~rzlNk9# zH9<~W9XeZlD*Xw3SOd6~fuQgjE_S^23O{Q(4AvmHe1L{% z1fy^|KUw)@o?5(){@5@ga&`NVBYspCnyBmme#1jk&jrr)Twu+dC46D-3c90%xRses zs!=<}u6rNnk&1gcbfJ;9!7dCjSQ@Ng7N{T;8=iF7WNtNb3n8sBl|j$Q5X3F+ zES`lasRfFR_XNGx5Nl^FqI$;1v52G5xG{<7D7}-Mu-D_$Stz^ABZz?3nj)7Ml*#*% z{&YG`H4H+`v6=2E;b>BY%dUU^nu|l-_)QOnsJw(t=5>`VUJ#ThVzy1W zuaug*s&T@-)STGIsh(DXot&;zfS{&QVlBkcVb3VWuy@`GHRU@m>pPP3`I@_|ejhgyO>)1Cl>H zfSk8Fb*^|cm`Y)Y;`uX2c=6mZI{Q1pj)BX|qOy2o1`Tolc#VyJ_MJx);p~&5MW<$1i@F)|n z>Bc}|Ylny(#YtwIn~LZ9?R1WK0Ze6JYlorONcx%*bxZJ&T*#OtKHZQAD$57v6EA=w zaT=Jf&nj;z9;8!A;wW<};t^<2$#dALhzB?*9ntP0K?bHE9zlwgBu>C-(>95=5hUZ4{odOMz___@+I96N#160&_Suwhv`2YX_07*qo IM6N<$f`l-uh5!Hn 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 030ea29e0e8ce4224058d63e39965bf6d88d8339..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2738 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 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 2273e633c9787b5875bd9148cdd648e9a5bb56cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46351 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 jL=dR*#AoZ@P -#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 e9e17f54944c30e1d0ee794d44bbcc253a03acc8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2092 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq8sKd6mGxU^Rn*LA+qju0R{0zJCEe zA+A6LqhMr)!2kdMZI`mt0X@H>B*-tAfsuoUo1IlTe&*hzhj;Y)OKD7f^ykO5YVlit z4>}ire*59qV_&w)FK27r3{Cy|?>bgID^;H;`Tc1?`PZ6HPkg3amnnO2`)9D(1g+4W z2FLkyUY_D-5tbABwx83gY3=!QYZ@))87+9HFM8r%N@i*a*TeU->T^xxlily=HVa%% z@bYAmh`1WtQl)qA(42g8OQ+M?yEJ?5-!u*^_~;!}H0`;IL-gC*}UpP-4Gj5Z5g^wJd-4K#Gr-6~?Z`@Qq-_qXgf-(>&9SJN=x|1>A=Pn`pCMD} zw>dr)+s)XIJDK7Vb> zugkAL{(gQXCDi7yQBPXIohNAyU-mSYY*rH}(Pd!{Qccu($2R@XhP|b;+vCe7*G2cp z-RW8{w{7)B%grYfllLCJZ0!GG+3q#!w`G)Xyb&ng{`%3E6W32v#fnC$&zFAqL2dQ% z+Wx-FkK>cfWF{g?fs)+|r!s2iXEdHg#2 z>!hX^M4>QXt?%^LSS zd)%*>_s+UJpY{Ia2c4hR+PJg)b)Ki~&7~ecY4hB#!M|RApTA%71KCiVpH@YJFJnVhX)rf05a zp=+rCw$i{z*T7IA(9ldFqokz3N?*Ucyj(96q#B5Us`c`V(jBWGodB9B0WvWpqck_k z%E~1_ximL5uf)nK0I0N>!EpKi-TG*1z;;?$Wu#`NXOu9Q8X6tA5D*AdBaUPaR84qh zN=XJtiA)Xi9iS3PBqhF?xv3?U1*r^RSLqkzrQ2@`^FuSoHw37P!O+y)%*@ij#Mofz zFV=5BCBjJN1ZP$ORT$}+dpkKf6$Ntxr5%u@LxVgS(lT>WfkB|BUs{lppO{jtZ)&C= zR+eR9VpO4TWMFBbpOcxF9iN$;pBHavWMN@st_O6Dj7M=R&@2W|S3j3^P6