| // Copyright 2014 The Chromium 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 <algorithm> |
| #include <memory> |
| #include <type_traits> |
| |
| #include "base/auto_reset.h" |
| #include "base/logging.h" |
| #include "base/memory/free_deleter.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "build/chromeos_buildflags.h" |
| #include "ui/events/devices/x11/device_data_manager_x11.h" |
| #include "ui/events/devices/x11/touch_factory_x11.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/keyboard_code_conversion_x.h" |
| #include "ui/events/platform/platform_event_dispatcher.h" |
| #include "ui/events/platform/x11/x11_hotplug_event_handler.h" |
| #include "ui/events/x/events_x_utils.h" |
| #include "ui/events/x/x11_event_translation.h" |
| #include "ui/gfx/x/connection.h" |
| #include "ui/gfx/x/extension_manager.h" |
| #include "ui/gfx/x/future.h" |
| #include "ui/gfx/x/x11_atom_cache.h" |
| #include "ui/gfx/x/x11_window_event_manager.h" |
| #include "ui/gfx/x/xkb.h" |
| #include "ui/gfx/x/xproto.h" |
| |
| #if defined(USE_GLIB) |
| #include "ui/events/platform/x11/x11_event_watcher_glib.h" |
| #else |
| #include "ui/events/platform/x11/x11_event_watcher_fdwatch.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| #include "ui/events/ozone/chromeos/cursor_controller.h" |
| #endif |
| |
| #if defined(USE_OZONE) |
| #include "ui/base/ui_base_features.h" |
| #endif |
| |
| namespace ui { |
| |
| namespace { |
| |
| void InitializeXkb(x11::Connection* connection) { |
| auto& xkb = connection->xkb(); |
| |
| // Ask the server not to send KeyRelease event when the user holds down a key. |
| // crbug.com/138092 |
| xkb |
| .PerClientFlags({ |
| .deviceSpec = |
| static_cast<x11::Xkb::DeviceSpec>(x11::Xkb::Id::UseCoreKbd), |
| .change = x11::Xkb::PerClientFlag::DetectableAutoRepeat, |
| .value = x11::Xkb::PerClientFlag::DetectableAutoRepeat, |
| }) |
| .OnResponse(base::BindOnce([](x11::Xkb::PerClientFlagsResponse response) { |
| if (!response || |
| !static_cast<bool>(response->supported & |
| x11::Xkb::PerClientFlag::DetectableAutoRepeat)) { |
| DVLOG(1) << "Could not set XKB auto repeat flag."; |
| } |
| })); |
| |
| constexpr auto kXkbAllMapPartMask = static_cast<x11::Xkb::MapPart>(0xff); |
| xkb.SelectEvents(x11::Xkb::SelectEventsRequest{ |
| .deviceSpec = static_cast<x11::Xkb::DeviceSpec>(x11::Xkb::Id::UseCoreKbd), |
| .affectWhich = x11::Xkb::EventType::NewKeyboardNotify, |
| .selectAll = x11::Xkb::EventType::NewKeyboardNotify, |
| .affectMap = kXkbAllMapPartMask, |
| }); |
| } |
| |
| x11::Time ExtractTimeFromXEvent(const x11::Event& xev) { |
| if (auto* key = xev.As<x11::KeyEvent>()) |
| return key->time; |
| if (auto* button = xev.As<x11::ButtonEvent>()) |
| return button->time; |
| if (auto* motion = xev.As<x11::MotionNotifyEvent>()) |
| return motion->time; |
| if (auto* crossing = xev.As<x11::CrossingEvent>()) |
| return crossing->time; |
| if (auto* prop = xev.As<x11::PropertyNotifyEvent>()) |
| return prop->time; |
| if (auto* sel_clear = xev.As<x11::SelectionClearEvent>()) |
| return sel_clear->time; |
| if (auto* sel_req = xev.As<x11::SelectionRequestEvent>()) |
| return sel_req->time; |
| if (auto* sel_notify = xev.As<x11::SelectionNotifyEvent>()) |
| return sel_notify->time; |
| if (auto* dev_changed = xev.As<x11::Input::DeviceChangedEvent>()) |
| return dev_changed->time; |
| if (auto* device = xev.As<x11::Input::DeviceEvent>()) |
| return device->time; |
| if (auto* xi_crossing = xev.As<x11::Input::CrossingEvent>()) |
| return xi_crossing->time; |
| return x11::Time::CurrentTime; |
| } |
| |
| void UpdateDeviceList() { |
| auto* connection = x11::Connection::Get(); |
| DeviceListCacheX11::GetInstance()->UpdateDeviceList(connection); |
| TouchFactory::GetInstance()->UpdateDeviceList(connection); |
| DeviceDataManagerX11::GetInstance()->UpdateDeviceList(connection); |
| } |
| |
| } // namespace |
| |
| #if defined(USE_GLIB) |
| using X11EventWatcherImpl = X11EventWatcherGlib; |
| #else |
| using X11EventWatcherImpl = X11EventWatcherFdWatch; |
| #endif |
| |
| X11EventSource::X11EventSource(x11::Connection* connection) |
| : watcher_(std::make_unique<X11EventWatcherImpl>(this)), |
| connection_(connection), |
| dummy_initialized_(false) { |
| DCHECK(connection_); |
| connection_->AddEventObserver(this); |
| |
| DeviceDataManagerX11::CreateInstance(); |
| InitializeXkb(connection_); |
| |
| watcher_->StartWatching(); |
| } |
| |
| X11EventSource::~X11EventSource() { |
| if (dummy_initialized_) |
| connection_->DestroyWindow({dummy_window_}); |
| connection_->RemoveEventObserver(this); |
| } |
| |
| // static |
| bool X11EventSource::HasInstance() { |
| return GetInstance(); |
| } |
| |
| // static |
| X11EventSource* X11EventSource::GetInstance() { |
| return static_cast<X11EventSource*>(PlatformEventSource::GetInstance()); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // X11EventSource, public |
| |
| x11::Time X11EventSource::GetCurrentServerTime() { |
| DCHECK(connection_); |
| |
| if (!dummy_initialized_) { |
| // Create a new Window and Atom that will be used for the property change. |
| dummy_window_ = connection_->GenerateId<x11::Window>(); |
| connection_->CreateWindow(x11::CreateWindowRequest{ |
| .wid = dummy_window_, |
| .parent = connection_->default_root(), |
| .width = 1, |
| .height = 1, |
| .override_redirect = x11::Bool32(true), |
| }); |
| dummy_atom_ = x11::GetAtom("CHROMIUM_TIMESTAMP"); |
| dummy_window_events_ = std::make_unique<x11::XScopedEventSelector>( |
| dummy_window_, x11::EventMask::PropertyChange); |
| dummy_initialized_ = true; |
| } |
| |
| // Make a no-op property change on |dummy_window_|. |
| std::vector<uint8_t> data{0}; |
| connection_->ChangeProperty(x11::ChangePropertyRequest{ |
| .window = static_cast<x11::Window>(dummy_window_), |
| .property = dummy_atom_, |
| .type = x11::Atom::STRING, |
| .format = CHAR_BIT, |
| .data_len = 1, |
| .data = base::RefCountedBytes::TakeVector(&data), |
| }); |
| |
| // Observe the resulting PropertyNotify event to obtain the timestamp. |
| connection_->Sync(); |
| connection_->ReadResponses(); |
| |
| auto time = x11::Time::CurrentTime; |
| auto pred = [&](const x11::Event& event) { |
| auto* prop = event.As<x11::PropertyNotifyEvent>(); |
| if (prop && prop->window == dummy_window_) { |
| time = prop->time; |
| return true; |
| } |
| return false; |
| }; |
| |
| auto& events = connection_->events(); |
| auto it = std::find_if(events.begin(), events.end(), pred); |
| if (it != events.end()) |
| *it = x11::Event(); |
| return time; |
| } |
| |
| x11::Time X11EventSource::GetTimestamp() { |
| if (auto* dispatching_event = connection_->dispatching_event()) { |
| auto timestamp = ExtractTimeFromXEvent(*dispatching_event); |
| if (timestamp != x11::Time::CurrentTime) |
| return timestamp; |
| } |
| DVLOG(1) << "Making a round trip to get a recent server timestamp."; |
| return GetCurrentServerTime(); |
| } |
| |
| absl::optional<gfx::Point> |
| X11EventSource::GetRootCursorLocationFromCurrentEvent() const { |
| auto* event = connection_->dispatching_event(); |
| if (!event) |
| return absl::nullopt; |
| |
| auto* device = event->As<x11::Input::DeviceEvent>(); |
| auto* crossing = event->As<x11::Input::CrossingEvent>(); |
| auto* touch_factory = ui::TouchFactory::GetInstance(); |
| |
| bool is_valid_event = false; |
| if (event->As<x11::ButtonEvent>() || event->As<x11::MotionNotifyEvent>() || |
| event->As<x11::CrossingEvent>()) { |
| is_valid_event = true; |
| } else if (device && |
| (device->opcode == x11::Input::DeviceEvent::ButtonPress || |
| device->opcode == x11::Input::DeviceEvent::ButtonRelease || |
| device->opcode == x11::Input::DeviceEvent::Motion)) { |
| is_valid_event = touch_factory->ShouldProcessDeviceEvent(*device); |
| } else if (crossing && |
| (crossing->opcode == x11::Input::CrossingEvent::Enter || |
| crossing->opcode == x11::Input::CrossingEvent::Leave)) { |
| is_valid_event = touch_factory->ShouldProcessCrossingEvent(*crossing); |
| } |
| |
| if (is_valid_event) |
| return ui::EventSystemLocationFromXEvent(*event); |
| return absl::nullopt; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // X11EventSource, protected |
| |
| void X11EventSource::OnEvent(const x11::Event& x11_event) { |
| bool should_update_device_list = false; |
| |
| if (x11_event.As<x11::Input::HierarchyEvent>()) { |
| should_update_device_list = true; |
| } else if (auto* device = x11_event.As<x11::Input::DeviceChangedEvent>()) { |
| if (device->reason == x11::Input::ChangeReason::DeviceChange) { |
| should_update_device_list = true; |
| } else if (device->reason == x11::Input::ChangeReason::SlaveSwitch) { |
| ui::DeviceDataManagerX11::GetInstance()->InvalidateScrollClasses( |
| device->sourceid); |
| } |
| } |
| |
| if (should_update_device_list) { |
| UpdateDeviceList(); |
| hotplug_event_handler_->OnHotplugEvent(); |
| } |
| |
| auto* crossing = x11_event.As<x11::CrossingEvent>(); |
| if (crossing && crossing->opcode == x11::CrossingEvent::EnterNotify && |
| crossing->detail != x11::NotifyDetail::Inferior && |
| crossing->mode != x11::NotifyMode::Ungrab) { |
| // Clear stored scroll data |
| ui::DeviceDataManagerX11::GetInstance()->InvalidateScrollClasses( |
| DeviceDataManagerX11::kAllDevices); |
| } |
| |
| auto* mapping = x11_event.As<x11::MappingNotifyEvent>(); |
| if (mapping && mapping->request == x11::Mapping::Pointer) |
| DeviceDataManagerX11::GetInstance()->UpdateButtonMap(); |
| |
| auto translated_event = ui::BuildEventFromXEvent(x11_event); |
| // Ignore native platform-events only if they correspond to mouse events. |
| // Allow other types of events to still be handled |
| if (ui::PlatformEventSource::ShouldIgnoreNativePlatformEvents() && |
| translated_event && translated_event->IsMouseEvent()) { |
| return; |
| } |
| if (translated_event && translated_event->type() != ET_UNKNOWN) { |
| #if BUILDFLAG(IS_CHROMEOS_ASH) |
| if (translated_event->IsLocatedEvent()) { |
| ui::CursorController::GetInstance()->SetCursorLocation( |
| translated_event->AsLocatedEvent()->location_f()); |
| } |
| #endif |
| DispatchEvent(translated_event.get()); |
| } |
| } |
| |
| void X11EventSource::OnDispatcherListChanged() { |
| watcher_->StartWatching(); |
| |
| if (!hotplug_event_handler_) { |
| hotplug_event_handler_ = std::make_unique<X11HotplugEventHandler>(); |
| // Force the initial device query to have an update list of active devices. |
| hotplug_event_handler_->OnHotplugEvent(); |
| } |
| } |
| |
| // static |
| #if defined(USE_X11) |
| std::unique_ptr<PlatformEventSource> PlatformEventSource::CreateDefault() { |
| #if defined(USE_OZONE) |
| if (features::IsUsingOzonePlatform()) |
| return nullptr; |
| #endif |
| return std::make_unique<X11EventSource>(x11::Connection::Get()); |
| } |
| #endif |
| |
| } // namespace ui |