The test process directly constructs display groups and windows through InputManager IPC, replacing WMS (Window Management Service) and DMS (Display Management Service), to achieve independent functional verification of the input subsystem.
Prerequisite skill: For infrastructure such as permission acquisition, server bypass, compilation and deployment, and hidumper capture, see ohos-dev-distributed-mmi-device-test-harness; for virtual device creation and event injection, see ohos-dev-distributed-mmi-uinput-virtual-device。
When to Use
- Simulate display groups, users, primary screens, extended screens, mirrored screens, and window stacks as required by the test
- Test input functions that depend on display group/window state (device binding, cursor routing, focus dispatch, hit testing)
- Verify scenarios such as a single display group with multiple screens, multiple display groups with multiple users, inconsistent mirror screen ratios, and foreground user switching
- When you do not want to or cannot start the full WMS/DMS stack
Summary of verified capabilities
DAYU200 real device dual_group_interleave_test Already covers dual display group construction, multi-window injection, per-group
cursor/button/keyboard/focus/captureMode/wheel isolation, hot-plug binding cleanup, cross-binding key state, and per-window
cursor appearance. For detailed steps, dump evidence, and PARTIAL items, see evals/test-results.md, do not copy long empirical test tables into
SKILL.md main flow.
Scenario-First modeling flow
Do not start from a fixed demo. First normalize the user requirements into this model table, then generate UserScreenInfo and window injection:
| Dimension | Required content | Key constraints |
|---|---|---|
| User | userId, and whether foreground/ACTIVE |
When simulating only the foreground user, all target UserScreenInfo should be writable to the active display group state |
| DisplayGroup | groupId、type、mainDisplayId、focusWindowId |
There must be one GROUP_DEFAULT with id=0; other groups use GROUP_SPECIAL |
| Display | The displayId, position, size, dpi, and direction of the primary/extended/mirrored screens |
screenArea must be consistent with the display coordinates/size; extended screen coordinates must not unintentionally overlap |
| Mirror | Mirror source display, mirror display, scaling ratio, or letterbox/crop policy | When the mirror screen ratio is inconsistent with the primary screen, coordinate mapping must be modeled explicitly; do not assume 1:1 |
| Screen | The ScreenInfo |
screens corresponding to each display; the count and ids must cover all displays |
| Window | Focus window, background window, floating window, occluding window, and hotArea for each screen | win.groupId/displayId must match the target display; zOrder must reflect the stacking relationship |
| Focus | The focus window of each display, and the group-level keyboard focus | DisplayGroupInfo.focusWindowId is key to keyboard routing; the focus window of each screen must also be reflected in the window stack |
When outputting the simulation plan, first provide the model table, then provide C++ helper or pseudocode. Do not just paste a single fixed SetupDualDisplayGroups()。
Standard scenario template
Single display group: primary screen + extended screen + primary screen mirror
Used to verify multi-screen routing within one user/one display group, per-screen focus windows, mirror screen coordinate mapping, and multi-window stacking:
| Object | Suggested modeling |
|---|---|
| group | groupId=0, GROUP_DEFAULT, mainDisplayId=0 |
| Primary screen | displayId=0, screenArea={0,0,720,1280} |
| Extended screen | displayId=1, screenArea={720,0,1920,1080}, tiled horizontally with the primary screen |
| Mirror screen | displayId=2, screenArea={2640,0,1024,768}, mirrors the primary screen but with a different ratio |
| Focus window | At least one focus window per display, for example 1000/1100/1200 |
| Stacked windows | Each screen's background window, status bar/system layer, and floating window use different zOrder |
| hotArea | Both the mirror screen and the extended screen must be configured pointerHotAreas, used for hit testing |
When the mirror screen has an inconsistent aspect ratio, the expected coordinate strategy must be clearly stated in the test description:
- fit/letterbox: maintain the main screen aspect ratio; some areas of the mirror screen have no valid hotArea.
- stretch: main screen coordinates are mapped to the mirror screen using independent X/Y scaling.
- crop: the mirror screen only covers the cropped area of the main screen.
MMI's window hit testing only looks at the injected display/window geometry information; if you want to verify mirror coordinate conversion, the test code must convert the source screen coordinates to the mirror screen display coordinates before injecting events or verifying the target window.
Multiple display groups and multiple users: each group is main screen + extended screen + mirror screen
Used to verify display group isolation under different foreground users or parallel foreground scenarios:
| Object | Suggested modeling |
|---|---|
| user A group | userId=100, groupId=0, GROUP_DEFAULT |
| user B group | userId=101, groupId=10, GROUP_SPECIAL or assign a non-zero group according to the model under test |
| Each group | contains all three display types: main/extended/mirror |
| displayId | Globally unique; different groups cannot reuse the same displayId |
| windowId | Globally unique; it is recommended to number them in segments by group/display |
| focus | Each group sets a group-level focusWindowId; each display has its own hittable focus window |
| mirror mismatch | At least one mirror screen in each group has a different aspect ratio, used to verify coordinate and hit isolation |
If the underlying UserScreenInfo expresses only one user at a time, call UpdateDisplayInfoseparately for each user, and state in the report the simulated assumption that "multiple users are all foreground/ACTIVE". Do not mix windows from multiple users into the same group and then claim to have verified multi-user isolation.
Construct display groups (replacing DMS)
Below is the minimal construction snippet. A real test should generate multiple displays, screens, and groups according to the scenario model above.
#include "input_manager.h"
UserScreenInfo screenInfo;
screenInfo.userId = 100;
// Group 0: 默认组(主屏)
DisplayGroupInfo group0;
group0.id = 0;
group0.name = "default";
group0.type = GroupType::GROUP_DEFAULT;
group0.mainDisplayId = 0;
group0.focusWindowId = 1000;
DisplayInfo disp0;
disp0.id = 0;
disp0.x = 0; disp0.y = 0;
disp0.width = 720; disp0.height = 1280;
disp0.dpi = 240;
disp0.name = "main";
disp0.direction = DIRECTION0;
disp0.displayDirection = DIRECTION0;
disp0.screenArea = { .id = 0, .area = { 0, 0, 720, 1280 } };
group0.displaysInfo.push_back(disp0);
// Group 1: 特殊组(另一个显示组;单组多屏时可改为给 group0 增加 display1/display2)
DisplayGroupInfo group1;
group1.id = 1;
group1.name = "secondary";
group1.type = GroupType::GROUP_SPECIAL;
group1.mainDisplayId = 1;
group1.focusWindowId = 2000;
DisplayInfo disp1;
disp1.id = 1;
disp1.x = 0; disp1.y = 0;
disp1.width = 1920; disp1.height = 1080;
disp1.dpi = 160;
disp1.name = "secondary";
disp1.direction = DIRECTION0;
disp1.displayDirection = DIRECTION0;
disp1.screenArea = { .id = 1, .area = { 0, 0, 1920, 1080 } };
group1.displaysInfo.push_back(disp1);
// 单组多屏时,扩展屏/镜像屏也可以 push 到同一个 group:
// group0.displaysInfo.push_back(extendedDisplay);
// group0.displaysInfo.push_back(mirrorDisplay);
screenInfo.displayGroups.push_back(group0);
screenInfo.displayGroups.push_back(group1);
// 物理屏幕信息(必须与 displaysInfo 对应)
ScreenInfo screen0;
screen0.id = 0;
screen0.uniqueId = "default0";
screen0.width = 720; screen0.height = 1280;
screen0.physicalWidth = 62; screen0.physicalHeight = 110;
screen0.dpi = 240; screen0.ppi = 295;
screen0.tpDirection = DIRECTION0;
screenInfo.screens.push_back(screen0);
ScreenInfo screen1;
screen1.id = 1;
screen1.uniqueId = "secondary1";
screen1.width = 1920; screen1.height = 1080;
screen1.physicalWidth = 531; screen1.physicalHeight = 299;
screen1.dpi = 160; screen1.ppi = 92;
screen1.tpDirection = DIRECTION0;
screenInfo.screens.push_back(screen1);
InputManager::GetInstance()->UpdateDisplayInfo(screenInfo);
Key constraints
| Field | Constraint | Consequence |
|---|---|---|
group0.type |
There must be one GROUP_DEFAULT |
InitDisplayGroupInfo validating groupId==MAIN_GROUPID |
group0.id |
The DEFAULT group id must be 0 | Non-zero will be rejected |
focusWindowId |
Must be preset in DisplayGroupInfo | Keyboard event routing depends on this value; if not set, it becomes -1, causing keyboard events to fail to dispatch |
screenArea |
Must be consistent with width/height | Cursor boundary clamping depends on this value |
screens count and ID |
Must correspond to displaysInfo | Screen information is used for DPI/PPI calculation |
userState |
Default USER_ACTIVE, no need to set |
When not ACTIVE, displayGroupInfoMap_ is not written |
displayId |
Globally unique | Reusing displayId across multiple groups/users will cause confusion in routing and dump interpretation |
| Mirror screen aspect ratio | The mapping strategy must be explicitly stated | Mirror screens with different aspect ratios cannot be directly asserted for hits using main screen coordinates |
Return code
UpdateDisplayInfo returning -201 is normal — this is UpdateUIExtensionInfo 's error code and does not affect the writing of display group data.
Inject virtual windows (replacing WMS)
Each display needs at least one hittable window. For complex scenarios, inject each display's own background window, focus window, and stacked windows that need verification:
WindowGroupInfo wgi0;
wgi0.focusWindowId = 1000;
wgi0.displayId = 0;
WindowInfo win0;
win0.id = 1000;
win0.pid = getpid();
win0.uid = getuid();
win0.area = { 0, 0, 720, 1280 };
win0.defaultHotAreas = { { 0, 0, 720, 1280 } };
win0.pointerHotAreas = { { 0, 0, 720, 1280 } };
win0.agentWindowId = -1;
win0.flags = 0;
win0.action = WINDOW_UPDATE_ACTION::ADD;
win0.displayId = 0;
win0.groupId = 0;
win0.zOrder = 1.0f;
wgi0.windowsInfo.push_back(win0);
InputManager::GetInstance()->UpdateWindowInfo(wgi0);
Key constraints
| Field | Constraint |
|---|---|
win.groupId |
Must match the target display group ID |
win.displayId |
Must match the display ID within the display group |
win.pid |
Use getpid() — required for permission checks |
win.action |
Use ADDfor the first time, use CHANGE |
wgi.focusWindowId |
Takes effect for MAIN_GROUPID; non-main groups are read from displayGroupInfoMap_ |
pointerHotAreas |
Mouse event hit testing depends on this area |
win.id |
Globally unique |
zOrder |
Explicitly distinguished by business hierarchy |
Per-screen window stack pattern
For each display, it is recommended to construct at least:
| Window | zOrder | Purpose |
|---|---|---|
| background/app | 10 | Default hit target |
| panel/status/system | 100 | Verify system-layer priority |
| popup/floating | 200+ | Verify overlapping hits and fallback after DEL |
The per-screen focus window can be the background or a dedicated input window; this needs to be indicated in the model table.
Incremental window operations
ADD/CHANGE/DEL for a single window, without needing to re-inject all windows:
static void InjectSingleWindow(int32_t winId, int32_t groupId, int32_t displayId,
Rect area, float zOrder, WINDOW_UPDATE_ACTION action, int32_t focusWinId = -1)
{
WindowGroupInfo wgi;
wgi.focusWindowId = focusWinId;
wgi.displayId = displayId;
WindowInfo win;
win.id = winId;
win.pid = getpid();
win.uid = getuid();
win.area = area;
win.defaultHotAreas = { area };
win.pointerHotAreas = { area };
win.agentWindowId = -1;
win.flags = 0;
win.action = action;
win.displayId = displayId;
win.groupId = groupId;
win.zOrder = zOrder;
wgi.windowsInfo.push_back(win);
InputManager::GetInstance()->UpdateWindowInfo(wgi);
}
UpdateDisplayInfoByIncrementalInfo handling:ADD append (replace if it already exists),CHANGE replace in place,DEL remove from the list.
Dynamic focus switching
Main group (groupId=0)
UpdateWindowInfo uses the passed-in wgi.focusWindowId, taking effect directly.
Non-main group (groupId!=0)
UpdateWindowInfo ignores the passed-in focusWindowId, always reading from displayGroupInfoMap_ .Must update focus via UpdateDisplayInfo :
SetupDualDisplayGroups(1000, 2002); // 重新调用 UpdateDisplayInfo
sleep(1s);
InjectVirtualWindows(); // 重新注入窗口(同步 focusWinId 全局变量)
sleep(1s);
In multi-screen scenarios, distinguish between:
- group-level keyboard focus: determined by
DisplayGroupInfo.focusWindowId. - Expected focus window per screen: reflected by the test window stack and hit coordinates; do not mistakenly assume that MMI automatically maintains an independent keyboard focus for each display.
- If the requirement claims that "each screen has its own focus window," the test should clarify whether this is the window stack/hit-target focus or the input framework's actual keyboard focus. The two must not be conflated.
Implementation pattern: use a global variable to track focus,SetupDualDisplayGroups and InjectVirtualWindows share:
static int32_t g_focusWin0 = 1000;
static int32_t g_focusWin1 = 2000;
static void SetupDualDisplayGroups(int32_t focusWin0 = 1000, int32_t focusWin1 = 2000)
{
g_focusWin0 = focusWin0;
g_focusWin1 = focusWin1;
// ... UpdateDisplayInfo with focusWin0/focusWin1
}
static void InjectVirtualWindows()
{
// wgi.focusWindowId = g_focusWin0; // 不再硬编码
// ...
}
Multi-window z-order hit testing
Basic pattern: different areas, different layers
// Group 0: 3 个窗口
// win1000: 应用窗口(背景) area={0,80,720,1200} zOrder=10
// win1001: 状态栏 area={0,0,720,80} zOrder=100
// win1002: 悬浮球 area={600,600,120,120} zOrder=200
SelectWindowInfo Traverse in descending zOrder; the first window hit by pointerHotAreas is the target. In overlapping areas, the higher zOrder takes priority.
Full-screen overlap pattern: position-independent verification
ADD multiple full-screen windows (with different zOrder), no cursor positioning needed — full-screen windows are hit at any cursor position:
InjectSingleWindow(1005, 0, 0, {0,0,720,1280}, 50.0f, WINDOW_UPDATE_ACTION::ADD, g_focusWin0);
InjectSingleWindow(1006, 0, 0, {0,0,720,1280}, 150.0f, WINDOW_UPDATE_ACTION::ADD, g_focusWin0);
InjectSingleWindow(1007, 0, 0, {0,0,720,1280}, 250.0f, WINDOW_UPDATE_ACTION::ADD, g_focusWin0);
// 点击 → Cursor Info.windowId = 1007 (z=250 最高)
// DEL win1007 → 点击 → windowId 变为原有最高层(可能是 win1002=z200,不一定是 win1006=z150)
Note: after DEL of a full-screen window, the hit target depends on the zOrder of all remaining windows, including existing non-full-screen windows. In the example above, after DEL win1007(z=250), if the cursor position is within the hotArea of win1002(z=200), then win1002 is hit rather than win1006(z=150).
Hot area pass-through
area and pointerHotAreas can differ. When the cursor is inside area but outside pointerHotAreas , the click passes through to the lower window:
// win1004: area 大于 hotArea
WindowInfo win;
win.area = { 100, 200, 400, 400 };
win.defaultHotAreas = { { 200, 300, 200, 200 } }; // hotArea 小于 area
win.pointerHotAreas = { { 200, 300, 200, 200 } };
// 点击 (150,250) — area 内但 hotArea 外 → 穿透到下层
// 点击 (350,450) — hotArea 内 → 命中此窗口
Window lifecycle (ADD → CHANGE → DEL)
// ADD: 新增弹窗
InjectSingleWindow(1003, 0, 0, {200,200,320,320}, 300.0f, WINDOW_UPDATE_ACTION::ADD, 1000);
// 点击 (360,360) → 命中 win1003
// CHANGE: 移动弹窗位置
InjectSingleWindow(1003, 0, 0, {400,800,320,320}, 300.0f, WINDOW_UPDATE_ACTION::CHANGE, 1000);
// 点击原位置 (360,360) → 穿透到 win1000
// DEL: 删除弹窗
InjectSingleWindow(1003, 0, 0, {0,0,0,0}, 0.0f, WINDOW_UPDATE_ACTION::DEL, 1000);
// 窗口列表恢复
Available verification APIs
// 设备绑定(详见 ohos-dev-distributed-mmi-device-test-harness)
InputManager::GetInstance()->BindDeviceToDisplayGroupByDisplay(deviceId, displayId, msg);
InputManager::GetInstance()->UnbindDeviceFromDisplayGroup(deviceId, msg);
// 光标样式(per-window)
PointerStyle style;
style.id = 13; // MOUSE_ICON::CROSS
style.size = 3;
style.color = 0xFF0000; // red
InputManager::GetInstance()->SetPointerStyle(windowId, style);
InputManager::GetInstance()->GetPointerStyle(windowId, style);
// 全局光标大小/颜色
InputManager::GetInstance()->SetPointerSize(4);
InputManager::GetInstance()->SetPointerColor(0x00FF00);
// 捕获模式
InputManager::GetInstance()->EnterCaptureMode(windowId);
InputManager::GetInstance()->LeaveCaptureMode(windowId);
Execution order
1. InitNativeToken() — 权限 (见 ohos-dev-distributed-mmi-device-test-harness)
2. 建立 scenario model — 用户、显示组、主/扩展/镜像屏、窗口栈、焦点
3. UpdateDisplayInfo() — 注入 display group/screen 信息(等 1 秒)
4. UpdateWindowInfo() x N — 每个 display 注入窗口栈(等 1 秒)
5. 创建虚拟设备 + FindDevice — 见 ohos-dev-distributed-mmi-uinput-virtual-device
6. BindDeviceToDisplayGroupByDisplay()
7. warm-up 事件注入(2 次,初始化 group state)
8. 测试逻辑 + DumpPhase() — dump 捕获见 ohos-dev-distributed-mmi-device-test-harness
9. 清理:解绑 → UI_DEV_DESTROY → close(fd)
Known limitations
| Limitation | Cause | Workaround |
|---|---|---|
| Cursor positioning is imprecise | libinput mouse acceleration causes uinput relative movement to be unable to reach exact absolute coordinates | Use a fullscreen window to avoid depending on cursor position |
focusWindowId=-1 In the DisplayGroups dump |
dump reads displayGroupInfoMap_, the focus of MAIN_GROUPID is passed in from UpdateWindowInfo passed in |
Verify the actual focus via KeyboardStateByGroup or SequenceSnapshots to verify the actual focus |
| Non-main-group focus switching | UpdateWindowInfo ignores the non-main-group focusWindowId |
Must be updated via UpdateDisplayInfo update |
| After DEL fullscreen, hits an unexpected window | Among the remaining windows, the one with the highest zOrder is hit, including pre-existing non-fullscreen windows | Pay attention to the zOrder relationships of existing windows |
Common Mistakes
| Error | Consequence | Fix |
|---|---|---|
If focusWindowId |
is not set, keyboard events are not dispatched to any window | Preset in DisplayGroupInfo |
groupId != 0 groups with GROUP_DEFAULT |
InitDisplayGroupInfo reject |
Non-0 groups use GROUP_SPECIAL |
The window's groupId does not match the display group |
Events are routed to the wrong group | Ensure consistency |
| Multiple screens/groups reuse displayId or windowId | dump and routing evidence cannot be audited | Globally unique numbering |
| Mirrored screens have different scales but continue to use the primary screen's coordinates | An assertion is hit incorrectly | First define the fit/stretch/crop mapping strategy |
| Claim independent keyboard focus per screen but only set group focus | The conclusion does not hold | Distinguish between group-level keyboard focus and per-screen window hit targets |
| Non-main groups switch focus via UpdateWindowInfo | focusWindowId is ignored | Focus must be switched via UpdateDisplayInfo |
| All windows use the same zOrder | Hit order is indeterminate | Assign different zOrder values according to business hierarchy |
| Forgetting to inject windows | Mouse events have no hit test target | At least one fullscreen window per group |
| If UpdateDisplayInfo returns -201, consider it a failure | The data has actually been written | -201 comes from UIExtension and can be ignored |
| InjectVirtualWindows hardcodes focusWindowId | Overwritten after dynamic focus switching | Use global variables to synchronize focus |
| After DEL fullscreen window, assume the second-highest fullscreen window is hit | Pre-existing non-fullscreen windows may have higher zOrder | Check the zOrder relationships of all windows |