功能:桌面连接可配置化——每卡设置、地址支持 IP/域名/VPN 隧道
- 新增 DesktopStore:~/.config/vdi-terminal/desktops.json 持久化连接参数, 首次启动生成三张默认卡(待分配),老盒子自动从 client.conf 迁移 server 地址 - 新增 DesktopSettingsDialog:按桌面编辑协议/地址/应用名/分辨率/帧率, 置顶显示避免被全屏 kiosk 遮挡 - 桌面列表每张卡增加“设置”按钮;管理菜单 Ctrl+Alt+Shift+M 增加入口 - DesktopService 改为从 store 读取;地址为空的桌面显示待分配且不可连接 - 修复:默认卡未按 endpoint 重算状态导致迁移后仍显示待分配 - 实测真机通过:Windows 自动迁移可连接、对话框显示与保存正常
This commit is contained in:
parent
66f0cacb32
commit
8ee0cd2038
@ -24,7 +24,9 @@ add_executable(kiosk-shell
|
||||
src/main_window.cpp
|
||||
src/login_page.cpp
|
||||
src/desktop_list_page.cpp
|
||||
src/desktop_settings_dialog.cpp
|
||||
src/desktop_service.cpp
|
||||
src/desktop_store.cpp
|
||||
src/moonlight_session.cpp
|
||||
src/state_machine.cpp
|
||||
src/config.cpp
|
||||
|
||||
@ -132,6 +132,16 @@ void DesktopListPage::rebuildCards() {
|
||||
state->setStyleSheet(QStringLiteral("color: %1; font-weight: bold;").arg(stateColor));
|
||||
row->addWidget(state);
|
||||
|
||||
// “设置”:每张卡都可编辑连接参数(协议/地址/应用名/分辨率/帧率)
|
||||
auto* editBtn = new QPushButton(QStringLiteral("设置"), card);
|
||||
editBtn->setMinimumSize(88, 42);
|
||||
connect(editBtn, &QPushButton::clicked, this, [this, id = d.id]() {
|
||||
if (!busy_) {
|
||||
emit editRequested(id);
|
||||
}
|
||||
});
|
||||
row->addWidget(editBtn);
|
||||
|
||||
if (d.state == DesktopState::kAvailable) {
|
||||
auto* connectBtn = new QPushButton(QStringLiteral("连接"), card);
|
||||
connectBtn->setMinimumSize(110, 42);
|
||||
|
||||
@ -36,6 +36,8 @@ public:
|
||||
signals:
|
||||
// 用户点击一张可用桌面卡片
|
||||
void desktopSelected(const QString& desktopId);
|
||||
// 用户点击某张卡片的“设置”按钮
|
||||
void editRequested(const QString& desktopId);
|
||||
// 用户点击退出登录
|
||||
void logoutRequested();
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
#include "desktop_service.h"
|
||||
|
||||
#include "desktop_store.h"
|
||||
|
||||
namespace vdi {
|
||||
|
||||
void DesktopService::login(const QString& username, const QString& password, AuthCallback callback) {
|
||||
@ -20,59 +22,34 @@ void DesktopService::login(const QString& username, const QString& password, Aut
|
||||
}
|
||||
|
||||
void DesktopService::listDesktops(DesktopListCallback callback) {
|
||||
// 本地演示数据:Windows 可用(Moonlight),Ubuntu/麒麟尚未分配连接信息。
|
||||
// 桌面与连接参数来自本地连接配置(现场可配);
|
||||
// 正式版本替换为 /api/v1/terminal/session/desktops(按用户绑定返回)。
|
||||
std::vector<DesktopInfo> desktops;
|
||||
|
||||
DesktopInfo windows;
|
||||
windows.id = QStringLiteral("desk-win10-01");
|
||||
windows.name = QStringLiteral("Windows 10");
|
||||
windows.os = QStringLiteral("Windows 10 22H2 专业版");
|
||||
windows.protocol = Protocol::kMoonlight;
|
||||
windows.state = DesktopState::kAvailable;
|
||||
windows.endpoint = QStringLiteral("192.168.1.70");
|
||||
windows.app = QStringLiteral("Desktop");
|
||||
desktops.push_back(windows);
|
||||
|
||||
DesktopInfo ubuntu;
|
||||
ubuntu.id = QStringLiteral("desk-ubuntu-01");
|
||||
ubuntu.name = QStringLiteral("Ubuntu");
|
||||
ubuntu.os = QStringLiteral("Ubuntu 桌面");
|
||||
ubuntu.protocol = Protocol::kRdp;
|
||||
ubuntu.state = DesktopState::kUnassigned;
|
||||
desktops.push_back(ubuntu);
|
||||
|
||||
DesktopInfo kylin;
|
||||
kylin.id = QStringLiteral("desk-kylin-01");
|
||||
kylin.name = QStringLiteral("麒麟");
|
||||
kylin.os = QStringLiteral("银河麒麟桌面");
|
||||
kylin.protocol = Protocol::kRdp;
|
||||
kylin.state = DesktopState::kUnassigned;
|
||||
desktops.push_back(kylin);
|
||||
|
||||
std::vector<DesktopInfo> desktops = DesktopStore::load();
|
||||
if (callback) {
|
||||
callback(std::move(desktops), QString());
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopService::createTicket(const QString& desktopId, TicketCallback callback) {
|
||||
// 本地演示票据:直接由桌面列表数据构造。
|
||||
// 本地演示票据:由连接配置构造。
|
||||
// 正式版本替换为 /api/v1/terminal/session/connect-ticket(短期一次性票据)。
|
||||
ConnectionTicket ticket;
|
||||
ticket.desktopId = desktopId;
|
||||
|
||||
std::vector<DesktopInfo> desktops;
|
||||
QString error;
|
||||
listDesktops([&desktops, &error](std::vector<DesktopInfo> list, const QString& err) {
|
||||
desktops = std::move(list);
|
||||
error = err;
|
||||
});
|
||||
|
||||
for (const DesktopInfo& d : desktops) {
|
||||
if (d.id == desktopId) {
|
||||
listDesktops([&ticket, &error, desktopId](std::vector<DesktopInfo> list, const QString& err) {
|
||||
if (!err.isEmpty()) {
|
||||
error = err;
|
||||
return;
|
||||
}
|
||||
for (const DesktopInfo& d : list) {
|
||||
if (d.id != desktopId) {
|
||||
continue;
|
||||
}
|
||||
if (d.state != DesktopState::kAvailable) {
|
||||
error = QStringLiteral("桌面 %1 尚未分配连接信息").arg(d.name);
|
||||
break;
|
||||
error = QStringLiteral("桌面 %1 尚未配置连接地址,请先在“设置”中完成配置")
|
||||
.arg(d.name);
|
||||
return;
|
||||
}
|
||||
ticket.protocol = d.protocol;
|
||||
ticket.endpoint = d.endpoint;
|
||||
@ -80,10 +57,12 @@ void DesktopService::createTicket(const QString& desktopId, TicketCallback callb
|
||||
ticket.width = d.width;
|
||||
ticket.height = d.height;
|
||||
ticket.fps = d.fps;
|
||||
error.clear();
|
||||
break;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (error.isEmpty() && ticket.endpoint.isEmpty()) {
|
||||
error = QStringLiteral("桌面不存在");
|
||||
}
|
||||
});
|
||||
|
||||
if (callback) {
|
||||
callback(ticket, error);
|
||||
|
||||
143
terminal/kiosk-shell/src/desktop_settings_dialog.cpp
Normal file
143
terminal/kiosk-shell/src/desktop_settings_dialog.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
#include "desktop_settings_dialog.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "desktop_store.h"
|
||||
|
||||
namespace vdi {
|
||||
|
||||
DesktopSettingsDialog::DesktopSettingsDialog(std::vector<DesktopInfo> desktops,
|
||||
const QString& selectId, QWidget* parent)
|
||||
: QDialog(parent), desktops_(std::move(desktops)) {
|
||||
setWindowTitle(QStringLiteral("桌面连接设置"));
|
||||
setMinimumWidth(560);
|
||||
// Kiosk 主窗口是全屏窗口,必须置顶否则对话框会被完全遮挡
|
||||
setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
|
||||
|
||||
auto* root = new QVBoxLayout(this);
|
||||
root->setSpacing(14);
|
||||
|
||||
auto* hint = new QLabel(
|
||||
QStringLiteral("为每张桌面配置连接参数。地址支持 IP、域名或 VPN 隧道地址;"
|
||||
"地址留空表示待分配,卡片将不可连接。"),
|
||||
this);
|
||||
hint->setWordWrap(true);
|
||||
hint->setStyleSheet(QStringLiteral("color: #8a94a6;"));
|
||||
root->addWidget(hint);
|
||||
|
||||
auto* form = new QFormLayout();
|
||||
form->setHorizontalSpacing(14);
|
||||
form->setVerticalSpacing(12);
|
||||
|
||||
comboDesktop_ = new QComboBox(this);
|
||||
for (const DesktopInfo& d : desktops_) {
|
||||
comboDesktop_->addItem(d.name, d.id);
|
||||
}
|
||||
form->addRow(QStringLiteral("桌面"), comboDesktop_);
|
||||
|
||||
comboProtocol_ = new QComboBox(this);
|
||||
comboProtocol_->addItem(QStringLiteral("Moonlight"), QStringLiteral("moonlight"));
|
||||
comboProtocol_->addItem(QStringLiteral("RDP"), QStringLiteral("rdp"));
|
||||
form->addRow(QStringLiteral("协议"), comboProtocol_);
|
||||
|
||||
editEndpoint_ = new QLineEdit(this);
|
||||
editEndpoint_->setPlaceholderText(QStringLiteral("例如 192.168.1.70 或 VPN 隧道地址"));
|
||||
form->addRow(QStringLiteral("服务器地址"), editEndpoint_);
|
||||
|
||||
editApp_ = new QLineEdit(this);
|
||||
editApp_->setPlaceholderText(QStringLiteral("Sunshine 应用名,通常为 Desktop"));
|
||||
form->addRow(QStringLiteral("应用名(Moonlight)"), editApp_);
|
||||
|
||||
comboResolution_ = new QComboBox(this);
|
||||
const char* resolutions[] = {"1920x1080", "2560x1440", "1920x1440", "3840x2160", "1280x720"};
|
||||
for (const char* r : resolutions) {
|
||||
comboResolution_->addItem(QString::fromLatin1(r), QString::fromLatin1(r));
|
||||
}
|
||||
form->addRow(QStringLiteral("分辨率"), comboResolution_);
|
||||
|
||||
comboFps_ = new QComboBox(this);
|
||||
comboFps_->addItem(QStringLiteral("60"), 60);
|
||||
comboFps_->addItem(QStringLiteral("30"), 30);
|
||||
form->addRow(QStringLiteral("帧率"), comboFps_);
|
||||
|
||||
root->addLayout(form);
|
||||
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
buttons->button(QDialogButtonBox::Ok)->setText(QStringLiteral("保存"));
|
||||
buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("取消"));
|
||||
root->addWidget(buttons);
|
||||
|
||||
connect(comboDesktop_, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
|
||||
&DesktopSettingsDialog::onDesktopChanged);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &DesktopSettingsDialog::onAccepted);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
// 预选指定桌面
|
||||
int index = 0;
|
||||
if (!selectId.isEmpty()) {
|
||||
for (int i = 0; i < comboDesktop_->count(); ++i) {
|
||||
if (comboDesktop_->itemData(i).toString() == selectId) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
comboDesktop_->setCurrentIndex(index);
|
||||
loadCurrent();
|
||||
}
|
||||
|
||||
void DesktopSettingsDialog::onDesktopChanged(int index) {
|
||||
Q_UNUSED(index);
|
||||
loadCurrent();
|
||||
}
|
||||
|
||||
void DesktopSettingsDialog::loadCurrent() {
|
||||
const QString id = comboDesktop_->currentData().toString();
|
||||
for (const DesktopInfo& d : desktops_) {
|
||||
if (d.id != id) {
|
||||
continue;
|
||||
}
|
||||
loading_ = true;
|
||||
comboProtocol_->setCurrentIndex(d.protocol == Protocol::kRdp ? 1 : 0);
|
||||
editEndpoint_->setText(d.endpoint);
|
||||
editApp_->setText(d.app);
|
||||
int resIndex = comboResolution_->findData(
|
||||
QStringLiteral("%1x%2").arg(d.width).arg(d.height));
|
||||
comboResolution_->setCurrentIndex(resIndex >= 0 ? resIndex : 0);
|
||||
int fpsIndex = comboFps_->findData(d.fps);
|
||||
comboFps_->setCurrentIndex(fpsIndex >= 0 ? fpsIndex : 0);
|
||||
loading_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DesktopSettingsDialog::onAccepted() {
|
||||
const QString id = comboDesktop_->currentData().toString();
|
||||
for (DesktopInfo& d : desktops_) {
|
||||
if (d.id != id) {
|
||||
continue;
|
||||
}
|
||||
d.protocol = comboProtocol_->currentData().toString() == QStringLiteral("rdp")
|
||||
? Protocol::kRdp
|
||||
: Protocol::kMoonlight;
|
||||
d.endpoint = editEndpoint_->text().trimmed();
|
||||
d.app = editApp_->text().trimmed();
|
||||
const QString res = comboResolution_->currentData().toString();
|
||||
sscanf(res.toLatin1().constData(), "%dx%d", &d.width, &d.height);
|
||||
d.fps = comboFps_->currentData().toInt();
|
||||
d.state = d.endpoint.isEmpty() ? DesktopState::kUnassigned : DesktopState::kAvailable;
|
||||
DesktopStore::update(d);
|
||||
break;
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
} // namespace vdi
|
||||
52
terminal/kiosk-shell/src/desktop_settings_dialog.h
Normal file
52
terminal/kiosk-shell/src/desktop_settings_dialog.h
Normal file
@ -0,0 +1,52 @@
|
||||
// 桌面连接设置对话框
|
||||
//
|
||||
// 现场环境每套部署的桌面地址不同:选择一张桌面卡,
|
||||
// 编辑协议(Moonlight/RDP)、地址(IP / 域名 / VPN 隧道地址)、
|
||||
// 应用名、分辨率和帧率,保存后立即生效。
|
||||
// 地址为空 = 待分配,卡片不可连接。
|
||||
|
||||
#ifndef KIOSK_SHELL_DESKTOP_SETTINGS_DIALOG_H
|
||||
#define KIOSK_SHELL_DESKTOP_SETTINGS_DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <vector>
|
||||
|
||||
#include "desktop_session.h"
|
||||
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
|
||||
namespace vdi {
|
||||
|
||||
class DesktopSettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DesktopSettingsDialog(std::vector<DesktopInfo> desktops,
|
||||
const QString& selectId = QString(),
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
// 保存后的桌面列表(供调用方刷新)
|
||||
std::vector<DesktopInfo> desktops() const { return desktops_; }
|
||||
|
||||
private slots:
|
||||
void onDesktopChanged(int index);
|
||||
void onAccepted();
|
||||
|
||||
private:
|
||||
void loadCurrent();
|
||||
|
||||
std::vector<DesktopInfo> desktops_;
|
||||
|
||||
QComboBox* comboDesktop_ = nullptr;
|
||||
QComboBox* comboProtocol_ = nullptr;
|
||||
QLineEdit* editEndpoint_ = nullptr;
|
||||
QLineEdit* editApp_ = nullptr;
|
||||
QComboBox* comboResolution_ = nullptr;
|
||||
QComboBox* comboFps_ = nullptr;
|
||||
bool loading_ = false;
|
||||
};
|
||||
|
||||
} // namespace vdi
|
||||
|
||||
#endif // KIOSK_SHELL_DESKTOP_SETTINGS_DIALOG_H
|
||||
160
terminal/kiosk-shell/src/desktop_store.cpp
Normal file
160
terminal/kiosk-shell/src/desktop_store.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
#include "desktop_store.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
namespace vdi {
|
||||
namespace {
|
||||
|
||||
// 三张默认桌面卡:id/name/os 固定,连接参数由现场配置。
|
||||
// endpoint 为空 = 待分配,配置地址后即变为可连接。
|
||||
std::vector<DesktopInfo> defaultDesktops(const QString& migratedServer) {
|
||||
std::vector<DesktopInfo> list;
|
||||
|
||||
DesktopInfo win;
|
||||
win.id = QStringLiteral("desk-win10-01");
|
||||
win.name = QStringLiteral("Windows 10");
|
||||
win.os = QStringLiteral("Windows 桌面");
|
||||
win.protocol = Protocol::kMoonlight;
|
||||
win.endpoint = migratedServer; // 兼容老盒子:迁移 client.conf 的 server
|
||||
win.app = QStringLiteral("Desktop");
|
||||
win.width = 1920;
|
||||
win.height = 1080;
|
||||
win.fps = 60;
|
||||
list.push_back(win);
|
||||
|
||||
DesktopInfo ubuntu;
|
||||
ubuntu.id = QStringLiteral("desk-ubuntu-01");
|
||||
ubuntu.name = QStringLiteral("Ubuntu");
|
||||
ubuntu.os = QStringLiteral("Ubuntu 桌面");
|
||||
ubuntu.protocol = Protocol::kRdp;
|
||||
list.push_back(ubuntu);
|
||||
|
||||
DesktopInfo kylin;
|
||||
kylin.id = QStringLiteral("desk-kylin-01");
|
||||
kylin.name = QStringLiteral("麒麟");
|
||||
kylin.os = QStringLiteral("银河麒麟桌面");
|
||||
kylin.protocol = Protocol::kRdp;
|
||||
list.push_back(kylin);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
DesktopInfo fromJson(const QJsonObject& obj) {
|
||||
DesktopInfo d;
|
||||
d.id = obj.value(QStringLiteral("id")).toString();
|
||||
d.name = obj.value(QStringLiteral("name")).toString();
|
||||
d.os = obj.value(QStringLiteral("os")).toString();
|
||||
d.protocol = obj.value(QStringLiteral("protocol")).toString() == QStringLiteral("rdp")
|
||||
? Protocol::kRdp
|
||||
: Protocol::kMoonlight;
|
||||
d.endpoint = obj.value(QStringLiteral("endpoint")).toString();
|
||||
d.app = obj.value(QStringLiteral("app")).toString();
|
||||
d.width = obj.value(QStringLiteral("width")).toInt(1920);
|
||||
d.height = obj.value(QStringLiteral("height")).toInt(1080);
|
||||
d.fps = obj.value(QStringLiteral("fps")).toInt(60);
|
||||
d.state = d.endpoint.isEmpty() ? DesktopState::kUnassigned : DesktopState::kAvailable;
|
||||
return d;
|
||||
}
|
||||
|
||||
QJsonObject toJson(const DesktopInfo& d) {
|
||||
QJsonObject obj;
|
||||
obj.insert(QStringLiteral("id"), d.id);
|
||||
obj.insert(QStringLiteral("name"), d.name);
|
||||
obj.insert(QStringLiteral("os"), d.os);
|
||||
obj.insert(QStringLiteral("protocol"),
|
||||
d.protocol == Protocol::kRdp ? QStringLiteral("rdp") : QStringLiteral("moonlight"));
|
||||
obj.insert(QStringLiteral("endpoint"), d.endpoint);
|
||||
obj.insert(QStringLiteral("app"), d.app);
|
||||
obj.insert(QStringLiteral("width"), d.width);
|
||||
obj.insert(QStringLiteral("height"), d.height);
|
||||
obj.insert(QStringLiteral("fps"), d.fps);
|
||||
return obj;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString DesktopStore::storePath() {
|
||||
return QString::fromStdString(configDir()) + QStringLiteral("/desktops.json");
|
||||
}
|
||||
|
||||
std::vector<DesktopInfo> DesktopStore::load() {
|
||||
std::vector<DesktopInfo> result;
|
||||
|
||||
QFile file(storePath());
|
||||
if (file.exists() && file.open(QIODevice::ReadOnly)) {
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
||||
const QJsonArray arr = doc.object().value(QStringLiteral("desktops")).toArray();
|
||||
for (const QJsonValue& v : arr) {
|
||||
DesktopInfo d = fromJson(v.toObject());
|
||||
if (!d.id.isEmpty()) {
|
||||
result.push_back(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保三张默认卡始终存在(新环境骨架 / 防止手工编辑删卡)。
|
||||
// 兼容老盒子:client.conf 里只要配过 server,就迁移为 Windows 桌面地址。
|
||||
const TerminalConfig cfg = loadConfig();
|
||||
const QString migrated = result.empty() && !cfg.server.empty()
|
||||
? QString::fromStdString(cfg.server)
|
||||
: QString();
|
||||
const std::vector<DesktopInfo> defaults = defaultDesktops(migrated);
|
||||
for (const DesktopInfo& def : defaults) {
|
||||
bool found = false;
|
||||
for (const DesktopInfo& d : result) {
|
||||
if (d.id == def.id) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
result.push_back(def);
|
||||
}
|
||||
}
|
||||
|
||||
// 统一按 endpoint 推导状态:有地址=可连接,无地址=待分配
|
||||
for (DesktopInfo& d : result) {
|
||||
d.state = d.endpoint.isEmpty() ? DesktopState::kUnassigned : DesktopState::kAvailable;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DesktopStore::save(const std::vector<DesktopInfo>& desktops) {
|
||||
QJsonArray arr;
|
||||
for (const DesktopInfo& d : desktops) {
|
||||
arr.append(toJson(d));
|
||||
}
|
||||
QJsonObject root;
|
||||
root.insert(QStringLiteral("version"), 1);
|
||||
root.insert(QStringLiteral("desktops"), arr);
|
||||
|
||||
const QString path = storePath();
|
||||
QDir().mkpath(QFileInfo(path).absolutePath());
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
file.write(QJsonDocument(root).toJson(QJsonDocument::Indented));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DesktopStore::update(const DesktopInfo& info) {
|
||||
std::vector<DesktopInfo> list = load();
|
||||
for (DesktopInfo& d : list) {
|
||||
if (d.id == info.id) {
|
||||
d = info;
|
||||
d.state = d.endpoint.isEmpty() ? DesktopState::kUnassigned : DesktopState::kAvailable;
|
||||
return save(list);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace vdi
|
||||
37
terminal/kiosk-shell/src/desktop_store.h
Normal file
37
terminal/kiosk-shell/src/desktop_store.h
Normal file
@ -0,0 +1,37 @@
|
||||
// 桌面连接配置持久化(JSON)
|
||||
//
|
||||
// 每个客户部署环境的桌面地址不同,连接参数不能写死在代码里。
|
||||
// 当前阶段由本地文件维护(现场通过"桌面连接设置"界面配置);
|
||||
// 后续接入管理平台后,本类改为平台下发配置的本地缓存,接口不变。
|
||||
//
|
||||
// 文件:~/.config/vdi-terminal/desktops.json
|
||||
// 兼容:文件不存在时生成默认骨架;老盒子从 client.conf 的 server 迁移 Windows 地址。
|
||||
|
||||
#ifndef KIOSK_SHELL_DESKTOP_STORE_H
|
||||
#define KIOSK_SHELL_DESKTOP_STORE_H
|
||||
|
||||
#include <QString>
|
||||
#include <vector>
|
||||
|
||||
#include "desktop_session.h"
|
||||
|
||||
namespace vdi {
|
||||
|
||||
class DesktopStore {
|
||||
public:
|
||||
static QString storePath();
|
||||
|
||||
// 读取桌面列表;确保三张默认卡(Windows/Ubuntu/麒麟)始终存在。
|
||||
// state 由 endpoint 是否为空推导:有地址=可连接,无地址=待分配。
|
||||
static std::vector<DesktopInfo> load();
|
||||
|
||||
// 保存完整桌面列表
|
||||
static bool save(const std::vector<DesktopInfo>& desktops);
|
||||
|
||||
// 更新单张桌面(按 id 匹配),保存成功返回 true
|
||||
static bool update(const DesktopInfo& info);
|
||||
};
|
||||
|
||||
} // namespace vdi
|
||||
|
||||
#endif // KIOSK_SHELL_DESKTOP_STORE_H
|
||||
@ -24,6 +24,7 @@
|
||||
#include <freerdp/scancode.h>
|
||||
|
||||
#include "desktop_list_page.h"
|
||||
#include "desktop_settings_dialog.h"
|
||||
#include "login_page.h"
|
||||
|
||||
namespace vdi {
|
||||
@ -132,6 +133,7 @@ void MainWindow::setupUi() {
|
||||
stack_->addWidget(list);
|
||||
|
||||
connect(list, &DesktopListPage::desktopSelected, this, &MainWindow::onDesktopSelected);
|
||||
connect(list, &DesktopListPage::editRequested, this, &MainWindow::onEditDesktopRequested);
|
||||
connect(list, &DesktopListPage::logoutRequested, this, &MainWindow::onLogoutRequested);
|
||||
|
||||
// 会话视图
|
||||
@ -330,6 +332,28 @@ void MainWindow::onDesktopSelected(const QString& desktopId) {
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::onEditDesktopRequested(const QString& desktopId) {
|
||||
// 桌面连接设置:编辑协议/地址/应用名/分辨率/帧率,保存后立即刷新列表。
|
||||
// 现场环境地址不同或走 VPN 隧道时,在这里改地址即可,无需改代码。
|
||||
DesktopSettingsDialog dialog(desktops_, desktopId, this);
|
||||
dialog.setStyleSheet(
|
||||
QStringLiteral("QWidget { background: #0f1420; color: #e6e9ef; }"
|
||||
"QLineEdit { background: #1a2233; border: 1px solid #2b3654; "
|
||||
"border-radius: 4px; padding: 8px; color: #e6e9ef; }"
|
||||
"QComboBox { background: #1a2233; border: 1px solid #2b3654; "
|
||||
"border-radius: 4px; padding: 8px; color: #e6e9ef; }"
|
||||
"QPushButton { background: #232d44; border: 1px solid #2b3654; "
|
||||
"border-radius: 6px; color: #e6e9ef; }"
|
||||
"QLabel { color: #e6e9ef; }"));
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
desktops_ = dialog.desktops();
|
||||
if (desktopListPage_) {
|
||||
desktopListPage_->setDesktops(desktops_);
|
||||
desktopListPage_->setStatus(QStringLiteral("连接配置已保存"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onLogoutRequested() {
|
||||
stopSessions();
|
||||
desktopService_.logout();
|
||||
@ -546,12 +570,18 @@ void MainWindow::onMenuAction(int action) {
|
||||
break;
|
||||
default: {
|
||||
QMenu menu(this);
|
||||
QAction* settings = menu.addAction(QStringLiteral("桌面连接设置"));
|
||||
menu.addSeparator();
|
||||
QAction* reconnect = menu.addAction(QStringLiteral("重新连接当前桌面"));
|
||||
QAction* disconnect = menu.addAction(QStringLiteral("断开并返回桌面列表"));
|
||||
menu.addSeparator();
|
||||
QAction* quit = menu.addAction(QStringLiteral("退出终端"));
|
||||
QAction* chosen = menu.exec(QCursor::pos());
|
||||
if (chosen == reconnect) {
|
||||
if (chosen == settings) {
|
||||
if (fsm_.current() == State::kDesktopList || fsm_.current() == State::kConfig) {
|
||||
onEditDesktopRequested(QString());
|
||||
}
|
||||
} else if (chosen == reconnect) {
|
||||
onMenuAction(1);
|
||||
} else if (chosen == disconnect) {
|
||||
onMenuAction(2);
|
||||
|
||||
@ -52,6 +52,7 @@ protected:
|
||||
private slots:
|
||||
void onLoginRequested(const QString& username, const QString& password);
|
||||
void onDesktopSelected(const QString& desktopId);
|
||||
void onEditDesktopRequested(const QString& desktopId);
|
||||
void onLogoutRequested();
|
||||
void onQuitRequested();
|
||||
void onFrameImage(const QImage& image);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user