// tests for the emulated mouse: tests/test_mouse.cpp // // The two things worth proving about a relative mouse are that a frame which changes // nothing sends nothing, and that a button release is never conditional on movement. // Everything else about it is arithmetic. #include #include #include "mouse.h" #include "test.h" using kr::mouse::Button; using kr::mouse::Frame; using kr::mouse::MouseReport; using kr::mouse::MouseState; namespace { bool allZero(const MouseReport &r) { const uint8_t *b = reinterpret_cast(&r); for (size_t i = 0; i < sizeof(MouseReport); i++) { if (b[i] != 0) return false; } return true; } } // namespace int main() { CASE("the report is the five bytes the descriptor promises"); { CHECK_EQ(sizeof(MouseReport), 5); // The layout is asserted, not assumed: buttons, X, Y, wheel, pan. A struct that // reordered itself would still compile and would move the target's cursor with the // wheel byte. CHECK_EQ(offsetof(MouseReport, buttons), 0); CHECK_EQ(offsetof(MouseReport, dx), 1); CHECK_EQ(offsetof(MouseReport, dy), 2); CHECK_EQ(offsetof(MouseReport, wheel), 3); CHECK_EQ(offsetof(MouseReport, pan), 4); } CASE("button bits are the report's order, not the DOM's"); { CHECK_EQ(kr::mouse::kBtnLeft, 0x01); CHECK_EQ(kr::mouse::kBtnRight, 0x02); CHECK_EQ(kr::mouse::kBtnMiddle, 0x04); CHECK_EQ(kr::mouse::kBtnBackward, 0x08); CHECK_EQ(kr::mouse::kBtnForward, 0x10); CHECK_EQ(kr::mouse::kBtnAll, 0x1F); } CASE("movement produces a report, nothing produces nothing"); { MouseState m; MouseReport r; CHECK(m.apply(Frame{0, 30, -4, 0, 0}, r)); CHECK_EQ(r.buttons, 0); CHECK_EQ(r.dx, 30); CHECK_EQ(r.dy, -4); CHECK_EQ(r.wheel, 0); CHECK_EQ(r.pan, 0); // An idle frame is the whole endpoint for that frame and says nothing, so it is // dropped rather than sent -- this is what stops a chatty controller keeping a // target busy at 125 Hz with a pointer that is not moving. CHECK(!m.apply(Frame{0, 0, 0, 0, 0}, r)); CHECK(!m.apply(Frame{0, 0, 0, 0, 0}, r)); } CASE("a button change is a report even with no movement at all"); { MouseState m; MouseReport r; CHECK(m.apply(Frame{kr::mouse::kBtnLeft, 0, 0, 0, 0}, r)); CHECK_EQ(r.buttons, kr::mouse::kBtnLeft); CHECK(allZero(kr::mouse::kReportClear)); // the report a fail-safe sends // Repeating the same button byte with no movement is not a new state. CHECK(!m.apply(Frame{kr::mouse::kBtnLeft, 0, 0, 0, 0}, r)); CHECK(m.apply(Frame{0, 0, 0, 0, 0}, r)); // ...but letting go is CHECK_EQ(r.buttons, 0); } CASE("buttons are restated, not diffed"); { MouseState m; MouseReport r; m.apply(Frame{kr::mouse::kBtnLeft, 0, 0, 0, 0}, r); m.apply(Frame{kr::mouse::kBtnLeft | kr::mouse::kBtnRight, 0, 0, 0, 0}, r); CHECK_EQ(r.buttons, kr::mouse::kBtnLeft | kr::mouse::kBtnRight); // Letting go of one reports the state that remains, which is the whole reason a // frame lost in the network cannot strand a button: the next frame is the truth. m.apply(Frame{kr::mouse::kBtnRight, 5, 0, 0, 0}, r); CHECK_EQ(r.buttons, kr::mouse::kBtnRight); CHECK_EQ(r.dx, 5); CHECK_EQ(m.buttons(), kr::mouse::kBtnRight); CHECK(m.down()); } CASE("only the five buttons the report has"); { MouseState m; MouseReport r; // The parser refuses a sixth bit, so this is a second line of defence rather than // the behaviour anyone relies on: whatever arrives cannot set a padding bit. m.apply(Frame{0xFF, 1, 0, 0, 0}, r); CHECK_EQ(r.buttons, 0x1F); CHECK_EQ(m.buttons(), 0x1F); } CASE("releaseAll lets go, from any state, and is idempotent"); { MouseState m; MouseReport r; // Nothing was ever down; letting go is still well-defined and leaves the same state. m.releaseAll(); CHECK_EQ(m.buttons(), 0); CHECK(!m.down()); m.apply(Frame{kr::mouse::kBtnMiddle, 0, 0, 0, 0}, r); m.releaseAll(); CHECK_EQ(m.buttons(), 0); CHECK(!m.down()); m.releaseAll(); CHECK_EQ(m.buttons(), 0); // And after letting go, the next movement frame restates the empty button byte -- so // a release that never reached the target is corrected by the next one regardless. CHECK(m.apply(Frame{0, 4, 0, 0, 0}, r)); CHECK_EQ(r.buttons, 0); } CASE("the full range of one signed byte survives"); { MouseState m; MouseReport r; CHECK(m.apply(Frame{0, 127, -127, 127, -127}, r)); CHECK_EQ(r.dx, 127); CHECK_EQ(r.dy, -127); CHECK_EQ(r.wheel, 127); CHECK_EQ(r.pan, -127); } CASE("report equality is byte equality"); { const MouseReport a{1, 2, 3, 4, 5}; const MouseReport b{1, 2, 3, 4, 5}; const MouseReport c{1, 2, 3, 4, 6}; const MouseReport d{0, 2, 3, 4, 5}; CHECK(kr::mouse::reportsEqual(a, b)); CHECK(!kr::mouse::reportsEqual(a, c)); // pan differs: still a different report CHECK(!kr::mouse::reportsEqual(a, d)); } return report("mouse"); }