diff --git a/sharing/linux/qml_tray_app/FileShareTray.qml b/sharing/linux/qml_tray_app/FileShareTray.qml index 02de5ee9..ded62aa1 100644 --- a/sharing/linux/qml_tray_app/FileShareTray.qml +++ b/sharing/linux/qml_tray_app/FileShareTray.qml @@ -21,11 +21,17 @@ ApplicationWindow { fileShareController.hideToTray() } + SettingsPanel { + id: settingsPanel + } + ColumnLayout { anchors.fill: parent spacing: 0 - AppHeader {} + AppHeader { + onSettingsRequested: settingsPanel.open() + } // ── Body ───────────────────────────────────────────────────────── RowLayout { diff --git a/sharing/linux/qml_tray_app/components/AppHeader.qml b/sharing/linux/qml_tray_app/components/AppHeader.qml index e3fdceba..ed19b58c 100644 --- a/sharing/linux/qml_tray_app/components/AppHeader.qml +++ b/sharing/linux/qml_tray_app/components/AppHeader.qml @@ -6,6 +6,8 @@ Item { Layout.fillWidth: true height: 80 + signal settingsRequested() + readonly property color textPrimary: "#111827" readonly property color textMuted: "#6b7280" readonly property color accent: "#16a34a" @@ -60,5 +62,28 @@ Item { onClicked: fileShareController.running ? fileShareController.stop() : fileShareController.start() } } + + 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/file_share_tray_controller.cc b/sharing/linux/qml_tray_app/file_share_tray_controller.cc index d3c06dcc..ba480503 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.cc +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.cc @@ -23,6 +23,30 @@ bool IsTerminalPayloadStatus(NearbyConnectionsQtFacade::PayloadStatus status) { status == NearbyConnectionsQtFacade::PayloadStatus::kCanceled; } +QString NormalizeConnectionStrategy(const QString& strategy) { + const QString token = strategy.trimmed().toLower(); + if (token == QStringLiteral("p2pstar") || token == QStringLiteral("star")) { + return QStringLiteral("P2pStar"); + } + if (token == QStringLiteral("p2ppointtopoint") || + token == QStringLiteral("pointtopoint") || + token == QStringLiteral("point_to_point")) { + return QStringLiteral("P2pPointToPoint"); + } + return QStringLiteral("P2pCluster"); +} + +NearbyConnectionsQtFacade::Strategy StrategyFromName( + const QString& normalized_strategy) { + if (normalized_strategy == QStringLiteral("P2pStar")) { + return NearbyConnectionsQtFacade::Strategy::kP2pStar; + } + if (normalized_strategy == QStringLiteral("P2pPointToPoint")) { + return NearbyConnectionsQtFacade::Strategy::kP2pPointToPoint; + } + return NearbyConnectionsQtFacade::Strategy::kP2pCluster; +} + } // namespace FileShareTrayController::FileShareTrayController(QObject* parent) @@ -54,11 +78,49 @@ void FileShareTrayController::LoadSettings() { if (!stored_device_name.isEmpty()) { device_name_ = stored_device_name; } + + auto_accept_incoming_ = + settings.value(QStringLiteral("autoAcceptIncoming"), auto_accept_incoming_) + .toBool(); + bluetooth_enabled_ = + settings.value(QStringLiteral("bluetoothEnabled"), bluetooth_enabled_).toBool(); + ble_enabled_ = + settings.value(QStringLiteral("bleEnabled"), ble_enabled_).toBool(); + wifi_lan_enabled_ = + settings.value(QStringLiteral("wifiLanEnabled"), wifi_lan_enabled_).toBool(); + wifi_hotspot_enabled_ = + settings.value(QStringLiteral("wifiHotspotEnabled"), wifi_hotspot_enabled_).toBool(); + web_rtc_enabled_ = + settings.value(QStringLiteral("webRtcEnabled"), web_rtc_enabled_).toBool(); + connection_strategy_ = NormalizeConnectionStrategy( + settings.value(QStringLiteral("connectionStrategy"), connection_strategy_) + .toString()); + + const QString stored_service_id = + settings.value(QStringLiteral("serviceId"), serviceId()).toString().trimmed(); + if (!stored_service_id.isEmpty()) { + service_id_ = stored_service_id.toStdString(); + } + + const QString stored_log_path = + settings.value(QStringLiteral("logPath"), log_path_).toString().trimmed(); + if (!stored_log_path.isEmpty()) { + log_path_ = stored_log_path; + } } void FileShareTrayController::SaveSettings() const { QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlFileTrayApp")); settings.setValue(QStringLiteral("deviceName"), device_name_); + settings.setValue(QStringLiteral("autoAcceptIncoming"), auto_accept_incoming_); + settings.setValue(QStringLiteral("bluetoothEnabled"), bluetooth_enabled_); + settings.setValue(QStringLiteral("bleEnabled"), ble_enabled_); + settings.setValue(QStringLiteral("wifiLanEnabled"), wifi_lan_enabled_); + settings.setValue(QStringLiteral("wifiHotspotEnabled"), wifi_hotspot_enabled_); + settings.setValue(QStringLiteral("webRtcEnabled"), web_rtc_enabled_); + settings.setValue(QStringLiteral("connectionStrategy"), connection_strategy_); + settings.setValue(QStringLiteral("serviceId"), serviceId()); + settings.setValue(QStringLiteral("logPath"), log_path_); settings.sync(); } @@ -79,6 +141,73 @@ void FileShareTrayController::setDeviceName(const QString& device_name) { } } +void FileShareTrayController::setAutoAcceptIncoming(bool enabled) { + if (auto_accept_incoming_ == enabled) return; + auto_accept_incoming_ = enabled; + emit autoAcceptIncomingChanged(); + SaveSettings(); +} + +void FileShareTrayController::setBluetoothEnabled(bool enabled) { + if (bluetooth_enabled_ == enabled) return; + bluetooth_enabled_ = enabled; + emit bluetoothEnabledChanged(); + SaveSettings(); +} + +void FileShareTrayController::setBleEnabled(bool enabled) { + if (ble_enabled_ == enabled) return; + ble_enabled_ = enabled; + emit bleEnabledChanged(); + SaveSettings(); +} + +void FileShareTrayController::setWifiLanEnabled(bool enabled) { + if (wifi_lan_enabled_ == enabled) return; + wifi_lan_enabled_ = enabled; + emit wifiLanEnabledChanged(); + SaveSettings(); +} + +void FileShareTrayController::setWifiHotspotEnabled(bool enabled) { + if (wifi_hotspot_enabled_ == enabled) return; + wifi_hotspot_enabled_ = enabled; + emit wifiHotspotEnabledChanged(); + SaveSettings(); +} + +void FileShareTrayController::setWebRtcEnabled(bool enabled) { + if (web_rtc_enabled_ == enabled) return; + web_rtc_enabled_ = enabled; + emit webRtcEnabledChanged(); + SaveSettings(); +} + +void FileShareTrayController::setConnectionStrategy(const QString& strategy) { + const QString normalized = NormalizeConnectionStrategy(strategy); + if (connection_strategy_ == normalized) return; + connection_strategy_ = normalized; + emit connectionStrategyChanged(); + SaveSettings(); +} + +void FileShareTrayController::setServiceId(const QString& service_id) { + const QString trimmed = service_id.trimmed(); + if (trimmed.isEmpty() || trimmed.toStdString() == service_id_) return; + service_id_ = trimmed.toStdString(); + emit serviceIdChanged(); + SaveSettings(); +} + +void FileShareTrayController::setLogPath(const QString& path) { + const QString trimmed = path.trimmed(); + if (trimmed.isEmpty() || trimmed == log_path_) return; + log_path_ = trimmed; + emit logPathChanged(); + SaveSettings(); + ReopenLogFile(); +} + void FileShareTrayController::start() { if (running_) { return; @@ -327,10 +456,13 @@ FileShareTrayController::BuildConnectionListener() { if (incoming) { SetStatus(QStringLiteral("Incoming connection from %1").arg(peer)); + if (auto_accept_incoming_) { + acceptIncomingInternal(endpoint); + } + } else { + // Always accept outgoing connections (initiated by us for send). + acceptIncomingInternal(endpoint); } - - // Always auto-accept connections for this app. - acceptIncomingInternal(endpoint); }, Qt::QueuedConnection); }; @@ -576,18 +708,18 @@ FileShareTrayController::BuildPayloadListener() { NearbyConnectionsQtFacade::MediumSelection FileShareTrayController::BuildMediumSelection() const { NearbyConnectionsQtFacade::MediumSelection selection; - selection.bluetooth = true; - selection.ble = true; - selection.wifi_lan = true; - selection.wifi_hotspot = true; - selection.web_rtc = false; + selection.bluetooth = bluetooth_enabled_; + selection.ble = ble_enabled_; + selection.wifi_lan = wifi_lan_enabled_; + selection.wifi_hotspot = wifi_hotspot_enabled_; + selection.web_rtc = web_rtc_enabled_; return selection; } NearbyConnectionsQtFacade::AdvertisingOptions FileShareTrayController::BuildAdvertisingOptions() const { NearbyConnectionsQtFacade::AdvertisingOptions options; - options.strategy = NearbyConnectionsQtFacade::Strategy::kP2pPointToPoint; + options.strategy = StrategyFromName(connection_strategy_); options.allowed_mediums = BuildMediumSelection(); options.auto_upgrade_bandwidth = true; options.enable_bluetooth_listening = true; @@ -598,7 +730,7 @@ FileShareTrayController::BuildAdvertisingOptions() const { NearbyConnectionsQtFacade::DiscoveryOptions FileShareTrayController::BuildDiscoveryOptions() const { NearbyConnectionsQtFacade::DiscoveryOptions options; - options.strategy = NearbyConnectionsQtFacade::Strategy::kP2pPointToPoint; + options.strategy = StrategyFromName(connection_strategy_); options.allowed_mediums = BuildMediumSelection(); return options; } diff --git a/sharing/linux/qml_tray_app/file_share_tray_controller.h b/sharing/linux/qml_tray_app/file_share_tray_controller.h index a6aaec69..0e89ddd2 100644 --- a/sharing/linux/qml_tray_app/file_share_tray_controller.h +++ b/sharing/linux/qml_tray_app/file_share_tray_controller.h @@ -30,6 +30,15 @@ class FileShareTrayController : public QObject { Q_PROPERTY(QStringList connectedDevices READ connectedDevices NOTIFY connectedDevicesChanged) Q_PROPERTY(QVariantMap endpointMediums READ endpointMediums NOTIFY endpointMediumsChanged) Q_PROPERTY(QVariantList transfers READ transfers NOTIFY transfersChanged) + Q_PROPERTY(bool autoAcceptIncoming READ autoAcceptIncoming WRITE setAutoAcceptIncoming NOTIFY autoAcceptIncomingChanged) + Q_PROPERTY(bool bluetoothEnabled READ bluetoothEnabled WRITE setBluetoothEnabled NOTIFY bluetoothEnabledChanged) + Q_PROPERTY(bool bleEnabled READ bleEnabled WRITE setBleEnabled NOTIFY bleEnabledChanged) + Q_PROPERTY(bool wifiLanEnabled READ wifiLanEnabled WRITE setWifiLanEnabled NOTIFY wifiLanEnabledChanged) + Q_PROPERTY(bool wifiHotspotEnabled READ wifiHotspotEnabled WRITE setWifiHotspotEnabled NOTIFY wifiHotspotEnabledChanged) + Q_PROPERTY(bool webRtcEnabled READ webRtcEnabled WRITE setWebRtcEnabled NOTIFY webRtcEnabledChanged) + Q_PROPERTY(QString connectionStrategy READ connectionStrategy WRITE setConnectionStrategy NOTIFY connectionStrategyChanged) + Q_PROPERTY(QString serviceId READ serviceId WRITE setServiceId NOTIFY serviceIdChanged) + Q_PROPERTY(QString logPath READ logPath WRITE setLogPath NOTIFY logPathChanged) public: explicit FileShareTrayController(QObject* parent = nullptr); @@ -51,6 +60,33 @@ class FileShareTrayController : public QObject { QVariantMap endpointMediums() const { return endpoint_mediums_; } QVariantList transfers() const { return transfers_; } + bool autoAcceptIncoming() const { return auto_accept_incoming_; } + void setAutoAcceptIncoming(bool enabled); + + bool bluetoothEnabled() const { return bluetooth_enabled_; } + void setBluetoothEnabled(bool enabled); + + bool bleEnabled() const { return ble_enabled_; } + void setBleEnabled(bool enabled); + + bool wifiLanEnabled() const { return wifi_lan_enabled_; } + void setWifiLanEnabled(bool enabled); + + bool wifiHotspotEnabled() const { return wifi_hotspot_enabled_; } + void setWifiHotspotEnabled(bool enabled); + + bool webRtcEnabled() const { return web_rtc_enabled_; } + void setWebRtcEnabled(bool enabled); + + QString connectionStrategy() const { return connection_strategy_; } + void setConnectionStrategy(const QString& strategy); + + QString serviceId() const { return QString::fromStdString(service_id_); } + void setServiceId(const QString& service_id); + + QString logPath() const { return log_path_; } + void setLogPath(const QString& path); + Q_INVOKABLE void start(); Q_INVOKABLE void stop(); Q_INVOKABLE void switchToReceiveMode(); @@ -72,6 +108,15 @@ class FileShareTrayController : public QObject { void connectedDevicesChanged(); void endpointMediumsChanged(); void transfersChanged(); + void autoAcceptIncomingChanged(); + void bluetoothEnabledChanged(); + void bleEnabledChanged(); + void wifiLanEnabledChanged(); + void wifiHotspotEnabledChanged(); + void webRtcEnabledChanged(); + void connectionStrategyChanged(); + void serviceIdChanged(); + void logPathChanged(); void requestTrayMessage(const QString& title, const QString& body); @@ -129,10 +174,19 @@ class FileShareTrayController : public QObject { QString mode_ = QStringLiteral("Receive"); QString device_name_ = QStringLiteral("NearbyQtFile"); - const std::string service_id_ = "com.nearby.qml.tray"; + std::string service_id_ = "com.nearby.qml.tray"; QString status_message_ = QStringLiteral("Idle"); bool running_ = false; + bool auto_accept_incoming_ = true; + bool bluetooth_enabled_ = true; + bool ble_enabled_ = true; + bool wifi_lan_enabled_ = true; + bool wifi_hotspot_enabled_ = true; + bool web_rtc_enabled_ = false; + QString connection_strategy_ = QStringLiteral("P2pPointToPoint"); + QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log"); + QString pending_send_file_path_; QString pending_send_file_name_; QString target_endpoint_for_send_; @@ -153,7 +207,6 @@ class FileShareTrayController : public QObject { QHash outgoing_file_payload_to_name_; QSet send_terminal_notified_; - QString log_path_ = QStringLiteral("/tmp/nearby_qml_file_tray.log"); QFile log_file_; }; diff --git a/sharing/linux/qml_tray_app/resources_file_share.qrc b/sharing/linux/qml_tray_app/resources_file_share.qrc index b5d8797e..c28debe5 100644 --- a/sharing/linux/qml_tray_app/resources_file_share.qrc +++ b/sharing/linux/qml_tray_app/resources_file_share.qrc @@ -6,6 +6,7 @@ components/AnimatedBlob.qml components/DeviceCard.qml components/TransferCard.qml + components/SettingsPanel.qml tray_icon.png