| // Copyright 2017 The Fuchsia Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "src/lib/ui/input/device_state.h" | |
| #include <fuchsia/ui/input/cpp/fidl.h> | |
| #include <lib/async/cpp/task.h> | |
| #include <lib/async/default.h> | |
| #include <lib/fidl/cpp/clone.h> | |
| #include <lib/syslog/cpp/macros.h> | |
| #include <lib/trace/event.h> | |
| #include <lib/zx/clock.h> | |
| namespace { | |
| int64_t InputEventTimestampNow() { return zx::clock::get_monotonic().get(); } | |
| // TODO(fxbug.dev/24476): Remove this. | |
| // Turn 64-bit id into two floats: high bits and low bits. | |
| void PointerTraceHACK(trace_async_id_t id, float* fa, float* fb) { | |
| uint32_t ia, ib; | |
| ia = (uint32_t)(id >> 32); | |
| ib = (uint32_t)id; | |
| memcpy(fa, &ia, sizeof(float)); | |
| memcpy(fb, &ib, sizeof(float)); | |
| } | |
| } // namespace | |
| namespace ui_input { | |
| constexpr zx::duration kKeyRepeatSlow = zx::msec(250); | |
| constexpr zx::duration kKeyRepeatFast = zx::msec(75); | |
| KeyboardState::KeyboardState(DeviceState* device_state) | |
| : device_state_(device_state), keymap_(qwerty_map), weak_ptr_factory_(this) { | |
| char* keys = getenv("gfxconsole.keymap"); | |
| if (keys && !strcmp(keys, "dvorak")) { | |
| keymap_ = dvorak_map; | |
| } | |
| } | |
| void KeyboardState::SendEvent(fuchsia::ui::input::KeyboardEventPhase phase, uint32_t key, | |
| uint64_t modifiers, uint64_t timestamp) { | |
| fuchsia::ui::input::InputEvent ev; | |
| fuchsia::ui::input::KeyboardEvent kb; | |
| kb.phase = phase; | |
| kb.event_time = timestamp; | |
| kb.device_id = device_state_->device_id(); | |
| kb.hid_usage = key; | |
| kb.code_point = hid_map_key( | |
| key, modifiers & (fuchsia::ui::input::kModifierShift | fuchsia::ui::input::kModifierCapsLock), | |
| keymap_); | |
| kb.modifiers = modifiers; | |
| ev.set_keyboard(std::move(kb)); | |
| device_state_->callback()(std::move(ev)); | |
| } | |
| void KeyboardState::Update(fuchsia::ui::input::InputReport input_report) { | |
| TRACE_DURATION("input", "device_state_update", "device_type", "keyboard"); | |
| TRACE_FLOW_END("input", "report_to_device_state", input_report.trace_id); | |
| FX_DCHECK(input_report.keyboard); | |
| uint64_t now = input_report.event_time; | |
| std::vector<uint32_t> old_keys = keys_; | |
| keys_.clear(); | |
| repeat_keys_.clear(); | |
| for (uint32_t key : input_report.keyboard->pressed_keys) { | |
| keys_.push_back(key); | |
| auto it = std::find(old_keys.begin(), old_keys.end(), key); | |
| if (it != old_keys.end()) { | |
| old_keys.erase(it); | |
| continue; | |
| } | |
| SendEvent(fuchsia::ui::input::KeyboardEventPhase::PRESSED, key, modifiers_, now); | |
| uint64_t modifiers = modifiers_; | |
| switch (key) { | |
| case HID_USAGE_KEY_LEFT_SHIFT: | |
| modifiers_ |= fuchsia::ui::input::kModifierLeftShift; | |
| break; | |
| case HID_USAGE_KEY_RIGHT_SHIFT: | |
| modifiers_ |= fuchsia::ui::input::kModifierRightShift; | |
| break; | |
| case HID_USAGE_KEY_LEFT_CTRL: | |
| modifiers_ |= fuchsia::ui::input::kModifierLeftControl; | |
| break; | |
| case HID_USAGE_KEY_RIGHT_CTRL: | |
| modifiers_ |= fuchsia::ui::input::kModifierRightControl; | |
| break; | |
| case HID_USAGE_KEY_LEFT_ALT: | |
| modifiers_ |= fuchsia::ui::input::kModifierLeftAlt; | |
| break; | |
| case HID_USAGE_KEY_RIGHT_ALT: | |
| modifiers_ |= fuchsia::ui::input::kModifierRightAlt; | |
| break; | |
| case HID_USAGE_KEY_LEFT_GUI: | |
| modifiers_ |= fuchsia::ui::input::kModifierLeftSuper; | |
| break; | |
| case HID_USAGE_KEY_RIGHT_GUI: | |
| modifiers_ |= fuchsia::ui::input::kModifierRightSuper; | |
| break; | |
| default: | |
| break; | |
| } | |
| // Don't repeat modifier by themselves | |
| if (modifiers == modifiers_) { | |
| repeat_keys_.push_back(key); | |
| } | |
| } | |
| // If any key was released as well, do not repeat | |
| if (!old_keys.empty()) { | |
| repeat_keys_.clear(); | |
| } | |
| for (uint32_t key : old_keys) { | |
| SendEvent(fuchsia::ui::input::KeyboardEventPhase::RELEASED, key, modifiers_, now); | |
| switch (key) { | |
| case HID_USAGE_KEY_LEFT_SHIFT: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierLeftShift); | |
| break; | |
| case HID_USAGE_KEY_RIGHT_SHIFT: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierRightShift); | |
| break; | |
| case HID_USAGE_KEY_LEFT_CTRL: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierLeftControl); | |
| break; | |
| case HID_USAGE_KEY_RIGHT_CTRL: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierRightControl); | |
| break; | |
| case HID_USAGE_KEY_LEFT_ALT: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierLeftAlt); | |
| break; | |
| case HID_USAGE_KEY_RIGHT_ALT: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierRightAlt); | |
| break; | |
| case HID_USAGE_KEY_LEFT_GUI: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierLeftSuper); | |
| break; | |
| case HID_USAGE_KEY_RIGHT_GUI: | |
| modifiers_ &= (~fuchsia::ui::input::kModifierRightSuper); | |
| break; | |
| case HID_USAGE_KEY_CAPSLOCK: | |
| if (modifiers_ & fuchsia::ui::input::kModifierCapsLock) { | |
| modifiers_ &= (~fuchsia::ui::input::kModifierCapsLock); | |
| } else { | |
| modifiers_ |= fuchsia::ui::input::kModifierCapsLock; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| if (!repeat_keys_.empty()) { | |
| ScheduleRepeat(++repeat_sequence_, kKeyRepeatSlow); | |
| } else { | |
| ++repeat_sequence_; | |
| } | |
| } | |
| void KeyboardState::Repeat(uint64_t sequence) { | |
| if (sequence != repeat_sequence_) { | |
| return; | |
| } | |
| uint64_t now = InputEventTimestampNow(); | |
| for (uint32_t key : repeat_keys_) { | |
| SendEvent(fuchsia::ui::input::KeyboardEventPhase::REPEAT, key, modifiers_, now); | |
| } | |
| ScheduleRepeat(sequence, kKeyRepeatFast); | |
| } | |
| void KeyboardState::ScheduleRepeat(uint64_t sequence, zx::duration delta) { | |
| async::PostDelayedTask( | |
| async_get_default_dispatcher(), | |
| [weak = weak_ptr_factory_.GetWeakPtr(), sequence] { | |
| if (weak) | |
| weak->Repeat(sequence); | |
| }, | |
| delta); | |
| } | |
| void MouseState::OnRegistered() {} | |
| void MouseState::OnUnregistered() {} | |
| void MouseState::SendEvent(float rel_x, float rel_y, int64_t timestamp, | |
| fuchsia::ui::input::PointerEventPhase phase, uint32_t buttons) { | |
| fuchsia::ui::input::InputEvent ev; | |
| fuchsia::ui::input::PointerEvent pt; | |
| pt.event_time = timestamp; | |
| pt.device_id = device_state_->device_id(); | |
| pt.pointer_id = device_state_->device_id(); | |
| pt.phase = phase; | |
| pt.buttons = buttons; | |
| pt.type = fuchsia::ui::input::PointerEventType::MOUSE; | |
| pt.x = rel_x; | |
| pt.y = rel_y; | |
| ev.set_pointer(std::move(pt)); | |
| device_state_->callback()(std::move(ev)); | |
| } | |
| void MouseState::Update(fuchsia::ui::input::InputReport input_report, | |
| fuchsia::math::Size display_size) { | |
| TRACE_DURATION("input", "device_state_update", "device_type", "mouse"); | |
| TRACE_FLOW_END("input", "report_to_device_state", input_report.trace_id); | |
| FX_DCHECK(input_report.mouse); | |
| uint64_t now = input_report.event_time; | |
| uint8_t pressed = | |
| (input_report.mouse->pressed_buttons ^ buttons_) & input_report.mouse->pressed_buttons; | |
| uint8_t released = (input_report.mouse->pressed_buttons ^ buttons_) & buttons_; | |
| buttons_ = input_report.mouse->pressed_buttons; | |
| // TODO(jpoichet) Update once we have an API to capture mouse. | |
| // TODO(fxbug.dev/23622): Quantize the mouse value to the range [0, display_width - | |
| // mouse_resolution] | |
| position_.x = std::max(0.0f, std::min(position_.x + input_report.mouse->rel_x, | |
| static_cast<float>(display_size.width))); | |
| position_.y = std::max(0.0f, std::min(position_.y + input_report.mouse->rel_y, | |
| static_cast<float>(display_size.height))); | |
| if (!pressed && !released) { | |
| SendEvent(position_.x, position_.y, now, fuchsia::ui::input::PointerEventPhase::MOVE, buttons_); | |
| } else { | |
| if (pressed) { | |
| SendEvent(position_.x, position_.y, now, fuchsia::ui::input::PointerEventPhase::DOWN, | |
| pressed); | |
| } | |
| if (released) { | |
| SendEvent(position_.x, position_.y, now, fuchsia::ui::input::PointerEventPhase::UP, released); | |
| } | |
| } | |
| } | |
| void StylusState::SendEvent(int64_t timestamp, fuchsia::ui::input::PointerEventPhase phase, | |
| fuchsia::ui::input::PointerEventType type, float x, float y, | |
| uint32_t buttons) { | |
| fuchsia::ui::input::PointerEvent pt; | |
| pt.event_time = timestamp; | |
| pt.device_id = device_state_->device_id(); | |
| pt.pointer_id = 1; | |
| pt.type = type; | |
| pt.phase = phase; | |
| pt.x = x; | |
| pt.y = y; | |
| pt.buttons = buttons; | |
| stylus_ = pt; | |
| fuchsia::ui::input::InputEvent ev; | |
| ev.set_pointer(std::move(pt)); | |
| device_state_->callback()(std::move(ev)); | |
| } | |
| void StylusState::Update(fuchsia::ui::input::InputReport input_report, | |
| fuchsia::math::Size display_size) { | |
| TRACE_DURATION("input", "device_state_update", "device_type", "stylus"); | |
| TRACE_FLOW_END("input", "report_to_device_state", input_report.trace_id); | |
| FX_DCHECK(input_report.stylus); | |
| fuchsia::ui::input::StylusDescriptor* descriptor = device_state_->stylus_descriptor(); | |
| FX_DCHECK(descriptor); | |
| const bool previous_stylus_down = stylus_down_; | |
| const bool previous_stylus_in_range = stylus_in_range_; | |
| stylus_down_ = input_report.stylus->is_in_contact; | |
| stylus_in_range_ = input_report.stylus->in_range; | |
| fuchsia::ui::input::PointerEventPhase phase = fuchsia::ui::input::PointerEventPhase::DOWN; | |
| if (stylus_down_) { | |
| if (previous_stylus_down) { | |
| phase = fuchsia::ui::input::PointerEventPhase::MOVE; | |
| } | |
| } else { | |
| if (previous_stylus_down) { | |
| phase = fuchsia::ui::input::PointerEventPhase::UP; | |
| } else { | |
| if (stylus_in_range_ && !previous_stylus_in_range) { | |
| inverted_stylus_ = input_report.stylus->is_inverted; | |
| phase = fuchsia::ui::input::PointerEventPhase::ADD; | |
| } else if (!stylus_in_range_ && previous_stylus_in_range) { | |
| phase = fuchsia::ui::input::PointerEventPhase::REMOVE; | |
| } else if (stylus_in_range_) { | |
| phase = fuchsia::ui::input::PointerEventPhase::HOVER; | |
| } else { | |
| return; | |
| } | |
| } | |
| } | |
| uint64_t now = input_report.event_time; | |
| if (phase == fuchsia::ui::input::PointerEventPhase::UP) { | |
| SendEvent(now, phase, | |
| inverted_stylus_ ? fuchsia::ui::input::PointerEventType::INVERTED_STYLUS | |
| : fuchsia::ui::input::PointerEventType::STYLUS, | |
| stylus_.x, stylus_.y, stylus_.buttons); | |
| } else { | |
| // Quantize the value to [0, 1) based on the resolution. | |
| float x_denominator = | |
| (1 + static_cast<float>(descriptor->x.range.max - descriptor->x.range.min) / | |
| static_cast<float>(descriptor->x.resolution)) * | |
| static_cast<float>(descriptor->x.resolution); | |
| float x = static_cast<float>(display_size.width * | |
| (input_report.stylus->x - descriptor->x.range.min)) / | |
| x_denominator; | |
| float y_denominator = | |
| (1 + static_cast<float>(descriptor->y.range.max - descriptor->y.range.min) / | |
| static_cast<float>(descriptor->y.resolution)) * | |
| static_cast<float>(descriptor->y.resolution); | |
| float y = static_cast<float>(display_size.height * | |
| (input_report.stylus->y - descriptor->y.range.min)) / | |
| y_denominator; | |
| uint32_t buttons = 0; | |
| if (input_report.stylus->pressed_buttons & fuchsia::ui::input::kStylusBarrel) { | |
| buttons |= fuchsia::ui::input::kStylusPrimaryButton; | |
| } | |
| SendEvent(now, phase, | |
| inverted_stylus_ ? fuchsia::ui::input::PointerEventType::INVERTED_STYLUS | |
| : fuchsia::ui::input::PointerEventType::STYLUS, | |
| x, y, buttons); | |
| } | |
| } | |
| void TouchscreenState::Update(fuchsia::ui::input::InputReport input_report, | |
| fuchsia::math::Size display_size) { | |
| TRACE_DURATION("input", "device_state_update", "device_type", "touchscreen"); | |
| TRACE_FLOW_END("input", "report_to_device_state", input_report.trace_id); | |
| FX_DCHECK(input_report.touchscreen); | |
| fuchsia::ui::input::TouchscreenDescriptor* descriptor = device_state_->touchscreen_descriptor(); | |
| FX_DCHECK(descriptor); | |
| std::vector<fuchsia::ui::input::PointerEvent> old_pointers = std::move(pointers_); | |
| pointers_.clear(); | |
| uint64_t now = input_report.event_time; | |
| for (auto& touch : input_report.touchscreen->touches) { | |
| fuchsia::ui::input::PointerEvent pt; | |
| pt.event_time = now; | |
| pt.device_id = device_state_->device_id(); | |
| pt.phase = fuchsia::ui::input::PointerEventPhase::ADD; | |
| for (auto it = old_pointers.begin(); it != old_pointers.end(); ++it) { | |
| FX_DCHECK(touch.finger_id >= 0); | |
| if (it->pointer_id == static_cast<uint32_t>(touch.finger_id)) { | |
| pt.phase = fuchsia::ui::input::PointerEventPhase::MOVE; | |
| old_pointers.erase(it); | |
| break; | |
| } | |
| } | |
| pt.pointer_id = touch.finger_id; | |
| pt.type = fuchsia::ui::input::PointerEventType::TOUCH; | |
| // Quantize the value to [0, 1) based on the resolution. | |
| // The large number of casts here are so the multiplications don't cause | |
| // overflows. | |
| float x_denominator = | |
| (1 + static_cast<float>(descriptor->x.range.max - descriptor->x.range.min) / | |
| static_cast<float>(descriptor->x.resolution)) * | |
| static_cast<float>(descriptor->x.resolution); | |
| float x = static_cast<float>(display_size.width * | |
| static_cast<float>(touch.x - descriptor->x.range.min)) / | |
| x_denominator; | |
| float y_denominator = | |
| (1 + static_cast<float>(descriptor->y.range.max - descriptor->y.range.min) / | |
| static_cast<float>(descriptor->y.resolution)) * | |
| static_cast<float>(descriptor->y.resolution); | |
| float y = static_cast<float>(display_size.height * | |
| static_cast<float>(touch.y - descriptor->y.range.min)) / | |
| y_denominator; | |
| pt.x = x; | |
| pt.y = y; | |
| // TODO(fxbug.dev/24476): Use proper trace_id field for tracing flow. | |
| trace_async_id_t pt_async_id = TRACE_NONCE(); | |
| PointerTraceHACK(pt_async_id, &pt.radius_major, &pt.radius_minor); | |
| pointers_.push_back(pt); | |
| TRACE_FLOW_BEGIN("input", "dispatch_event_to_presentation", pt_async_id); | |
| fuchsia::ui::input::InputEvent ev; | |
| ev.set_pointer(std::move(pt)); | |
| device_state_->callback()(std::move(ev)); | |
| } | |
| for (const auto& pointer : old_pointers) { | |
| fuchsia::ui::input::PointerEvent old = fidl::Clone(pointer); | |
| old.phase = fuchsia::ui::input::PointerEventPhase::REMOVE; | |
| old.event_time = now; | |
| // TODO(fxbug.dev/24476): Use proper trace_id field for tracing flow. | |
| trace_async_id_t old_id = TRACE_NONCE(); | |
| PointerTraceHACK(old_id, &old.radius_major, &old.radius_minor); | |
| TRACE_FLOW_BEGIN("input", "dispatch_event_to_presentation", old_id); | |
| fuchsia::ui::input::InputEvent ev; | |
| ev.set_pointer(std::move(old)); | |
| device_state_->callback()(std::move(ev)); | |
| } | |
| } | |
| void SensorState::Update(fuchsia::ui::input::InputReport input_report) { | |
| TRACE_DURATION("input", "device_state_update", "device_type", "sensor"); | |
| TRACE_FLOW_END("input", "report_to_device_state", input_report.trace_id); | |
| FX_DCHECK(input_report.sensor); | |
| FX_DCHECK(device_state_->sensor_descriptor()); | |
| // Every sensor report gets routed via unique device_id. | |
| device_state_->sensor_callback()(device_state_->device_id(), std::move(input_report)); | |
| } | |
| void MediaButtonState::Update(fuchsia::ui::input::InputReport report) { | |
| FX_DCHECK(report.media_buttons); | |
| FX_DCHECK(device_state_->media_buttons_descriptor()); | |
| device_state_->media_buttons_callback()(std::move(report)); | |
| } | |
| DeviceState::DeviceState(uint32_t device_id, fuchsia::ui::input::DeviceDescriptor* descriptor, | |
| OnEventCallback callback) | |
| : device_id_(device_id), | |
| descriptor_(descriptor), | |
| keyboard_(this), | |
| mouse_(this), | |
| stylus_(this), | |
| touchscreen_(this), | |
| callback_(std::move(callback)), | |
| sensor_(this), | |
| sensor_callback_(nullptr), | |
| media_buttons_callback_(nullptr), | |
| media_buttons_(this) {} | |
| DeviceState::DeviceState(uint32_t device_id, fuchsia::ui::input::DeviceDescriptor* descriptor, | |
| OnSensorEventCallback callback) | |
| : device_id_(device_id), | |
| descriptor_(descriptor), | |
| keyboard_(this), | |
| mouse_(this), | |
| stylus_(this), | |
| touchscreen_(this), | |
| callback_(nullptr), | |
| sensor_(this), | |
| sensor_callback_(std::move(callback)), | |
| media_buttons_callback_(nullptr), | |
| media_buttons_(this) {} | |
| DeviceState::DeviceState(uint32_t device_id, fuchsia::ui::input::DeviceDescriptor* descriptor, | |
| OnMediaButtonsEventCallback callback) | |
| : device_id_(device_id), | |
| descriptor_(descriptor), | |
| keyboard_(this), | |
| mouse_(this), | |
| stylus_(this), | |
| touchscreen_(this), | |
| callback_(nullptr), | |
| sensor_(this), | |
| sensor_callback_(nullptr), | |
| media_buttons_callback_(std::move(callback)), | |
| media_buttons_(this) {} | |
| DeviceState::~DeviceState() {} | |
| void DeviceState::OnRegistered() { | |
| if (descriptor_->keyboard) { | |
| keyboard_.OnRegistered(); | |
| } | |
| if (descriptor_->mouse) { | |
| mouse_.OnRegistered(); | |
| } | |
| if (descriptor_->stylus) { | |
| stylus_.OnRegistered(); | |
| } | |
| if (descriptor_->touchscreen) { | |
| touchscreen_.OnRegistered(); | |
| } | |
| if (descriptor_->sensor) { | |
| sensor_.OnRegistered(); | |
| } | |
| if (descriptor_->media_buttons) { | |
| media_buttons_.OnRegistered(); | |
| } | |
| } | |
| void DeviceState::OnUnregistered() { | |
| if (descriptor_->keyboard) { | |
| keyboard_.OnUnregistered(); | |
| } | |
| if (descriptor_->mouse) { | |
| mouse_.OnUnregistered(); | |
| } | |
| if (descriptor_->stylus) { | |
| stylus_.OnUnregistered(); | |
| } | |
| if (descriptor_->touchscreen) { | |
| touchscreen_.OnUnregistered(); | |
| } | |
| if (descriptor_->sensor) { | |
| sensor_.OnUnregistered(); | |
| } | |
| if (descriptor_->media_buttons) { | |
| media_buttons_.OnUnregistered(); | |
| } | |
| } | |
| void DeviceState::Update(fuchsia::ui::input::InputReport input_report, | |
| fuchsia::math::Size display_size) { | |
| if (input_report.keyboard && descriptor_->keyboard) { | |
| keyboard_.Update(std::move(input_report)); | |
| } else if (input_report.mouse && descriptor_->mouse) { | |
| mouse_.Update(std::move(input_report), display_size); | |
| } else if (input_report.stylus && descriptor_->stylus) { | |
| stylus_.Update(std::move(input_report), display_size); | |
| } else if (input_report.touchscreen && descriptor_->touchscreen) { | |
| touchscreen_.Update(std::move(input_report), display_size); | |
| } else if (input_report.sensor && descriptor_->sensor) { | |
| sensor_.Update(std::move(input_report)); | |
| } else if (input_report.media_buttons && descriptor_->media_buttons) { | |
| media_buttons_.Update(std::move(input_report)); | |
| } | |
| } | |
| } // namespace ui_input |