Files
nearby/sharing/linux/qml_tray_app/nearby_tray_controller.cc
T

1214 lines
42 KiB
C++

#include "nearby_tray_controller.h"
#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QMetaObject>
#include <QSettings>
#include <QSysInfo>
#include <QTextStream>
#include <memory>
namespace {
using NearbyConnectionsQtFacade =
nearby::sharing::NearbyConnectionsQtFacade;
QString NormalizeMediumsMode(QString mode) {
mode = mode.trimmed().toLower();
if (mode == QStringLiteral("all") || mode == QStringLiteral("bluetooth") ||
mode == QStringLiteral("ble") || mode == QStringLiteral("wifi_lan") ||
mode == QStringLiteral("wifi_hotspot") ||
mode == QStringLiteral("web_rtc") ||
mode == QStringLiteral("balanced")) {
return mode;
}
return QStringLiteral("balanced");
}
NearbyConnectionsQtFacade::MediumSelection BuildMediumSelectionForMode(
const QString& normalized_mode) {
NearbyConnectionsQtFacade::MediumSelection selection;
selection.bluetooth = false;
selection.ble = false;
selection.web_rtc = false;
selection.wifi_lan = false;
selection.wifi_hotspot = false;
if (normalized_mode == QStringLiteral("all")) {
selection.bluetooth = true;
selection.ble = true;
selection.web_rtc = true;
selection.wifi_lan = true;
selection.wifi_hotspot = true;
} else if (normalized_mode == QStringLiteral("bluetooth")) {
selection.bluetooth = true;
} else if (normalized_mode == QStringLiteral("ble")) {
selection.ble = true;
} else if (normalized_mode == QStringLiteral("wifi_lan")) {
selection.wifi_lan = true;
} else if (normalized_mode == QStringLiteral("wifi_hotspot")) {
selection.wifi_hotspot = true;
} else if (normalized_mode == QStringLiteral("web_rtc")) {
selection.web_rtc = true;
} else {
// Balanced default mirrors previous behavior.
selection.bluetooth = true;
selection.ble = true;
selection.wifi_lan = true;
}
return selection;
}
QString NormalizeConnectionStrategy(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
NearbyTrayController::NearbyTrayController(QObject* parent) : QObject(parent) {
const QString host = QSysInfo::machineHostName();
if (!host.isEmpty()) {
device_name_ = host;
}
LoadSettings();
ReopenLogFile();
LogLine(QStringLiteral("Started Nearby tray controller"));
}
NearbyTrayController::~NearbyTrayController() {
stop();
if (log_file_.isOpen()) {
log_file_.close();
}
}
void NearbyTrayController::LoadSettings() {
QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlTrayApp"));
const QString stored_mode =
settings.value(QStringLiteral("mode"), mode_).toString();
mode_ = stored_mode.trimmed().toLower() == QStringLiteral("send")
? QStringLiteral("Send")
: QStringLiteral("Receive");
const QString stored_device_name =
settings.value(QStringLiteral("deviceName"), device_name_)
.toString()
.trimmed();
if (!stored_device_name.isEmpty()) {
device_name_ = stored_device_name;
}
const QString stored_service_id =
settings
.value(QStringLiteral("serviceId"),
QString::fromStdString(service_id_))
.toString()
.trimmed();
if (!stored_service_id.isEmpty()) {
service_id_ = stored_service_id.toStdString();
}
mediums_mode_ = NormalizeMediumsMode(
settings.value(QStringLiteral("mediumsMode"), mediums_mode_).toString());
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();
auto_accept_incoming_ = settings
.value(QStringLiteral("autoAcceptIncoming"),
auto_accept_incoming_)
.toBool();
connection_strategy_ = NormalizeConnectionStrategy(
settings
.value(QStringLiteral("connectionStrategy"), connection_strategy_)
.toString());
const QString stored_log_path =
settings.value(QStringLiteral("logPath"), log_path_).toString().trimmed();
if (!stored_log_path.isEmpty()) {
log_path_ = stored_log_path;
}
}
void NearbyTrayController::SaveSettings() const {
QSettings settings(QStringLiteral("Nearby"), QStringLiteral("QmlTrayApp"));
settings.setValue(QStringLiteral("mode"), mode_);
settings.setValue(QStringLiteral("deviceName"), device_name_);
settings.setValue(QStringLiteral("serviceId"),
QString::fromStdString(service_id_));
settings.setValue(QStringLiteral("mediumsMode"), mediums_mode_);
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("autoAcceptIncoming"), auto_accept_incoming_);
settings.setValue(QStringLiteral("connectionStrategy"), connection_strategy_);
settings.setValue(QStringLiteral("logPath"), log_path_);
settings.sync();
}
void NearbyTrayController::setMode(const QString& mode) {
const QString normalized =
mode.trimmed().toLower() == QStringLiteral("send")
? QStringLiteral("Send")
: QStringLiteral("Receive");
if (mode_ == normalized) {
return;
}
mode_ = normalized;
emit modeChanged();
SaveSettings();
LogLine(QStringLiteral("Mode changed to %1").arg(mode_));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setDeviceName(const QString& device_name) {
const QString trimmed = device_name.trimmed();
if (trimmed.isEmpty() || trimmed == device_name_) {
return;
}
device_name_ = trimmed;
emit deviceNameChanged();
SaveSettings();
LogLine(QStringLiteral("Device name changed to %1").arg(device_name_));
}
void NearbyTrayController::setServiceId(const QString& service_id) {
const std::string value = service_id.trimmed().toStdString();
if (value.empty() || value == service_id_) {
return;
}
service_id_ = value;
emit serviceIdChanged();
SaveSettings();
LogLine(QStringLiteral("Service ID changed to %1")
.arg(QString::fromStdString(service_id_)));
}
void NearbyTrayController::setMediumsMode(const QString& mode) {
const QString normalized = NormalizeMediumsMode(mode);
if (mediums_mode_ == normalized) {
return;
}
mediums_mode_ = normalized;
emit mediumsModeChanged();
SaveSettings();
LogLine(QStringLiteral("Mediums mode changed to %1").arg(mediums_mode_));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setBluetoothEnabled(bool enabled) {
if (bluetooth_enabled_ == enabled) {
return;
}
bluetooth_enabled_ = enabled;
emit bluetoothEnabledChanged();
SaveSettings();
LogLine(QStringLiteral("Bluetooth %1").arg(enabled ? "enabled" : "disabled"));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setBleEnabled(bool enabled) {
if (ble_enabled_ == enabled) {
return;
}
ble_enabled_ = enabled;
emit bleEnabledChanged();
SaveSettings();
LogLine(QStringLiteral("BLE %1").arg(enabled ? "enabled" : "disabled"));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setWifiLanEnabled(bool enabled) {
if (wifi_lan_enabled_ == enabled) {
return;
}
wifi_lan_enabled_ = enabled;
emit wifiLanEnabledChanged();
SaveSettings();
LogLine(QStringLiteral("WiFi LAN %1").arg(enabled ? "enabled" : "disabled"));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setWifiHotspotEnabled(bool enabled) {
if (wifi_hotspot_enabled_ == enabled) {
return;
}
wifi_hotspot_enabled_ = enabled;
emit wifiHotspotEnabledChanged();
SaveSettings();
LogLine(QStringLiteral("WiFi Hotspot %1").arg(enabled ? "enabled" : "disabled"));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setWebRtcEnabled(bool enabled) {
if (web_rtc_enabled_ == enabled) {
return;
}
web_rtc_enabled_ = enabled;
emit webRtcEnabledChanged();
SaveSettings();
LogLine(QStringLiteral("WebRTC %1").arg(enabled ? "enabled" : "disabled"));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setAutoAcceptIncoming(bool enabled) {
if (auto_accept_incoming_ == enabled) {
return;
}
auto_accept_incoming_ = enabled;
emit autoAcceptIncomingChanged();
SaveSettings();
LogLine(QStringLiteral("Auto-accept incoming connections %1")
.arg(enabled ? "enabled" : "disabled"));
}
void NearbyTrayController::setConnectionStrategy(const QString& strategy) {
const QString normalized = NormalizeConnectionStrategy(strategy);
if (connection_strategy_ == normalized) {
return;
}
connection_strategy_ = normalized;
emit connectionStrategyChanged();
SaveSettings();
LogLine(QStringLiteral("Connection strategy changed to %1")
.arg(connection_strategy_));
if (running_) {
stop();
start();
}
}
void NearbyTrayController::setLogPath(const QString& path) {
const QString trimmed = path.trimmed();
if (trimmed.isEmpty() || trimmed == log_path_) {
return;
}
log_path_ = trimmed;
emit logPathChanged();
SaveSettings();
ReopenLogFile();
LogLine(QStringLiteral("Log path changed to %1").arg(log_path_));
}
void NearbyTrayController::start() {
if (running_) {
return;
}
running_ = true;
emit runningChanged();
if (mode_ == QStringLiteral("Send")) {
startSendMode();
} else {
startReceiveMode();
}
}
void NearbyTrayController::stop() {
if (!running_) {
return;
}
running_ = false;
emit runningChanged();
service_.StopDiscovery(
service_id_, [this](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, status]() {
LogLine(QStringLiteral("StopDiscovery: %1")
.arg(StatusToString(status)));
},
Qt::QueuedConnection);
});
service_.StopAdvertising(
service_id_, [this](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, status]() {
LogLine(QStringLiteral("StopAdvertising: %1")
.arg(StatusToString(status)));
},
Qt::QueuedConnection);
});
service_.StopAllEndpoints(
[this](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, status]() {
LogLine(QStringLiteral("StopAllEndpoints: %1")
.arg(StatusToString(status)));
},
Qt::QueuedConnection);
});
discovered_devices_.clear();
connected_devices_.clear();
pending_connections_.clear();
endpoint_peer_names_.clear();
endpoint_mediums_.clear();
emit discoveredDevicesChanged();
emit connectedDevicesChanged();
emit pendingConnectionsChanged();
emit endpointMediumsChanged();
SetStatus(QStringLiteral("Stopped"));
}
void NearbyTrayController::connectToDevice(const QString& endpoint_id) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return;
}
const QString peer = PeerLabelForEndpoint(endpoint);
if (!running_) {
start();
}
SetStatus(QStringLiteral("Requesting connection to %1").arg(peer));
LogLine(QStringLiteral("RequestConnection %1").arg(endpoint));
service_.RequestConnection(
service_id_, BuildEndpointInfo(), endpoint.toStdString(),
BuildConnectionOptions(), BuildConnectionListener(),
[this, endpoint](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, endpoint, status]() {
const QString peer = PeerLabelForEndpoint(endpoint);
SetStatus(QStringLiteral("RequestConnection(%1): %2")
.arg(peer, StatusToString(status)));
LogLine(QStringLiteral("RequestConnection(%1): %2")
.arg(endpoint, StatusToString(status)));
},
Qt::QueuedConnection);
});
}
void NearbyTrayController::disconnectDevice(const QString& endpoint_id) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return;
}
service_.DisconnectFromEndpoint(
service_id_, endpoint.toStdString(),
[this, endpoint](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, endpoint, status]() {
const QString peer = PeerLabelForEndpoint(endpoint);
SetStatus(QStringLiteral("Disconnect(%1): %2")
.arg(peer, StatusToString(status)));
LogLine(QStringLiteral("Disconnect(%1): %2")
.arg(endpoint, StatusToString(status)));
if (status == NearbyConnectionsQtFacade::Status::kSuccess) {
RemoveConnectedDevice(endpoint);
RemovePendingConnection(endpoint);
endpoint_peer_names_.remove(endpoint);
endpoint_mediums_.remove(endpoint);
emit endpointMediumsChanged();
}
},
Qt::QueuedConnection);
});
}
void NearbyTrayController::acceptIncoming(const QString& endpoint_id) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return;
}
RemovePendingConnection(endpoint);
service_.AcceptConnection(
service_id_, endpoint.toStdString(), BuildPayloadListener(),
[this, endpoint](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, endpoint, status]() {
const QString peer = PeerLabelForEndpoint(endpoint);
SetStatus(QStringLiteral("AcceptConnection(%1): %2")
.arg(peer, StatusToString(status)));
LogLine(QStringLiteral("AcceptConnection(%1): %2")
.arg(endpoint, StatusToString(status)));
},
Qt::QueuedConnection);
});
}
void NearbyTrayController::rejectIncoming(const QString& endpoint_id) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return;
}
RemovePendingConnection(endpoint);
disconnectDevice(endpoint);
}
void NearbyTrayController::initiateBandwidthUpgrade(const QString& endpoint_id) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return;
}
service_.InitiateBandwidthUpgrade(
service_id_, endpoint.toStdString(),
[this, endpoint](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, endpoint, status]() {
const QString peer = PeerLabelForEndpoint(endpoint);
SetStatus(QStringLiteral("InitiateBandwidthUpgrade(%1): %2")
.arg(peer, StatusToString(status)));
LogLine(QStringLiteral("InitiateBandwidthUpgrade(%1): %2")
.arg(endpoint, StatusToString(status)));
},
Qt::QueuedConnection);
});
}
void NearbyTrayController::sendText(const QString& endpoint_id,
const QString& text) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty() || text.isEmpty()) {
return;
}
QByteArray utf8 = text.toUtf8();
std::vector<uint8_t> bytes(utf8.begin(), utf8.end());
auto payload = service_.CreateBytesPayload(std::move(bytes));
const qlonglong payload_id = payload.id;
UpsertTransfer(endpoint, payload_id, QStringLiteral("Queued"), 0,
static_cast<qulonglong>(bytes.size()),
QStringLiteral("outgoing"));
const std::vector<std::string> endpoint_ids{endpoint.toStdString()};
service_.SendPayload(
service_id_, endpoint_ids, std::move(payload),
[this, endpoint, payload_id](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, endpoint, payload_id, status]() {
LogLine(QStringLiteral("SendPayload(%1, %2): %3")
.arg(endpoint)
.arg(payload_id)
.arg(StatusToString(status)));
if (status != NearbyConnectionsQtFacade::Status::kSuccess) {
UpsertTransfer(endpoint, payload_id, QStringLiteral("SendFailed"),
0, 0, QStringLiteral("outgoing"));
}
},
Qt::QueuedConnection);
});
}
QString NearbyTrayController::mediumForEndpoint(const QString& endpoint_id) const {
return endpoint_mediums_.value(endpoint_id).toString();
}
QString NearbyTrayController::peerNameForEndpoint(const QString& endpoint_id) const {
return PeerLabelForEndpoint(endpoint_id);
}
void NearbyTrayController::clearTransfers() {
transfers_.clear();
transfer_row_for_payload_.clear();
emit transfersChanged();
}
void NearbyTrayController::hideToTray() {
emit requestTrayMessage(
QStringLiteral("Nearby Tray"),
QStringLiteral("App is still running in the system tray."));
}
void NearbyTrayController::startSendMode() {
discovered_devices_.clear();
emit discoveredDevicesChanged();
auto discovery_options = BuildDiscoveryOptions();
LogLine(QStringLiteral("ble: %1, bluetooth: %2, wifi_lan: %3, wifi_hotspot: $4").arg(
discovery_options.allowed_mediums.ble
).arg(
discovery_options.allowed_mediums.bluetooth
).arg(
discovery_options.allowed_mediums.wifi_lan).arg(discovery_options.allowed_mediums.wifi_hotspot)
);
auto discovery_listener = BuildDiscoveryListener();
service_.StartDiscovery(
service_id_,discovery_options, discovery_listener,
[this](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, status]() {
SetStatus(QStringLiteral("StartDiscovery: %1")
.arg(StatusToString(status)));
LogLine(QStringLiteral("StartDiscovery: %1")
.arg(StatusToString(status)));
if (status != NearbyConnectionsQtFacade::Status::kSuccess) {
running_ = false;
emit runningChanged();
}
},
Qt::QueuedConnection);
});
}
void NearbyTrayController::startReceiveMode() {
auto advert_options = BuildAdvertisingOptions();
LogLine(QStringLiteral("ble: %1, bluetooth: %2, wifi_lan: %3, wifi_hotspot: %4").arg(
advert_options.allowed_mediums.ble
).arg(
advert_options.allowed_mediums.bluetooth
).arg(
advert_options.allowed_mediums.wifi_lan
).arg(advert_options.allowed_mediums.wifi_hotspot));
service_.StartAdvertising(
service_id_, BuildEndpointInfo(), BuildAdvertisingOptions(),
BuildConnectionListener(),
[this](NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, status]() {
SetStatus(QStringLiteral("StartAdvertising: %1")
.arg(StatusToString(status)));
LogLine(QStringLiteral("StartAdvertising: %1")
.arg(StatusToString(status)));
if (status != NearbyConnectionsQtFacade::Status::kSuccess) {
running_ = false;
emit runningChanged();
}
},
Qt::QueuedConnection);
});
}
std::vector<uint8_t> NearbyTrayController::BuildEndpointInfo() const {
QByteArray endpoint = device_name_.toUtf8();
return std::vector<uint8_t>(endpoint.begin(), endpoint.end());
}
NearbyConnectionsQtFacade::ConnectionListener
NearbyTrayController::BuildConnectionListener() {
NearbyConnectionsQtFacade::ConnectionListener listener;
listener.initiated_cb =
[this](const std::string& endpoint_id,
const NearbyConnectionsQtFacade::ConnectionInfo& info) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id),
incoming = info.is_incoming_connection,
peer_name = QString::fromStdString(info.peer_name)]() {
SetPeerNameForEndpoint(endpoint, peer_name);
const QString peer = PeerLabelForEndpoint(endpoint);
LogLine(QStringLiteral("Connection initiated endpoint=%1 incoming=%2")
.arg(endpoint)
.arg(incoming));
if (incoming) {
AddPendingConnection(endpoint);
SetStatus(
QStringLiteral("Incoming connection from %1").arg(peer));
if (auto_accept_incoming_) {
acceptIncoming(endpoint);
}
} else {
acceptIncoming(endpoint);
}
},
Qt::QueuedConnection);
};
listener.accepted_cb = [this](const std::string& endpoint_id) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id)]() {
const QString peer = PeerLabelForEndpoint(endpoint);
AddConnectedDevice(endpoint);
RemovePendingConnection(endpoint);
SetStatus(QStringLiteral("Connected to %1").arg(peer));
LogLine(QStringLiteral("Connection accepted endpoint=%1 peer=%2")
.arg(endpoint, peer));
},
Qt::QueuedConnection);
};
listener.rejected_cb =
[this](const std::string& endpoint_id, NearbyConnectionsQtFacade::Status status) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id), status]() {
const QString peer = PeerLabelForEndpoint(endpoint);
RemovePendingConnection(endpoint);
SetStatus(QStringLiteral("Connection rejected by %1 (%2)")
.arg(peer, StatusToString(status)));
LogLine(QStringLiteral("Connection rejected endpoint=%1 status=%2")
.arg(endpoint, StatusToString(status)));
},
Qt::QueuedConnection);
};
listener.disconnected_cb = [this](const std::string& endpoint_id) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id)]() {
const QString peer = PeerLabelForEndpoint(endpoint);
RemoveConnectedDevice(endpoint);
RemovePendingConnection(endpoint);
endpoint_peer_names_.remove(endpoint);
endpoint_mediums_.remove(endpoint);
emit endpointMediumsChanged();
SetStatus(QStringLiteral("Disconnected from %1").arg(peer));
LogLine(QStringLiteral("Disconnected endpoint=%1").arg(endpoint));
},
Qt::QueuedConnection);
};
listener.bandwidth_changed_cb =
[this](const std::string& endpoint_id, NearbyConnectionsQtFacade::Medium medium) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id), medium]() {
const QString medium_name = MediumToString(medium);
endpoint_mediums_[endpoint] = medium_name;
emit endpointMediumsChanged();
UpdateTransferMediumForEndpoint(endpoint, medium_name);
LogLine(QStringLiteral("Bandwidth changed endpoint=%1 medium=%2")
.arg(endpoint, medium_name));
},
Qt::QueuedConnection);
};
return listener;
}
NearbyConnectionsQtFacade::DiscoveryListener
NearbyTrayController::BuildDiscoveryListener() {
NearbyConnectionsQtFacade::DiscoveryListener listener;
listener.endpoint_found_cb =
[this](const std::string& endpoint_id,
const NearbyConnectionsQtFacade::DiscoveredEndpointInfo& info) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id),
peer_name = QString::fromStdString(info.peer_name)]() {
SetPeerNameForEndpoint(endpoint, peer_name);
AddDiscoveredDevice(endpoint);
LogLine(QStringLiteral("Discovered endpoint=%1 peer=%2")
.arg(endpoint, PeerLabelForEndpoint(endpoint)));
},
Qt::QueuedConnection);
};
listener.endpoint_lost_cb = [this](const std::string& endpoint_id) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id)]() {
RemoveDiscoveredDevice(endpoint);
endpoint_peer_names_.remove(endpoint);
LogLine(QStringLiteral("Lost endpoint=%1").arg(endpoint));
},
Qt::QueuedConnection);
};
listener.endpoint_distance_changed_cb =
[this](const std::string& endpoint_id, NearbyConnectionsQtFacade::DistanceInfo info) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id), info]() {
LogLine(QStringLiteral("Distance changed endpoint=%1 value=%2")
.arg(endpoint)
.arg(static_cast<int>(info)));
},
Qt::QueuedConnection);
};
return listener;
}
NearbyConnectionsQtFacade::PayloadListener
NearbyTrayController::BuildPayloadListener() {
NearbyConnectionsQtFacade::PayloadListener listener;
listener.payload_cb =
[this](const std::string& endpoint_id,
NearbyConnectionsQtFacade::Payload payload) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id),
payload = std::move(payload)]() {
if (payload.type == NearbyConnectionsQtFacade::Payload::Type::kBytes) {
const auto& bytes = payload.bytes;
const QString text = QString::fromUtf8(
reinterpret_cast<const char*>(bytes.data()),
static_cast<int>(bytes.size()));
// Check if this is a FILE: metadata message (arrives before file)
if (text.startsWith(QStringLiteral("FILE:"))) {
QString filename = text.mid(5).trimmed();
if (!filename.isEmpty()) {
pending_file_names_[endpoint] = filename;
LogLine(QStringLiteral("Received file metadata endpoint=%1 name=%2")
.arg(endpoint, filename));
}
}
emit payloadReceived(endpoint, QStringLiteral("bytes"), text);
LogLine(QStringLiteral("Received bytes payload endpoint=%1 id=%2 size=%3")
.arg(endpoint)
.arg(payload.id)
.arg(bytes.size()));
} else if (payload.type ==
NearbyConnectionsQtFacade::Payload::Type::kFile) {
const QString path = QString::fromStdString(payload.file_path);
QString file_name =
QString::fromStdString(payload.file_name);
// Use pending filename if available
if (pending_file_names_.contains(endpoint)) {
file_name = pending_file_names_.take(endpoint);
}
const QString final_path =
FinalizeReceivedFilePath(path, file_name, payload.id);
emit payloadReceived(endpoint, QStringLiteral("file"),
final_path);
LogLine(QStringLiteral(
"Received file payload endpoint=%1 id=%2 path=%3 saved=%4 name=%5")
.arg(endpoint)
.arg(payload.id)
.arg(path, final_path, file_name));
}
},
Qt::QueuedConnection);
};
listener.payload_progress_cb =
[this](const std::string& endpoint_id,
const NearbyConnectionsQtFacade::PayloadTransferUpdate& update) {
QMetaObject::invokeMethod(
this,
[this, endpoint = QString::fromStdString(endpoint_id),
update]() {
QString direction = QStringLiteral("incoming");
if (transfer_row_for_payload_.contains(update.payload_id)) {
const int row = transfer_row_for_payload_.value(update.payload_id);
if (row >= 0 && row < transfers_.size()) {
const QVariantMap transfer = transfers_[row].toMap();
const QString existing_direction =
transfer.value(QStringLiteral("direction")).toString();
if (!existing_direction.isEmpty()) {
direction = existing_direction;
}
}
}
UpsertTransfer(endpoint, update.payload_id,
PayloadStatusToString(update.status),
update.bytes_transferred, update.total_bytes,
direction);
},
Qt::QueuedConnection);
};
return listener;
}
NearbyConnectionsQtFacade::MediumSelection
NearbyTrayController::BuildMediumSelection() const {
NearbyConnectionsQtFacade::MediumSelection selection;
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
NearbyTrayController::BuildAdvertisingOptions() const {
NearbyConnectionsQtFacade::AdvertisingOptions options;
options.strategy = StrategyFromName(connection_strategy_);
options.allowed_mediums = BuildMediumSelection();
options.auto_upgrade_bandwidth = true;
options.enable_bluetooth_listening = true;
options.enforce_topology_constraints = true;
return options;
}
NearbyConnectionsQtFacade::DiscoveryOptions NearbyTrayController::BuildDiscoveryOptions()
const {
NearbyConnectionsQtFacade::DiscoveryOptions options;
options.strategy = StrategyFromName(connection_strategy_);
options.allowed_mediums = BuildMediumSelection();
return options;
}
NearbyConnectionsQtFacade::ConnectionOptions NearbyTrayController::BuildConnectionOptions()
const {
NearbyConnectionsQtFacade::ConnectionOptions options;
options.allowed_mediums = BuildMediumSelection();
options.non_disruptive_hotspot_mode = true;
return options;
}
void NearbyTrayController::AddDiscoveredDevice(const QString& endpoint_id) {
if (discovered_devices_.contains(endpoint_id)) {
return;
}
discovered_devices_.append(endpoint_id);
emit discoveredDevicesChanged();
}
void NearbyTrayController::RemoveDiscoveredDevice(const QString& endpoint_id) {
if (!discovered_devices_.removeOne(endpoint_id)) {
return;
}
emit discoveredDevicesChanged();
}
void NearbyTrayController::AddConnectedDevice(const QString& endpoint_id) {
if (connected_devices_.contains(endpoint_id)) {
return;
}
connected_devices_.append(endpoint_id);
emit connectedDevicesChanged();
}
void NearbyTrayController::RemoveConnectedDevice(const QString& endpoint_id) {
if (!connected_devices_.removeOne(endpoint_id)) {
return;
}
emit connectedDevicesChanged();
}
void NearbyTrayController::AddPendingConnection(const QString& endpoint_id) {
if (pending_connections_.contains(endpoint_id)) {
return;
}
pending_connections_.append(endpoint_id);
emit pendingConnectionsChanged();
}
void NearbyTrayController::RemovePendingConnection(const QString& endpoint_id) {
if (!pending_connections_.removeOne(endpoint_id)) {
return;
}
emit pendingConnectionsChanged();
}
void NearbyTrayController::SetPeerNameForEndpoint(const QString& endpoint_id,
const QString& peer_name) {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return;
}
const QString previous = endpoint_peer_names_.value(endpoint).trimmed();
const QString trimmed_name = peer_name.trimmed();
if (trimmed_name.isEmpty()) {
if (previous.isEmpty()) {
return;
}
endpoint_peer_names_.remove(endpoint);
emit discoveredDevicesChanged();
emit connectedDevicesChanged();
emit pendingConnectionsChanged();
return;
}
if (previous == trimmed_name) {
return;
}
endpoint_peer_names_[endpoint] = trimmed_name;
emit discoveredDevicesChanged();
emit connectedDevicesChanged();
emit pendingConnectionsChanged();
}
QString NearbyTrayController::PeerLabelForEndpoint(
const QString& endpoint_id) const {
const QString endpoint = endpoint_id.trimmed();
if (endpoint.isEmpty()) {
return QStringLiteral("Unknown device");
}
const QString peer_name = endpoint_peer_names_.value(endpoint).trimmed();
return peer_name.isEmpty() ? QStringLiteral("Unknown device") : peer_name;
}
QString NearbyTrayController::FinalizeReceivedFilePath(
const QString& received_path, const QString& received_file_name,
qlonglong payload_id) const {
const QString source = received_path.trimmed();
if (source.isEmpty()) {
return source;
}
QFileInfo source_info(source);
const QString source_abs = source_info.absoluteFilePath();
const QString source_dir = source_info.absolutePath();
QString target_name = QFileInfo(received_file_name.trimmed()).fileName();
if (target_name.isEmpty()) {
target_name = source_info.fileName();
}
if (target_name.isEmpty()) {
target_name = QStringLiteral("payload_%1.bin").arg(payload_id);
}
const QFileInfo target_name_info(target_name);
const QString stem =
target_name_info.completeBaseName().isEmpty()
? target_name_info.fileName()
: target_name_info.completeBaseName();
const QString suffix = target_name_info.completeSuffix();
QString target_path = QDir(source_dir).filePath(target_name);
int suffix_index = 1;
while (target_path != source_abs && QFileInfo::exists(target_path)) {
const QString next_name =
suffix.isEmpty()
? QStringLiteral("%1_%2").arg(stem).arg(suffix_index)
: QStringLiteral("%1_%2.%3").arg(stem).arg(suffix_index).arg(
suffix);
target_path = QDir(source_dir).filePath(next_name);
++suffix_index;
}
if (target_path == source_abs) {
return source_abs;
}
if (QFile::rename(source_abs, target_path)) {
return target_path;
}
if (QFile::copy(source_abs, target_path)) {
QFile::remove(source_abs);
return target_path;
}
return source_abs;
}
void NearbyTrayController::UpsertTransfer(const QString& endpoint_id,
qlonglong payload_id,
const QString& status,
qulonglong bytes_transferred,
qulonglong total_bytes,
const QString& direction) {
const QString medium = mediumForEndpoint(endpoint_id);
const double progress =
total_bytes > 0
? static_cast<double>(bytes_transferred) /
static_cast<double>(total_bytes)
: 0.0;
QVariantMap transfer{
{QStringLiteral("payloadId"), payload_id},
{QStringLiteral("endpointId"), endpoint_id},
{QStringLiteral("status"), status},
{QStringLiteral("bytesTransferred"),
static_cast<qulonglong>(bytes_transferred)},
{QStringLiteral("totalBytes"), static_cast<qulonglong>(total_bytes)},
{QStringLiteral("progress"), progress},
{QStringLiteral("medium"), medium},
{QStringLiteral("direction"), direction},
};
if (transfer_row_for_payload_.contains(payload_id)) {
const int row = transfer_row_for_payload_.value(payload_id);
if (row >= 0 && row < transfers_.size()) {
transfers_[row] = transfer;
emit transfersChanged();
return;
}
}
transfer_row_for_payload_.insert(payload_id, transfers_.size());
transfers_.append(transfer);
emit transfersChanged();
}
void NearbyTrayController::UpdateTransferMediumForEndpoint(
const QString& endpoint_id, const QString& medium) {
bool changed = false;
for (int i = 0; i < transfers_.size(); ++i) {
QVariantMap row = transfers_[i].toMap();
if (row.value(QStringLiteral("endpointId")).toString() != endpoint_id) {
continue;
}
row[QStringLiteral("medium")] = medium;
transfers_[i] = row;
changed = true;
}
if (changed) {
emit transfersChanged();
}
}
void NearbyTrayController::SetStatus(const QString& status) {
if (status == status_message_) {
return;
}
status_message_ = status;
emit statusMessageChanged();
LogLine(QStringLiteral("Status: %1").arg(status_message_));
}
void NearbyTrayController::LogLine(const QString& line) {
if (!log_file_.isOpen()) {
ReopenLogFile();
}
if (!log_file_.isOpen()) {
return;
}
QTextStream stream(&log_file_);
stream << QDateTime::currentDateTimeUtc().toString(Qt::ISODate)
<< " " << line << "\n";
stream.flush();
}
void NearbyTrayController::ReopenLogFile() {
if (log_file_.isOpen()) {
log_file_.close();
}
log_file_.setFileName(log_path_);
log_file_.open(QIODevice::Append | QIODevice::Text | QIODevice::WriteOnly);
}
QString NearbyTrayController::StatusToString(NearbyConnectionsQtFacade::Status status) {
switch (status) {
case NearbyConnectionsQtFacade::Status::kSuccess:
return QStringLiteral("Success");
case NearbyConnectionsQtFacade::Status::kError:
return QStringLiteral("Error");
case NearbyConnectionsQtFacade::Status::kOutOfOrderApiCall:
return QStringLiteral("OutOfOrderApiCall");
case NearbyConnectionsQtFacade::Status::kAlreadyHaveActiveStrategy:
return QStringLiteral("AlreadyHaveActiveStrategy");
case NearbyConnectionsQtFacade::Status::kAlreadyAdvertising:
return QStringLiteral("AlreadyAdvertising");
case NearbyConnectionsQtFacade::Status::kAlreadyDiscovering:
return QStringLiteral("AlreadyDiscovering");
case NearbyConnectionsQtFacade::Status::kAlreadyListening:
return QStringLiteral("AlreadyListening");
case NearbyConnectionsQtFacade::Status::kEndpointIOError:
return QStringLiteral("EndpointIOError");
case NearbyConnectionsQtFacade::Status::kEndpointUnknown:
return QStringLiteral("EndpointUnknown");
case NearbyConnectionsQtFacade::Status::kConnectionRejected:
return QStringLiteral("ConnectionRejected");
case NearbyConnectionsQtFacade::Status::kAlreadyConnectedToEndpoint:
return QStringLiteral("AlreadyConnectedToEndpoint");
case NearbyConnectionsQtFacade::Status::kNotConnectedToEndpoint:
return QStringLiteral("NotConnectedToEndpoint");
case NearbyConnectionsQtFacade::Status::kBluetoothError:
return QStringLiteral("BluetoothError");
case NearbyConnectionsQtFacade::Status::kBleError:
return QStringLiteral("BleError");
case NearbyConnectionsQtFacade::Status::kWifiLanError:
return QStringLiteral("WifiLanError");
case NearbyConnectionsQtFacade::Status::kPayloadUnknown:
return QStringLiteral("PayloadUnknown");
case NearbyConnectionsQtFacade::Status::kReset:
return QStringLiteral("Reset");
case NearbyConnectionsQtFacade::Status::kTimeout:
return QStringLiteral("Timeout");
case NearbyConnectionsQtFacade::Status::kUnknown:
return QStringLiteral("Unknown");
case NearbyConnectionsQtFacade::Status::kNextValue:
return QStringLiteral("NextValue");
}
return QStringLiteral("Unknown");
}
QString NearbyTrayController::PayloadStatusToString(
NearbyConnectionsQtFacade::PayloadStatus status) {
switch (status) {
case NearbyConnectionsQtFacade::PayloadStatus::kSuccess:
return QStringLiteral("Success");
case NearbyConnectionsQtFacade::PayloadStatus::kFailure:
return QStringLiteral("Failure");
case NearbyConnectionsQtFacade::PayloadStatus::kInProgress:
return QStringLiteral("InProgress");
case NearbyConnectionsQtFacade::PayloadStatus::kCanceled:
return QStringLiteral("Canceled");
}
return QStringLiteral("Unknown");
}
QString NearbyTrayController::MediumToString(NearbyConnectionsQtFacade::Medium medium) {
switch (medium) {
case NearbyConnectionsQtFacade::Medium::kUnknown:
return QStringLiteral("Unknown");
case NearbyConnectionsQtFacade::Medium::kMdns:
return QStringLiteral("mDNS");
case NearbyConnectionsQtFacade::Medium::kBluetooth:
return QStringLiteral("Bluetooth");
case NearbyConnectionsQtFacade::Medium::kWifiHotspot:
return QStringLiteral("WiFiHotspot");
case NearbyConnectionsQtFacade::Medium::kBle:
return QStringLiteral("BLE");
case NearbyConnectionsQtFacade::Medium::kWifiLan:
return QStringLiteral("WiFiLAN");
case NearbyConnectionsQtFacade::Medium::kWifiAware:
return QStringLiteral("WiFiAware");
case NearbyConnectionsQtFacade::Medium::kNfc:
return QStringLiteral("NFC");
case NearbyConnectionsQtFacade::Medium::kWifiDirect:
return QStringLiteral("WiFiDirect");
case NearbyConnectionsQtFacade::Medium::kWebRtc:
return QStringLiteral("WebRTC");
case NearbyConnectionsQtFacade::Medium::kBleL2Cap:
return QStringLiteral("BLEL2CAP");
}
return QStringLiteral("Unknown");
}