| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/base/x/x11_user_input_monitor.h" |
| |
| #include "base/logging.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "ui/events/devices/x11/xinput_util.h" |
| #include "ui/events/keycodes/keyboard_code_conversion_x.h" |
| #include "ui/gfx/x/future.h" |
| |
| namespace ui { |
| |
| XUserInputMonitor::XUserInputMonitor( |
| const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) |
| : io_task_runner_(io_task_runner) {} |
| |
| XUserInputMonitor::~XUserInputMonitor() { |
| DCHECK(!connection_); |
| } |
| |
| void XUserInputMonitor::WillDestroyCurrentMessageLoop() { |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| StopMonitor(); |
| } |
| |
| void XUserInputMonitor::OnEvent(const x11::Event& event) { |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| DCHECK(write_key_press_callback_); |
| |
| auto* raw = event.As<x11::Input::RawDeviceEvent>(); |
| if (!raw || (raw->opcode != x11::Input::RawDeviceEvent::RawKeyPress && |
| raw->opcode != x11::Input::RawDeviceEvent::RawKeyRelease)) { |
| return; |
| } |
| |
| EventType type = raw->opcode == x11::Input::RawDeviceEvent::RawKeyPress |
| ? EventType::kKeyPressed |
| : EventType::kKeyReleased; |
| |
| auto key_sym = |
| connection_->KeycodeToKeysym(static_cast<x11::KeyCode>(raw->detail), 0); |
| KeyboardCode key_code = KeyboardCodeFromXKeysym(key_sym); |
| counter_.OnKeyboardEvent(type, key_code); |
| |
| // Update count value in shared memory. |
| if (key_press_count_mapping_) { |
| write_key_press_callback_.Run(*key_press_count_mapping_, |
| GetKeyPressCount()); |
| } |
| } |
| |
| uint32_t XUserInputMonitor::GetKeyPressCount() const { |
| return counter_.GetKeyPressCount(); |
| } |
| |
| void XUserInputMonitor::StartMonitor(WriteKeyPressCallback& callback) { |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| write_key_press_callback_ = callback; |
| |
| if (!connection_) { |
| // TODO(jamiewalch): We should pass the connection in. |
| if (auto* connection = x11::Connection::Get()) { |
| connection_ = connection->Clone(); |
| } else { |
| LOG(ERROR) << "Couldn't open X connection"; |
| StopMonitor(); |
| return; |
| } |
| } |
| |
| connection_->AddEventObserver(this); |
| if (!connection_->xinput().present()) { |
| LOG(ERROR) << "X Input extension not available."; |
| StopMonitor(); |
| return; |
| } |
| |
| x11::Input::XIEventMask mask{}; |
| SetXinputMask(&mask, x11::Input::RawDeviceEvent::RawKeyPress); |
| SetXinputMask(&mask, x11::Input::RawDeviceEvent::RawKeyRelease); |
| connection_->xinput().XISelectEvents( |
| {connection_->default_root(), |
| {{x11::Input::DeviceId::AllMaster, {mask}}}}); |
| connection_->Flush(); |
| |
| // Register OnConnectionData() to be called every time there is something to |
| // read from |connection_|. |
| watch_controller_ = base::FileDescriptorWatcher::WatchReadable( |
| connection_->GetFd(), |
| base::BindRepeating(&XUserInputMonitor::OnConnectionData, |
| base::Unretained(this))); |
| |
| // Start observing message loop destruction if we start monitoring the first |
| // event. |
| base::CurrentThread::Get()->AddDestructionObserver(this); |
| |
| // Fetch pending events if any. |
| OnConnectionData(); |
| } |
| |
| void XUserInputMonitor::StartMonitorWithMapping( |
| WriteKeyPressCallback& callback, |
| base::WritableSharedMemoryMapping mapping) { |
| StartMonitor(callback); |
| key_press_count_mapping_ = |
| std::make_unique<base::WritableSharedMemoryMapping>(std::move(mapping)); |
| } |
| |
| void XUserInputMonitor::StopMonitor() { |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| |
| watch_controller_.reset(); |
| connection_.reset(); |
| key_press_count_mapping_.reset(); |
| |
| // Stop observing message loop destruction if no event is being monitored. |
| base::CurrentThread::Get()->RemoveDestructionObserver(this); |
| } |
| |
| void XUserInputMonitor::OnConnectionData() { |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| connection_->DispatchAll(); |
| } |
| |
| } // namespace ui |