openharmonyinsight/openharmony-skills

ohos-dev-distributed-mmi-uinput-virtual-device

Use when testing OpenHarmony input subsystem features (mouse/keyboard/touchpad event flow, device binding, display group isolation) on a real device without physical peripherals.

First seen Jun 16, 2026

Installation

$ npx skills add openharmonyinsight/openharmony-skills --skill ohos-dev-distributed-mmi-uinput-virtual-device

Summary

  • Use when testing OpenHarmony input subsystem features (mouse/keyboard/touchpad event flow, device binding, display group isolation) on a real device without physical peripherals.
  • Covers /dev/uinput device creation, event injection, InputManager device discovery, and hot-plug simulation.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from openharmonyinsight/openharmony-skills · top by installs.

npx skills add openharmonyinsight/openharmony-skills

Browse all from openharmonyinsight/openharmony-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 33
Default branch main
Open issues 2
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Version0.2.0
More metadata
author
openharmony
scope
domain
stage
development
domain
mmi
capability
uinput-virtual-device
version
0.2.0
status
draft
tags
["multimodalinput","uinput","virtual-device","event-injection","hot-plug"]
related-skills
["ohos-dev-distributed-mmi-device-test-harness","ohos-dev-distributed-mmi-wms-dms-simulation"]

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,807 B
  • docs SUMMARY.md 341 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 17 installs

SKILL.md

uinput 虚拟设备测试

通过 /dev/uinput 创建虚拟 HID 设备,注入精确事件序列,验证输入子系统行为。

When to Use

  • 验证输入事件流(鼠标移动、按键、触控板)在真机上的行为
  • 测试设备绑定(BindDeviceToDisplayGroupByDisplay)和状态隔离
  • 模拟热拔插场景
  • 不需要或没有物理外设时

设备创建

关键约束

  • bustype 必须用 BUSUSB(0x03)。BUSVIRTUAL 会被 MMI 过滤,不触发设备发现回调
  • vendor/product 需唯一,避免与真实设备冲突。推荐 vendor=0x93a,product 自定义
  • 鼠标必须同时注册 EVKEY+EVREL,否则不被识别为 pointer 设备

鼠标

int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
ioctl(fd, UI_SET_EVBIT, EV_KEY);
ioctl(fd, UI_SET_EVBIT, EV_REL);
ioctl(fd, UI_SET_KEYBIT, BTN_LEFT);
ioctl(fd, UI_SET_KEYBIT, BTN_RIGHT);   // 可选
ioctl(fd, UI_SET_KEYBIT, BTN_MIDDLE);  // 可选
ioctl(fd, UI_SET_RELBIT, REL_X);
ioctl(fd, UI_SET_RELBIT, REL_Y);
ioctl(fd, UI_SET_RELBIT, REL_WHEEL);   // 可选

struct uinput_user_dev ud = {};
strncpy(ud.name, "TestMouseA", UINPUT_MAX_NAME_SIZE - 1);
ud.id.bustype = BUS_USB;  // 必须
ud.id.vendor  = 0x93a;
ud.id.product = 0xAA01;   // 每个虚拟设备用不同值
ud.id.version = 1;
write(fd, &ud, sizeof(ud));
ioctl(fd, UI_DEV_CREATE);

键盘

int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
ioctl(fd, UI_SET_EVBIT, EV_KEY);
// 不注册 EV_REL —— 确保被识别为键盘而非鼠标
for (int k = KEY_ESC; k <= KEY_RIGHTMETA; k++) {
    ioctl(fd, UI_SET_KEYBIT, k);
}
// ... 同上构造 uinput_user_dev, bustype=BUS_USB

事件注入

每个事件序列必须以 SYN_REPORT 结尾:

// 鼠标相对移动
void InjectMotion(int fd, int dx, int dy) {
    struct input_event ev = {};
    ev.type = EV_REL; ev.code = REL_X; ev.value = dx;
    write(fd, &ev, sizeof(ev));
    ev.code = REL_Y; ev.value = dy;
    write(fd, &ev, sizeof(ev));
    ev.type = EV_SYN; ev.code = SYN_REPORT; ev.value = 0;
    write(fd, &ev, sizeof(ev));
}

// 按键/按钮(鼠标按钮和键盘按键共用)
void InjectKey(int fd, int code, int pressed) {
    struct input_event ev = {};
    ev.type = EV_KEY; ev.code = code; ev.value = pressed;
    write(fd, &ev, sizeof(ev));
    ev.type = EV_SYN; ev.code = SYN_REPORT; ev.value = 0;
    write(fd, &ev, sizeof(ev));
}

// code: BTN_LEFT, BTN_RIGHT (鼠标按钮)
//       KEY_A, KEY_LEFTCTRL, KEY_LEFTSHIFT (键盘按键)
// pressed: 1=按下, 0=释放

InputManager 设备发现

GetDeviceIds / GetDevice 是异步回调。创建 uinput 设备后需要等待 MMI 枚举:

static int FindDevice(const std::string &name) {
    struct FindState {
        std::mutex mtx;
        std::condition_variable cv;
        int foundId = -1;
        int pending = 0;
        bool done = false;
    };
    for (int attempt = 0; attempt < 30; ++attempt) {
        auto state = std::make_shared<FindState>();
        InputManager::GetInstance()->GetDeviceIds([state, name](std::vector<int32_t> &ids) {
            {
                std::lock_guard<std::mutex> lock(state->mtx);
                state->pending = static_cast<int>(ids.size());
                if (state->pending == 0) {
                    state->done = true;
                    state->cv.notify_one();
                    return;
                }
            }
            for (auto id : ids) {
                InputManager::GetInstance()->GetDevice(id,
                    [state, id, name](std::shared_ptr<InputDevice> dev) {
                        bool notify = false;
                        {
                            std::lock_guard<std::mutex> lock(state->mtx);
                            if (dev && dev->GetName() == name && state->foundId < 0) {
                                state->foundId = id;
                            }
                            if (--state->pending == 0) {
                                state->done = true;
                                notify = true;
                            }
                        }
                        if (notify) {
                            state->cv.notify_one();
                        }
                    });
            }
        });
        std::unique_lock<std::mutex> lock(state->mtx);
        state->cv.wait_for(lock, std::chrono::milliseconds(500), [&]{ return state->done; });
        if (state->foundId >= 0) return state->foundId;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    return -1;
}

创建后至少等 3 秒再调 FindDevice,给 libinput 和 MMI 枚举的时间。

热拔插模拟

ioctl(fd, UI_DEV_DESTROY);
close(fd);
// MMI 的 OnInputDeviceRemoved 会自动清理绑定
// 等 2 秒后 dump 验证 RuntimeBindings 已清理

状态隔离验证模式

测试 per-group 隔离时的标准模式:

1. 创建 2 个虚拟设备,绑定到不同 group
2. warm-up:每个设备注入 2-3 个事件触发懒初始化
3. 对设备 A 注入值 X(如 +20,+10 移动),对设备 B 注入值 Y(如 -15,-8)
4. dump → 验证 group0 反映 X,group1 反映 Y,互不影响

warm-up 必要性:绑定后首次事件触发 EnsureGroupStatecursorPosMap_ 初始化。跳过 warm-up 会导致 dump 中 group 状态为空。

Phase-marker + hidumper 状态快照

测试代码写 marker,后台脚本捕获 hidumper:

// 测试代码中
static void DumpPhase(const char *label) {
    FILE *f = fopen("/data/local/tmp/phase.txt", "w");
    if (f) { fprintf(f, "%s\n", label); fclose(f); }
    std::this_thread::sleep_for(std::chrono::seconds(3));
}
#!/system/bin/sh
# run_with_dump.sh — 后台捕获 hidumper
DUMP=/data/local/tmp/dump.txt
PHASE=/data/local/tmp/phase.txt
rm -f "$DUMP" "$PHASE"
$TEST_BIN &
PID=$!
LAST=""
while kill -0 $PID 2>/dev/null; do
    if [ -f "$PHASE" ]; then
        CUR=$(cat "$PHASE" 2>/dev/null)
        if [ "$CUR" != "$LAST" ] && [ -n "$CUR" ]; then
            echo "===== DUMP: $CUR =====" >> "$DUMP"
            hidumper -s MultimodalInput -a '-G' >> "$DUMP" 2>&1
            LAST="$CUR"
        fi
    fi
    sleep 1
done
wait $PID 2>/dev/null

清理

// 必须在测试退出前销毁所有虚拟设备
ioctl(fd, UI_DEV_DESTROY);
close(fd);

不销毁的虚拟设备会在进程退出时自动清理,但 MMI 的设备移除通知可能延迟。

Common Mistakes

错误 后果 修复
bustype = BUS_VIRTUAL 设备不被 MMI 识别 BUS_USB
缺少 SYN_REPORT 事件不被处理 每个事件序列末尾加 SYN_REPORT
创建后立即 FindDevice 找不到设备 等 3 秒
跳过 warm-up 事件 per-group 状态未初始化 绑定后注入 2-3 个移动事件
鼠标不注册 EV_REL 不被识别为 pointer 设备 同时注册 EVKEY + EVREL
多个设备用同一 product ID 可能混淆设备发现 每个设备用不同 product