Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ build-profile.json5.*

.claude
.vscode
.temp
.temp
.DS_Store
3 changes: 2 additions & 1 deletion deps/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ download
.stamp
file.lst
buildroot
output
output
.DS_Store
2 changes: 1 addition & 1 deletion entry/build-profile.json5
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"arkOptions": {
"obfuscation": {
"ruleOptions": {
"enable": false,
"enable": true,
"files": [
"./obfuscation-rules.txt"
]
Expand Down
5 changes: 5 additions & 0 deletions entry/src/main/cpp/include/vnc_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct VncFrameInfo {

using VncResizeCallback = std::function<void(int width, int height)>;
using VncFrameCallback = std::function<void(const VncFrameInfo& info)>;
using VncClipboardCallback = std::function<void(const char* text, int len)>;

class VncClient {
public:
Expand All @@ -33,9 +34,11 @@ class VncClient {

static void sendMouseEvent(int x, int y, int buttonMask);
static void sendKeyEvent(uint32_t key, bool down);
static void sendCutText(const char* text, int len);

static void setResizeCallback(VncResizeCallback cb);
static void setFrameCallback(VncFrameCallback cb);
static void setClipboardCallback(VncClipboardCallback cb);

static uint8_t* getFrameBuffer();
static int getFrameWidth();
Expand Down Expand Up @@ -63,12 +66,14 @@ class VncClient {

static VncResizeCallback resizeCallback_;
static VncFrameCallback frameCallback_;
static VncClipboardCallback cutTextCallback_;
static std::mutex socketMutex_;

// libvncclient callbacks
static rfbBool onResize(rfbClient* cl);
static void onUpdate(rfbClient* cl, int x, int y, int w, int h);
static char* getPassword(rfbClient* cl);
static void onCutText(rfbClient* cl, const char* text, int textlen);
static bool checkConnection();
};

Expand Down
147 changes: 122 additions & 25 deletions entry/src/main/cpp/napi_init.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "napi/native_api.h"
#include <atomic>
#include <assert.h>
#include <cerrno>
#include <cstdint>
Expand Down Expand Up @@ -41,9 +42,9 @@ struct data_buffer {
size_t size;
};

int serial_input_fd = -1;
napi_threadsafe_function on_data_callback = nullptr;
napi_threadsafe_function on_shutdown_callback = nullptr;
std::atomic<int> serial_input_fd{-1};
std::atomic<napi_threadsafe_function> on_data_callback{nullptr};
std::atomic<napi_threadsafe_function> on_shutdown_callback{nullptr};

std::mutex buffer_mtx;
std::string temp_buffer = "";
Expand All @@ -60,6 +61,7 @@ static QemuSystemEntry getQemuSystemEntry() {

const char *libQemuPath = "libqemu-system-aarch64.so";

// 故意不 dlclose:QEMU 符号在整个进程生命周期内必须有效
void *libQemuHandle = dlopen(libQemuPath, RTLD_LAZY);

if (!libQemuHandle) {
Expand Down Expand Up @@ -103,6 +105,7 @@ static QemuImgEntry getQemuImgEntry() {

// QCOW2 文件头结构(简化版)
// 参考: https://github.com/qemu/qemu/blob/master/docs/interop/qcow2.txt
#pragma pack(push, 1)
struct Qcow2Header
{
uint32_t magic; // 0-3: Magic number 'QFI\xfb'
Expand All @@ -125,6 +128,7 @@ struct Qcow2Header
uint32_t refcount_order; // 96-99: refcount_bits = 1 << refcount_order
uint32_t header_length; // 100-103
};
#pragma pack(pop)

// 大端转小端(网络字节序转主机字节序)
static uint32_t be32toh_manual(uint32_t val)
Expand Down Expand Up @@ -228,8 +232,8 @@ static std::string getQcow2Info(const std::string &imagePath)
<< "}}"
<< "}";

OH_LOG_INFO(LOG_APP, "QCOW2 info: version=%{public}d, virtual_size=%{public}llu, cluster_size=%{public}d",
version, (unsigned long long)virtual_size, cluster_size);
OH_LOG_INFO(LOG_APP, "QCOW2 info: version=%{public}d, virtual_size=%{public}llu, cluster_size=%{public}llu",
version, (unsigned long long)virtual_size, (unsigned long long)cluster_size);

return json.str();
}
Expand Down Expand Up @@ -280,10 +284,6 @@ struct QcowSnapshotHeader
};
#pragma pack(pop)

// 标记 qemu-img 是否已调用过(用于检测是否需要重启)
static bool qemu_img_called = false;
static bool qemu_img_failed = false;

// 读取 QCOW2 快照列表(直接解析文件,不调用 qemu-img)
static std::string getSnapshotsFromFile(const std::string &imagePath)
{
Expand Down Expand Up @@ -474,7 +474,7 @@ static bool initQemuLibrary()
}

// 加载库
// 使用 RTLD_GLOBAL 确保符号可见,RTLD_NOW 立即解析
// 使用 RTLD_LOCAL 避免符号污染,RTLD_LAZY 延迟解析
g_qemu_lib_handle = dlopen(libPath.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!g_qemu_lib_handle)
{
Expand Down Expand Up @@ -823,6 +823,54 @@ static void call_on_shutdown_callback(napi_env env, napi_value js_callback, void
napi_call_function(env, global, js_callback, 0, nullptr, nullptr);
}

// Strip echoed CSI resize sequences from raw serial output
// Kernel tty echo mode reflects \x1b[8;<rows>;<cols>t back to the console
// [修复] 维护跨 buffer 的 partial match 状态,防止 CSI 序列被 read 切断时产生乱码
// 仅 serial_output_worker 单线程访问,不需互斥锁
static uint8_t csi_partial_buf[32];
static ssize_t csi_partial_len = 0;

ssize_t stripEchoedCsi(uint8_t *buf, ssize_t len) {
// 如果上次有残留的 partial CSI,先拼接
uint8_t combined[1024 + 32];
ssize_t combined_len = 0;
uint8_t *src = buf;

if (csi_partial_len > 0) {
memcpy(combined, csi_partial_buf, csi_partial_len);
memcpy(combined + csi_partial_len, buf, len);
combined_len = csi_partial_len + len;
src = combined;
csi_partial_len = 0;
} else {
combined_len = len;
}

// raw pattern: 0x1B 0x5B 0x38 0x3B <digits> 0x3B <digits> 0x74
ssize_t out = 0;
ssize_t i = 0;
for (; i < combined_len; ) {
if (src[i] == 0x1B && i + 3 < combined_len &&
src[i + 1] == 0x5B && src[i + 2] == 0x38 && src[i + 3] == 0x3B) {
ssize_t j = i + 4;
while (j < combined_len && src[j] != 0x74) j++;
if (j < combined_len) {
i = j + 1; // skip entire CSI sequence
continue;
}
// Partial match at end of buffer: save for next call
ssize_t remaining = combined_len - i;
if (remaining <= sizeof(csi_partial_buf)) {
memcpy(csi_partial_buf, src + i, remaining);
csi_partial_len = remaining;
}
break; // Don't output partial CSI
}
buf[out++] = src[i++];
}
return out;
}

std::string convert_to_hex(const uint8_t *buffer, int r) {
std::string hex;
for (int i = 0; i < r; i++) {
Expand Down Expand Up @@ -850,8 +898,9 @@ void send_data_to_callback(const std::string &hex, napi_threadsafe_function call
void on_serial_data_received(const std::string &hex) {
if (hex.length() > 0) {
std::lock_guard<std::mutex> lk(buffer_mtx);
if (on_data_callback != nullptr) {
send_data_to_callback(hex, on_data_callback);
auto cb = on_data_callback.load(std::memory_order_acquire);
if (cb != nullptr) {
send_data_to_callback(hex, cb);
} else {
temp_buffer.append(hex);
}
Expand Down Expand Up @@ -898,9 +947,13 @@ void serial_output_worker(const char *unix_socket_path) {
return;
}

serial_input_fd = client_fd;
serial_input_fd.store(client_fd, std::memory_order_release);

OH_LOG_INFO(LOG_APP, "Connected to unix socket: %{public}d", serial_input_fd);
OH_LOG_INFO(LOG_APP, "Connected to unix socket: %{public}d", serial_input_fd.load(std::memory_order_acquire));

// [省电] 自适应 poll 间隔:有数据时 10ms,空闲时逐渐放宽到 500ms
int poll_interval = 10;
int idle_count = 0;

while (true) {

Expand All @@ -909,13 +962,34 @@ void serial_output_worker(const char *unix_socket_path) {
struct pollfd fds[2];
fds[0].fd = client_fd;
fds[0].events = POLLIN;
int res = poll(fds, 1, 100);
int res = poll(fds, 1, poll_interval);

if (res < 0) {
// poll 错误(如 EINTR),记录并继续
OH_LOG_WARN(LOG_APP, "poll error: %{public}d, errno=%{public}d", res, errno);
continue;
}
if (res == 0) {
// 超时,无数据可读,逐步放宽 poll 间隔
if (idle_count < 10000) idle_count++;
if (idle_count > 10) poll_interval = 50;
if (idle_count > 50) poll_interval = 200;
if (idle_count > 100) poll_interval = 500;
continue;
}

// 有数据,重置间隔
poll_interval = 10;
idle_count = 0;

uint8_t buffer[1024];
for (int i = 0; i < res; i += 1) {
int fd = fds[i].fd;
ssize_t r = read(fd, buffer, sizeof(buffer) - 1);
if (r > 0) {
// 过滤 VM tty echo 回显的 CSI resize 序列 \x1b[8;H;Wt
r = stripEchoedCsi(buffer, r);
if (r <= 0) continue;
// pretty print
auto hex = convert_to_hex(buffer, r);
// call callback registered by ArkTS
Expand All @@ -939,12 +1013,20 @@ void serial_output_worker(const char *unix_socket_path) {

// 清理 socket 资源,但保留回调以便新的 worker 线程使用
close(client_fd);
serial_input_fd = -1;
serial_input_fd.store(-1, std::memory_order_release);
OH_LOG_INFO(LOG_APP, "Closed serial socket fd: %{public}d", client_fd);

if (on_data_callback != nullptr) {
napi_release_threadsafe_function(on_data_callback, napi_threadsafe_function_release_mode::napi_tsfn_release);
on_data_callback = nullptr;
if (on_data_callback.load(std::memory_order_acquire) != nullptr) {
napi_threadsafe_function cb = on_data_callback.exchange(nullptr, std::memory_order_acq_rel);
if (cb != nullptr) {
napi_release_threadsafe_function(cb, napi_threadsafe_function_release_mode::napi_tsfn_release);
}
}
if (on_shutdown_callback.load(std::memory_order_acquire) != nullptr) {
napi_threadsafe_function cb = on_shutdown_callback.exchange(nullptr, std::memory_order_acq_rel);
if (cb != nullptr) {
napi_release_threadsafe_function(cb, napi_tsfn_release);
}
}

OH_LOG_INFO(LOG_APP, "Serial unix socket broken: %{public}d", errno);
Expand Down Expand Up @@ -1022,8 +1104,10 @@ static napi_value startVM(napi_env env, napi_callback_info info) {

OH_LOG_INFO(LOG_APP, "qemuEntry exited with: %{public}d", status);

if (on_shutdown_callback != nullptr) {
napi_call_threadsafe_function(on_shutdown_callback, nullptr, napi_tsfn_nonblocking);
// [修复] 单次 load 消除 race window(两次 load 之间可能被 exchange 为 null)
auto cb = on_shutdown_callback.load(std::memory_order_acquire);
if (cb != nullptr) {
napi_call_threadsafe_function(cb, nullptr, napi_tsfn_nonblocking);
}
});
vm_loop.detach();
Expand All @@ -1038,7 +1122,8 @@ static napi_value startVM(napi_env env, napi_callback_info info) {

static napi_value sendInput(napi_env env, napi_callback_info info) {

if (serial_input_fd < 0) {
int fd = serial_input_fd.load(std::memory_order_acquire);
if (fd < 0) {
return nullptr;
}

Expand All @@ -1063,7 +1148,7 @@ static napi_value sendInput(napi_env env, napi_callback_info info) {
while (written < (int)length)
{
// P0-02修复: 移除assert,使用显式错误处理
int size = write(serial_input_fd, (uint8_t *)data + written, length - written);
int size = write(fd, (uint8_t *)data + written, length - written);
if (size < 0) {
OH_LOG_ERROR(LOG_APP, "Serial write failed: errno=%{public}d", errno);
break;
Expand All @@ -1089,11 +1174,15 @@ static napi_value onData(napi_env env, napi_callback_info info) {

{
std::lock_guard<std::mutex> lk(buffer_mtx);
// 释放旧的 TSFN,防止重复注册导致资源泄漏
napi_threadsafe_function old_cb = on_data_callback.exchange(data_callback, std::memory_order_acq_rel);
if (old_cb != nullptr) {
napi_release_threadsafe_function(old_cb, napi_tsfn_release);
}
if (!temp_buffer.empty()) {
send_data_to_callback(temp_buffer, data_callback);
temp_buffer.clear();
}
on_data_callback = data_callback;
}

return nullptr;
Expand All @@ -1105,10 +1194,18 @@ static napi_value onShutdown(napi_env env, napi_callback_info info) {
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

// 释放旧的 TSFN,防止 WebTerminal 和 EmulatorListGrid 重复注册导致资源泄漏
napi_threadsafe_function old_cb = on_shutdown_callback.exchange(nullptr, std::memory_order_acq_rel);
if (old_cb != nullptr) {
napi_release_threadsafe_function(old_cb, napi_tsfn_release);
}

napi_value data_cb_name;
napi_create_string_utf8(env, "shutdown_callback", NAPI_AUTO_LENGTH, &data_cb_name);
napi_threadsafe_function new_cb;
napi_create_threadsafe_function(env, args[0], nullptr, data_cb_name, 0, 1, nullptr, nullptr, nullptr,
call_on_shutdown_callback, &on_shutdown_callback);
call_on_shutdown_callback, &new_cb);
on_shutdown_callback.store(new_cb, std::memory_order_release);

return nullptr;
}
Expand Down
Loading