#include "cache_dialog.h"
#include "configure.h"
#include "linglong/utils/gettext.h"
#include "permissionDialog.h"
#include "tl/expected.hpp"
#include <QApplication>
#include <QCommandLineParser>
#include <QSocketNotifier>
#include <QTimer>
#include <QWidget>
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
namespace {
constexpr std::string_view protocol_version = "1.0";
int writeMessage(const linglong::api::types::v1::DialogMessage &message) noexcept
{
auto content = nlohmann::json(message).dump();
uint32_t size = content.size();
std::string rawData{ reinterpret_cast<char *>(&size), 4 };
rawData.append(content);
std::string::size_type offset{ 0 };
auto expectedWrite = rawData.size();
while (true) {
auto bytesWrite = ::write(STDOUT_FILENO, rawData.data() + offset, expectedWrite - offset);
if (bytesWrite == -1) {
if (errno == EINTR) {
continue;
}
std::cerr << "write failed:" << ::strerror(errno) << std::endl;
return -1;
}
offset += bytesWrite;
if (offset == expectedWrite) {
break;
}
}
return 0;
}
tl::expected<linglong::api::types::v1::DialogMessage, int> readMessage() noexcept
{
using namespace std::chrono_literals;
uint32_t len{ 0 };
uint32_t curLen{ 0 };
std::string rawData;
QEventLoop loop;
QSocketNotifier notifier(STDIN_FILENO, QSocketNotifier::Read);
QTimer timer;
QObject::connect(&timer, &QTimer::timeout, [&loop] {
std::cerr << "TIMEOUT" << std::endl;
loop.exit(-1);
});
QObject::connect(¬ifier,
&QSocketNotifier::activated,
[&loop, &len, &curLen, &timer, &rawData] {
int32_t bytes{ 0 };
ssize_t bytesRead{ 0 };
if (curLen < 4) {
bytes = 4 - curLen;
bytesRead = ::read(STDIN_FILENO,
reinterpret_cast<unsigned char *>(&len) + curLen,
bytes);
if (bytesRead == -1) {
if (errno == EINTR) {
timer.start(4s);
return;
}
std::cerr << "read failed:" << ::strerror(errno) << std::endl;
loop.exit(errno);
return;
}
curLen += bytesRead;
if (bytesRead != bytes) {
timer.start(4s);
return;
}
rawData.resize(len);
}
bytes = len - (curLen - 4);
bytesRead = ::read(STDIN_FILENO, rawData.data() + (curLen - 4), bytes);
if (bytesRead == -1) {
if (errno == EINTR) {
timer.start(4s);
return;
}
std::cerr << "read failed:" << ::strerror(errno) << std::endl;
loop.exit(errno);
return;
}
if (bytesRead != bytes) {
timer.start(4s);
return;
}
loop.exit(0);
});
timer.start(4s);
if (auto ret = loop.exec(); ret != 0) {
return tl::unexpected{ ret };
}
linglong::api::types::v1::DialogMessage message;
try {
message = nlohmann::json::parse(rawData).get<linglong::api::types::v1::DialogMessage>();
} catch (const std::exception &e) {
std::cerr << "caught exception:" << e.what() << std::endl;
return tl::unexpected{ -1 };
}
return message;
}
int showPermissionDialog()
{
auto payload = nlohmann::json(linglong::api::types::v1::DialogHandShakePayload{
.version = std::string{ protocol_version } })
.dump();
auto handshake =
linglong::api::types::v1::DialogMessage{ .payload = std::move(payload), .type = "Handshake" };
if (writeMessage(handshake) == -1) {
return -1;
}
auto request = readMessage();
if (!request) {
std::cerr << "failed to read message" << std::endl;
return -1;
}
if (request->type != "Request") {
std::cerr << "unexpected message type:" << request->type << std::endl;
return -1;
}
linglong::api::types::v1::ApplicationPermissionsRequest perms;
try {
auto required = nlohmann::json::parse(request->payload);
auto content = required.get<linglong::api::types::v1::ApplicationPermissionsRequest>();
perms = std::move(content);
} catch (const std::exception &e) {
std::cerr << "caught exception:" << e.what() << std::endl;
return -1;
}
auto *dialog = new PermissionDialog{ perms };
dialog->show();
return 0;
}
}
int main(int argc, char *argv[])
{
bindtextdomain(PACKAGE_LOCALE_DOMAIN, PACKAGE_LOCALE_DIR);
textdomain(PACKAGE_LOCALE_DOMAIN);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseHighDpiPixmaps);
QApplication::setAttribute(Qt::ApplicationAttribute::AA_EnableHighDpiScaling);
#endif
QApplication app{ argc, argv };
Q_INIT_RESOURCE(cache_dialog_resource);
QApplication::setApplicationName("ll-dialog");
QApplication::setApplicationVersion(LINGLONG_VERSION);
QCommandLineParser parser;
parser.setApplicationDescription("dialog for interaction");
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption modeOption(QStringList{ "m", "mode" }, "setting dialog mode", "mode");
QCommandLineOption idOption("id", "Application id", "id");
parser.addOption(modeOption);
parser.addOption(idOption);
parser.process(app);
auto mode = parser.value(modeOption);
if (mode.isEmpty()) {
return -1;
}
if (mode == "permission") {
if (showPermissionDialog() != 0) {
return -1;
}
}
if (mode == "startup") {
auto id = parser.value(idOption);
if (id.isEmpty()) {
parser.showHelp();
return -1;
}
auto *dialog = new CacheDialog(id);
dialog->show();
}
return QApplication::exec();
}