mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
added tray support for qt application
This commit is contained in:
@@ -72,6 +72,7 @@ jobs:
|
|||||||
libbluetooth-dev \
|
libbluetooth-dev \
|
||||||
libxkbcommon-dev \
|
libxkbcommon-dev \
|
||||||
libxcb-cursor0 \
|
libxcb-cursor0 \
|
||||||
|
dbus-daemon \
|
||||||
patchelf \
|
patchelf \
|
||||||
qt6-base-dev \
|
qt6-base-dev \
|
||||||
qt6-declarative-dev \
|
qt6-declarative-dev \
|
||||||
@@ -80,6 +81,14 @@ jobs:
|
|||||||
qt6-tools-dev-tools \
|
qt6-tools-dev-tools \
|
||||||
libqrencode-dev
|
libqrencode-dev
|
||||||
|
|
||||||
|
- name: Test tray application lifecycle
|
||||||
|
run: |
|
||||||
|
bazel test \
|
||||||
|
--@com_google_protobuf//bazel/toolchains:prefer_prebuilt_protoc=true \
|
||||||
|
--copt='-DGITHUB_BUILD' \
|
||||||
|
//sharing/linux/app:application_controller_test \
|
||||||
|
//sharing/linux/app:backend_test
|
||||||
|
|
||||||
- name: Build minimal AppImage inputs
|
- name: Build minimal AppImage inputs
|
||||||
run: |
|
run: |
|
||||||
bazel build \
|
bazel build \
|
||||||
@@ -88,7 +97,9 @@ jobs:
|
|||||||
//sharing/linux/app:appimage
|
//sharing/linux/app:appimage
|
||||||
|
|
||||||
- name: Package and validate minimal AppImage
|
- name: Package and validate minimal AppImage
|
||||||
run: ./sharing/linux/app/packaging/package_minimal_appimage.sh
|
env:
|
||||||
|
RUN_BACKEND_SMOKE_TEST: 1
|
||||||
|
run: dbus-run-session -- ./sharing/linux/app/packaging/package_minimal_appimage.sh
|
||||||
|
|
||||||
- name: Generate SHA256 checksum
|
- name: Generate SHA256 checksum
|
||||||
working-directory: sharing/linux/dist-minimal
|
working-directory: sharing/linux/dist-minimal
|
||||||
@@ -100,6 +111,9 @@ jobs:
|
|||||||
- name: Validate AppImage output
|
- name: Validate AppImage output
|
||||||
run: |
|
run: |
|
||||||
test -x sharing/linux/dist-minimal/QuickShare-x86_64.AppImage
|
test -x sharing/linux/dist-minimal/QuickShare-x86_64.AppImage
|
||||||
|
test -x sharing/linux/dist-minimal/QuickShare.AppDir/AppRun
|
||||||
|
test -e sharing/linux/dist-minimal/QuickShare.AppDir/usr/lib/libQt6DBus.so.6
|
||||||
|
test -s sharing/linux/dist-minimal/backend-smoke-test.log
|
||||||
file sharing/linux/dist-minimal/QuickShare-x86_64.AppImage
|
file sharing/linux/dist-minimal/QuickShare-x86_64.AppImage
|
||||||
|
|
||||||
- name: Publish GitHub Release
|
- name: Publish GitHub Release
|
||||||
|
|||||||
+41
-1
@@ -1,6 +1,6 @@
|
|||||||
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
load("@rules_cc//cc:cc_library.bzl", "cc_library")
|
||||||
load("@rules_cc//cc:cc_test.bzl", "cc_test")
|
load("@rules_cc//cc:cc_test.bzl", "cc_test")
|
||||||
load("@rules_qt//:qt.bzl", "qt_cc_binary", "qt_cc_library", "qt_resource_via_qrc")
|
load("@rules_qt//:qt.bzl", "qt_cc_binary", "qt_cc_library", "qt_cc_test", "qt_resource_via_qrc")
|
||||||
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
|
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
|
||||||
|
|
||||||
load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands")
|
load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands")
|
||||||
@@ -69,6 +69,45 @@ qt_cc_library(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
qt_cc_library(
|
||||||
|
name = "application_controller",
|
||||||
|
srcs = ["application_controller.cc"],
|
||||||
|
hdrs = ["application_controller.h"],
|
||||||
|
deps = [
|
||||||
|
":backend",
|
||||||
|
"@rules_qt//:qt_core",
|
||||||
|
"@rules_qt//:qt_dbus",
|
||||||
|
"@rules_qt//:qt_gui",
|
||||||
|
"@rules_qt//:qt_hdrs",
|
||||||
|
"@rules_qt//:qt_quick",
|
||||||
|
"@rules_qt//:qt_widgets",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_cc_test(
|
||||||
|
name = "application_controller_test",
|
||||||
|
size = "small",
|
||||||
|
srcs = ["application_controller_test.cc"],
|
||||||
|
env = {"QT_QPA_PLATFORM": "offscreen"},
|
||||||
|
deps = [
|
||||||
|
":application_controller",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
"@rules_qt//:qt_core",
|
||||||
|
"@rules_qt//:qt_gui",
|
||||||
|
"@rules_qt//:qt_hdrs",
|
||||||
|
"@rules_qt//:qt_labs_platform",
|
||||||
|
"@rules_qt//:qt_opengl",
|
||||||
|
"@rules_qt//:qt_qml",
|
||||||
|
"@rules_qt//:qt_qml_meta",
|
||||||
|
"@rules_qt//:qt_qml_workerscript",
|
||||||
|
"@rules_qt//:qt_quick",
|
||||||
|
"@rules_qt//:qt_quick_controls2",
|
||||||
|
"@rules_qt//:qt_quick_layouts",
|
||||||
|
"@rules_qt//:qt_quick_shapes",
|
||||||
|
"@rules_qt//:qt_widgets",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
cc_test(
|
cc_test(
|
||||||
name = "backend_test",
|
name = "backend_test",
|
||||||
size = "small",
|
size = "small",
|
||||||
@@ -134,6 +173,7 @@ qt_cc_binary(
|
|||||||
"QT_QPA_PLATFORM": "wayland;xcb",
|
"QT_QPA_PLATFORM": "wayland;xcb",
|
||||||
},
|
},
|
||||||
deps = [
|
deps = [
|
||||||
|
":application_controller",
|
||||||
":app_resources",
|
":app_resources",
|
||||||
":backend",
|
":backend",
|
||||||
":native_file_logging",
|
":native_file_logging",
|
||||||
|
|||||||
@@ -0,0 +1,337 @@
|
|||||||
|
#include "sharing/linux/app/application_controller.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusInterface>
|
||||||
|
#include <QDBusReply>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QIcon>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
#include "sharing/linux/app/backend.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr char kApplicationService[] = "com.google.quickshare";
|
||||||
|
constexpr char kApplicationPath[] = "/com/google/quickshare";
|
||||||
|
constexpr char kApplicationInterface[] = "com.google.quickshare";
|
||||||
|
constexpr char kNotificationService[] = "org.freedesktop.Notifications";
|
||||||
|
constexpr char kNotificationPath[] = "/org/freedesktop/Notifications";
|
||||||
|
constexpr char kNotificationInterface[] = "org.freedesktop.Notifications";
|
||||||
|
constexpr int kOfferTimeoutMilliseconds = 45 * 1000;
|
||||||
|
constexpr int kResultTimeoutMilliseconds = 10 * 1000;
|
||||||
|
|
||||||
|
QString FormatBytes(qint64 bytes) {
|
||||||
|
if (bytes <= 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
static constexpr const char* kUnits[] = {"B", "KB", "MB", "GB", "TB"};
|
||||||
|
double value = static_cast<double>(bytes);
|
||||||
|
int unit = 0;
|
||||||
|
while (value >= 1024.0 && unit < 4) {
|
||||||
|
value /= 1024.0;
|
||||||
|
++unit;
|
||||||
|
}
|
||||||
|
const int precision = unit == 0 ? 0 : (value >= 10.0 ? 1 : 2);
|
||||||
|
return QStringLiteral("%1 %2").arg(QString::number(value, 'f', precision),
|
||||||
|
QString::fromLatin1(kUnits[unit]));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ApplicationController::ApplicationController(QApplication& application,
|
||||||
|
QObject* parent)
|
||||||
|
: QObject(parent), application_(application) {
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (bus.isConnected()) {
|
||||||
|
bus.connect(kNotificationService, kNotificationPath, kNotificationInterface,
|
||||||
|
QStringLiteral("ActionInvoked"), this,
|
||||||
|
SLOT(OnNotificationAction(uint, QString)));
|
||||||
|
bus.connect(kNotificationService, kNotificationPath, kNotificationInterface,
|
||||||
|
QStringLiteral("NotificationClosed"), this,
|
||||||
|
SLOT(OnNotificationClosed(uint, uint)));
|
||||||
|
|
||||||
|
QDBusInterface notifications(kNotificationService, kNotificationPath,
|
||||||
|
kNotificationInterface, bus);
|
||||||
|
const QDBusReply<QStringList> capabilities =
|
||||||
|
notifications.call(QStringLiteral("GetCapabilities"));
|
||||||
|
actions_supported_ =
|
||||||
|
capabilities.isValid() && capabilities.value().contains("actions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationController::~ApplicationController() {
|
||||||
|
const QList<qint64> pending_offers = transfer_to_notification_.keys();
|
||||||
|
for (qint64 share_target_id : pending_offers) {
|
||||||
|
CloseOfferNotification(share_target_id);
|
||||||
|
}
|
||||||
|
if (tray_icon_ != nullptr) {
|
||||||
|
tray_icon_->setContextMenu(nullptr);
|
||||||
|
}
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (owns_dbus_service_ && bus.isConnected()) {
|
||||||
|
bus.unregisterService(kApplicationService);
|
||||||
|
}
|
||||||
|
if (bus.isConnected()) {
|
||||||
|
bus.unregisterObject(kApplicationPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApplicationController::ClaimSingleInstance() {
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (!bus.isConnected()) {
|
||||||
|
qWarning() << "Session D-Bus is unavailable; single-instance activation "
|
||||||
|
"and actionable notifications are disabled";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bus.registerObject(kApplicationPath, this,
|
||||||
|
QDBusConnection::ExportScriptableSlots)) {
|
||||||
|
qWarning() << "Unable to export QuickShare activation object:"
|
||||||
|
<< bus.lastError().message();
|
||||||
|
}
|
||||||
|
if (bus.registerService(kApplicationService)) {
|
||||||
|
owns_dbus_service_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusInterface existing(kApplicationService, kApplicationPath,
|
||||||
|
kApplicationInterface, bus);
|
||||||
|
const QDBusReply<void> activation =
|
||||||
|
existing.call(QStringLiteral("ShowWindow"));
|
||||||
|
if (activation.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qWarning() << "Unable to claim or activate the QuickShare D-Bus service:"
|
||||||
|
<< activation.error().message();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::InitializeTray() {
|
||||||
|
tray_menu_ = std::make_unique<QMenu>();
|
||||||
|
tray_menu_->addAction(tr("Show QuickShare"), this,
|
||||||
|
&ApplicationController::ShowWindow);
|
||||||
|
tray_menu_->addSeparator();
|
||||||
|
tray_menu_->addAction(tr("Quit"), this, &ApplicationController::Quit);
|
||||||
|
|
||||||
|
tray_icon_ = new QSystemTrayIcon(
|
||||||
|
QIcon(QStringLiteral(":/icons/quickshare.svg")), this);
|
||||||
|
tray_icon_->setToolTip(tr("QuickShare"));
|
||||||
|
tray_icon_->setContextMenu(tray_menu_.get());
|
||||||
|
connect(tray_icon_, &QSystemTrayIcon::activated, this,
|
||||||
|
[this](QSystemTrayIcon::ActivationReason reason) {
|
||||||
|
if (reason == QSystemTrayIcon::Trigger ||
|
||||||
|
reason == QSystemTrayIcon::DoubleClick) {
|
||||||
|
ShowWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tray_icon_->show();
|
||||||
|
|
||||||
|
auto* tray_availability_timer = new QTimer(this);
|
||||||
|
tray_availability_timer->setInterval(2000);
|
||||||
|
connect(tray_availability_timer, &QTimer::timeout, this,
|
||||||
|
&ApplicationController::UpdateTrayAvailability);
|
||||||
|
tray_availability_timer->start();
|
||||||
|
UpdateTrayAvailability();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::SetBackend(Backend* backend) {
|
||||||
|
if (backend == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
connect(backend, &Backend::incomingOffer, this,
|
||||||
|
&ApplicationController::OnIncomingOffer);
|
||||||
|
connect(backend, &Backend::incomingOfferResolved, this,
|
||||||
|
&ApplicationController::OnIncomingOfferResolved);
|
||||||
|
connect(backend, &Backend::transferFinished, this,
|
||||||
|
&ApplicationController::OnTransferFinished);
|
||||||
|
connect(this, &ApplicationController::acceptRequested, backend,
|
||||||
|
&Backend::accept);
|
||||||
|
connect(this, &ApplicationController::rejectRequested, backend,
|
||||||
|
&Backend::reject);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::AttachWindow(QObject* root_object) {
|
||||||
|
window_ = qobject_cast<QQuickWindow*>(root_object);
|
||||||
|
if (window_ != nullptr && pending_activation_) {
|
||||||
|
pending_activation_ = false;
|
||||||
|
ShowWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::ShowWindow() {
|
||||||
|
if (window_ == nullptr) {
|
||||||
|
pending_activation_ = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window_->show();
|
||||||
|
window_->raise();
|
||||||
|
window_->requestActivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::Quit() {
|
||||||
|
application_.quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::OnIncomingOffer(qint64 share_target_id,
|
||||||
|
const QString& device_name,
|
||||||
|
int attachment_count,
|
||||||
|
qint64 total_bytes) {
|
||||||
|
if (!actions_supported_) {
|
||||||
|
ShowWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseOfferNotification(share_target_id);
|
||||||
|
const QStringList actions = {
|
||||||
|
QStringLiteral("default"), QStringLiteral("Open"),
|
||||||
|
QStringLiteral("accept"), QStringLiteral("Accept"),
|
||||||
|
QStringLiteral("decline"), QStringLiteral("Decline"),
|
||||||
|
};
|
||||||
|
const uint notification_id = SendNotification(
|
||||||
|
tr("Incoming share"),
|
||||||
|
IncomingOfferBody(device_name, attachment_count, total_bytes), actions,
|
||||||
|
kOfferTimeoutMilliseconds);
|
||||||
|
if (notification_id == 0) {
|
||||||
|
ShowWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
notification_to_transfer_.insert(notification_id, share_target_id);
|
||||||
|
transfer_to_notification_.insert(share_target_id, notification_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::OnIncomingOfferResolved(qint64 share_target_id) {
|
||||||
|
CloseOfferNotification(share_target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::OnTransferFinished(qint64 share_target_id,
|
||||||
|
bool receive_mode,
|
||||||
|
const QString& device_name,
|
||||||
|
const QString& status) {
|
||||||
|
CloseOfferNotification(share_target_id);
|
||||||
|
if (!receive_mode ||
|
||||||
|
!background_accepted_transfers_.remove(share_target_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool complete = status == QStringLiteral("kComplete");
|
||||||
|
const QString peer =
|
||||||
|
device_name.isEmpty() ? tr("the nearby device") : device_name;
|
||||||
|
SendNotification(complete ? tr("Share received") : tr("Share failed"),
|
||||||
|
complete
|
||||||
|
? tr("Finished receiving from %1").arg(peer)
|
||||||
|
: tr("Could not receive the share from %1").arg(peer),
|
||||||
|
{}, kResultTimeoutMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::OnNotificationAction(uint notification_id,
|
||||||
|
const QString& action_key) {
|
||||||
|
const auto transfer = notification_to_transfer_.constFind(notification_id);
|
||||||
|
if (transfer == notification_to_transfer_.cend()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const qint64 share_target_id = transfer.value();
|
||||||
|
|
||||||
|
if (action_key == QStringLiteral("default")) {
|
||||||
|
ShowWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (action_key == QStringLiteral("accept")) {
|
||||||
|
background_accepted_transfers_.insert(share_target_id);
|
||||||
|
emit acceptRequested(share_target_id);
|
||||||
|
} else if (action_key == QStringLiteral("decline")) {
|
||||||
|
emit rejectRequested(share_target_id);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CloseOfferNotification(share_target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::OnNotificationClosed(uint notification_id,
|
||||||
|
uint /*reason*/) {
|
||||||
|
RemoveNotificationMapping(notification_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::UpdateTrayAvailability() {
|
||||||
|
const bool available = QSystemTrayIcon::isSystemTrayAvailable();
|
||||||
|
application_.setQuitOnLastWindowClosed(!available);
|
||||||
|
if (close_to_tray_ == available) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close_to_tray_ = available;
|
||||||
|
emit closeToTrayChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ApplicationController::SendNotification(const QString& summary,
|
||||||
|
const QString& body,
|
||||||
|
const QStringList& actions,
|
||||||
|
int timeout_milliseconds) {
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (!bus.isConnected()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QDBusInterface notifications(kNotificationService, kNotificationPath,
|
||||||
|
kNotificationInterface, bus);
|
||||||
|
const QDBusReply<uint> reply =
|
||||||
|
notifications.call(QStringLiteral("Notify"), QStringLiteral("QuickShare"),
|
||||||
|
uint{0}, QStringLiteral("quickshare"), summary, body,
|
||||||
|
actions, QVariantMap{}, timeout_milliseconds);
|
||||||
|
if (!reply.isValid()) {
|
||||||
|
qWarning() << "Unable to send desktop notification:"
|
||||||
|
<< reply.error().message();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return reply.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::CloseOfferNotification(qint64 share_target_id) {
|
||||||
|
const auto notification =
|
||||||
|
transfer_to_notification_.constFind(share_target_id);
|
||||||
|
if (notification == transfer_to_notification_.cend()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint notification_id = notification.value();
|
||||||
|
RemoveNotificationMapping(notification_id);
|
||||||
|
|
||||||
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
if (!bus.isConnected()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDBusInterface notifications(kNotificationService, kNotificationPath,
|
||||||
|
kNotificationInterface, bus);
|
||||||
|
notifications.asyncCall(QStringLiteral("CloseNotification"), notification_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationController::RemoveNotificationMapping(uint notification_id) {
|
||||||
|
const auto transfer = notification_to_transfer_.find(notification_id);
|
||||||
|
if (transfer == notification_to_transfer_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transfer_to_notification_.remove(transfer.value());
|
||||||
|
notification_to_transfer_.erase(transfer);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ApplicationController::IncomingOfferBody(const QString& device_name,
|
||||||
|
int attachment_count,
|
||||||
|
qint64 total_bytes) const {
|
||||||
|
const QString peer =
|
||||||
|
device_name.isEmpty() ? tr("A nearby device") : device_name;
|
||||||
|
QString contents;
|
||||||
|
if (attachment_count > 0) {
|
||||||
|
contents = attachment_count == 1 ? tr("1 item")
|
||||||
|
: tr("%1 items").arg(attachment_count);
|
||||||
|
} else {
|
||||||
|
contents = FormatBytes(total_bytes);
|
||||||
|
}
|
||||||
|
if (contents.isEmpty()) {
|
||||||
|
contents = tr("a share");
|
||||||
|
}
|
||||||
|
return tr("%1 wants to send %2")
|
||||||
|
.arg(peer, contents);
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QSet>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class Backend;
|
||||||
|
class QApplication;
|
||||||
|
class QMenu;
|
||||||
|
class QQuickWindow;
|
||||||
|
class QSystemTrayIcon;
|
||||||
|
|
||||||
|
class ApplicationController : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_CLASSINFO("D-Bus Interface", "com.google.quickshare")
|
||||||
|
Q_PROPERTY(bool closeToTray READ closeToTray NOTIFY closeToTrayChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ApplicationController(QApplication& application,
|
||||||
|
QObject* parent = nullptr);
|
||||||
|
~ApplicationController() override;
|
||||||
|
|
||||||
|
// Returns false after successfully activating an existing instance.
|
||||||
|
bool ClaimSingleInstance();
|
||||||
|
void InitializeTray();
|
||||||
|
void SetBackend(Backend* backend);
|
||||||
|
void AttachWindow(QObject* root_object);
|
||||||
|
|
||||||
|
bool closeToTray() const { return close_to_tray_; }
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
Q_SCRIPTABLE void ShowWindow();
|
||||||
|
void Quit();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closeToTrayChanged();
|
||||||
|
void acceptRequested(qint64 share_target_id);
|
||||||
|
void rejectRequested(qint64 share_target_id);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void OnIncomingOffer(qint64 share_target_id, const QString& device_name,
|
||||||
|
int attachment_count, qint64 total_bytes);
|
||||||
|
void OnIncomingOfferResolved(qint64 share_target_id);
|
||||||
|
void OnTransferFinished(qint64 share_target_id, bool receive_mode,
|
||||||
|
const QString& device_name, const QString& status);
|
||||||
|
void OnNotificationAction(uint notification_id, const QString& action_key);
|
||||||
|
void OnNotificationClosed(uint notification_id, uint reason);
|
||||||
|
void UpdateTrayAvailability();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ApplicationControllerTestPeer;
|
||||||
|
|
||||||
|
uint SendNotification(const QString& summary, const QString& body,
|
||||||
|
const QStringList& actions, int timeout_milliseconds);
|
||||||
|
void CloseOfferNotification(qint64 share_target_id);
|
||||||
|
void RemoveNotificationMapping(uint notification_id);
|
||||||
|
QString IncomingOfferBody(const QString& device_name, int attachment_count,
|
||||||
|
qint64 total_bytes) const;
|
||||||
|
|
||||||
|
QApplication& application_;
|
||||||
|
QPointer<QQuickWindow> window_;
|
||||||
|
QSystemTrayIcon* tray_icon_ = nullptr;
|
||||||
|
std::unique_ptr<QMenu> tray_menu_;
|
||||||
|
QHash<uint, qint64> notification_to_transfer_;
|
||||||
|
QHash<qint64, uint> transfer_to_notification_;
|
||||||
|
QSet<qint64> background_accepted_transfers_;
|
||||||
|
bool close_to_tray_ = false;
|
||||||
|
bool actions_supported_ = false;
|
||||||
|
bool owns_dbus_service_ = false;
|
||||||
|
bool pending_activation_ = false;
|
||||||
|
};
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#include "sharing/linux/app/application_controller.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QQuickWindow>
|
||||||
|
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
class ApplicationControllerTestPeer {
|
||||||
|
public:
|
||||||
|
static void AddOfferNotification(ApplicationController& controller,
|
||||||
|
uint notification_id,
|
||||||
|
qint64 share_target_id) {
|
||||||
|
controller.notification_to_transfer_.insert(notification_id,
|
||||||
|
share_target_id);
|
||||||
|
controller.transfer_to_notification_.insert(share_target_id,
|
||||||
|
notification_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void InvokeAction(ApplicationController& controller,
|
||||||
|
uint notification_id, const QString& action) {
|
||||||
|
controller.OnNotificationAction(notification_id, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Dismiss(ApplicationController& controller, uint notification_id) {
|
||||||
|
controller.OnNotificationClosed(notification_id, /*reason=*/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DeliverOfferWithoutActions(ApplicationController& controller,
|
||||||
|
qint64 share_target_id) {
|
||||||
|
controller.actions_supported_ = false;
|
||||||
|
controller.OnIncomingOffer(share_target_id, QStringLiteral("Peer"), 1, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool HasNotification(const ApplicationController& controller,
|
||||||
|
uint notification_id) {
|
||||||
|
return controller.notification_to_transfer_.contains(notification_id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QApplication& TestApplication() {
|
||||||
|
static int argc = 1;
|
||||||
|
static char application_name[] = "application_controller_test";
|
||||||
|
static char* argv[] = {application_name, nullptr};
|
||||||
|
static QApplication application(argc, argv);
|
||||||
|
return application;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ApplicationControllerTest, NativeActionsRouteToTransfer) {
|
||||||
|
ApplicationController controller(TestApplication());
|
||||||
|
qint64 accepted_target = 0;
|
||||||
|
qint64 rejected_target = 0;
|
||||||
|
QObject::connect(&controller, &ApplicationController::acceptRequested,
|
||||||
|
[&accepted_target](qint64 id) { accepted_target = id; });
|
||||||
|
QObject::connect(&controller, &ApplicationController::rejectRequested,
|
||||||
|
[&rejected_target](qint64 id) { rejected_target = id; });
|
||||||
|
|
||||||
|
ApplicationControllerTestPeer::AddOfferNotification(controller, 10, 101);
|
||||||
|
ApplicationControllerTestPeer::InvokeAction(controller, 10,
|
||||||
|
QStringLiteral("accept"));
|
||||||
|
ApplicationControllerTestPeer::AddOfferNotification(controller, 11, 102);
|
||||||
|
ApplicationControllerTestPeer::InvokeAction(controller, 11,
|
||||||
|
QStringLiteral("decline"));
|
||||||
|
|
||||||
|
EXPECT_EQ(accepted_target, 101);
|
||||||
|
EXPECT_EQ(rejected_target, 102);
|
||||||
|
EXPECT_FALSE(ApplicationControllerTestPeer::HasNotification(controller, 10));
|
||||||
|
EXPECT_FALSE(ApplicationControllerTestPeer::HasNotification(controller, 11));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ApplicationControllerTest, DismissalDoesNotResolveTransfer) {
|
||||||
|
ApplicationController controller(TestApplication());
|
||||||
|
int responses = 0;
|
||||||
|
QObject::connect(&controller, &ApplicationController::acceptRequested,
|
||||||
|
[&responses](qint64) { ++responses; });
|
||||||
|
QObject::connect(&controller, &ApplicationController::rejectRequested,
|
||||||
|
[&responses](qint64) { ++responses; });
|
||||||
|
ApplicationControllerTestPeer::AddOfferNotification(controller, 20, 201);
|
||||||
|
|
||||||
|
ApplicationControllerTestPeer::Dismiss(controller, 20);
|
||||||
|
|
||||||
|
EXPECT_EQ(responses, 0);
|
||||||
|
EXPECT_FALSE(ApplicationControllerTestPeer::HasNotification(controller, 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ApplicationControllerTest, DefaultActionShowsWindow) {
|
||||||
|
ApplicationController controller(TestApplication());
|
||||||
|
QQuickWindow window;
|
||||||
|
window.hide();
|
||||||
|
controller.AttachWindow(&window);
|
||||||
|
ApplicationControllerTestPeer::AddOfferNotification(controller, 30, 301);
|
||||||
|
|
||||||
|
ApplicationControllerTestPeer::InvokeAction(controller, 30,
|
||||||
|
QStringLiteral("default"));
|
||||||
|
|
||||||
|
EXPECT_TRUE(window.isVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ApplicationControllerTest, MissingNotificationActionsShowsWindow) {
|
||||||
|
ApplicationController controller(TestApplication());
|
||||||
|
QQuickWindow window;
|
||||||
|
window.hide();
|
||||||
|
controller.AttachWindow(&window);
|
||||||
|
|
||||||
|
ApplicationControllerTestPeer::DeliverOfferWithoutActions(controller, 401);
|
||||||
|
|
||||||
|
EXPECT_TRUE(window.isVisible());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
@@ -31,6 +31,7 @@ using NearbySharingService = nearby::sharing::NearbySharingService;
|
|||||||
|
|
||||||
constexpr std::chrono::seconds kShutdownTimeout(10);
|
constexpr std::chrono::seconds kShutdownTimeout(10);
|
||||||
constexpr int kReceiveTimeoutMilliseconds = 30 * 1000;
|
constexpr int kReceiveTimeoutMilliseconds = 30 * 1000;
|
||||||
|
constexpr int kIncomingOfferTimeoutMilliseconds = 45 * 1000;
|
||||||
|
|
||||||
QString ToQString(const std::string& value) {
|
QString ToQString(const std::string& value) {
|
||||||
return QString::fromStdString(value);
|
return QString::fromStdString(value);
|
||||||
@@ -491,6 +492,7 @@ bool Backend::sendFile(qint64 share_target_id, const QString& path) {
|
|||||||
|
|
||||||
void Backend::accept(qint64 share_target_id) {
|
void Backend::accept(qint64 share_target_id) {
|
||||||
if (service_ != nullptr && !shutting_down_) {
|
if (service_ != nullptr && !shutting_down_) {
|
||||||
|
ResolveIncomingOffer(share_target_id);
|
||||||
service_->Accept(share_target_id,
|
service_->Accept(share_target_id,
|
||||||
StatusCallback(QStringLiteral("Accept transfer")));
|
StatusCallback(QStringLiteral("Accept transfer")));
|
||||||
}
|
}
|
||||||
@@ -498,6 +500,7 @@ void Backend::accept(qint64 share_target_id) {
|
|||||||
|
|
||||||
void Backend::reject(qint64 share_target_id) {
|
void Backend::reject(qint64 share_target_id) {
|
||||||
if (service_ != nullptr && !shutting_down_) {
|
if (service_ != nullptr && !shutting_down_) {
|
||||||
|
ResolveIncomingOffer(share_target_id);
|
||||||
service_->Reject(share_target_id,
|
service_->Reject(share_target_id,
|
||||||
StatusCallback(QStringLiteral("Reject transfer")));
|
StatusCallback(QStringLiteral("Reject transfer")));
|
||||||
}
|
}
|
||||||
@@ -517,6 +520,11 @@ void Backend::shutdown() {
|
|||||||
shutting_down_ = true;
|
shutting_down_ = true;
|
||||||
monitoring_requested_ = false;
|
monitoring_requested_ = false;
|
||||||
receive_timeout_timer_.stop();
|
receive_timeout_timer_.stop();
|
||||||
|
const QList<QTimer*> offer_timers = incoming_offer_timers_.values();
|
||||||
|
incoming_offer_timers_.clear();
|
||||||
|
for (QTimer* timer : offer_timers) {
|
||||||
|
timer->stop();
|
||||||
|
}
|
||||||
auto& fast_init_manager = *fast_init_manager_;
|
auto& fast_init_manager = *fast_init_manager_;
|
||||||
if (fast_init_manager.IsScanning()) {
|
if (fast_init_manager.IsScanning()) {
|
||||||
fast_init_manager.StopScanning(nullptr);
|
fast_init_manager.StopScanning(nullptr);
|
||||||
@@ -559,24 +567,80 @@ void Backend::OnTransferUpdate(
|
|||||||
const nearby::sharing::AttachmentContainer& attachments,
|
const nearby::sharing::AttachmentContainer& attachments,
|
||||||
const TransferMetadata& transfer) {
|
const TransferMetadata& transfer) {
|
||||||
const int64_t total_bytes = attachments.GetTotalAttachmentsSize();
|
const int64_t total_bytes = attachments.GetTotalAttachmentsSize();
|
||||||
|
const int attachment_count = attachments.GetAttachmentCount();
|
||||||
QPointer<Backend> backend(this);
|
QPointer<Backend> backend(this);
|
||||||
PostStatus(this, [backend, receive_mode, target, transfer, total_bytes]() {
|
PostStatus(this, [backend, receive_mode, target, transfer, total_bytes,
|
||||||
|
attachment_count]() {
|
||||||
if (!backend || backend->shutting_down_) {
|
if (!backend || backend->shutting_down_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
backend->targets_.ApplyTarget(target);
|
backend->targets_.ApplyTarget(target);
|
||||||
backend->transfers_.ApplyTransfer(receive_mode, target, transfer,
|
backend->transfers_.ApplyTransfer(receive_mode, target, transfer,
|
||||||
total_bytes);
|
total_bytes);
|
||||||
|
if (!transfer.is_final_status()) {
|
||||||
|
backend->finished_transfer_ids_.remove(target.id);
|
||||||
|
}
|
||||||
if (receive_mode &&
|
if (receive_mode &&
|
||||||
transfer.status() ==
|
transfer.status() ==
|
||||||
TransferMetadata::Status::kAwaitingLocalConfirmation) {
|
TransferMetadata::Status::kAwaitingLocalConfirmation) {
|
||||||
backend->receive_offer_received_ = true;
|
backend->receive_offer_received_ = true;
|
||||||
backend->receive_timeout_timer_.stop();
|
backend->receive_timeout_timer_.stop();
|
||||||
|
backend->StartIncomingOfferTimer(target.id);
|
||||||
emit backend->incomingTransfer(target.id);
|
emit backend->incomingTransfer(target.id);
|
||||||
|
emit backend->incomingOffer(target.id, ToQString(target.device_name),
|
||||||
|
attachment_count, total_bytes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receive_mode) {
|
||||||
|
backend->ResolveIncomingOffer(target.id);
|
||||||
|
}
|
||||||
|
if (transfer.is_final_status() &&
|
||||||
|
!backend->finished_transfer_ids_.contains(target.id)) {
|
||||||
|
backend->finished_transfer_ids_.insert(target.id);
|
||||||
|
emit backend->transferFinished(
|
||||||
|
target.id, receive_mode, ToQString(target.device_name),
|
||||||
|
ToQString(TransferMetadata::StatusToString(transfer.status())));
|
||||||
|
if (receive_mode) {
|
||||||
|
backend->StartFastInitiationMonitoring();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Backend::StartIncomingOfferTimer(qint64 share_target_id) {
|
||||||
|
ResolveIncomingOffer(share_target_id);
|
||||||
|
auto* timer = new QTimer(this);
|
||||||
|
timer->setSingleShot(true);
|
||||||
|
timer->setInterval(kIncomingOfferTimeoutMilliseconds);
|
||||||
|
connect(timer, &QTimer::timeout, this, [this, share_target_id]() {
|
||||||
|
OnIncomingOfferTimeout(share_target_id);
|
||||||
|
});
|
||||||
|
incoming_offer_timers_.insert(share_target_id, timer);
|
||||||
|
timer->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Backend::ResolveIncomingOffer(qint64 share_target_id) {
|
||||||
|
QTimer* timer = incoming_offer_timers_.take(share_target_id);
|
||||||
|
if (timer == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
timer->stop();
|
||||||
|
timer->deleteLater();
|
||||||
|
emit incomingOfferResolved(share_target_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Backend::OnIncomingOfferTimeout(qint64 share_target_id) {
|
||||||
|
if (!incoming_offer_timers_.contains(share_target_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ResolveIncomingOffer(share_target_id);
|
||||||
|
if (service_ != nullptr && !shutting_down_) {
|
||||||
|
service_->Reject(share_target_id,
|
||||||
|
StatusCallback(QStringLiteral("Expire incoming offer")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Backend::SetDesiredMode(Mode mode) {
|
void Backend::SetDesiredMode(Mode mode) {
|
||||||
desired_mode_ = mode;
|
desired_mode_ = mode;
|
||||||
DriveMode();
|
DriveMode();
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
|
#include <QHash>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
@@ -129,6 +131,11 @@ class Backend : public QObject,
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void incomingTransfer(qint64 share_target_id);
|
void incomingTransfer(qint64 share_target_id);
|
||||||
|
void incomingOffer(qint64 share_target_id, QString device_name,
|
||||||
|
int attachment_count, qint64 total_bytes);
|
||||||
|
void incomingOfferResolved(qint64 share_target_id);
|
||||||
|
void transferFinished(qint64 share_target_id, bool receive_mode,
|
||||||
|
QString device_name, QString status);
|
||||||
void outgoingTransferStartFailed(qint64 share_target_id);
|
void outgoingTransferStartFailed(qint64 share_target_id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -185,6 +192,9 @@ class Backend : public QObject,
|
|||||||
nearby::api::FastInitiationManager::Error error);
|
nearby::api::FastInitiationManager::Error error);
|
||||||
void OpenReceiveWindow(bool fallback);
|
void OpenReceiveWindow(bool fallback);
|
||||||
void OnReceiveTimeout();
|
void OnReceiveTimeout();
|
||||||
|
void StartIncomingOfferTimer(qint64 share_target_id);
|
||||||
|
void ResolveIncomingOffer(qint64 share_target_id);
|
||||||
|
void OnIncomingOfferTimeout(qint64 share_target_id);
|
||||||
std::function<void(NearbySharingService::StatusCodes)> StatusCallback(
|
std::function<void(NearbySharingService::StatusCodes)> StatusCallback(
|
||||||
QString operation);
|
QString operation);
|
||||||
void ReportStatus(const QString& operation,
|
void ReportStatus(const QString& operation,
|
||||||
@@ -199,6 +209,8 @@ class Backend : public QObject,
|
|||||||
TransferCallback send_transfer_callback_;
|
TransferCallback send_transfer_callback_;
|
||||||
TransferCallback receive_transfer_callback_;
|
TransferCallback receive_transfer_callback_;
|
||||||
QTimer receive_timeout_timer_;
|
QTimer receive_timeout_timer_;
|
||||||
|
QHash<qint64, QTimer*> incoming_offer_timers_;
|
||||||
|
QSet<qint64> finished_transfer_ids_;
|
||||||
Mode active_mode_ = Mode::kNone;
|
Mode active_mode_ = Mode::kNone;
|
||||||
Mode desired_mode_ = Mode::kNone;
|
Mode desired_mode_ = Mode::kNone;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
|||||||
@@ -39,9 +39,18 @@ class BackendTestPeer {
|
|||||||
return backend.monitoring_requested_;
|
return backend.monitoring_requested_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool HasIncomingOfferTimer(const Backend& backend,
|
||||||
|
int64_t share_target_id) {
|
||||||
|
return backend.incoming_offer_timers_.contains(share_target_id);
|
||||||
|
}
|
||||||
|
|
||||||
static void ExpireReceiveWindow(Backend& backend) {
|
static void ExpireReceiveWindow(Backend& backend) {
|
||||||
backend.OnReceiveTimeout();
|
backend.OnReceiveTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ExpireIncomingOffer(Backend& backend, int64_t share_target_id) {
|
||||||
|
backend.OnIncomingOfferTimeout(share_target_id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -97,10 +106,16 @@ class TestSharingService final : public nearby::sharing::NearbySharingService {
|
|||||||
std::function<void(StatusCodes)> callback) override {
|
std::function<void(StatusCodes)> callback) override {
|
||||||
callback(StatusCodes::kOk);
|
callback(StatusCodes::kOk);
|
||||||
}
|
}
|
||||||
void Accept(int64_t, std::function<void(StatusCodes)> callback) override {
|
void Accept(int64_t share_target_id,
|
||||||
|
std::function<void(StatusCodes)> callback) override {
|
||||||
|
++accept_count_;
|
||||||
|
last_accepted_target_ = share_target_id;
|
||||||
callback(StatusCodes::kOk);
|
callback(StatusCodes::kOk);
|
||||||
}
|
}
|
||||||
void Reject(int64_t, std::function<void(StatusCodes)> callback) override {
|
void Reject(int64_t share_target_id,
|
||||||
|
std::function<void(StatusCodes)> callback) override {
|
||||||
|
++reject_count_;
|
||||||
|
last_rejected_target_ = share_target_id;
|
||||||
callback(StatusCodes::kOk);
|
callback(StatusCodes::kOk);
|
||||||
}
|
}
|
||||||
void Cancel(int64_t, std::function<void(StatusCodes)> callback) override {
|
void Cancel(int64_t, std::function<void(StatusCodes)> callback) override {
|
||||||
@@ -148,9 +163,18 @@ class TestSharingService final : public nearby::sharing::NearbySharingService {
|
|||||||
receive_callback_->OnTransferUpdate(target, attachments, metadata);
|
receive_callback_->OnTransferUpdate(target, attachments, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int accept_count() const { return accept_count_; }
|
||||||
|
int reject_count() const { return reject_count_; }
|
||||||
|
int64_t last_accepted_target() const { return last_accepted_target_; }
|
||||||
|
int64_t last_rejected_target() const { return last_rejected_target_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool send_registered_ = false;
|
bool send_registered_ = false;
|
||||||
nearby::sharing::TransferUpdateCallback* receive_callback_ = nullptr;
|
nearby::sharing::TransferUpdateCallback* receive_callback_ = nullptr;
|
||||||
|
int accept_count_ = 0;
|
||||||
|
int reject_count_ = 0;
|
||||||
|
int64_t last_accepted_target_ = 0;
|
||||||
|
int64_t last_rejected_target_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class TestFastInitiationManager final
|
class TestFastInitiationManager final
|
||||||
@@ -286,6 +310,73 @@ TEST_F(BackendFastInitiationTest, IncomingOfferCancelsReceiveTimeout) {
|
|||||||
|
|
||||||
EXPECT_TRUE(BackendTestPeer::IsReceiving(*backend_));
|
EXPECT_TRUE(BackendTestPeer::IsReceiving(*backend_));
|
||||||
EXPECT_FALSE(BackendTestPeer::IsReceiveTimerActive(*backend_));
|
EXPECT_FALSE(BackendTestPeer::IsReceiveTimerActive(*backend_));
|
||||||
|
EXPECT_TRUE(BackendTestPeer::HasIncomingOfferTimer(*backend_, target.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BackendFastInitiationTest, AcceptCancelsIncomingOfferTimeout) {
|
||||||
|
fast_init_manager_.FireDiscovered();
|
||||||
|
DrainEvents();
|
||||||
|
|
||||||
|
nearby::sharing::ShareTarget target;
|
||||||
|
target.id = 11;
|
||||||
|
auto attachments = nearby::sharing::AttachmentContainer::Builder().Build();
|
||||||
|
service_.FireReceiveTransferUpdate(
|
||||||
|
target, *attachments,
|
||||||
|
nearby::sharing::TransferMetadataBuilder()
|
||||||
|
.set_status(nearby::sharing::TransferMetadata::Status::
|
||||||
|
kAwaitingLocalConfirmation)
|
||||||
|
.build());
|
||||||
|
DrainEvents();
|
||||||
|
|
||||||
|
backend_->accept(target.id);
|
||||||
|
|
||||||
|
EXPECT_FALSE(BackendTestPeer::HasIncomingOfferTimer(*backend_, target.id));
|
||||||
|
EXPECT_EQ(service_.accept_count(), 1);
|
||||||
|
EXPECT_EQ(service_.last_accepted_target(), target.id);
|
||||||
|
BackendTestPeer::ExpireIncomingOffer(*backend_, target.id);
|
||||||
|
EXPECT_EQ(service_.reject_count(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BackendFastInitiationTest, IncomingOfferTimeoutRejectsExactlyOnce) {
|
||||||
|
fast_init_manager_.FireDiscovered();
|
||||||
|
DrainEvents();
|
||||||
|
|
||||||
|
nearby::sharing::ShareTarget target;
|
||||||
|
target.id = 12;
|
||||||
|
auto attachments = nearby::sharing::AttachmentContainer::Builder().Build();
|
||||||
|
service_.FireReceiveTransferUpdate(
|
||||||
|
target, *attachments,
|
||||||
|
nearby::sharing::TransferMetadataBuilder()
|
||||||
|
.set_status(nearby::sharing::TransferMetadata::Status::
|
||||||
|
kAwaitingLocalConfirmation)
|
||||||
|
.build());
|
||||||
|
DrainEvents();
|
||||||
|
|
||||||
|
BackendTestPeer::ExpireIncomingOffer(*backend_, target.id);
|
||||||
|
BackendTestPeer::ExpireIncomingOffer(*backend_, target.id);
|
||||||
|
|
||||||
|
EXPECT_FALSE(BackendTestPeer::HasIncomingOfferTimer(*backend_, target.id));
|
||||||
|
EXPECT_EQ(service_.reject_count(), 1);
|
||||||
|
EXPECT_EQ(service_.last_rejected_target(), target.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BackendFastInitiationTest, FinalReceiveRestartsMonitoring) {
|
||||||
|
fast_init_manager_.FireDiscovered();
|
||||||
|
DrainEvents();
|
||||||
|
|
||||||
|
nearby::sharing::ShareTarget target;
|
||||||
|
target.id = 13;
|
||||||
|
auto attachments = nearby::sharing::AttachmentContainer::Builder().Build();
|
||||||
|
service_.FireReceiveTransferUpdate(
|
||||||
|
target, *attachments,
|
||||||
|
nearby::sharing::TransferMetadataBuilder()
|
||||||
|
.set_status(nearby::sharing::TransferMetadata::Status::kComplete)
|
||||||
|
.build());
|
||||||
|
DrainEvents();
|
||||||
|
|
||||||
|
EXPECT_TRUE(BackendTestPeer::IsIdle(*backend_));
|
||||||
|
EXPECT_TRUE(BackendTestPeer::IsMonitoringRequested(*backend_));
|
||||||
|
EXPECT_TRUE(fast_init_manager_.IsScanning());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(BackendFastInitiationTest, DiscoveryStopsFastInitiationScanning) {
|
TEST_F(BackendFastInitiationTest, DiscoveryStopsFastInitiationScanning) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
#include "application_controller.h"
|
||||||
#include "backend.h"
|
#include "backend.h"
|
||||||
#include "qobject.h"
|
#include "qobject.h"
|
||||||
#include "sharing/linux/app/native_file_logging.h"
|
#include "sharing/linux/app/native_file_logging.h"
|
||||||
@@ -92,8 +93,8 @@ void InstallTerminationCleanup(QApplication& app) {
|
|||||||
fcntl(g_signal_pipe[1], F_SETFL,
|
fcntl(g_signal_pipe[1], F_SETFL,
|
||||||
fcntl(g_signal_pipe[1], F_GETFL, 0) | O_NONBLOCK);
|
fcntl(g_signal_pipe[1], F_GETFL, 0) | O_NONBLOCK);
|
||||||
|
|
||||||
auto* notifier = new QSocketNotifier(g_signal_pipe[0],
|
auto* notifier =
|
||||||
QSocketNotifier::Read, &app);
|
new QSocketNotifier(g_signal_pipe[0], QSocketNotifier::Read, &app);
|
||||||
QObject::connect(notifier, &QSocketNotifier::activated, &app,
|
QObject::connect(notifier, &QSocketNotifier::activated, &app,
|
||||||
[&app, notifier](int socket) {
|
[&app, notifier](int socket) {
|
||||||
notifier->setEnabled(false);
|
notifier->setEnabled(false);
|
||||||
@@ -103,7 +104,7 @@ void InstallTerminationCleanup(QApplication& app) {
|
|||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
struct sigaction action {};
|
struct sigaction action{};
|
||||||
action.sa_handler = HandleTerminationSignal;
|
action.sa_handler = HandleTerminationSignal;
|
||||||
sigemptyset(&action.sa_mask);
|
sigemptyset(&action.sa_mask);
|
||||||
action.sa_flags = 0;
|
action.sa_flags = 0;
|
||||||
@@ -118,13 +119,33 @@ int main(int argc, char* argv[]) {
|
|||||||
nearby::sharing::linux::NativeFileLogging::Initialize();
|
nearby::sharing::linux::NativeFileLogging::Initialize();
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
app.setApplicationName(QStringLiteral("QuickShare"));
|
app.setApplicationName(QStringLiteral("QuickShare"));
|
||||||
|
app.setOrganizationDomain(QStringLiteral("com.google"));
|
||||||
app.setDesktopFileName(QStringLiteral("quickshare"));
|
app.setDesktopFileName(QStringLiteral("quickshare"));
|
||||||
app.setWindowIcon(QIcon(QStringLiteral(":/icons/quickshare.svg")));
|
app.setWindowIcon(QIcon(QStringLiteral(":/icons/quickshare.svg")));
|
||||||
|
app.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
|
ApplicationController application_controller(app);
|
||||||
|
if (!application_controller.ClaimSingleInstance()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Backend backend;
|
Backend backend;
|
||||||
|
application_controller.SetBackend(&backend);
|
||||||
|
application_controller.InitializeTray();
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
InstallTerminationCleanup(app);
|
InstallTerminationCleanup(app);
|
||||||
|
|
||||||
|
engine.rootContext()->setContextProperty("backend", &backend);
|
||||||
|
engine.rootContext()->setContextProperty("appController",
|
||||||
|
&application_controller);
|
||||||
|
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
|
||||||
|
&application_controller,
|
||||||
|
[&application_controller](QObject* object, const QUrl&) {
|
||||||
|
if (object != nullptr) {
|
||||||
|
application_controller.AttachWindow(object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const int fontId = QFontDatabase::addApplicationFont(":/googlesans_var.ttf");
|
const int fontId = QFontDatabase::addApplicationFont(":/googlesans_var.ttf");
|
||||||
|
|
||||||
if (fontId != -1) {
|
if (fontId != -1) {
|
||||||
@@ -137,8 +158,6 @@ int main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsHotReloadEnabled()) {
|
if (IsHotReloadEnabled()) {
|
||||||
engine.rootContext()->setContextProperty("backend", &backend);
|
|
||||||
|
|
||||||
const QDir qml_source_dir = QmlSourceDir();
|
const QDir qml_source_dir = QmlSourceDir();
|
||||||
const QUrl source_url =
|
const QUrl source_url =
|
||||||
QUrl::fromLocalFile(qml_source_dir.absoluteFilePath("main.qml"));
|
QUrl::fromLocalFile(qml_source_dir.absoluteFilePath("main.qml"));
|
||||||
@@ -191,7 +210,6 @@ int main(int argc, char* argv[]) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.rootContext()->setContextProperty("backend", &backend);
|
|
||||||
// Loaded via the qrc scheme since it's compiled into the binary
|
// Loaded via the qrc scheme since it's compiled into the binary
|
||||||
const QUrl url(QStringLiteral("qrc:/main.qml"));
|
const QUrl url(QStringLiteral("qrc:/main.qml"));
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ Window {
|
|||||||
title: qsTr("QuickShare")
|
title: qsTr("QuickShare")
|
||||||
color: "#DCF5FF"
|
color: "#DCF5FF"
|
||||||
|
|
||||||
|
onClosing: function(close) {
|
||||||
|
if (appController.closeToTray) {
|
||||||
|
close.accepted = false
|
||||||
|
root.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FontLoader {
|
FontLoader {
|
||||||
id: googlesans
|
id: googlesans
|
||||||
|
|||||||
Reference in New Issue
Block a user