blob: 2ef187db39fe1e239d0e9bcfc72c4cfd0fe1df21 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_HOST_LINUX_EI_SENDER_SESSION_H_
#define REMOTING_HOST_LINUX_EI_SENDER_SESSION_H_
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/files/scoped_file.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/types/expected.h"
#include "remoting/host/base/loggable.h"
#include "remoting/host/base/pointer_utils.h"
#include "remoting/proto/event.pb.h"
#include "third_party/libei/cipd/include/libei-1.0/libei.h"
namespace remoting {
class EiKeymap;
class GnomeKeyboardLayoutMonitor;
class GnomeInputInjector;
// Manages a sender-client connection to an EIS implementation to allow
// injecting input events.
class EiSenderSession {
public:
using CreateCallback = base::OnceCallback<void(
base::expected<std::unique_ptr<EiSenderSession>, Loggable>)>;
~EiSenderSession();
base::WeakPtr<EiSenderSession> GetWeakPtr();
void SetKeyboardLayoutMonitor(
base::WeakPtr<GnomeKeyboardLayoutMonitor> monitor);
void SetInputInjector(base::WeakPtr<GnomeInputInjector> input_injector);
// Injects an event for the provided |usb_keycode|. |is_press| should be true
// for key-down and repeat events, and false for release events.
void InjectKeyEvent(std::uint32_t usb_keycode, bool is_press);
// Injects an absolute pointer move to the specified location. |region_id|
// identifies the logical monitor containing the move target. |fractional_x|
// and |fractional_y| are in the range [0.0, 1.0] and represent the position
// of the move target within the monitor, with 0, 0 representing the top left
// corner and 1, 1 representing the bottom right.
void InjectAbsolutePointerMove(std::string_view region_id,
float fractional_x,
float fractional_y);
// Injects a relative move, specified in logical pixels.
void InjectRelativePointerMove(std::int32_t delta_x, std::int32_t delta_y);
// Injects an event for the provided |button|. |is_press| should be true for
// button-down events and false for button-up.
void InjectButton(protocol::MouseEvent::MouseButton button, bool is_press);
// Injects a "smooth" (pixel-exact) scroll event, where |delta_x| and
// |delta_y| represent logical pixels. The direction is the same used by the
// MouseEvent proto. That is, positive values scroll up and to the left.
void InjectScrollDelta(double delta_x, double delta_y);
// Injects a "discrete" (wheel-tick) scroll event, where |ticks_x| and
// |ticks_y| represent wheel ticks (or fractions thereof). The direction is
// the same used by the MouseEvent proto. That is, positive values scroll up
// and to the left.
void InjectScrollDiscrete(float ticks_x, float ticks_y);
// Asynchronously attempts to establish a session with an EIS implementation
// over |fd| and invokes |callback| with the result. Takes ownership of |fd|,
// closing it if the session cannot be established.
static void CreateWithFd(base::ScopedFD fd, CreateCallback callback);
private:
using InitCallback = base::OnceCallback<void(base::expected<void, Loggable>)>;
using EiPtr = CRefCounted<ei, ei_ref, ei_unref>;
using EiSeatPtr = CRefCounted<ei_seat, ei_seat_ref, ei_seat_unref>;
using EiDevicePtr = CRefCounted<ei_device, ei_device_ref, ei_device_unref>;
using EiRegionPtr = CRefCounted<ei_region, ei_region_ref, ei_region_unref>;
using EiTouchPtr = CRefCounted<ei_touch, ei_touch_ref, ei_touch_unref>;
using EiKeymapPtr = std::unique_ptr<EiKeymap>;
// Events do not allow additional refs, but one still needs to call unref to
// release them.
using EiEventPtr = std::unique_ptr<ei_event, DeleteFunc<ei_event_unref>>;
// Attached to each device as user data to track additional state.
struct DeviceState {
// New devices are paused until a resume event for them is received.
bool resumed = false;
// Whether we have told the EI server to expect events from this device.
bool emulating = false;
// TODO(rkjnsn): Include xkb_state for tracking layout state (e.g.,
// current group, lock state) for keyboards.
};
// Construct an uninitialized instance.
EiSenderSession();
// Attempt to initialize this instance, invoking |callback| with the result.
void InitWithFd(base::ScopedFD fd, InitCallback callback);
// Invoked whenever the libei-provided event fd becomes readable, signaling
// that there is work for the library to perform.
void OnFdReadable();
// Invoked in response to the various libei events.
void OnConnected();
void OnDisconnected(bool shutting_down);
void OnSeatAdded(EiSeatPtr seat);
void OnSeatRemoved(EiSeatPtr seat);
void OnDeviceAdded(EiDevicePtr device);
void OnDeviceRemoved(EiDevicePtr device);
void OnDevicePaused(EiDevicePtr device);
void OnDeviceResumed(EiDevicePtr device);
// Invoked when a keymap finishes loading.
void OnKeymapLoaded(EiDevicePtr device);
// Processes all events currently available from libei.
void ProcessEvents(bool shutting_down);
// Stores the provided |device| in |map| keyed by each region ID associated
// with the device. (The devices may be inserted more than once if it has
// multiple regions.)
void AddDeviceRegions(std::multimap<std::string,
std::pair<EiRegionPtr, EiDevicePtr>,
std::less<>>& map,
EiDevicePtr device);
// Called when a new device is added. Allocates an associated DeviceState
// object and attaches it to the device.
void AllocDeviceState(const EiDevicePtr& device);
// Gets the DeviceState associated with the passed device.
DeviceState& GetDeviceState(const EiDevicePtr& device);
// Called when a device is removed. Frees the associated DeviceState.
void FreeDeviceState(const EiDevicePtr& device);
InitCallback init_callback_;
EiPtr ei_;
// We currently assume that the first-received seat will be the default, and
// the compositor won't do things like add a new seat and then remove the
// original. If this turns out to be an invalid assumption, a vector of
// currently valid seats could be maintained like for keyboards and relative
// pointers.
EiSeatPtr default_seat_;
// Devices may be added and removed dynamically by the compositor at any time.
// Indeed, because a device with keyboard capability has a fixed keymap, the
// compositor must create a new device and remove the old one when the keymap
// changes. That might result in the pointer getting removed and readded as
// well if the compositor opts to provide both capabilities on the same
// device.
std::vector<std::tuple<EiDevicePtr, EiKeymapPtr>> keyboards_;
std::vector<EiDevicePtr> relative_pointers_;
std::vector<EiDevicePtr> button_devices_;
std::vector<EiDevicePtr> scroll_devices_;
// Touch and absolute pointer devices may have one or more region, each
// region corresponding to a stream being captured. The compositor may choose
// to provide one device with multiple regions, a separate device per region,
// or something in between. The regions are mapped to streams by means of a
// mapping ID string.
//
// The following multimaps allow efficiently looking up the appropriate region
// and device for each injected input event. (The region is needed to convert
// fractional coordinates to logical coordinates.).
//
// TODO(rkjnsn): Switch to std::flat_multimap when C++23 is available. That
// should be more efficient since the number of devices are expected to be
// relatively few and change infrequently.
std::multimap<std::string, std::pair<EiRegionPtr, EiDevicePtr>, std::less<>>
absolute_pointers_;
std::multimap<std::string, std::pair<EiRegionPtr, EiDevicePtr>, std::less<>>
touch_devices_;
// libei requires a new sequence number each time ei_device_start_emulating()
// is called. This tracks the most recently used sequence number.
std::uint32_t start_emulating_sequence_ = 0;
bool supports_touch_ = false;
bool supports_relative_pointer_ = false;
std::unique_ptr<base::FileDescriptorWatcher::Controller> fd_watcher_;
base::WeakPtr<GnomeKeyboardLayoutMonitor> keyboard_layout_monitor_;
base::WeakPtr<GnomeInputInjector> input_injector_;
int subtick_pixels_x_ = 0;
int subtick_pixels_y_ = 0;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<EiSenderSession> weak_ptr_factory_{this};
};
} // namespace remoting
#endif // REMOTING_HOST_LINUX_EI_SENDER_SESSION_H_