WMS/DMS Simulation
The test process directly constructs display groups and windows through InputManager IPC, replacing WMS (Window Manager Service) and DMS (Display Manager Service), to perform independent functional validation of the input subsystem.
Prerequisite skills: Infrastructure such as permission acquisition, server bypass, build and deployment, and hidumper capture is covered in
ohos-dev-distributed-mmi-device-test-harness
; virtual device creation and event injection are covered in
ohos-dev-distributed-mmi-uinput-virtual-device
.
When to Use
- Simulate display groups, users, main, extended, and mirror displays, and window stacks according to test requirements
- Test input features that depend on display group/window state, such as device binding, cursor routing, focus dispatch, and hit testing
- Validate scenarios such as a single display group with multiple displays, multiple display groups with multiple users, mismatched mirror-display aspect ratios, and foreground-user switching
- When the full WMS/DMS stack is not desired or cannot be started
Verified Capability Summary
On a real DAYU200 device,
dual_group_interleave_test
has covered dual-display-group construction, multi-window injection, per-group cursor/button/keyboard/focus/captureMode/scroll-wheel isolation, hot-plug binding cleanup, cross-binding key state, and per-window cursor appearance. Detailed steps, dump evidence, and PARTIAL items are available in
. Do not copy the lengthy real-device test table into the main
workflow.
Scenario-First Modeling Process
Do not start from a fixed demo. First normalize the user's requirements into this model table, then generate the
and window injections:
| Dimension | Required Content | Key Constraints |
|---|
| User | and whether the user is foreground/ACTIVE | When only foreground users are being simulated, every target should be writable into the active display-group state |
| DisplayGroup | , , , and | There must be one with id=0; other groups use |
| Display | The main, extended, or mirror display's , position, dimensions, dpi, and direction | must match the display's coordinates and dimensions; extended-display coordinates must not overlap unintentionally |
| Mirror | Mirror source display, mirror display, scale factor, or letterbox/crop strategy | When the mirror display has a different aspect ratio from the main display, explicitly model the coordinate mapping; do not assume 1:1 |
| Screen | A corresponding to each display | The number and IDs of must cover all displays |
| Window | The focus, background, floating, and occluding windows and hotArea for each display | / must match the target display; must reflect the stacking relationships |
| Focus | The focus window for each display and the group-level keyboard focus | DisplayGroupInfo.focusWindowId
is critical for keyboard routing; each display's focus window must also be represented in the window stack |
When producing a simulation plan, first provide the model table, followed by a C++ helper or pseudocode. Do not paste only a single fixed
implementation.
Standard Scenario Templates
Single Display Group: Main Display + Extended Display + Main Display Mirror
This scenario is used to validate multi-display routing within one user/display group, the focus window on each display, mirror-display coordinate mapping, and multi-window stacking:
| Object | Suggested Modeling |
|---|
| group | , , |
| Main display | , screenArea={0,0,720,1280}
|
| Extended display | , screenArea={720,0,1920,1080}
, horizontally adjacent to the main display |
| Mirror display | , screenArea={2640,0,1024,768}
, mirrors the main display but has a different aspect ratio |
| Focus window | At least one focus window per display, for example 1000/1100/1200 |
| Stacked windows | A background window, status bar/system layer, and floating window for each display, using different values |
| hotArea | Configure for both the mirror and extended displays for hit testing |
When the mirror display has a different aspect ratio, the test description must clearly state the expected coordinate strategy:
- fit/letterbox: Preserve the main display's aspect ratio; part of the mirror display has no valid hotArea.
- stretch: Map main-display coordinates to the mirror display using independent X/Y scale factors.
- crop: The mirror display covers only the cropped region of the main display.
MMI window hit testing relies only on the injected display/window geometry. To validate mirror-coordinate conversion, the test code must convert source-display coordinates into mirror-display coordinates before injecting the event or validating the target window.
Multiple Display Groups and Users: Each Group Contains a Main, Extended, and Mirror Display
This scenario is used to validate display-group isolation under different foreground-user or parallel-foreground scenarios:
| Object | Suggested Modeling |
|---|
| user A group | , , |
| user B group | , , , or another nonzero group assigned according to the model under test |
| Each group | Contains main, extended, and mirror displays |
| displayId | Globally unique; the same displayId cannot be reused across different groups |
| windowId | Globally unique; segmented numbering by group/display is recommended |
| focus | Each group has a group-level ; each display has its own hittable focus window |
| mirror mismatch | Each group has at least one mirror display with a different aspect ratio to validate coordinate and hit-test isolation |
If the underlying
can express only one user at a time, call
separately for each user and document the simulation assumption that "multiple users are all foreground/ACTIVE" in the report. Do not mix windows from multiple users into the same group and then claim that multi-user isolation has been verified.
Constructing Display Groups (Instead of DMS)
Below is the minimal construction snippet. A real test should generate multiple displays, screens, and groups according to the scenario model above.
cpp
#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 |
|---|
| There must be one | validates that groupId==MAIN_GROUPID |
| The DEFAULT group id must be 0 | A nonzero value is rejected |
| Must be preset in | Keyboard event routing depends on this value; if left unset, it becomes -1 and keyboard events cannot be dispatched |
| Must match width/height | Cursor boundary clamping depends on this value |
| Number and IDs of | Must correspond to | Screen information is used for DPI/PPI calculations |
| Defaults to and does not need to be set | When it is not ACTIVE, is not written |
| Must be globally unique | Reusing a displayId across groups or users makes routing and dump interpretation ambiguous |
| Mirror-display aspect ratio | The mapping strategy must be explicitly stated | A mirror display with a different aspect ratio cannot be asserted using main-display coordinates directly |
Return Code
It is normal for
to return -201. This is the error code of
and does not affect the display-group data being written.
Injecting Virtual Windows (Instead of WMS)
Each display requires at least one hittable window. Complex scenarios should inject each display's own background window, focus window, and layered windows that need to be validated:
cpp
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 |
|---|
| Must match the target display-group ID |
| Must match the display ID within the display group |
| Use —it is required for permission checks |
| Use for the first insertion and for updates |
| Takes effect for ; for non-main groups, the value is read from |
| Mouse-event hit testing depends on this region |
| Must be globally unique; reusing a windowId across displays or groups makes dumps and dispatch results unauditable |
| Must be explicitly assigned according to the business hierarchy; when all windows have the same zOrder, hit order cannot be used as evidence |
Per-Display Window Stack Pattern
Construct at least the following for each display:
| Window | zOrder | Purpose |
|---|
| background/app | 10 | Default hit target |
| panel/status/system | 100 | Verify system-layer priority |
| popup/floating | 200+ | Verify overlapping hit testing and fallback after DEL |
The focus window on each display may be the background window or a dedicated input window; indicate this in the model table.
Incremental Window Operations
An ADD/CHANGE/DEL operation for an individual window does not require reinjecting all windows:
cpp
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
handles the following operations:
appends the window, replacing it if it already exists;
replaces it in place;
removes it from the list.
Dynamic Focus Switching
Main Group (groupId=0)
uses the supplied
, which takes effect immediately.
Non-Main Group (groupId!=0)
ignores the supplied
and always reads it from
.
The focus must be updated through :
cpp
SetupDualDisplayGroups(1000, 2002); // 重新调用 UpdateDisplayInfo
sleep(1s);
InjectVirtualWindows(); // 重新注入窗口(同步 focusWinId 全局变量)
sleep(1s);
In multi-display scenarios, distinguish the following:
- Group-level keyboard focus: Determined by
DisplayGroupInfo.focusWindowId
.
- Expected focus window on each display: Represented by the test window stack and hit coordinates. Do not assume that MMI automatically maintains independent keyboard focus for each display.
- If a requirement states that "each screen has its own focus window," the test must explicitly clarify whether this means window-stack/hit-target focus or actual input-framework keyboard focus. The two must not be conflated.
Implementation pattern: Use global variables to track focus and share them between
and
:
cpp
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 Regions and Different Layers
cpp
// 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
iterates in descending zOrder, and the first window whose
contains the point becomes the target. In overlapping regions, the window with the higher zOrder takes priority.
Full-Screen Overlap Pattern: Position-Independent Validation
ADD multiple full-screen windows with different zOrder values so that cursor positioning is unnecessary. A full-screen window is hit at any cursor position:
cpp
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 a full-screen window is deleted, the hit target depends on the zOrder of all remaining windows, including existing non-full-screen windows. In the example above, after deleting win1007 (z=250), if the cursor is within the hotArea of win1002 (z=200), win1002 is hit rather than win1006 (z=150).
Hot-Area Passthrough
and
may differ. When the pointer is within
but outside
, the click passes through to the window below:
cpp
// 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)
cpp
// 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 Validation APIs
cpp
// 设备绑定(详见 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 inaccurate | libinput mouse acceleration makes it impossible to map uinput relative movement precisely to absolute coordinates | Use full-screen windows to avoid depending on cursor position |
| appears in the DisplayGroups dump | The dump reads , while the MAIN_GROUPID focus is supplied through | Verify the actual focus through or |
| Focus switching in a non-main group | ignores the non-main group's | The focus must be updated through |
| An unexpected window is hit after deleting a full-screen window | The highest zOrder among all remaining windows is hit, including existing non-full-screen windows | Check the zOrder relationships of all windows |
Common Mistakes
| Mistake | Consequence | Fix |
|---|
| Not setting | Keyboard events are not dispatched to any window | Preset it in |
| Using for a group with | rejects the group | Use for nonzero groups |
| The window's does not match the display group | Events are routed to the wrong group | Ensure that they match |
| Reusing displayId or windowId across displays/groups | Dump and routing evidence is unauditable | Use globally unique IDs |
| Using main-display coordinates when the mirror display has a different aspect ratio | Hit assertions are incorrect | Define the fit/stretch/crop mapping strategy first |
| Claiming independent keyboard focus for each screen while only setting the group focus | The conclusion is invalid | Distinguish group-level keyboard focus from per-screen hit targets |
| Switching focus in a non-main group through | is ignored | Switch focus through |
| Using the same zOrder for all windows | Hit order becomes indeterminate | Assign different zOrder values according to the business layer |
| Forgetting to inject windows | Mouse events have no hit-test target | Inject at least one full-screen window per group |
| Treating an return value of -201 as a failure | The data was actually written | -201 comes from UIExtension and can be ignored |
| Hard-coding in | The value is overwritten after dynamic focus switching | Use global variables to synchronize the focus |
| Assuming the second-highest full-screen window will be hit after DEL | Existing non-full-screen windows may have a higher zOrder | Check the zOrder relationships of all windows |