// 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 = ; // 内部结构,公开 // 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 #include #include #include #include #include #include #include #include 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 onFrame; // 会话状态变化 std::function 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 running_{false}; std::atomic 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