变更详情:feat(kiosk-shell): 登录/桌面列表产品流程 + Moonlight 会话 + 看门狗进程组修复 - 登录页改为用户认证(本地演示认证,账号密码非空通过) - 新增桌面列表页:Windows/Moonlight 可连接,Ubuntu/麒麟待分配 - 新增 DesktopService 适配层(登录/桌面列表/连接票据,后续接 API) - 新增 MoonlightSession:QProcess 参数数组直启 moonlight-qt,退出回列表 - 状态机扩展 AUTHENTICATING / DESKTOP_LIST - 移除开机自动 RDP,收敛为 monitor -> kiosk-shell 单一自启链 - Monitor 按进程组结束整棵进程树,修复孤儿串流进程 - vdi-moonlight 启动器新增 vsync/frame-pacing/overlay 低延迟开关
117 lines
4.2 KiB
C++
117 lines
4.2 KiB
C++
// RDP 会话封装(内嵌 FreeRDP 3 客户端 API)
|
||
//
|
||
// 对应《VDI 云桌面软件开发方案》3.2 / 3.3 节:
|
||
// "以 libfreerdp3 API 内嵌(非拉起外部进程),保证会话内无缝切换与全局热键"
|
||
// 协议层 FreeRDP 3;图形管线走 GFX;4K 档切 H.265 解码管线
|
||
//
|
||
// FreeRDP 3 API 结构(与 2.x 差异较大):
|
||
// freerdp* instance = freerdp_new(); // 主对象
|
||
// instance->context = <rdpContext*>; // 内部结构,公开
|
||
// instance->context->gdi // GDI 渲染状态
|
||
// instance->PostConnect / PostDisconnect // 事件回调
|
||
// instance->pClientEntryPoints // 客户端入口点
|
||
//
|
||
// 渲染:gdi_graphics_pipeline_init_ex 的 pcRdpgfxUpdateSurfaceArea 回调
|
||
// 拿到解码后的帧缓冲,交由 Qt 绘制。
|
||
// 后续 freerdp-rk 分支替换为 GStreamer + mppvideodec 零拷贝管线。
|
||
|
||
#ifndef KIOSK_SHELL_RDP_SESSION_H
|
||
#define KIOSK_SHELL_RDP_SESSION_H
|
||
|
||
#include <atomic>
|
||
#include <functional>
|
||
#include <mutex>
|
||
#include <string>
|
||
#include <thread>
|
||
|
||
#include <freerdp/freerdp.h>
|
||
#include <freerdp/gdi/gdi.h>
|
||
#include <freerdp/gdi/gfx.h>
|
||
#include <freerdp/settings.h>
|
||
|
||
namespace vdi {
|
||
|
||
struct RdpOptions {
|
||
std::string server; // 主机或 host:port
|
||
std::string username;
|
||
std::string password;
|
||
std::string domain;
|
||
uint32_t keyboardLayout = 0;
|
||
int width = 1920;
|
||
int height = 1440;
|
||
int bpp = 32;
|
||
bool fullscreen = true;
|
||
// 图形管线能力组合。FreeRDP 3.32.1 的 /gfx 不接受单个管道名,
|
||
// 必须是 "|" 分隔的组合(实测 AVC420 单独使用会解析失败)。
|
||
std::string gfx = "progressive|RFX|AVC420|AVC444";
|
||
bool ignoreCertificate = true; // POC 阶段;正式产品必须做证书校验
|
||
};
|
||
|
||
struct RdpCallbacks {
|
||
// 每帧解码完成
|
||
std::function<void(const uint8_t* data, uint32_t width, uint32_t height, uint32_t stride,
|
||
uint32_t format)>
|
||
onFrame;
|
||
// 会话状态变化
|
||
std::function<void(bool connected, const std::string& message)> onState;
|
||
};
|
||
|
||
class RdpSession {
|
||
public:
|
||
RdpSession();
|
||
~RdpSession();
|
||
|
||
RdpSession(const RdpSession&) = delete;
|
||
RdpSession& operator=(const RdpSession&) = delete;
|
||
|
||
// 启动会话(内部线程执行阻塞式连接)
|
||
bool start(const RdpOptions& options, RdpCallbacks callbacks);
|
||
// 主动断开
|
||
void stop();
|
||
bool isRunning() const { return running_.load(); }
|
||
|
||
// 向远程会话注入键盘和鼠标事件;由 GUI 线程调用。
|
||
bool sendKeyEvent(bool pressed, bool repeat, uint32_t rdpScanCode);
|
||
bool sendMouseEvent(uint16_t flags, uint16_t x, uint16_t y);
|
||
|
||
private:
|
||
void run();
|
||
void applySettings(rdpSettings* settings);
|
||
|
||
// FreeRDP 回调(静态,通过 instance->context 扩展结构找回 C++ 对象)
|
||
static BOOL onPostConnect(freerdp* instance);
|
||
static void onPostDisconnect(freerdp* instance);
|
||
// NLA 认证回调:把配置中的账号/密码交给 FreeRDP
|
||
static BOOL onAuthenticateEx(freerdp* instance, char** username, char** password, char** domain,
|
||
rdp_auth_reason reason);
|
||
// GDI 输出回调:传统位图路径的更新完成。
|
||
static BOOL onBitmapUpdate(rdpContext* context, const BITMAP_UPDATE* bitmap);
|
||
static BOOL onEndPaint(rdpContext* context);
|
||
// GFX 完整帧边界:先让 FreeRDP 完成所有 surface 合成,再提交一次。
|
||
static UINT onEndFrame(RdpgfxClientContext* context,
|
||
const RDPGFX_END_FRAME_PDU* endFrame);
|
||
|
||
// 把当前 GDI 帧缓冲交给上层
|
||
void emitFrame();
|
||
|
||
RdpOptions options_;
|
||
RdpCallbacks callbacks_;
|
||
std::atomic<bool> running_{false};
|
||
std::atomic<bool> connected_{false};
|
||
freerdp* instance_ = nullptr;
|
||
rdpContext* context_ = nullptr;
|
||
std::thread worker_;
|
||
mutable std::mutex inputMutex_;
|
||
uint32_t frameWidth_ = 0;
|
||
uint32_t frameHeight_ = 0;
|
||
uint32_t frameStride_ = 0;
|
||
uint32_t frameFormat_ = 0;
|
||
bool frameDirty_ = false;
|
||
pcRdpgfxEndFrame originalEndFrame_ = nullptr;
|
||
bool gfxPipelineActive_ = false;
|
||
};
|
||
|
||
} // namespace vdi
|
||
|
||
#endif // KIOSK_SHELL_RDP_SESSION_H
|