#include "remoting/host/linux/ei_sender_session.h"
#include <linux/input-event-codes.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/types/expected.h"
#include "remoting/base/logging.h"
#include "remoting/host/base/loggable.h"
#include "remoting/host/linux/ei_input_injector.h"
#include "remoting/host/linux/ei_keyboard_layout_monitor.h"
#include "remoting/host/linux/ei_keymap.h"
#include "remoting/proto/event.pb.h"
#include "third_party/libei/cipd/include/libei-1.0/libei.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace remoting {
namespace {
constexpr int kPixelsPerTick = 120;
std::uint32_t ScaleAndClamp(float fraction,
std::uint32_t minimum,
std::uint32_t size) {
std::uint32_t scaled = base::ClampRound<std::uint32_t>(fraction * size);
scaled = std::clamp(scaled, std::uint32_t{0}, size - 1);
return scaled + minimum;
}
std::pair<std::uint32_t, std::uint32_t> CalculateXY(ei_region* region,
float fractional_x,
float fractional_y) {
std::uint32_t x = ScaleAndClamp(fractional_x, ei_region_get_x(region),
ei_region_get_width(region));
std::uint32_t y = ScaleAndClamp(fractional_y, ei_region_get_y(region),
ei_region_get_height(region));
return std::pair(x, y);
}
}
EiSenderSession::~EiSenderSession() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LOG(INFO) << "Ei session shutting down";
ei_disconnect(ei_.get());
ProcessEvents(true);
}
base::WeakPtr<EiSenderSession> EiSenderSession::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void EiSenderSession::SetKeyboardLayoutMonitor(
base::WeakPtr<EiKeyboardLayoutMonitor> monitor) {
keyboard_layout_monitor_ = monitor;
keyboard_layout_monitor_->OnKeymapChanged(
keyboards_.empty() ? nullptr : std::get<1>(keyboards_.back()).get());
}
void EiSenderSession::SetInputInjector(
base::WeakPtr<EiInputInjector> input_injector) {
input_injector_ = input_injector;
input_injector_->SetKeymap(
keyboards_.empty() ? nullptr : std::get<1>(keyboards_.back())->GetWeakPtr());
}
void EiSenderSession::InjectKeyEvent(std::uint32_t usb_keycode, bool is_press) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (keyboards_.empty()) {
LOG(ERROR) << "Received key event but there's no virtual keyboard";
return;
}
auto& [keyboard, _] = keyboards_.back();
ei_device_keyboard_key(
keyboard.get(),
ui::KeycodeConverter::DomCodeToEvdevCode(
ui::KeycodeConverter::UsbKeycodeToDomCode(usb_keycode)),
is_press);
ei_device_frame(keyboard.get(), ei_now(ei_.get()));
}
void EiSenderSession::InjectAbsolutePointerMove(std::string_view region_id,
float fractional_x,
float fractional_y) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto [first_equal, first_greater] = absolute_pointers_.equal_range(region_id);
if (first_equal == first_greater) {
LOG(ERROR) << "No absolute pointer for the requested region";
return;
}
auto& [region, device] = (--first_greater)->second;
auto [x, y] = CalculateXY(region.get(), fractional_x, fractional_y);
ei_device_pointer_motion_absolute(device.get(), x, y);
ei_device_frame(device.get(), ei_now(ei_.get()));
}
void EiSenderSession::InjectRelativePointerMove(std::int32_t delta_x,
std::int32_t delta_y) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (relative_pointers_.empty()) {
LOG(ERROR) << "Received relative motion but there's no relative pointer";
return;
}
auto& pointer = relative_pointers_.back();
ei_device_pointer_motion(pointer.get(), delta_x, delta_y);
ei_device_frame(pointer.get(), ei_now(ei_.get()));
}
void EiSenderSession::InjectButton(protocol::MouseEvent::MouseButton button,
bool is_press) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
uint32_t button_code;
switch (button) {
case protocol::MouseEvent_MouseButton_BUTTON_UNDEFINED:
return;
case protocol::MouseEvent_MouseButton_BUTTON_LEFT:
button_code = BTN_LEFT;
break;
case protocol::MouseEvent_MouseButton_BUTTON_MIDDLE:
button_code = BTN_MIDDLE;
break;
case protocol::MouseEvent_MouseButton_BUTTON_RIGHT:
button_code = BTN_RIGHT;
break;
case protocol::MouseEvent_MouseButton_BUTTON_BACK:
button_code = BTN_BACK;
break;
case protocol::MouseEvent_MouseButton_BUTTON_FORWARD:
button_code = BTN_FORWARD;
break;
default:
LOG(WARNING) << "Unknown mouse button: " << button;
return;
}
if (button_devices_.empty()) {
LOG(ERROR) << "Received button event but there's no button device";
return;
}
auto& button_device = button_devices_.back();
ei_device_button_button(button_device.get(), button_code, is_press);
ei_device_frame(button_device.get(), ei_now(ei_.get()));
}
void EiSenderSession::InjectScrollDelta(double delta_x, double delta_y) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (scroll_devices_.empty()) {
LOG(ERROR) << "Received scroll event but there's no scroll device";
return;
}
if (delta_x != 0) {
if ((delta_x > 0) != (subtick_pixels_x_ > 0)) {
subtick_pixels_x_ = 0;
}
}
if (delta_y != 0) {
if ((delta_y > 0) != (subtick_pixels_y_ > 0)) {
subtick_pixels_y_ = 0;
}
}
subtick_pixels_x_ += delta_x;
subtick_pixels_y_ += delta_y;
int ticks_x = subtick_pixels_x_ / kPixelsPerTick;
int ticks_y = subtick_pixels_y_ / kPixelsPerTick;
subtick_pixels_x_ %= kPixelsPerTick;
subtick_pixels_y_ %= kPixelsPerTick;
if (ticks_x == 0 && ticks_y == 0) {
return;
}
auto& scroll_device = button_devices_.back();
ei_device_scroll_discrete(scroll_device.get(), -ticks_x * 120,
-ticks_y * 120);
ei_device_frame(scroll_device.get(), ei_now(ei_.get()));
}
void EiSenderSession::InjectScrollDiscrete(float ticks_x, float ticks_y) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (scroll_devices_.empty()) {
LOG(ERROR) << "Received scroll event but there's no scroll device";
return;
}
subtick_pixels_x_ = 0;
subtick_pixels_y_ = 0;
auto& scroll_device = button_devices_.back();
ei_device_scroll_discrete(scroll_device.get(), -ticks_x * 120,
-ticks_y * 120);
ei_device_frame(scroll_device.get(), ei_now(ei_.get()));
}
void EiSenderSession::CreateWithFd(base::ScopedFD fd, CreateCallback callback) {
auto sender_session = base::WrapUnique(new EiSenderSession());
auto* raw = sender_session.get();
raw->InitWithFd(
std::move(fd),
base::BindOnce(
[](std::unique_ptr<EiSenderSession> sender_session,
CreateCallback callback, base::expected<void, Loggable> result) {
std::move(callback).Run(
result.transform([&]() { return std::move(sender_session); })
.transform_error([](Loggable&& error) {
error.AddContext(FROM_HERE,
"While creating new EI session");
return std::move(error);
}));
},
std::move(sender_session), std::move(callback)));
}
EiSenderSession::EiSenderSession() = default;
void EiSenderSession::InitWithFd(base::ScopedFD fd, InitCallback callback) {
init_callback_ = std::move(callback);
ei_ = EiPtr::Take(ei_new_sender(nullptr));
int result = ei_setup_backend_fd(ei_.get(), fd.release());
if (result != 0) {
std::move(init_callback_)
.Run(base::unexpected(Loggable(
FROM_HERE,
base::StrCat({"Failed to set up libei: ",
logging::SystemErrorCodeToString(-result)}))));
return;
}
fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(
ei_get_fd(ei_.get()), base::BindRepeating(&EiSenderSession::OnFdReadable,
base::Unretained(this)));
}
void EiSenderSession::OnFdReadable() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ei_dispatch(ei_.get());
ProcessEvents(false);
}
void EiSenderSession::OnConnected() {
HOST_LOG << "Connected to EIS";
}
void EiSenderSession::OnDisconnected(bool shutting_down) {
if (shutting_down) {
return;
}
if (init_callback_) {
std::move(init_callback_)
.Run(base::unexpected(
Loggable(FROM_HERE, "EIS denied connection request")));
return;
}
LOG(ERROR) << "Unexpectedly disconnected from EIS";
}
void EiSenderSession::OnSeatAdded(EiSeatPtr seat) {
if (default_seat_.get()) {
HOST_LOG << "Ignoring additional seat";
return;
}
if (!ei_seat_has_capability(seat.get(), EI_DEVICE_CAP_KEYBOARD)) {
LOG(WARNING) << "EIS does not offer keyboard input";
}
if (!ei_seat_has_capability(seat.get(), EI_DEVICE_CAP_POINTER_ABSOLUTE)) {
LOG(WARNING) << "EIS does not offer an absolute pointer device";
}
supports_touch_ = ei_seat_has_capability(seat.get(), EI_DEVICE_CAP_TOUCH);
supports_relative_pointer_ =
ei_seat_has_capability(seat.get(), EI_DEVICE_CAP_POINTER);
ei_seat_bind_capabilities(seat.get(), EI_DEVICE_CAP_POINTER,
EI_DEVICE_CAP_KEYBOARD,
EI_DEVICE_CAP_POINTER_ABSOLUTE, EI_DEVICE_CAP_TOUCH,
EI_DEVICE_CAP_BUTTON, EI_DEVICE_CAP_SCROLL, NULL);
if (init_callback_) {
std::move(init_callback_).Run(base::ok());
} else {
HOST_LOG << "EIS seat readded";
}
}
void EiSenderSession::OnSeatRemoved(EiSeatPtr seat) {
if (seat == default_seat_) {
default_seat_.reset();
LOG(WARNING) << "EIS seat removed";
}
}
void EiSenderSession::OnDeviceAdded(EiDevicePtr device) {
AllocDeviceState(device);
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_KEYBOARD)) {
keyboards_.push_back(
std::make_tuple(device, std::make_unique<EiKeymap>(device)));
std::get<1>(keyboards_.back())
->Load(base::BindOnce(&EiSenderSession::OnKeymapLoaded, GetWeakPtr(),
device));
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER)) {
relative_pointers_.push_back({device});
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_BUTTON)) {
button_devices_.push_back({device});
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_SCROLL)) {
scroll_devices_.push_back({device});
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER_ABSOLUTE)) {
AddDeviceRegions(absolute_pointers_, {device});
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_TOUCH)) {
AddDeviceRegions(touch_devices_, {device});
}
}
void EiSenderSession::OnDeviceRemoved(EiDevicePtr device) {
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_KEYBOARD)) {
bool is_current =
(!keyboards_.empty() && std::get<0>(keyboards_.back()) == device);
std::erase_if(keyboards_, [&device](auto& item) {
return std::get<0>(item) == device;
});
if (is_current && keyboard_layout_monitor_) {
keyboard_layout_monitor_->OnKeymapChanged(
keyboards_.empty() ? nullptr : std::get<1>(keyboards_.back()).get());
}
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER)) {
std::erase_if(relative_pointers_,
[&device](auto& item) { return item == device; });
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_BUTTON)) {
std::erase_if(button_devices_,
[&device](auto& item) { return item == device; });
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_SCROLL)) {
std::erase_if(scroll_devices_,
[&device](auto& item) { return item == device; });
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_POINTER_ABSOLUTE)) {
std::erase_if(absolute_pointers_, [&device](auto& item) {
return item.second.second == device;
});
}
if (ei_device_has_capability(device.get(), EI_DEVICE_CAP_TOUCH)) {
std::erase_if(touch_devices_, [&device](auto& item) {
return item.second.second == device;
});
}
FreeDeviceState(device);
}
void EiSenderSession::OnDevicePaused(EiDevicePtr device) {
GetDeviceState(device).resumed = false;
}
void EiSenderSession::OnDeviceResumed(EiDevicePtr device) {
GetDeviceState(device).resumed = true;
ei_device_start_emulating(device.get(), ++start_emulating_sequence_);
}
void EiSenderSession::OnKeymapLoaded(EiDevicePtr keyboard) {
if (!keyboards_.empty()) {
auto& [most_recent_keyboard, keymap] = keyboards_.back();
if (keyboard == most_recent_keyboard) {
if (keyboard_layout_monitor_) {
keyboard_layout_monitor_->OnKeymapChanged(keymap.get());
}
if (input_injector_) {
input_injector_->SetKeymap(keymap->GetWeakPtr());
}
}
}
}
void EiSenderSession::ProcessEvents(bool shutting_down) {
while (auto event = EiEventPtr(ei_get_event(ei_.get()))) {
switch (ei_event_get_type(event.get())) {
case EI_EVENT_CONNECT:
OnConnected();
break;
case EI_EVENT_DISCONNECT:
OnDisconnected(shutting_down);
return;
case EI_EVENT_SEAT_ADDED:
OnSeatAdded(EiSeatPtr::Ref(ei_event_get_seat(event.get())));
break;
case EI_EVENT_SEAT_REMOVED:
OnSeatRemoved(EiSeatPtr::Ref(ei_event_get_seat(event.get())));
break;
case EI_EVENT_DEVICE_ADDED:
OnDeviceAdded(EiDevicePtr::Ref(ei_event_get_device(event.get())));
break;
case EI_EVENT_DEVICE_REMOVED:
OnDeviceRemoved(EiDevicePtr::Ref(ei_event_get_device(event.get())));
break;
case EI_EVENT_DEVICE_PAUSED:
OnDevicePaused(EiDevicePtr::Ref(ei_event_get_device(event.get())));
break;
case EI_EVENT_DEVICE_RESUMED:
OnDeviceResumed(EiDevicePtr::Ref(ei_event_get_device(event.get())));
break;
case EI_EVENT_KEYBOARD_MODIFIERS:
break;
case EI_EVENT_SYNC:
break;
default:
std::string message = base::StringPrintf(
"Unexpected libei event type: %d", ei_event_get_type(event.get()));
if (init_callback_) {
std::move(init_callback_)
.Run(base::unexpected(Loggable(FROM_HERE, std::move(message))));
return;
}
LOG(ERROR) << message;
break;
}
}
}
void EiSenderSession::AddDeviceRegions(
std::multimap<std::string,
std::pair<EiRegionPtr, EiDevicePtr>,
std::less<>>& map,
EiDevicePtr device) {
for (size_t i = 0; ei_region* region = ei_device_get_region(device.get(), i);
++i) {
const char* mapping_id = ei_region_get_mapping_id(region);
std::string_view mapping_id_view =
mapping_id ? mapping_id : std::string_view{};
if (mapping_id_view.empty()) {
HOST_LOG << "Region found without mapping id";
}
map.emplace(std::piecewise_construct, std::tuple(mapping_id_view),
std::forward_as_tuple(EiRegionPtr::Ref(region), device));
}
}
void EiSenderSession::AllocDeviceState(const EiDevicePtr& device) {
ei_device_set_user_data(device.get(), new DeviceState());
}
EiSenderSession::DeviceState& EiSenderSession::GetDeviceState(
const EiDevicePtr& device) {
return *static_cast<DeviceState*>(ei_device_get_user_data(device.get()));
}
void EiSenderSession::FreeDeviceState(const EiDevicePtr& device) {
delete &GetDeviceState(device);
}
}