mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-19 13:00:50 +02:00
263 lines
9.3 KiB
C++
263 lines
9.3 KiB
C++
#ifndef MOCKTAIL_RUNTIME_ROBLOX_INPUT_ROUTER_H_
|
|
#define MOCKTAIL_RUNTIME_ROBLOX_INPUT_ROUTER_H_
|
|
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <mutex>
|
|
#include <vector>
|
|
|
|
#include "mocktail/platform/platform_runtime.h"
|
|
#include "mocktail/status.h"
|
|
#include "runtime/roblox_text_editor.h"
|
|
|
|
namespace mocktail {
|
|
namespace runtime {
|
|
|
|
struct RobloxInputViewport {
|
|
int32_t logical_width = 0;
|
|
int32_t logical_height = 0;
|
|
int32_t pixel_width = 0;
|
|
int32_t pixel_height = 0;
|
|
float density_scale = 1.0F;
|
|
|
|
bool valid() const {
|
|
return logical_width > 0 && logical_height > 0 && pixel_width > 0 &&
|
|
pixel_height > 0 && std::isfinite(density_scale) &&
|
|
density_scale > 0.0F;
|
|
}
|
|
};
|
|
|
|
struct AndroidKeyMapping {
|
|
int32_t scan_code = 0;
|
|
int32_t key_code = 0;
|
|
|
|
bool valid() const { return scan_code > 0 && key_code > 0; }
|
|
};
|
|
|
|
// Maps SDL's USB scancode to the Linux evdev scan code and Android KeyEvent
|
|
// keycode observed by the APK. Unmapped keys are rejected rather than passed
|
|
// through with an ABI-incompatible numeric value.
|
|
AndroidKeyMapping MapSdlKeyToAndroid(uint32_t sdl_scancode, uint32_t sdl_key = 0);
|
|
|
|
// Translates SDL button numbers to the nativePassMouseButton argument.
|
|
// The first three are Roblox's zero-based MouseButton1/2/3 indices, not Android
|
|
// MotionEvent button bits.
|
|
int32_t MapSdlMouseButtonToRoblox(uint8_t sdl_button);
|
|
|
|
int32_t MapSdlGamepadButtonToAndroid(uint8_t sdl_button);
|
|
int32_t RobloxGamepadType(platform::GamepadFamily family);
|
|
|
|
struct RobloxGamepadSink {
|
|
using SupportedKeyFn = Status (*)(void*, int32_t, int32_t, bool, int32_t);
|
|
using SupportedMotionFn = Status (*)(void*, int32_t, int32_t, int32_t, bool,
|
|
int32_t);
|
|
using ConnectFn = Status (*)(void*, int32_t, int32_t);
|
|
using DisconnectFn = Status (*)(void*, int32_t);
|
|
using ButtonFn = Status (*)(void*, int32_t, int32_t, bool);
|
|
using AxisFn = Status (*)(void*, int32_t, int32_t, float, float, float);
|
|
|
|
void* context = nullptr;
|
|
SupportedKeyFn supported_key = nullptr;
|
|
SupportedMotionFn supported_motion = nullptr;
|
|
ConnectFn connect = nullptr;
|
|
DisconnectFn disconnect = nullptr;
|
|
ButtonFn button = nullptr;
|
|
AxisFn axis = nullptr;
|
|
|
|
bool valid() const {
|
|
return supported_key != nullptr && supported_motion != nullptr &&
|
|
connect != nullptr && disconnect != nullptr && button != nullptr &&
|
|
axis != nullptr;
|
|
}
|
|
};
|
|
|
|
struct RobloxInputSink {
|
|
using MouseMoveFn = Status (*)(void* context, float x, float y, float delta_x,
|
|
float delta_y);
|
|
using MouseButtonFn = Status (*)(void* context, float x, float y,
|
|
bool pressed, int32_t button);
|
|
using MouseWheelFn = Status (*)(void* context, float x, float y,
|
|
float delta_y);
|
|
using TouchFn = Status (*)(void* context, int32_t pointer_id, float x,
|
|
float y, int32_t action, int32_t width,
|
|
int32_t height);
|
|
using KeyFn = Status (*)(void* context, bool pressed, int32_t scan_code,
|
|
int32_t key_code, bool repeat);
|
|
|
|
void* context = nullptr;
|
|
MouseMoveFn mouse_move = nullptr;
|
|
MouseButtonFn mouse_button = nullptr;
|
|
MouseWheelFn mouse_wheel = nullptr;
|
|
TouchFn touch = nullptr;
|
|
KeyFn key = nullptr;
|
|
RobloxTextSink text;
|
|
RobloxGamepadSink gamepad{};
|
|
};
|
|
|
|
enum class RobloxInputEventKind {
|
|
kNone = 0,
|
|
kMouseMotion,
|
|
kMouseButton,
|
|
kMouseWheel,
|
|
kTouch,
|
|
kKeyboard,
|
|
kText,
|
|
kFocus,
|
|
kViewport,
|
|
kGamepadConnection,
|
|
kGamepadButton,
|
|
kGamepadAxis,
|
|
};
|
|
|
|
enum class RobloxInputDispatchState {
|
|
kDispatched = 0,
|
|
kStateUpdated,
|
|
kIgnoredInactive,
|
|
kIgnoredUnfocused,
|
|
kIgnoredUnsupported,
|
|
kRejectedInvalid,
|
|
kNativeError,
|
|
};
|
|
|
|
struct RobloxInputDispatchResult {
|
|
RobloxInputDispatchState state = RobloxInputDispatchState::kStateUpdated;
|
|
RobloxInputEventKind kind = RobloxInputEventKind::kNone;
|
|
Status status = Status::Ok();
|
|
|
|
bool dispatched() const {
|
|
return state == RobloxInputDispatchState::kDispatched;
|
|
}
|
|
};
|
|
|
|
struct RobloxInputSnapshot {
|
|
bool active = false;
|
|
bool focused = false;
|
|
RobloxInputViewport viewport;
|
|
uint64_t mouse_events = 0;
|
|
uint64_t touch_events = 0;
|
|
uint64_t keyboard_events = 0;
|
|
uint64_t ignored_text_events = 0;
|
|
uint64_t text_events = 0;
|
|
// Zero after the host editor finishes, even if Roblox sends no hide callback.
|
|
uint64_t text_focus_generation = 0;
|
|
uint64_t native_errors = 0;
|
|
uint32_t active_mouse_buttons = 0;
|
|
uint32_t active_touches = 0;
|
|
uint32_t active_keys = 0;
|
|
uint64_t gamepad_events = 0;
|
|
uint32_t detected_gamepads = 0;
|
|
uint32_t connected_gamepads = 0;
|
|
};
|
|
|
|
// Stateful translation boundary from SDL platform events to the exact
|
|
// Android/Roblox native input ABI. It owns no JNI or SDL objects.
|
|
class RobloxInputRouter final {
|
|
public:
|
|
explicit RobloxInputRouter(RobloxInputSink sink,
|
|
RobloxTextDisplaySink display_sink = {},
|
|
platform::TextClipboard* clipboard = nullptr);
|
|
|
|
RobloxInputRouter(const RobloxInputRouter&) = delete;
|
|
RobloxInputRouter& operator=(const RobloxInputRouter&) = delete;
|
|
|
|
Status Activate(RobloxInputViewport viewport, bool initially_focused);
|
|
Status Deactivate();
|
|
Status BeginTextFocusSession(RobloxTextFocusSession session);
|
|
Status EndTextFocusSession(int64_t textbox_handle, uint64_t generation,
|
|
bool notify_native);
|
|
Status ReplaceFocusedTextFromEngine(uint64_t generation,
|
|
std::string authoritative_utf8);
|
|
Status UpdateTextFocusProperties(uint64_t generation,
|
|
const RobloxTextFocusProperties& properties);
|
|
RobloxInputDispatchResult HandleEvent(const platform::PlatformEvent& event);
|
|
RobloxInputSnapshot Snapshot() const;
|
|
// Raw mouse sends look deltas in host units instead of guest surface units.
|
|
// The reported pointer position stays in guest units either way.
|
|
void SetRawMouseEnabled(bool enabled);
|
|
|
|
private:
|
|
struct ActiveTouch {
|
|
int64_t touch_id = 0;
|
|
int64_t finger_id = 0;
|
|
int32_t pointer_id = -1;
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
};
|
|
|
|
struct ActiveKey {
|
|
uint32_t sdl_scancode = 0;
|
|
AndroidKeyMapping android;
|
|
};
|
|
|
|
struct ActiveGamepad {
|
|
int64_t instance_id = 0;
|
|
int32_t device_id = 0;
|
|
platform::GamepadDescriptor descriptor;
|
|
bool connected = false;
|
|
uint32_t pressed_buttons = 0;
|
|
std::array<int16_t, 6> raw_axes{};
|
|
std::array<float, 6> sent_axes{};
|
|
};
|
|
|
|
RobloxInputDispatchResult HandleMouseMotionLocked(
|
|
const platform::MouseMotionEvent& event);
|
|
RobloxInputDispatchResult HandleMouseButtonLocked(
|
|
const platform::MouseButtonEvent& event);
|
|
RobloxInputDispatchResult HandleMouseWheelLocked(
|
|
const platform::MouseWheelEvent& event);
|
|
RobloxInputDispatchResult HandleTouchLocked(
|
|
const platform::TouchEvent& event);
|
|
RobloxInputDispatchResult HandleKeyLocked(const platform::KeyEvent& event);
|
|
RobloxInputDispatchResult HandleGamepadConnectionLocked(
|
|
const platform::GamepadConnectionEvent& event);
|
|
RobloxInputDispatchResult HandleGamepadButtonLocked(
|
|
const platform::GamepadButtonEvent& event);
|
|
RobloxInputDispatchResult HandleGamepadAxisLocked(
|
|
const platform::GamepadAxisEvent& event);
|
|
std::vector<ActiveGamepad>::iterator FindGamepadLocked(int64_t instance_id);
|
|
Status ConfigureGamepadLocked(const ActiveGamepad& gamepad, bool available);
|
|
Status ConnectGamepadLocked(ActiveGamepad& gamepad);
|
|
Status DispatchGamepadAxesLocked(ActiveGamepad& gamepad, uint8_t axis,
|
|
bool* dispatched);
|
|
Status ReleaseGamepadInputsLocked(ActiveGamepad& gamepad);
|
|
Status DisconnectGamepadLocked(ActiveGamepad& gamepad);
|
|
Status DisconnectGamepadsLocked();
|
|
Status ReleasePressedInputsLocked();
|
|
// Keeps mouse_x_/mouse_y_ where they are for an event that carries no
|
|
// usable position, clamped to the current viewport. Before any absolute
|
|
// position has been seen, the held position is the viewport center.
|
|
void HoldPointerLocked(const platform::SurfaceCoordinateTransform& transform);
|
|
int32_t AllocatePointerIdLocked() const;
|
|
std::vector<ActiveTouch>::iterator FindTouchIteratorLocked(int64_t touch_id,
|
|
int64_t finger_id);
|
|
std::vector<ActiveKey>::iterator FindKeyIteratorLocked(uint32_t sdl_scancode);
|
|
RobloxInputDispatchResult NativeResultLocked(Status status,
|
|
RobloxInputEventKind kind);
|
|
|
|
const RobloxInputSink sink_;
|
|
RobloxTextEditor text_editor_;
|
|
mutable std::mutex mutex_;
|
|
RobloxInputSnapshot snapshot_;
|
|
std::vector<int32_t> active_mouse_buttons_;
|
|
std::vector<ActiveTouch> active_touches_;
|
|
std::vector<ActiveKey> active_keys_;
|
|
std::vector<ActiveGamepad> gamepads_;
|
|
// Last known pointer position in guest units. Relative-mode motion, and
|
|
// wheel or button events with zero coordinates, do not move it: the pointer
|
|
// stays where it was captured while the deltas pass through.
|
|
float mouse_x_ = 0.0f;
|
|
float mouse_y_ = 0.0f;
|
|
// True once an absolute position has been received since Activate.
|
|
bool pointer_anchored_ = false;
|
|
bool raw_mouse_ = false;
|
|
};
|
|
|
|
const char* RobloxInputDispatchStateName(RobloxInputDispatchState state);
|
|
const char* RobloxInputEventKindName(RobloxInputEventKind kind);
|
|
|
|
} // namespace runtime
|
|
} // namespace mocktail
|
|
|
|
#endif // MOCKTAIL_RUNTIME_ROBLOX_INPUT_ROUTER_H_
|