static void handle_touch_event (GdkWindow *window, const MirTouchEvent *mir_touch_event) { const MirInputEvent *mir_input_event = mir_touch_event_input_event (mir_touch_event); guint n = mir_touch_event_point_count (mir_touch_event); GdkEvent *gdk_event; guint i; for (i = 0; i < n; i++) { MirTouchAction action = mir_touch_event_action (mir_touch_event, i); if (action == mir_touch_action_up) gdk_event = gdk_event_new (GDK_TOUCH_END); else if (action == mir_touch_action_down) gdk_event = gdk_event_new (GDK_TOUCH_BEGIN); else gdk_event = gdk_event_new (GDK_TOUCH_UPDATE); gdk_event->touch.window = window; gdk_event->touch.sequence = GINT_TO_POINTER (mir_touch_event_id (mir_touch_event, i)); gdk_event->touch.time = mir_input_event_get_event_time (mir_input_event); gdk_event->touch.state = get_modifier_state (mir_touch_event_modifiers (mir_touch_event), 0); gdk_event->touch.x = mir_touch_event_axis_value (mir_touch_event, i, mir_touch_axis_x); gdk_event->touch.y = mir_touch_event_axis_value (mir_touch_event, i, mir_touch_axis_y); gdk_event->touch.x_root = mir_touch_event_axis_value (mir_touch_event, i, mir_touch_axis_x); gdk_event->touch.y_root = mir_touch_event_axis_value (mir_touch_event, i, mir_touch_axis_y); gdk_event->touch.emulating_pointer = TRUE; gdk_event_set_pointer_emulated (gdk_event, TRUE); send_event (window, get_pointer (window), gdk_event); } }
// This is the core of the touch validator. Given a valid event 'last_ev' which was the last_event to be dispatched // this function must dispatch events such that 'ev' is a valid event to dispatch. // Our requirements for touch validity are simple: // 1. A touch point (unique per ID) can not vanish without being released. // 2. A touch point can not appear without coming down. // Our algorithm to ensure a candidate event can be dispatched is as follows: // // First we look at the last event to build a set of expected touch ID's. That is to say // each touch point in the last event which did not have touch_action_up (signifying its dissapearance) // is expected to be in the candidate event. Likewise we go through the candidate event can produce a set of events // we have found. // // We now check for expected events which were not found, e.g. touch points which are missing a release. // For each of these touch points we can take the coordinates for these points from the last event // and dispatch an event which releases the missing point. // // Now we check for found touch points which were not expected. If these show up with mir_touch_action_down // things are fine. On the other hand if they show up with mir_touch_action_change then a touch point // has appeared before its gone down and thus we must inject an event signifying this touch going down. void mi::Validator::ensure_stream_validity_locked(std::lock_guard<std::mutex> const&, MirTouchEvent const* ev, MirTouchEvent const* last_ev) { TouchSet expected; for (size_t i = 0; i < mir_touch_event_point_count(last_ev); i++) { auto action = mir_touch_event_action(last_ev, i); if (action == mir_touch_action_up) continue; expected.insert(mir_touch_event_id(last_ev, i)); } TouchSet found; for (size_t i = 0; i < mir_touch_event_point_count(ev); i++) { auto id = mir_touch_event_id(ev, i); found.insert(id); } // Insert missing touch releases auto last_ev_copy = remove_old_releases_from(reinterpret_cast<MirEvent const*>(last_ev)); for (auto const& expected_id : expected) { if (found.find(expected_id) == found.end()) { auto inject_ev = add_missing_up(last_ev_copy.get(), expected_id); dispatch_valid_event(*inject_ev); last_ev_copy = remove_old_releases_from(inject_ev.get()); } } for (size_t i = 0; i < mir_touch_event_point_count(ev); i++) { auto id = mir_touch_event_id(ev, i); if (expected.find(id) == expected.end() && mir_touch_event_action(ev, i) != mir_touch_action_down) { auto inject_ev = add_missing_down(last_ev_copy.get(), ev, id); dispatch_valid_event(*inject_ev); last_ev_copy = std::move(inject_ev); } } }
void QMirClientInput::dispatchTouchEvent(QMirClientWindow *window, const MirInputEvent *ev) { const MirTouchEvent *tev = mir_input_event_get_touch_event(ev); // FIXME(loicm) Max pressure is device specific. That one is for the Samsung Galaxy Nexus. That // needs to be fixed as soon as the compat input lib adds query support. const float kMaxPressure = 1.28; const QRect kWindowGeometry = window->geometry(); QList<QWindowSystemInterface::TouchPoint> touchPoints; // TODO: Is it worth setting the Qt::TouchPointStationary ones? Currently they are left // as Qt::TouchPointMoved const unsigned int kPointerCount = mir_touch_event_point_count(tev); touchPoints.reserve(int(kPointerCount)); for (unsigned int i = 0; i < kPointerCount; ++i) { QWindowSystemInterface::TouchPoint touchPoint; const float kX = mir_touch_event_axis_value(tev, i, mir_touch_axis_x) + kWindowGeometry.x(); const float kY = mir_touch_event_axis_value(tev, i, mir_touch_axis_y) + kWindowGeometry.y(); // see bug lp:1346633 workaround comments elsewhere const float kW = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_major); const float kH = mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_minor); const float kP = mir_touch_event_axis_value(tev, i, mir_touch_axis_pressure); touchPoint.id = mir_touch_event_id(tev, i); touchPoint.normalPosition = QPointF(kX / kWindowGeometry.width(), kY / kWindowGeometry.height()); touchPoint.area = QRectF(kX - (kW / 2.0), kY - (kH / 2.0), kW, kH); touchPoint.pressure = kP / kMaxPressure; MirTouchAction touch_action = mir_touch_event_action(tev, i); switch (touch_action) { case mir_touch_action_down: mLastInputWindow = window; touchPoint.state = Qt::TouchPointPressed; break; case mir_touch_action_up: touchPoint.state = Qt::TouchPointReleased; break; case mir_touch_action_change: touchPoint.state = Qt::TouchPointMoved; break; default: Q_UNREACHABLE(); } touchPoints.append(touchPoint); } ulong timestamp = mir_input_event_get_event_time(ev) / 1000000; QWindowSystemInterface::handleTouchEvent(window->window(), timestamp, mTouchDevice, touchPoints); }
TEST_F(InputEventBuilder, makes_valid_touch_event) { unsigned touch_count = 3; MirTouchId touch_ids[] = {7, 9, 4}; MirTouchAction actions[] = { mir_touch_action_up, mir_touch_action_change, mir_touch_action_change}; MirTouchTooltype tooltypes[] = {mir_touch_tooltype_unknown, mir_touch_tooltype_finger, mir_touch_tooltype_stylus}; float x_axis_values[] = { 7, 14.3, 19.6 }; float y_axis_values[] = { 3, 9, 11 }; float pressure_values[] = {3, 9, 14.6}; float touch_major_values[] = {11, 9, 14}; float touch_minor_values[] = {13, 3, 9.13}; float size_values[] = {4, 9, 6}; auto ev = mev::make_event(device_id, timestamp, mac, modifiers); for (unsigned i = 0; i < touch_count; i++) { mev::add_touch(*ev, touch_ids[i], actions[i], tooltypes[i], x_axis_values[i], y_axis_values[i], pressure_values[i], touch_major_values[i], touch_minor_values[i], size_values[i]); } auto e = ev.get(); EXPECT_EQ(mir_event_type_input, mir_event_get_type(e)); auto ie = mir_event_get_input_event(e); EXPECT_EQ(mir_input_event_type_touch, mir_input_event_get_type(ie)); auto tev = mir_input_event_get_touch_event(ie); EXPECT_EQ(modifiers, mir_touch_event_modifiers(tev)); EXPECT_EQ(touch_count, mir_touch_event_point_count(tev)); EXPECT_EQ(mac, mir_touch_event_get_cookie(tev).mac); EXPECT_EQ(timestamp.count(), mir_touch_event_get_cookie(tev).timestamp); for (unsigned i = 0; i < touch_count; i++) { EXPECT_EQ(touch_ids[i], mir_touch_event_id(tev, i)); EXPECT_EQ(actions[i], mir_touch_event_action(tev, i)); EXPECT_EQ(tooltypes[i], mir_touch_event_tooltype(tev, i)); EXPECT_EQ(x_axis_values[i], mir_touch_event_axis_value(tev, i, mir_touch_axis_x)); EXPECT_EQ(y_axis_values[i], mir_touch_event_axis_value(tev, i, mir_touch_axis_y)); EXPECT_EQ(pressure_values[i], mir_touch_event_axis_value(tev, i, mir_touch_axis_pressure)); EXPECT_EQ(touch_major_values[i], mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_major)); EXPECT_EQ(touch_minor_values[i], mir_touch_event_axis_value(tev, i, mir_touch_axis_touch_minor)); EXPECT_EQ(size_values[i], mir_touch_event_axis_value(tev, i, mir_touch_axis_size)); } }