Implement keyboard/mouse control

To control the device from the computer:
 - retrieve mouse and keyboard SDL events;
 - convert them to Android events;
 - serialize them;
 - send them on the same socket used by the video stream (but in the
 opposite direction);
 - deserialize the events on the Android side;
 - inject them using the InputManager.
hidpi
Romain Vimont 7 years ago
parent 6605ab8e23
commit cabb102a04

@ -1,4 +1,4 @@
.PHONY: default release clean build-app build-server dist
.PHONY: default release clean build-app build-server dist dist-zip sums test
BUILD_DIR := build
DIST := dist
@ -36,3 +36,7 @@ dist-zip: dist
sums:
cd "$(DIST)"; \
sha256sum *.zip > SHA256SUM.txt
test:
+$(MAKE) -C server test
ninja -C "$(BUILD_DIR)" test

@ -3,6 +3,9 @@ project('scrcpy-app', 'c')
src = [
'src/scrcpy.c',
'src/command.c',
'src/control.c',
'src/controlevent.c',
'src/convert.c',
'src/decoder.c',
'src/frames.c',
'src/lockutil.c',
@ -26,3 +29,17 @@ dependencies = [
]
executable('scrcpy', src, dependencies: dependencies)
### TESTS
tests = [
['test_control_event_queue', ['tests/test_control_event_queue.c', 'src/controlevent.c']],
]
src_dir = include_directories('src')
foreach t : tests
exe = executable(t[0], t[1], include_directories: src_dir, dependencies: dependencies)
test(t[0], exe)
endforeach

@ -0,0 +1,829 @@
// copied from <https://android.googlesource.com/platform/frameworks/native/+/master/include/android/input.h>
// blob 08299899b6305a0fe74d7d2b8471b7cd0af49dc7
// (and modified)
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _ANDROID_INPUT_H
#define _ANDROID_INPUT_H
/**
* Meta key / modifer state.
*/
enum android_metastate {
/** No meta keys are pressed. */
AMETA_NONE = 0,
/** This mask is used to check whether one of the ALT meta keys is pressed. */
AMETA_ALT_ON = 0x02,
/** This mask is used to check whether the left ALT meta key is pressed. */
AMETA_ALT_LEFT_ON = 0x10,
/** This mask is used to check whether the right ALT meta key is pressed. */
AMETA_ALT_RIGHT_ON = 0x20,
/** This mask is used to check whether one of the SHIFT meta keys is pressed. */
AMETA_SHIFT_ON = 0x01,
/** This mask is used to check whether the left SHIFT meta key is pressed. */
AMETA_SHIFT_LEFT_ON = 0x40,
/** This mask is used to check whether the right SHIFT meta key is pressed. */
AMETA_SHIFT_RIGHT_ON = 0x80,
/** This mask is used to check whether the SYM meta key is pressed. */
AMETA_SYM_ON = 0x04,
/** This mask is used to check whether the FUNCTION meta key is pressed. */
AMETA_FUNCTION_ON = 0x08,
/** This mask is used to check whether one of the CTRL meta keys is pressed. */
AMETA_CTRL_ON = 0x1000,
/** This mask is used to check whether the left CTRL meta key is pressed. */
AMETA_CTRL_LEFT_ON = 0x2000,
/** This mask is used to check whether the right CTRL meta key is pressed. */
AMETA_CTRL_RIGHT_ON = 0x4000,
/** This mask is used to check whether one of the META meta keys is pressed. */
AMETA_META_ON = 0x10000,
/** This mask is used to check whether the left META meta key is pressed. */
AMETA_META_LEFT_ON = 0x20000,
/** This mask is used to check whether the right META meta key is pressed. */
AMETA_META_RIGHT_ON = 0x40000,
/** This mask is used to check whether the CAPS LOCK meta key is on. */
AMETA_CAPS_LOCK_ON = 0x100000,
/** This mask is used to check whether the NUM LOCK meta key is on. */
AMETA_NUM_LOCK_ON = 0x200000,
/** This mask is used to check whether the SCROLL LOCK meta key is on. */
AMETA_SCROLL_LOCK_ON = 0x400000,
};
/**
* Input event types.
*/
enum android_input_event_type {
/** Indicates that the input event is a key event. */
AINPUT_EVENT_TYPE_KEY = 1,
/** Indicates that the input event is a motion event. */
AINPUT_EVENT_TYPE_MOTION = 2
};
/**
* Key event actions.
*/
enum android_keyevent_action {
/** The key has been pressed down. */
AKEY_EVENT_ACTION_DOWN = 0,
/** The key has been released. */
AKEY_EVENT_ACTION_UP = 1,
/**
* Multiple duplicate key events have occurred in a row, or a
* complex string is being delivered. The repeat_count property
* of the key event contains the number of times the given key
* code should be executed.
*/
AKEY_EVENT_ACTION_MULTIPLE = 2
};
/**
* Key event flags.
*/
enum android_keyevent_flags {
/** This mask is set if the device woke because of this key event. */
AKEY_EVENT_FLAG_WOKE_HERE = 0x1,
/** This mask is set if the key event was generated by a software keyboard. */
AKEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
/** This mask is set if we don't want the key event to cause us to leave touch mode. */
AKEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
/**
* This mask is set if an event was known to come from a trusted
* part of the system. That is, the event is known to come from
* the user, and could not have been spoofed by a third party
* component.
*/
AKEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
/**
* This mask is used for compatibility, to identify enter keys that are
* coming from an IME whose enter key has been auto-labelled "next" or
* "done". This allows TextView to dispatch these as normal enter keys
* for old applications, but still do the appropriate action when
* receiving them.
*/
AKEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
/**
* When associated with up key events, this indicates that the key press
* has been canceled. Typically this is used with virtual touch screen
* keys, where the user can slide from the virtual key area on to the
* display: in that case, the application will receive a canceled up
* event and should not perform the action normally associated with the
* key. Note that for this to work, the application can not perform an
* action for a key until it receives an up or the long press timeout has
* expired.
*/
AKEY_EVENT_FLAG_CANCELED = 0x20,
/**
* This key event was generated by a virtual (on-screen) hard key area.
* Typically this is an area of the touchscreen, outside of the regular
* display, dedicated to "hardware" buttons.
*/
AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
/**
* This flag is set for the first key repeat that occurs after the
* long press timeout.
*/
AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
/**
* Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
* press action was executed while it was down.
*/
AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
/**
* Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
* tracked from its initial down. That is, somebody requested that tracking
* started on the key down and a long press has not caused
* the tracking to be canceled.
*/
AKEY_EVENT_FLAG_TRACKING = 0x200,
/**
* Set when a key event has been synthesized to implement default behavior
* for an event that the application did not handle.
* Fallback key events are generated by unhandled trackball motions
* (to emulate a directional keypad) and by certain unhandled key presses
* that are declared in the key map (such as special function numeric keypad
* keys when numlock is off).
*/
AKEY_EVENT_FLAG_FALLBACK = 0x400,
};
/**
* Bit shift for the action bits holding the pointer index as
* defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
*/
#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
/** Motion event actions */
enum android_motionevent_action {
/** Bit mask of the parts of the action code that are the action itself. */
AMOTION_EVENT_ACTION_MASK = 0xff,
/**
* Bits in the action code that represent a pointer index, used with
* AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
* down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
* index where the data for the pointer going up or down can be found.
*/
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00,
/** A pressed gesture has started, the motion contains the initial starting location. */
AMOTION_EVENT_ACTION_DOWN = 0,
/**
* A pressed gesture has finished, the motion contains the final release location
* as well as any intermediate points since the last down or move event.
*/
AMOTION_EVENT_ACTION_UP = 1,
/**
* A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
* AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
* any intermediate points since the last down or move event.
*/
AMOTION_EVENT_ACTION_MOVE = 2,
/**
* The current gesture has been aborted.
* You will not receive any more points in it. You should treat this as
* an up event, but not perform any action that you normally would.
*/
AMOTION_EVENT_ACTION_CANCEL = 3,
/**
* A movement has happened outside of the normal bounds of the UI element.
* This does not provide a full gesture, but only the initial location of the movement/touch.
*/
AMOTION_EVENT_ACTION_OUTSIDE = 4,
/**
* A non-primary pointer has gone down.
* The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
*/
AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
/**
* A non-primary pointer has gone up.
* The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
*/
AMOTION_EVENT_ACTION_POINTER_UP = 6,
/**
* A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
* The motion contains the most recent point, as well as any intermediate points since
* the last hover move event.
*/
AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
/**
* The motion event contains relative vertical and/or horizontal scroll offsets.
* Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
* and AMOTION_EVENT_AXIS_HSCROLL.
* The pointer may or may not be down when this event is dispatched.
* This action is always delivered to the winder under the pointer, which
* may not be the window currently touched.
*/
AMOTION_EVENT_ACTION_SCROLL = 8,
/** The pointer is not down but has entered the boundaries of a window or view. */
AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
/** The pointer is not down but has exited the boundaries of a window or view. */
AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
/* One or more buttons have been pressed. */
AMOTION_EVENT_ACTION_BUTTON_PRESS = 11,
/* One or more buttons have been released. */
AMOTION_EVENT_ACTION_BUTTON_RELEASE = 12,
};
/**
* Motion event flags.
*/
enum android_motionevent_flags {
/**
* This flag indicates that the window that received this motion event is partly
* or wholly obscured by another visible window above it. This flag is set to true
* even if the event did not directly pass through the obscured area.
* A security sensitive application can check this flag to identify situations in which
* a malicious application may have covered up part of its content for the purpose
* of misleading the user or hijacking touches. An appropriate response might be
* to drop the suspect touches or to take additional precautions to confirm the user's
* actual intent.
*/
AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
};
/**
* Motion event edge touch flags.
*/
enum android_motionevent_edge_touch_flags {
/** No edges intersected. */
AMOTION_EVENT_EDGE_FLAG_NONE = 0,
/** Flag indicating the motion event intersected the top edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_TOP = 0x01,
/** Flag indicating the motion event intersected the bottom edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
/** Flag indicating the motion event intersected the left edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
/** Flag indicating the motion event intersected the right edge of the screen. */
AMOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
};
/**
* Constants that identify each individual axis of a motion event.
* @anchor AMOTION_EVENT_AXIS
*/
enum android_motionevent_axis {
/**
* Axis constant: X axis of a motion event.
*
* - For a touch screen, reports the absolute X screen position of the center of
* the touch contact area. The units are display pixels.
* - For a touch pad, reports the absolute X surface position of the center of the touch
* contact area. The units are device-dependent.
* - For a mouse, reports the absolute X screen position of the mouse pointer.
* The units are display pixels.
* - For a trackball, reports the relative horizontal displacement of the trackball.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
* - For a joystick, reports the absolute X position of the joystick.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
*/
AMOTION_EVENT_AXIS_X = 0,
/**
* Axis constant: Y axis of a motion event.
*
* - For a touch screen, reports the absolute Y screen position of the center of
* the touch contact area. The units are display pixels.
* - For a touch pad, reports the absolute Y surface position of the center of the touch
* contact area. The units are device-dependent.
* - For a mouse, reports the absolute Y screen position of the mouse pointer.
* The units are display pixels.
* - For a trackball, reports the relative vertical displacement of the trackball.
* The value is normalized to a range from -1.0 (up) to 1.0 (down).
* - For a joystick, reports the absolute Y position of the joystick.
* The value is normalized to a range from -1.0 (up or far) to 1.0 (down or near).
*/
AMOTION_EVENT_AXIS_Y = 1,
/**
* Axis constant: Pressure axis of a motion event.
*
* - For a touch screen or touch pad, reports the approximate pressure applied to the surface
* by a finger or other tool. The value is normalized to a range from
* 0 (no pressure at all) to 1 (normal pressure), although values higher than 1
* may be generated depending on the calibration of the input device.
* - For a trackball, the value is set to 1 if the trackball button is pressed
* or 0 otherwise.
* - For a mouse, the value is set to 1 if the primary mouse button is pressed
* or 0 otherwise.
*/
AMOTION_EVENT_AXIS_PRESSURE = 2,
/**
* Axis constant: Size axis of a motion event.
*
* - For a touch screen or touch pad, reports the approximate size of the contact area in
* relation to the maximum detectable size for the device. The value is normalized
* to a range from 0 (smallest detectable size) to 1 (largest detectable size),
* although it is not a linear scale. This value is of limited use.
* To obtain calibrated size information, see
* {@link AMOTION_EVENT_AXIS_TOUCH_MAJOR} or {@link AMOTION_EVENT_AXIS_TOOL_MAJOR}.
*/
AMOTION_EVENT_AXIS_SIZE = 3,
/**
* Axis constant: TouchMajor axis of a motion event.
*
* - For a touch screen, reports the length of the major axis of an ellipse that
* represents the touch area at the point of contact.
* The units are display pixels.
* - For a touch pad, reports the length of the major axis of an ellipse that
* represents the touch area at the point of contact.
* The units are device-dependent.
*/
AMOTION_EVENT_AXIS_TOUCH_MAJOR = 4,
/**
* Axis constant: TouchMinor axis of a motion event.
*
* - For a touch screen, reports the length of the minor axis of an ellipse that
* represents the touch area at the point of contact.
* The units are display pixels.
* - For a touch pad, reports the length of the minor axis of an ellipse that
* represents the touch area at the point of contact.
* The units are device-dependent.
*
* When the touch is circular, the major and minor axis lengths will be equal to one another.
*/
AMOTION_EVENT_AXIS_TOUCH_MINOR = 5,
/**
* Axis constant: ToolMajor axis of a motion event.
*
* - For a touch screen, reports the length of the major axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* - For a touch pad, reports the length of the major axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* The units are device-dependent.
*
* When the touch is circular, the major and minor axis lengths will be equal to one another.
*
* The tool size may be larger than the touch size since the tool may not be fully
* in contact with the touch sensor.
*/
AMOTION_EVENT_AXIS_TOOL_MAJOR = 6,
/**
* Axis constant: ToolMinor axis of a motion event.
*
* - For a touch screen, reports the length of the minor axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* - For a touch pad, reports the length of the minor axis of an ellipse that
* represents the size of the approaching finger or tool used to make contact.
* The units are device-dependent.
*
* When the touch is circular, the major and minor axis lengths will be equal to one another.
*
* The tool size may be larger than the touch size since the tool may not be fully
* in contact with the touch sensor.
*/
AMOTION_EVENT_AXIS_TOOL_MINOR = 7,
/**
* Axis constant: Orientation axis of a motion event.
*
* - For a touch screen or touch pad, reports the orientation of the finger
* or tool in radians relative to the vertical plane of the device.
* An angle of 0 radians indicates that the major axis of contact is oriented
* upwards, is perfectly circular or is of unknown orientation. A positive angle
* indicates that the major axis of contact is oriented to the right. A negative angle
* indicates that the major axis of contact is oriented to the left.
* The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
* (finger pointing fully right).
* - For a stylus, the orientation indicates the direction in which the stylus
* is pointing in relation to the vertical axis of the current orientation of the screen.
* The range is from -PI radians to PI radians, where 0 is pointing up,
* -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
* is pointing right. See also {@link AMOTION_EVENT_AXIS_TILT}.
*/
AMOTION_EVENT_AXIS_ORIENTATION = 8,
/**
* Axis constant: Vertical Scroll axis of a motion event.
*
* - For a mouse, reports the relative movement of the vertical scroll wheel.
* The value is normalized to a range from -1.0 (down) to 1.0 (up).
*
* This axis should be used to scroll views vertically.
*/
AMOTION_EVENT_AXIS_VSCROLL = 9,
/**
* Axis constant: Horizontal Scroll axis of a motion event.
*
* - For a mouse, reports the relative movement of the horizontal scroll wheel.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
*
* This axis should be used to scroll views horizontally.
*/
AMOTION_EVENT_AXIS_HSCROLL = 10,
/**
* Axis constant: Z axis of a motion event.
*
* - For a joystick, reports the absolute Z position of the joystick.
* The value is normalized to a range from -1.0 (high) to 1.0 (low).
* <em>On game pads with two analog joysticks, this axis is often reinterpreted
* to report the absolute X position of the second joystick instead.</em>
*/
AMOTION_EVENT_AXIS_Z = 11,
/**
* Axis constant: X Rotation axis of a motion event.
*
* - For a joystick, reports the absolute rotation angle about the X axis.
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
*/
AMOTION_EVENT_AXIS_RX = 12,
/**
* Axis constant: Y Rotation axis of a motion event.
*
* - For a joystick, reports the absolute rotation angle about the Y axis.
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
*/
AMOTION_EVENT_AXIS_RY = 13,
/**
* Axis constant: Z Rotation axis of a motion event.
*
* - For a joystick, reports the absolute rotation angle about the Z axis.
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
* On game pads with two analog joysticks, this axis is often reinterpreted
* to report the absolute Y position of the second joystick instead.
*/
AMOTION_EVENT_AXIS_RZ = 14,
/**
* Axis constant: Hat X axis of a motion event.
*
* - For a joystick, reports the absolute X position of the directional hat control.
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
*/
AMOTION_EVENT_AXIS_HAT_X = 15,
/**
* Axis constant: Hat Y axis of a motion event.
*
* - For a joystick, reports the absolute Y position of the directional hat control.
* The value is normalized to a range from -1.0 (up) to 1.0 (down).
*/
AMOTION_EVENT_AXIS_HAT_Y = 16,
/**
* Axis constant: Left Trigger axis of a motion event.
*
* - For a joystick, reports the absolute position of the left trigger control.
* The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
*/
AMOTION_EVENT_AXIS_LTRIGGER = 17,
/**
* Axis constant: Right Trigger axis of a motion event.
*
* - For a joystick, reports the absolute position of the right trigger control.
* The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
*/
AMOTION_EVENT_AXIS_RTRIGGER = 18,
/**
* Axis constant: Throttle axis of a motion event.
*
* - For a joystick, reports the absolute position of the throttle control.
* The value is normalized to a range from 0.0 (fully open) to 1.0 (fully closed).
*/
AMOTION_EVENT_AXIS_THROTTLE = 19,
/**
* Axis constant: Rudder axis of a motion event.
*
* - For a joystick, reports the absolute position of the rudder control.
* The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
*/
AMOTION_EVENT_AXIS_RUDDER = 20,
/**
* Axis constant: Wheel axis of a motion event.
*
* - For a joystick, reports the absolute position of the steering wheel control.
* The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
*/
AMOTION_EVENT_AXIS_WHEEL = 21,
/**
* Axis constant: Gas axis of a motion event.
*
* - For a joystick, reports the absolute position of the gas (accelerator) control.
* The value is normalized to a range from 0.0 (no acceleration)
* to 1.0 (maximum acceleration).
*/
AMOTION_EVENT_AXIS_GAS = 22,
/**
* Axis constant: Brake axis of a motion event.
*
* - For a joystick, reports the absolute position of the brake control.
* The value is normalized to a range from 0.0 (no braking) to 1.0 (maximum braking).
*/
AMOTION_EVENT_AXIS_BRAKE = 23,
/**
* Axis constant: Distance axis of a motion event.
*
* - For a stylus, reports the distance of the stylus from the screen.
* A value of 0.0 indicates direct contact and larger values indicate increasing
* distance from the surface.
*/
AMOTION_EVENT_AXIS_DISTANCE = 24,
/**
* Axis constant: Tilt axis of a motion event.
*
* - For a stylus, reports the tilt angle of the stylus in radians where
* 0 radians indicates that the stylus is being held perpendicular to the
* surface, and PI/2 radians indicates that the stylus is being held flat
* against the surface.
*/
AMOTION_EVENT_AXIS_TILT = 25,
/**
* Axis constant: Generic scroll axis of a motion event.
*
* - This is used for scroll axis motion events that can't be classified as strictly
* vertical or horizontal. The movement of a rotating scroller is an example of this.
*/
AMOTION_EVENT_AXIS_SCROLL = 26,
/**
* Axis constant: The movement of x position of a motion event.
*
* - For a mouse, reports a difference of x position between the previous position.
* This is useful when pointer is captured, in that case the mouse pointer doesn't
* change the location but this axis reports the difference which allows the app
* to see how the mouse is moved.
*/
AMOTION_EVENT_AXIS_RELATIVE_X = 27,
/**
* Axis constant: The movement of y position of a motion event.
*
* Same as {@link RELATIVE_X}, but for y position.
*/
AMOTION_EVENT_AXIS_RELATIVE_Y = 28,
/**
* Axis constant: Generic 1 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_1 = 32,
/**
* Axis constant: Generic 2 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_2 = 33,
/**
* Axis constant: Generic 3 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_3 = 34,
/**
* Axis constant: Generic 4 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_4 = 35,
/**
* Axis constant: Generic 5 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_5 = 36,
/**
* Axis constant: Generic 6 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_6 = 37,
/**
* Axis constant: Generic 7 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_7 = 38,
/**
* Axis constant: Generic 8 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_8 = 39,
/**
* Axis constant: Generic 9 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_9 = 40,
/**
* Axis constant: Generic 10 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_10 = 41,
/**
* Axis constant: Generic 11 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_11 = 42,
/**
* Axis constant: Generic 12 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_12 = 43,
/**
* Axis constant: Generic 13 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_13 = 44,
/**
* Axis constant: Generic 14 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_14 = 45,
/**
* Axis constant: Generic 15 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_15 = 46,
/**
* Axis constant: Generic 16 axis of a motion event.
* The interpretation of a generic axis is device-specific.
*/
AMOTION_EVENT_AXIS_GENERIC_16 = 47,
// NOTE: If you add a new axis here you must also add it to several other files.
// Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
};
/**
* Constants that identify buttons that are associated with motion events.
* Refer to the documentation on the MotionEvent class for descriptions of each button.
*/
enum android_motionevent_buttons {
/** primary */
AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0,
/** secondary */
AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1,
/** tertiary */
AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
/** back */
AMOTION_EVENT_BUTTON_BACK = 1 << 3,
/** forward */
AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6,
};
/**
* Constants that identify tool types.
* Refer to the documentation on the MotionEvent class for descriptions of each tool type.
*/
enum android_motionevent_tool_type {
/** unknown */
AMOTION_EVENT_TOOL_TYPE_UNKNOWN = 0,
/** finger */
AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
/** stylus */
AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
/** mouse */
AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
/** eraser */
AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
};
/**
* Input source masks.
*
* Refer to the documentation on android.view.InputDevice for more details about input sources
* and their correct interpretation.
*/
enum android_input_source_class {
/** mask */
AINPUT_SOURCE_CLASS_MASK = 0x000000ff,
/** none */
AINPUT_SOURCE_CLASS_NONE = 0x00000000,
/** button */
AINPUT_SOURCE_CLASS_BUTTON = 0x00000001,
/** pointer */
AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
/** navigation */
AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
/** position */
AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
/** joystick */
AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
};
/**
* Input sources.
*/
enum android_input_source {
/** unknown */
AINPUT_SOURCE_UNKNOWN = 0x00000000,
/** keyboard */
AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
/** dpad */
AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
/** gamepad */
AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
/** touchscreen */
AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
/** mouse */
AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
/** stylus */
AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
/** bluetooth stylus */
AINPUT_SOURCE_BLUETOOTH_STYLUS = 0x00008000 | AINPUT_SOURCE_STYLUS,
/** trackball */
AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
/** mouse relative */
AINPUT_SOURCE_MOUSE_RELATIVE = 0x00020000 | AINPUT_SOURCE_CLASS_NAVIGATION,
/** touchpad */
AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
/** navigation */
AINPUT_SOURCE_TOUCH_NAVIGATION = 0x00200000 | AINPUT_SOURCE_CLASS_NONE,
/** joystick */
AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
/** rotary encoder */
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
/** any */
AINPUT_SOURCE_ANY = 0xffffff00,
};
/**
* Keyboard types.
*
* Refer to the documentation on android.view.InputDevice for more details.
*/
enum android_keyboard_type {
/** none */
AINPUT_KEYBOARD_TYPE_NONE = 0,
/** non alphabetic */
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
/** alphabetic */
AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
};
/**
* Constants used to retrieve information about the range of motion for a particular
* coordinate of a motion event.
*
* Refer to the documentation on android.view.InputDevice for more details about input sources
* and their correct interpretation.
*
* @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*} constants instead.
*/
enum android_motion_range {
/** x */
AINPUT_MOTION_RANGE_X = AMOTION_EVENT_AXIS_X,
/** y */
AINPUT_MOTION_RANGE_Y = AMOTION_EVENT_AXIS_Y,
/** pressure */
AINPUT_MOTION_RANGE_PRESSURE = AMOTION_EVENT_AXIS_PRESSURE,
/** size */
AINPUT_MOTION_RANGE_SIZE = AMOTION_EVENT_AXIS_SIZE,
/** touch major */
AINPUT_MOTION_RANGE_TOUCH_MAJOR = AMOTION_EVENT_AXIS_TOUCH_MAJOR,
/** touch minor */
AINPUT_MOTION_RANGE_TOUCH_MINOR = AMOTION_EVENT_AXIS_TOUCH_MINOR,
/** tool major */
AINPUT_MOTION_RANGE_TOOL_MAJOR = AMOTION_EVENT_AXIS_TOOL_MAJOR,
/** tool minor */
AINPUT_MOTION_RANGE_TOOL_MINOR = AMOTION_EVENT_AXIS_TOOL_MINOR,
/** orientation */
AINPUT_MOTION_RANGE_ORIENTATION = AMOTION_EVENT_AXIS_ORIENTATION,
};
#endif // _ANDROID_INPUT_H

@ -0,0 +1,745 @@
// copied from <https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h>
// blob 2164d6163e1646c22825e364cad4f3c47638effd
// (and modified)
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _ANDROID_KEYCODES_H
#define _ANDROID_KEYCODES_H
/**
* Key codes.
*/
enum android_keycode {
/** Unknown key code. */
AKEYCODE_UNKNOWN = 0,
/** Soft Left key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom left
* of the display. */
AKEYCODE_SOFT_LEFT = 1,
/** Soft Right key.
* Usually situated below the display on phones and used as a multi-function
* feature key for selecting a software defined function shown on the bottom right
* of the display. */
AKEYCODE_SOFT_RIGHT = 2,
/** Home key.
* This key is handled by the framework and is never delivered to applications. */
AKEYCODE_HOME = 3,
/** Back key. */
AKEYCODE_BACK = 4,
/** Call key. */
AKEYCODE_CALL = 5,
/** End Call key. */
AKEYCODE_ENDCALL = 6,
/** '0' key. */
AKEYCODE_0 = 7,
/** '1' key. */
AKEYCODE_1 = 8,
/** '2' key. */
AKEYCODE_2 = 9,
/** '3' key. */
AKEYCODE_3 = 10,
/** '4' key. */
AKEYCODE_4 = 11,
/** '5' key. */
AKEYCODE_5 = 12,
/** '6' key. */
AKEYCODE_6 = 13,
/** '7' key. */
AKEYCODE_7 = 14,
/** '8' key. */
AKEYCODE_8 = 15,
/** '9' key. */
AKEYCODE_9 = 16,
/** '*' key. */
AKEYCODE_STAR = 17,
/** '#' key. */
AKEYCODE_POUND = 18,
/** Directional Pad Up key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_UP = 19,
/** Directional Pad Down key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_DOWN = 20,
/** Directional Pad Left key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_LEFT = 21,
/** Directional Pad Right key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_RIGHT = 22,
/** Directional Pad Center key.
* May also be synthesized from trackball motions. */
AKEYCODE_DPAD_CENTER = 23,
/** Volume Up key.
* Adjusts the speaker volume up. */
AKEYCODE_VOLUME_UP = 24,
/** Volume Down key.
* Adjusts the speaker volume down. */
AKEYCODE_VOLUME_DOWN = 25,
/** Power key. */
AKEYCODE_POWER = 26,
/** Camera key.
* Used to launch a camera application or take pictures. */
AKEYCODE_CAMERA = 27,
/** Clear key. */
AKEYCODE_CLEAR = 28,
/** 'A' key. */
AKEYCODE_A = 29,
/** 'B' key. */
AKEYCODE_B = 30,
/** 'C' key. */
AKEYCODE_C = 31,
/** 'D' key. */
AKEYCODE_D = 32,
/** 'E' key. */
AKEYCODE_E = 33,
/** 'F' key. */
AKEYCODE_F = 34,
/** 'G' key. */
AKEYCODE_G = 35,
/** 'H' key. */
AKEYCODE_H = 36,
/** 'I' key. */
AKEYCODE_I = 37,
/** 'J' key. */
AKEYCODE_J = 38,
/** 'K' key. */
AKEYCODE_K = 39,
/** 'L' key. */
AKEYCODE_L = 40,
/** 'M' key. */
AKEYCODE_M = 41,
/** 'N' key. */
AKEYCODE_N = 42,
/** 'O' key. */
AKEYCODE_O = 43,
/** 'P' key. */
AKEYCODE_P = 44,
/** 'Q' key. */
AKEYCODE_Q = 45,
/** 'R' key. */
AKEYCODE_R = 46,
/** 'S' key. */
AKEYCODE_S = 47,
/** 'T' key. */
AKEYCODE_T = 48,
/** 'U' key. */
AKEYCODE_U = 49,
/** 'V' key. */
AKEYCODE_V = 50,
/** 'W' key. */
AKEYCODE_W = 51,
/** 'X' key. */
AKEYCODE_X = 52,
/** 'Y' key. */
AKEYCODE_Y = 53,
/** 'Z' key. */
AKEYCODE_Z = 54,
/** ',' key. */
AKEYCODE_COMMA = 55,
/** '.' key. */
AKEYCODE_PERIOD = 56,
/** Left Alt modifier key. */
AKEYCODE_ALT_LEFT = 57,
/** Right Alt modifier key. */
AKEYCODE_ALT_RIGHT = 58,
/** Left Shift modifier key. */
AKEYCODE_SHIFT_LEFT = 59,
/** Right Shift modifier key. */
AKEYCODE_SHIFT_RIGHT = 60,
/** Tab key. */
AKEYCODE_TAB = 61,
/** Space key. */
AKEYCODE_SPACE = 62,
/** Symbol modifier key.
* Used to enter alternate symbols. */
AKEYCODE_SYM = 63,
/** Explorer special function key.
* Used to launch a browser application. */
AKEYCODE_EXPLORER = 64,
/** Envelope special function key.
* Used to launch a mail application. */
AKEYCODE_ENVELOPE = 65,
/** Enter key. */
AKEYCODE_ENTER = 66,
/** Backspace key.
* Deletes characters before the insertion point, unlike {@link AKEYCODE_FORWARD_DEL}. */
AKEYCODE_DEL = 67,
/** '`' (backtick) key. */
AKEYCODE_GRAVE = 68,
/** '-'. */
AKEYCODE_MINUS = 69,
/** '=' key. */
AKEYCODE_EQUALS = 70,
/** '[' key. */
AKEYCODE_LEFT_BRACKET = 71,
/** ']' key. */
AKEYCODE_RIGHT_BRACKET = 72,
/** '\' key. */
AKEYCODE_BACKSLASH = 73,
/** ';' key. */
AKEYCODE_SEMICOLON = 74,
/** ''' (apostrophe) key. */
AKEYCODE_APOSTROPHE = 75,
/** '/' key. */
AKEYCODE_SLASH = 76,
/** '@' key. */
AKEYCODE_AT = 77,
/** Number modifier key.
* Used to enter numeric symbols.
* This key is not {@link AKEYCODE_NUM_LOCK}; it is more like {@link AKEYCODE_ALT_LEFT}. */
AKEYCODE_NUM = 78,
/** Headset Hook key.
* Used to hang up calls and stop media. */
AKEYCODE_HEADSETHOOK = 79,
/** Camera Focus key.
* Used to focus the camera. */
AKEYCODE_FOCUS = 80,
/** '+' key. */
AKEYCODE_PLUS = 81,
/** Menu key. */
AKEYCODE_MENU = 82,
/** Notification key. */
AKEYCODE_NOTIFICATION = 83,
/** Search key. */
AKEYCODE_SEARCH = 84,
/** Play/Pause media key. */
AKEYCODE_MEDIA_PLAY_PAUSE= 85,
/** Stop media key. */
AKEYCODE_MEDIA_STOP = 86,
/** Play Next media key. */
AKEYCODE_MEDIA_NEXT = 87,
/** Play Previous media key. */
AKEYCODE_MEDIA_PREVIOUS = 88,
/** Rewind media key. */
AKEYCODE_MEDIA_REWIND = 89,
/** Fast Forward media key. */
AKEYCODE_MEDIA_FAST_FORWARD = 90,
/** Mute key.
* Mutes the microphone, unlike {@link AKEYCODE_VOLUME_MUTE}. */
AKEYCODE_MUTE = 91,
/** Page Up key. */
AKEYCODE_PAGE_UP = 92,
/** Page Down key. */
AKEYCODE_PAGE_DOWN = 93,
/** Picture Symbols modifier key.
* Used to switch symbol sets (Emoji, Kao-moji). */
AKEYCODE_PICTSYMBOLS = 94,
/** Switch Charset modifier key.
* Used to switch character sets (Kanji, Katakana). */
AKEYCODE_SWITCH_CHARSET = 95,
/** A Button key.
* On a game controller, the A button should be either the button labeled A
* or the first button on the bottom row of controller buttons. */
AKEYCODE_BUTTON_A = 96,
/** B Button key.
* On a game controller, the B button should be either the button labeled B
* or the second button on the bottom row of controller buttons. */
AKEYCODE_BUTTON_B = 97,
/** C Button key.
* On a game controller, the C button should be either the button labeled C
* or the third button on the bottom row of controller buttons. */
AKEYCODE_BUTTON_C = 98,
/** X Button key.
* On a game controller, the X button should be either the button labeled X
* or the first button on the upper row of controller buttons. */
AKEYCODE_BUTTON_X = 99,
/** Y Button key.
* On a game controller, the Y button should be either the button labeled Y
* or the second button on the upper row of controller buttons. */
AKEYCODE_BUTTON_Y = 100,
/** Z Button key.
* On a game controller, the Z button should be either the button labeled Z
* or the third button on the upper row of controller buttons. */
AKEYCODE_BUTTON_Z = 101,
/** L1 Button key.
* On a game controller, the L1 button should be either the button labeled L1 (or L)
* or the top left trigger button. */
AKEYCODE_BUTTON_L1 = 102,
/** R1 Button key.
* On a game controller, the R1 button should be either the button labeled R1 (or R)
* or the top right trigger button. */
AKEYCODE_BUTTON_R1 = 103,
/** L2 Button key.
* On a game controller, the L2 button should be either the button labeled L2
* or the bottom left trigger button. */
AKEYCODE_BUTTON_L2 = 104,
/** R2 Button key.
* On a game controller, the R2 button should be either the button labeled R2
* or the bottom right trigger button. */
AKEYCODE_BUTTON_R2 = 105,
/** Left Thumb Button key.
* On a game controller, the left thumb button indicates that the left (or only)
* joystick is pressed. */
AKEYCODE_BUTTON_THUMBL = 106,
/** Right Thumb Button key.
* On a game controller, the right thumb button indicates that the right
* joystick is pressed. */
AKEYCODE_BUTTON_THUMBR = 107,
/** Start Button key.
* On a game controller, the button labeled Start. */
AKEYCODE_BUTTON_START = 108,
/** Select Button key.
* On a game controller, the button labeled Select. */
AKEYCODE_BUTTON_SELECT = 109,
/** Mode Button key.
* On a game controller, the button labeled Mode. */
AKEYCODE_BUTTON_MODE = 110,
/** Escape key. */
AKEYCODE_ESCAPE = 111,
/** Forward Delete key.
* Deletes characters ahead of the insertion point, unlike {@link AKEYCODE_DEL}. */
AKEYCODE_FORWARD_DEL = 112,
/** Left Control modifier key. */
AKEYCODE_CTRL_LEFT = 113,
/** Right Control modifier key. */
AKEYCODE_CTRL_RIGHT = 114,
/** Caps Lock key. */
AKEYCODE_CAPS_LOCK = 115,
/** Scroll Lock key. */
AKEYCODE_SCROLL_LOCK = 116,
/** Left Meta modifier key. */
AKEYCODE_META_LEFT = 117,
/** Right Meta modifier key. */
AKEYCODE_META_RIGHT = 118,
/** Function modifier key. */
AKEYCODE_FUNCTION = 119,
/** System Request / Print Screen key. */
AKEYCODE_SYSRQ = 120,
/** Break / Pause key. */
AKEYCODE_BREAK = 121,
/** Home Movement key.
* Used for scrolling or moving the cursor around to the start of a line
* or to the top of a list. */
AKEYCODE_MOVE_HOME = 122,
/** End Movement key.
* Used for scrolling or moving the cursor around to the end of a line
* or to the bottom of a list. */
AKEYCODE_MOVE_END = 123,
/** Insert key.
* Toggles insert / overwrite edit mode. */
AKEYCODE_INSERT = 124,
/** Forward key.
* Navigates forward in the history stack. Complement of {@link AKEYCODE_BACK}. */
AKEYCODE_FORWARD = 125,
/** Play media key. */
AKEYCODE_MEDIA_PLAY = 126,
/** Pause media key. */
AKEYCODE_MEDIA_PAUSE = 127,
/** Close media key.
* May be used to close a CD tray, for example. */
AKEYCODE_MEDIA_CLOSE = 128,
/** Eject media key.
* May be used to eject a CD tray, for example. */
AKEYCODE_MEDIA_EJECT = 129,
/** Record media key. */
AKEYCODE_MEDIA_RECORD = 130,
/** F1 key. */
AKEYCODE_F1 = 131,
/** F2 key. */
AKEYCODE_F2 = 132,
/** F3 key. */
AKEYCODE_F3 = 133,
/** F4 key. */
AKEYCODE_F4 = 134,
/** F5 key. */
AKEYCODE_F5 = 135,
/** F6 key. */
AKEYCODE_F6 = 136,
/** F7 key. */
AKEYCODE_F7 = 137,
/** F8 key. */
AKEYCODE_F8 = 138,
/** F9 key. */
AKEYCODE_F9 = 139,
/** F10 key. */
AKEYCODE_F10 = 140,
/** F11 key. */
AKEYCODE_F11 = 141,
/** F12 key. */
AKEYCODE_F12 = 142,
/** Num Lock key.
* This is the Num Lock key; it is different from {@link AKEYCODE_NUM}.
* This key alters the behavior of other keys on the numeric keypad. */
AKEYCODE_NUM_LOCK = 143,
/** Numeric keypad '0' key. */
AKEYCODE_NUMPAD_0 = 144,
/** Numeric keypad '1' key. */
AKEYCODE_NUMPAD_1 = 145,
/** Numeric keypad '2' key. */
AKEYCODE_NUMPAD_2 = 146,
/** Numeric keypad '3' key. */
AKEYCODE_NUMPAD_3 = 147,
/** Numeric keypad '4' key. */
AKEYCODE_NUMPAD_4 = 148,
/** Numeric keypad '5' key. */
AKEYCODE_NUMPAD_5 = 149,
/** Numeric keypad '6' key. */
AKEYCODE_NUMPAD_6 = 150,
/** Numeric keypad '7' key. */
AKEYCODE_NUMPAD_7 = 151,
/** Numeric keypad '8' key. */
AKEYCODE_NUMPAD_8 = 152,
/** Numeric keypad '9' key. */
AKEYCODE_NUMPAD_9 = 153,
/** Numeric keypad '/' key (for division). */
AKEYCODE_NUMPAD_DIVIDE = 154,
/** Numeric keypad '*' key (for multiplication). */
AKEYCODE_NUMPAD_MULTIPLY = 155,
/** Numeric keypad '-' key (for subtraction). */
AKEYCODE_NUMPAD_SUBTRACT = 156,
/** Numeric keypad '+' key (for addition). */
AKEYCODE_NUMPAD_ADD = 157,
/** Numeric keypad '.' key (for decimals or digit grouping). */
AKEYCODE_NUMPAD_DOT = 158,
/** Numeric keypad ',' key (for decimals or digit grouping). */
AKEYCODE_NUMPAD_COMMA = 159,
/** Numeric keypad Enter key. */
AKEYCODE_NUMPAD_ENTER = 160,
/** Numeric keypad '=' key. */
AKEYCODE_NUMPAD_EQUALS = 161,
/** Numeric keypad '(' key. */
AKEYCODE_NUMPAD_LEFT_PAREN = 162,
/** Numeric keypad ')' key. */
AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
/** Volume Mute key.
* Mutes the speaker, unlike {@link AKEYCODE_MUTE}.
* This key should normally be implemented as a toggle such that the first press
* mutes the speaker and the second press restores the original volume. */
AKEYCODE_VOLUME_MUTE = 164,
/** Info key.
* Common on TV remotes to show additional information related to what is
* currently being viewed. */
AKEYCODE_INFO = 165,
/** Channel up key.
* On TV remotes, increments the television channel. */
AKEYCODE_CHANNEL_UP = 166,
/** Channel down key.
* On TV remotes, decrements the television channel. */
AKEYCODE_CHANNEL_DOWN = 167,
/** Zoom in key. */
AKEYCODE_ZOOM_IN = 168,
/** Zoom out key. */
AKEYCODE_ZOOM_OUT = 169,
/** TV key.
* On TV remotes, switches to viewing live TV. */
AKEYCODE_TV = 170,
/** Window key.
* On TV remotes, toggles picture-in-picture mode or other windowing functions. */
AKEYCODE_WINDOW = 171,
/** Guide key.
* On TV remotes, shows a programming guide. */
AKEYCODE_GUIDE = 172,
/** DVR key.
* On some TV remotes, switches to a DVR mode for recorded shows. */
AKEYCODE_DVR = 173,
/** Bookmark key.
* On some TV remotes, bookmarks content or web pages. */
AKEYCODE_BOOKMARK = 174,
/** Toggle captions key.
* Switches the mode for closed-captioning text, for example during television shows. */
AKEYCODE_CAPTIONS = 175,
/** Settings key.
* Starts the system settings activity. */
AKEYCODE_SETTINGS = 176,
/** TV power key.
* On TV remotes, toggles the power on a television screen. */
AKEYCODE_TV_POWER = 177,
/** TV input key.
* On TV remotes, switches the input on a television screen. */
AKEYCODE_TV_INPUT = 178,
/** Set-top-box power key.
* On TV remotes, toggles the power on an external Set-top-box. */
AKEYCODE_STB_POWER = 179,
/** Set-top-box input key.
* On TV remotes, switches the input mode on an external Set-top-box. */
AKEYCODE_STB_INPUT = 180,
/** A/V Receiver power key.
* On TV remotes, toggles the power on an external A/V Receiver. */
AKEYCODE_AVR_POWER = 181,
/** A/V Receiver input key.
* On TV remotes, switches the input mode on an external A/V Receiver. */
AKEYCODE_AVR_INPUT = 182,
/** Red "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
AKEYCODE_PROG_RED = 183,
/** Green "programmable" key.
* On TV remotes, actsas a contextual/programmable key. */
AKEYCODE_PROG_GREEN = 184,
/** Yellow "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
AKEYCODE_PROG_YELLOW = 185,
/** Blue "programmable" key.
* On TV remotes, acts as a contextual/programmable key. */
AKEYCODE_PROG_BLUE = 186,
/** App switch key.
* Should bring up the application switcher dialog. */
AKEYCODE_APP_SWITCH = 187,
/** Generic Game Pad Button #1.*/
AKEYCODE_BUTTON_1 = 188,
/** Generic Game Pad Button #2.*/
AKEYCODE_BUTTON_2 = 189,
/** Generic Game Pad Button #3.*/
AKEYCODE_BUTTON_3 = 190,
/** Generic Game Pad Button #4.*/
AKEYCODE_BUTTON_4 = 191,
/** Generic Game Pad Button #5.*/
AKEYCODE_BUTTON_5 = 192,
/** Generic Game Pad Button #6.*/
AKEYCODE_BUTTON_6 = 193,
/** Generic Game Pad Button #7.*/
AKEYCODE_BUTTON_7 = 194,
/** Generic Game Pad Button #8.*/
AKEYCODE_BUTTON_8 = 195,
/** Generic Game Pad Button #9.*/
AKEYCODE_BUTTON_9 = 196,
/** Generic Game Pad Button #10.*/
AKEYCODE_BUTTON_10 = 197,
/** Generic Game Pad Button #11.*/
AKEYCODE_BUTTON_11 = 198,
/** Generic Game Pad Button #12.*/
AKEYCODE_BUTTON_12 = 199,
/** Generic Game Pad Button #13.*/
AKEYCODE_BUTTON_13 = 200,
/** Generic Game Pad Button #14.*/
AKEYCODE_BUTTON_14 = 201,
/** Generic Game Pad Button #15.*/
AKEYCODE_BUTTON_15 = 202,
/** Generic Game Pad Button #16.*/
AKEYCODE_BUTTON_16 = 203,
/** Language Switch key.
* Toggles the current input language such as switching between English and Japanese on
* a QWERTY keyboard. On some devices, the same function may be performed by
* pressing Shift+Spacebar. */
AKEYCODE_LANGUAGE_SWITCH = 204,
/** Manner Mode key.
* Toggles silent or vibrate mode on and off to make the device behave more politely
* in certain settings such as on a crowded train. On some devices, the key may only
* operate when long-pressed. */
AKEYCODE_MANNER_MODE = 205,
/** 3D Mode key.
* Toggles the display between 2D and 3D mode. */
AKEYCODE_3D_MODE = 206,
/** Contacts special function key.
* Used to launch an address book application. */
AKEYCODE_CONTACTS = 207,
/** Calendar special function key.
* Used to launch a calendar application. */
AKEYCODE_CALENDAR = 208,
/** Music special function key.
* Used to launch a music player application. */
AKEYCODE_MUSIC = 209,
/** Calculator special function key.
* Used to launch a calculator application. */
AKEYCODE_CALCULATOR = 210,
/** Japanese full-width / half-width key. */
AKEYCODE_ZENKAKU_HANKAKU = 211,
/** Japanese alphanumeric key. */
AKEYCODE_EISU = 212,
/** Japanese non-conversion key. */
AKEYCODE_MUHENKAN = 213,
/** Japanese conversion key. */
AKEYCODE_HENKAN = 214,
/** Japanese katakana / hiragana key. */
AKEYCODE_KATAKANA_HIRAGANA = 215,
/** Japanese Yen key. */
AKEYCODE_YEN = 216,
/** Japanese Ro key. */
AKEYCODE_RO = 217,
/** Japanese kana key. */
AKEYCODE_KANA = 218,
/** Assist key.
* Launches the global assist activity. Not delivered to applications. */
AKEYCODE_ASSIST = 219,
/** Brightness Down key.
* Adjusts the screen brightness down. */
AKEYCODE_BRIGHTNESS_DOWN = 220,
/** Brightness Up key.
* Adjusts the screen brightness up. */
AKEYCODE_BRIGHTNESS_UP = 221,
/** Audio Track key.
* Switches the audio tracks. */
AKEYCODE_MEDIA_AUDIO_TRACK = 222,
/** Sleep key.
* Puts the device to sleep. Behaves somewhat like {@link AKEYCODE_POWER} but it
* has no effect if the device is already asleep. */
AKEYCODE_SLEEP = 223,
/** Wakeup key.
* Wakes up the device. Behaves somewhat like {@link AKEYCODE_POWER} but it
* has no effect if the device is already awake. */
AKEYCODE_WAKEUP = 224,
/** Pairing key.
* Initiates peripheral pairing mode. Useful for pairing remote control
* devices or game controllers, especially if no other input mode is
* available. */
AKEYCODE_PAIRING = 225,
/** Media Top Menu key.
* Goes to the top of media menu. */
AKEYCODE_MEDIA_TOP_MENU = 226,
/** '11' key. */
AKEYCODE_11 = 227,
/** '12' key. */
AKEYCODE_12 = 228,
/** Last Channel key.
* Goes to the last viewed channel. */
AKEYCODE_LAST_CHANNEL = 229,
/** TV data service key.
* Displays data services like weather, sports. */
AKEYCODE_TV_DATA_SERVICE = 230,
/** Voice Assist key.
* Launches the global voice assist activity. Not delivered to applications. */
AKEYCODE_VOICE_ASSIST = 231,
/** Radio key.
* Toggles TV service / Radio service. */
AKEYCODE_TV_RADIO_SERVICE = 232,
/** Teletext key.
* Displays Teletext service. */
AKEYCODE_TV_TELETEXT = 233,
/** Number entry key.
* Initiates to enter multi-digit channel nubmber when each digit key is assigned
* for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
* User Control Code. */
AKEYCODE_TV_NUMBER_ENTRY = 234,
/** Analog Terrestrial key.
* Switches to analog terrestrial broadcast service. */
AKEYCODE_TV_TERRESTRIAL_ANALOG = 235,
/** Digital Terrestrial key.
* Switches to digital terrestrial broadcast service. */
AKEYCODE_TV_TERRESTRIAL_DIGITAL = 236,
/** Satellite key.
* Switches to digital satellite broadcast service. */
AKEYCODE_TV_SATELLITE = 237,
/** BS key.
* Switches to BS digital satellite broadcasting service available in Japan. */
AKEYCODE_TV_SATELLITE_BS = 238,
/** CS key.
* Switches to CS digital satellite broadcasting service available in Japan. */
AKEYCODE_TV_SATELLITE_CS = 239,
/** BS/CS key.
* Toggles between BS and CS digital satellite services. */
AKEYCODE_TV_SATELLITE_SERVICE = 240,
/** Toggle Network key.
* Toggles selecting broacast services. */
AKEYCODE_TV_NETWORK = 241,
/** Antenna/Cable key.
* Toggles broadcast input source between antenna and cable. */
AKEYCODE_TV_ANTENNA_CABLE = 242,
/** HDMI #1 key.
* Switches to HDMI input #1. */
AKEYCODE_TV_INPUT_HDMI_1 = 243,
/** HDMI #2 key.
* Switches to HDMI input #2. */
AKEYCODE_TV_INPUT_HDMI_2 = 244,
/** HDMI #3 key.
* Switches to HDMI input #3. */
AKEYCODE_TV_INPUT_HDMI_3 = 245,
/** HDMI #4 key.
* Switches to HDMI input #4. */
AKEYCODE_TV_INPUT_HDMI_4 = 246,
/** Composite #1 key.
* Switches to composite video input #1. */
AKEYCODE_TV_INPUT_COMPOSITE_1 = 247,
/** Composite #2 key.
* Switches to composite video input #2. */
AKEYCODE_TV_INPUT_COMPOSITE_2 = 248,
/** Component #1 key.
* Switches to component video input #1. */
AKEYCODE_TV_INPUT_COMPONENT_1 = 249,
/** Component #2 key.
* Switches to component video input #2. */
AKEYCODE_TV_INPUT_COMPONENT_2 = 250,
/** VGA #1 key.
* Switches to VGA (analog RGB) input #1. */
AKEYCODE_TV_INPUT_VGA_1 = 251,
/** Audio description key.
* Toggles audio description off / on. */
AKEYCODE_TV_AUDIO_DESCRIPTION = 252,
/** Audio description mixing volume up key.
* Louden audio description volume as compared with normal audio volume. */
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253,
/** Audio description mixing volume down key.
* Lessen audio description volume as compared with normal audio volume. */
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254,
/** Zoom mode key.
* Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.) */
AKEYCODE_TV_ZOOM_MODE = 255,
/** Contents menu key.
* Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
* Code */
AKEYCODE_TV_CONTENTS_MENU = 256,
/** Media context menu key.
* Goes to the context menu of media contents. Corresponds to Media Context-sensitive
* Menu (0x11) of CEC User Control Code. */
AKEYCODE_TV_MEDIA_CONTEXT_MENU = 257,
/** Timer programming key.
* Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
* CEC User Control Code. */
AKEYCODE_TV_TIMER_PROGRAMMING = 258,
/** Help key. */
AKEYCODE_HELP = 259,
AKEYCODE_NAVIGATE_PREVIOUS = 260,
AKEYCODE_NAVIGATE_NEXT = 261,
AKEYCODE_NAVIGATE_IN = 262,
AKEYCODE_NAVIGATE_OUT = 263,
/** Primary stem key for Wear
* Main power/reset button on watch. */
AKEYCODE_STEM_PRIMARY = 264,
/** Generic stem key 1 for Wear */
AKEYCODE_STEM_1 = 265,
/** Generic stem key 2 for Wear */
AKEYCODE_STEM_2 = 266,
/** Generic stem key 3 for Wear */
AKEYCODE_STEM_3 = 267,
/** Directional Pad Up-Left */
AKEYCODE_DPAD_UP_LEFT = 268,
/** Directional Pad Down-Left */
AKEYCODE_DPAD_DOWN_LEFT = 269,
/** Directional Pad Up-Right */
AKEYCODE_DPAD_UP_RIGHT = 270,
/** Directional Pad Down-Right */
AKEYCODE_DPAD_DOWN_RIGHT = 271,
/** Skip forward media key */
AKEYCODE_MEDIA_SKIP_FORWARD = 272,
/** Skip backward media key */
AKEYCODE_MEDIA_SKIP_BACKWARD = 273,
/** Step forward media key.
* Steps media forward one from at a time. */
AKEYCODE_MEDIA_STEP_FORWARD = 274,
/** Step backward media key.
* Steps media backward one from at a time. */
AKEYCODE_MEDIA_STEP_BACKWARD = 275,
/** Put device to sleep unless a wakelock is held. */
AKEYCODE_SOFT_SLEEP = 276,
/** Cut key. */
AKEYCODE_CUT = 277,
/** Copy key. */
AKEYCODE_COPY = 278,
/** Paste key. */
AKEYCODE_PASTE = 279,
/** fingerprint navigation key, up. */
AKEYCODE_SYSTEM_NAVIGATION_UP = 280,
/** fingerprint navigation key, down. */
AKEYCODE_SYSTEM_NAVIGATION_DOWN = 281,
/** fingerprint navigation key, left. */
AKEYCODE_SYSTEM_NAVIGATION_LEFT = 282,
/** fingerprint navigation key, right. */
AKEYCODE_SYSTEM_NAVIGATION_RIGHT = 283,
/** all apps */
AKEYCODE_ALL_APPS = 284
};
#endif // _ANDROID_KEYCODES_H

@ -0,0 +1,99 @@
#include "control.h"
#include "lockutil.h"
SDL_bool controller_init(struct controller *controller, TCPsocket video_socket) {
if (!control_event_queue_init(&controller->queue)) {
return SDL_FALSE;
}
if (!(controller->mutex = SDL_CreateMutex())) {
return SDL_FALSE;
}
if (!(controller->event_cond = SDL_CreateCond())) {
SDL_DestroyMutex(controller->mutex);
return SDL_FALSE;
}
controller->video_socket = video_socket;
controller->stopped = SDL_FALSE;
return SDL_TRUE;
}
void controller_destroy(struct controller *controller) {
SDL_DestroyCond(controller->event_cond);
SDL_DestroyMutex(controller->mutex);
control_event_queue_destroy(&controller->queue);
}
SDL_bool controller_push_event(struct controller *controller, struct control_event *event) {
SDL_bool res;
mutex_lock(controller->mutex);
SDL_bool was_empty = control_event_queue_is_empty(&controller->queue);
res = control_event_queue_push(&controller->queue, event);
if (was_empty) {
cond_signal(controller->event_cond);
}
mutex_unlock(controller->mutex);
return res;
}
static SDL_bool process_event(struct controller *controller, struct control_event *event) {
unsigned char serialized_event[SERIALIZED_EVENT_MAX_SIZE];
int length = control_event_serialize(event, serialized_event);
if (!length) {
return SDL_FALSE;
}
int w = SDLNet_TCP_Send(controller->video_socket, serialized_event, length);
return w == length;
}
static int run_controller(void *data) {
struct controller *controller = data;
mutex_lock(controller->mutex);
for (;;) {
while (!controller->stopped && control_event_queue_is_empty(&controller->queue)) {
cond_wait(controller->event_cond, controller->mutex);
}
if (controller->stopped) {
// stop immediately, do not process further events
break;
}
struct control_event event;
while (control_event_queue_take(&controller->queue, &event)) {
if (!process_event(controller, &event)) {
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Cannot write event to socket");
goto end;
}
}
}
end:
mutex_unlock(controller->mutex);
return 0;
}
SDL_bool controller_start(struct controller *controller) {
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Starting controller thread");
controller->thread = SDL_CreateThread(run_controller, "controller", controller);
if (!controller->thread) {
SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Could not start controller thread");
return SDL_FALSE;
}
return SDL_TRUE;
}
void controller_stop(struct controller *controller) {
mutex_lock(controller->mutex);
controller->stopped = SDL_TRUE;
cond_signal(controller->event_cond);
mutex_unlock(controller->mutex);
}
void controller_join(struct controller *controller) {
SDL_WaitThread(controller->thread, NULL);
}

@ -0,0 +1,29 @@
#ifndef CONTROL_H
#define CONTROL_H
#include "controlevent.h"
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_net.h>
#include <SDL2/SDL_stdinc.h>
struct controller {
TCPsocket video_socket;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *event_cond;
SDL_bool stopped;
struct control_event_queue queue;
};
SDL_bool controller_init(struct controller *controller, TCPsocket video_socket);
void controller_destroy(struct controller *controller);
SDL_bool controller_start(struct controller *controller);
void controller_stop(struct controller *controller);
void controller_join(struct controller *controller);
// expose simple API to hide control_event_queue
SDL_bool controller_push_event(struct controller *controller, struct control_event *event);
#endif

@ -0,0 +1,91 @@
#include "controlevent.h"
#include <SDL2/SDL_log.h>
#include <SDL2/SDL_stdinc.h>
#include "lockutil.h"
static inline void write16(Uint8 *buf, Uint16 value) {
buf[0] = value >> 8;
buf[1] = value;
}
static inline void write32(Uint8 *buf, Uint32 value) {
buf[0] = value >> 24;
buf[1] = value >> 16;
buf[2] = value >> 8;
buf[3] = value;
}
int control_event_serialize(struct control_event *event, unsigned char *buf) {
buf[0] = event->type;
switch (event->type) {
case CONTROL_EVENT_TYPE_KEYCODE:
buf[1] = event->keycode_event.action;
write32(&buf[2], event->keycode_event.keycode);
write32(&buf[6], event->keycode_event.metastate);
return 10;
case CONTROL_EVENT_TYPE_TEXT: {
// write length (1 byte) + date (non nul-terminated)
size_t len = strlen(event->text_event.text);
if (len > TEXT_MAX_LENGTH) {
len = TEXT_MAX_LENGTH;
}
buf[1] = (Uint8) len;
memcpy(&buf[2], &event->text_event.text, len);
return 2 + len;
}
case CONTROL_EVENT_TYPE_MOUSE:
buf[1] = event->mouse_event.action;
write32(&buf[2], event->mouse_event.buttons);
write32(&buf[6], (Uint32) event->mouse_event.x);
write32(&buf[10], (Uint32) event->mouse_event.y);
return 14;
case CONTROL_EVENT_TYPE_SCROLL:
write32(&buf[1], (Uint32) event->scroll_event.x);
write32(&buf[5], (Uint32) event->scroll_event.y);
write32(&buf[9], (Uint32) event->scroll_event.hscroll);
write32(&buf[13], (Uint32) event->scroll_event.vscroll);
return 17;
default:
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unknown event type: %u\n", (unsigned) event->type);
return 0;
}
}
SDL_bool control_event_queue_is_empty(struct control_event_queue *queue) {
return queue->head == queue->tail;
}
SDL_bool control_event_queue_is_full(struct control_event_queue *queue) {
return (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE == queue->tail;
}
SDL_bool control_event_queue_init(struct control_event_queue *queue) {
queue->head = 0;
queue->tail = 0;
// the current implementation may not fail
return SDL_TRUE;
}
void control_event_queue_destroy(struct control_event_queue *queue) {
// nothing to do in the current implementation
}
SDL_bool control_event_queue_push(struct control_event_queue *queue, struct control_event *event) {
if (control_event_queue_is_full(queue)) {
return SDL_FALSE;
}
queue->data[queue->head] = *event;
queue->head = (queue->head + 1) % CONTROL_EVENT_QUEUE_SIZE;
return SDL_TRUE;
}
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event) {
if (control_event_queue_is_empty(queue)) {
return SDL_FALSE;
}
*event = queue->data[queue->tail];
queue->tail = (queue->tail + 1) % CONTROL_EVENT_QUEUE_SIZE;
return SDL_TRUE;
}

@ -0,0 +1,66 @@
#ifndef CONTROL_EVENT_H
#define CONTROL_EVENT_H
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_stdinc.h>
#include "android/input.h"
#include "android/keycodes.h"
#define CONTROL_EVENT_QUEUE_SIZE 64
#define SERIALIZED_EVENT_MAX_SIZE 33
#define TEXT_MAX_LENGTH 31
enum control_event_type {
CONTROL_EVENT_TYPE_KEYCODE,
CONTROL_EVENT_TYPE_TEXT,
CONTROL_EVENT_TYPE_MOUSE,
CONTROL_EVENT_TYPE_SCROLL,
};
struct control_event {
enum control_event_type type;
union {
struct {
enum android_keyevent_action action;
enum android_keycode keycode;
enum android_metastate metastate;
} keycode_event;
struct {
char text[TEXT_MAX_LENGTH + 1]; // nul-terminated string
} text_event;
struct {
enum android_motionevent_action action;
enum android_motionevent_buttons buttons;
Sint32 x;
Sint32 y;
} mouse_event;
struct {
Sint32 x;
Sint32 y;
Sint32 hscroll;
Sint32 vscroll;
} scroll_event;
};
};
struct control_event_queue {
struct control_event data[CONTROL_EVENT_QUEUE_SIZE];
int head;
int tail;
};
// buf size must be at least SERIALIZED_EVENT_MAX_SIZE
int control_event_serialize(struct control_event *event, unsigned char *buf);
SDL_bool control_event_queue_init(struct control_event_queue *queue);
void control_event_queue_destroy(struct control_event_queue *queue);
SDL_bool control_event_queue_is_empty(struct control_event_queue *queue);
SDL_bool control_event_queue_is_full(struct control_event_queue *queue);
// event is copied, the queue does not use the event after the function returns
SDL_bool control_event_queue_push(struct control_event_queue *queue, struct control_event *event);
SDL_bool control_event_queue_take(struct control_event_queue *queue, struct control_event *event);
#endif

@ -0,0 +1,214 @@
#include "convert.h"
#define MAP(FROM, TO) case FROM: *to = TO; return SDL_TRUE
#define FAIL default: return SDL_FALSE
static SDL_bool convert_keycode_action(SDL_EventType from, enum android_keyevent_action *to) {
switch (from) {
MAP(SDL_KEYDOWN, AKEY_EVENT_ACTION_DOWN);
MAP(SDL_KEYUP, AKEY_EVENT_ACTION_UP);
FAIL;
}
}
static enum android_metastate autocomplete_metastate(enum android_metastate metastate) {
// fill dependant flags
if (metastate & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
metastate |= AMETA_SHIFT_ON;
}
if (metastate & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
metastate |= AMETA_CTRL_ON;
}
if (metastate & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
metastate |= AMETA_ALT_ON;
}
if (metastate & (AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON)) {
metastate |= AMETA_META_ON;
}
return metastate;
}
static enum android_metastate convert_meta_state(SDL_Keymod mod) {
enum android_metastate metastate = 0;
if (mod & KMOD_LSHIFT) {
metastate |= AMETA_SHIFT_LEFT_ON;
}
if (mod & KMOD_RSHIFT) {
metastate |= AMETA_SHIFT_RIGHT_ON;
}
if (mod & KMOD_LCTRL) {
metastate |= AMETA_CTRL_LEFT_ON;
}
if (mod & KMOD_RCTRL) {
metastate |= AMETA_CTRL_RIGHT_ON;
}
if (mod & KMOD_LALT) {
metastate |= AMETA_ALT_LEFT_ON;
}
if (mod & KMOD_RALT) {
metastate |= AMETA_ALT_RIGHT_ON;
}
if (mod & KMOD_LGUI) { // Windows key
metastate |= AMETA_META_LEFT_ON;
}
if (mod & KMOD_RGUI) { // Windows key
metastate |= AMETA_META_RIGHT_ON;
}
if (mod & KMOD_NUM) {
metastate |= AMETA_NUM_LOCK_ON;
}
if (mod & KMOD_CAPS) {
metastate |= AMETA_CAPS_LOCK_ON;
}
if (mod & KMOD_MODE) { // Alt Gr
// no mapping?
}
// fill the dependent fields
return autocomplete_metastate(metastate);
}
static SDL_bool convert_keycode(SDL_Keycode from, enum android_keycode *to) {
switch (from) {
MAP(SDLK_LCTRL, AKEYCODE_CTRL_LEFT);
MAP(SDLK_LSHIFT, AKEYCODE_SHIFT_LEFT);
MAP(SDLK_LALT, AKEYCODE_ALT_LEFT);
MAP(SDLK_LGUI, AKEYCODE_META_LEFT);
MAP(SDLK_RCTRL, AKEYCODE_CTRL_RIGHT);
MAP(SDLK_RSHIFT, AKEYCODE_SHIFT_RIGHT);
MAP(SDLK_RALT, AKEYCODE_ALT_RIGHT);
MAP(SDLK_RGUI, AKEYCODE_META_RIGHT);
MAP(SDLK_RETURN, AKEYCODE_ENTER);
MAP(SDLK_ESCAPE, AKEYCODE_ESCAPE);
MAP(SDLK_BACKSPACE, AKEYCODE_DEL);
MAP(SDLK_TAB, AKEYCODE_TAB);
MAP(SDLK_SPACE, AKEYCODE_SPACE);
MAP(SDLK_F1, AKEYCODE_F1);
MAP(SDLK_F2, AKEYCODE_F2);
MAP(SDLK_F3, AKEYCODE_F3);
MAP(SDLK_F4, AKEYCODE_F4);
MAP(SDLK_F5, AKEYCODE_F5);
MAP(SDLK_F6, AKEYCODE_F6);
MAP(SDLK_F7, AKEYCODE_F7);
MAP(SDLK_F8, AKEYCODE_F8);
MAP(SDLK_F9, AKEYCODE_F9);
MAP(SDLK_F10, AKEYCODE_F10);
MAP(SDLK_F11, AKEYCODE_F11);
MAP(SDLK_F12, AKEYCODE_F12);
MAP(SDLK_PRINTSCREEN, AKEYCODE_SYSRQ);
MAP(SDLK_SCROLLLOCK, AKEYCODE_SCROLL_LOCK);
MAP(SDLK_PAUSE, AKEYCODE_BREAK);
MAP(SDLK_INSERT, AKEYCODE_INSERT);
MAP(SDLK_HOME, AKEYCODE_HOME);
MAP(SDLK_PAGEUP, AKEYCODE_PAGE_UP);
MAP(SDLK_DELETE, AKEYCODE_FORWARD_DEL);
MAP(SDLK_END, AKEYCODE_MOVE_END);
MAP(SDLK_PAGEDOWN, AKEYCODE_PAGE_DOWN);
MAP(SDLK_RIGHT, AKEYCODE_DPAD_RIGHT);
MAP(SDLK_LEFT, AKEYCODE_DPAD_LEFT);
MAP(SDLK_DOWN, AKEYCODE_DPAD_DOWN);
MAP(SDLK_UP, AKEYCODE_DPAD_UP);
MAP(SDLK_NUMLOCKCLEAR, AKEYCODE_NUM_LOCK);
MAP(SDLK_KP_DIVIDE, AKEYCODE_NUMPAD_DIVIDE);
MAP(SDLK_KP_MULTIPLY, AKEYCODE_NUMPAD_MULTIPLY);
MAP(SDLK_KP_MINUS, AKEYCODE_NUMPAD_SUBTRACT);
MAP(SDLK_KP_PLUS, AKEYCODE_NUMPAD_ADD);
MAP(SDLK_KP_ENTER, AKEYCODE_NUMPAD_ENTER);
MAP(SDLK_KP_1, AKEYCODE_NUMPAD_1);
MAP(SDLK_KP_2, AKEYCODE_NUMPAD_2);
MAP(SDLK_KP_3, AKEYCODE_NUMPAD_3);
MAP(SDLK_KP_4, AKEYCODE_NUMPAD_4);
MAP(SDLK_KP_5, AKEYCODE_NUMPAD_5);
MAP(SDLK_KP_6, AKEYCODE_NUMPAD_6);
MAP(SDLK_KP_7, AKEYCODE_NUMPAD_7);
MAP(SDLK_KP_8, AKEYCODE_NUMPAD_8);
MAP(SDLK_KP_9, AKEYCODE_NUMPAD_9);
MAP(SDLK_KP_0, AKEYCODE_NUMPAD_0);
MAP(SDLK_KP_PERIOD, AKEYCODE_NUMPAD_DOT);
FAIL;
}
}
static SDL_bool convert_mouse_action(SDL_EventType from, enum android_motionevent_action *to) {
switch (from) {
MAP(SDL_MOUSEBUTTONDOWN, AMOTION_EVENT_ACTION_DOWN);
MAP(SDL_MOUSEBUTTONUP, AMOTION_EVENT_ACTION_UP);
FAIL;
}
}
static enum android_motionevent_buttons convert_mouse_buttons(Uint32 state) {
enum android_motionevent_buttons buttons = 0;
if (state & SDL_BUTTON_LMASK) {
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
}
if (state & SDL_BUTTON_RMASK) {
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
}
if (state & SDL_BUTTON_MMASK) {
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
}
if (state & SDL_BUTTON_X1) {
buttons |= AMOTION_EVENT_BUTTON_BACK;
}
if (state & SDL_BUTTON_X2) {
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
}
return buttons;
}
SDL_bool input_key_from_sdl_to_android(SDL_KeyboardEvent *from, struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_KEYCODE;
if (!convert_keycode_action(from->type, &to->keycode_event.action)) {
return SDL_FALSE;
}
if (!convert_keycode(from->keysym.sym, &to->keycode_event.keycode)) {
return SDL_FALSE;
}
to->keycode_event.metastate = convert_meta_state(from->keysym.mod);
return SDL_TRUE;
}
SDL_bool mouse_button_from_sdl_to_android(SDL_MouseButtonEvent *from, struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE;
if (!convert_mouse_action(from->type, &to->mouse_event.action)) {
return SDL_FALSE;
}
to->mouse_event.buttons = convert_mouse_buttons(SDL_BUTTON(from->button));
to->mouse_event.x = from->x;
to->mouse_event.y = from->y;
return SDL_TRUE;
}
SDL_bool mouse_motion_from_sdl_to_android(SDL_MouseMotionEvent *from, struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_MOUSE;
to->mouse_event.action = AMOTION_EVENT_ACTION_MOVE;
to->mouse_event.buttons = convert_mouse_buttons(from->state);
to->mouse_event.x = from->x;
to->mouse_event.y = from->y;
return SDL_TRUE;
}
SDL_bool mouse_wheel_from_sdl_to_android(struct complete_mouse_wheel_event *from, struct control_event *to) {
to->type = CONTROL_EVENT_TYPE_SCROLL;
to->scroll_event.x = from->x;
to->scroll_event.y = from->y;
SDL_MouseWheelEvent *wheel = from->mouse_wheel_event;
int mul = wheel->direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
to->scroll_event.hscroll = mul * wheel->x;
to->scroll_event.vscroll = mul * wheel->y;
return SDL_TRUE;
}

@ -0,0 +1,20 @@
#ifndef CONVERT_H
#define CONVERT_H
#include <SDL2/SDL_stdinc.h>
#include <SDL2/SDL_events.h>
#include "controlevent.h"
// on Android, a scroll event requires the current mouse position
struct complete_mouse_wheel_event {
SDL_MouseWheelEvent *mouse_wheel_event;
Sint32 x;
Sint32 y;
};
SDL_bool input_key_from_sdl_to_android(SDL_KeyboardEvent *from, struct control_event *to);
SDL_bool mouse_button_from_sdl_to_android(SDL_MouseButtonEvent *from, struct control_event *to);
SDL_bool mouse_motion_from_sdl_to_android(SDL_MouseMotionEvent *from, struct control_event *to);
SDL_bool mouse_wheel_from_sdl_to_android(struct complete_mouse_wheel_event *from, struct control_event *to);
#endif

@ -9,6 +9,8 @@
#include <SDL2/SDL_net.h>
#include "command.h"
#include "control.h"
#include "convert.h"
#include "decoder.h"
#include "events.h"
#include "frames.h"
@ -21,20 +23,40 @@
#define MIN(X,Y) (X) < (Y) ? (X) : (Y)
#define MAX(X,Y) (X) > (Y) ? (X) : (Y)
static struct frames frames;
static struct decoder decoder;
struct size {
Uint16 width;
Uint16 height;
};
static struct frames frames;
static struct decoder decoder;
static struct controller controller;
static SDL_Window *window;
static SDL_Renderer *renderer;
static SDL_Texture *texture;
static struct size frame_size;
static SDL_bool texture_empty = SDL_TRUE;
static SDL_bool fullscreen = SDL_FALSE;
static long timestamp_ms(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec * 1000 + tv.tv_usec / 1000;
}
static void count_frame(void) {
static long ts = 0;
static int nbframes = 0;
long now = timestamp_ms();
++nbframes;
if (now - ts > 1000) {
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%d fps", nbframes);
ts = now;
nbframes = 0;
}
}
static TCPsocket listen_on_port(Uint16 port) {
IPaddress addr = {
.host = INADDR_ANY,
@ -214,6 +236,155 @@ static int wait_for_success(process_t proc, const char *name) {
return 0;
}
static SDL_bool handle_new_frame(void) {
mutex_lock(frames.mutex);
AVFrame *frame = frames.rendering_frame;
frames.rendering_frame_consumed = SDL_TRUE;
if (!decoder.skip_frames) {
cond_signal(frames.rendering_frame_consumed_cond);
}
struct size current_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(window, renderer, &texture, frame_size, current_frame_size)) {
return SDL_FALSE;
}
frame_size = current_frame_size;
update_texture(frame, texture);
mutex_unlock(frames.mutex);
render(renderer, texture);
return SDL_TRUE;
}
static void handle_text_input(SDL_TextInputEvent *event) {
struct control_event control_event;
control_event.type = CONTROL_EVENT_TYPE_TEXT;
strncpy(control_event.text_event.text, event->text, TEXT_MAX_LENGTH);
control_event.text_event.text[TEXT_MAX_LENGTH] = '\0';
if (!controller_push_event(&controller, &control_event)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send text event");
}
}
static void handle_key(SDL_KeyboardEvent *event) {
SDL_Keycode keycode = event->keysym.sym;
SDL_bool ctrl = event->keysym.mod & (KMOD_LCTRL | KMOD_RCTRL);
SDL_bool shift = event->keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT);
SDL_bool repeat = event->repeat;
// Capture Ctrl+x: optimal size
if (keycode == SDLK_x && !repeat && ctrl && !shift) {
if (event->type == SDL_KEYDOWN) {
struct size optimal_size = get_optimal_window_size(window, frame_size);
SDL_SetWindowSize(window, optimal_size.width, optimal_size.height);
}
return;
}
// Capture Ctrl+f: switch fullscreen
if (keycode == SDLK_f && !repeat && ctrl && !shift) {
if (event->type == SDL_KEYDOWN) {
Uint32 new_mode = fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (!SDL_SetWindowFullscreen(window, new_mode)) {
fullscreen = !fullscreen;
render(renderer, texture_empty ? NULL : texture);
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not switch fullscreen mode: %s", SDL_GetError());
}
}
return;
}
struct control_event control_event;
if (input_key_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(&controller, &control_event)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send control event");
}
}
}
static void handle_mouse_motion(SDL_MouseMotionEvent *event) {
struct control_event control_event;
if (mouse_motion_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(&controller, &control_event)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send mouse motion event");
}
}
}
static void handle_mouse_button(SDL_MouseButtonEvent *event) {
struct control_event control_event;
if (mouse_button_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(&controller, &control_event)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send mouse button event");
}
}
}
static void handle_mouse_wheel(struct complete_mouse_wheel_event *event) {
struct control_event control_event;
if (mouse_wheel_from_sdl_to_android(event, &control_event)) {
if (!controller_push_event(&controller, &control_event)) {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Cannot send wheel button event");
}
}
}
void event_loop(void) {
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_DECODER_STOPPED:
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Video decoder stopped");
case SDL_QUIT:
return;
case EVENT_NEW_FRAME:
if (!handle_new_frame()) {
return;
}
texture_empty = SDL_FALSE;
count_frame(); // display fps for debug
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
render(renderer, texture_empty ? NULL : texture);
break;
}
break;
case SDL_TEXTINPUT: {
handle_text_input(&event.text);
break;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
handle_key(&event.key);
break;
case SDL_MOUSEMOTION:
handle_mouse_motion(&event.motion);
break;
case SDL_MOUSEWHEEL: {
struct complete_mouse_wheel_event complete_event;
complete_event.mouse_wheel_event = &event.wheel;
int x;
int y;
SDL_GetMouseState(&x, &y);
complete_event.x = (Sint32) x;
complete_event.y = (Sint32) y;
handle_mouse_wheel(&complete_event);
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
handle_mouse_button(&event.button);
break;
}
}
}
SDL_bool show_screen(const char *serial, Uint16 local_port) {
SDL_bool ret = 0;
@ -258,7 +429,6 @@ SDL_bool show_screen(const char *serial, Uint16 local_port) {
goto screen_finally_adb_reverse_remove;
}
struct size frame_size;
char device_name[DEVICE_NAME_FIELD_LENGTH];
// screenrecord does not send frames when the screen content does not change
@ -292,10 +462,24 @@ SDL_bool show_screen(const char *serial, Uint16 local_port) {
goto screen_finally_destroy_frames;
}
if (!controller_init(&controller, device_socket)) {
ret = SDL_FALSE;
SDLNet_TCP_Close(device_socket);
stop_server(server);
goto screen_finally_stop_decoder;
}
if (!controller_start(&controller)) {
ret = SDL_FALSE;
SDLNet_TCP_Close(device_socket);
stop_server(server);
goto screen_finally_destroy_controller;
}
if (SDL_Init(SDL_INIT_VIDEO)) {
SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Could not initialize SDL: %s", SDL_GetError());
ret = SDL_FALSE;
goto screen_finally_stop_decoder;
goto screen_finally_stop_and_join_controller;
}
// FIXME it may crash in SDL_Quit in i965_dri.so
// As a workaround, do not call SDL_Quit() (we are exiting anyway).
@ -307,7 +491,7 @@ SDL_bool show_screen(const char *serial, Uint16 local_port) {
}
struct size window_size = get_initial_optimal_size(frame_size);
SDL_Window *window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
window = SDL_CreateWindow(device_name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
window_size.width, window_size.height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
if (!window) {
SDL_LogCritical(SDL_LOG_CATEGORY_SYSTEM, "Could not create window: %s", SDL_GetError());
@ -315,7 +499,7 @@ SDL_bool show_screen(const char *serial, Uint16 local_port) {
goto screen_finally_stop_decoder;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (!renderer) {
SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Could not create renderer: %s", SDL_GetError());
ret = SDL_FALSE;
@ -329,7 +513,7 @@ SDL_bool show_screen(const char *serial, Uint16 local_port) {
}
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Initial texture: %" PRIu16 "x%" PRIu16, frame_size.width, frame_size.height);
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, frame_size.width, frame_size.height);
texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, SDL_TEXTUREACCESS_STREAMING, frame_size.width, frame_size.height);
if (!texture) {
SDL_LogCritical(SDL_LOG_CATEGORY_RENDER, "Could not create texture: %s", SDL_GetError());
ret = SDL_FALSE;
@ -339,87 +523,8 @@ SDL_bool show_screen(const char *serial, Uint16 local_port) {
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
long ts = timestamp_ms();
int nbframes = 0;
SDL_bool texture_empty = SDL_TRUE;
SDL_bool fullscreen = SDL_FALSE;
SDL_Event event;
while (SDL_WaitEvent(&event)) {
switch (event.type) {
case EVENT_DECODER_STOPPED:
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Video decoder stopped");
case SDL_QUIT:
goto screen_quit;
case EVENT_NEW_FRAME:
mutex_lock(frames.mutex);
AVFrame *frame = frames.rendering_frame;
frames.rendering_frame_consumed = SDL_TRUE;
if (!decoder.skip_frames) {
cond_signal(frames.rendering_frame_consumed_cond);
}
struct size current_frame_size = {frame->width, frame->height};
if (!prepare_for_frame(window, renderer, &texture, frame_size, current_frame_size)) {
goto screen_quit;
}
frame_size = current_frame_size;
update_texture(frame, texture);
mutex_unlock(frames.mutex);
texture_empty = SDL_FALSE;
long now = timestamp_ms();
++nbframes;
if (now - ts > 1000) {
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "%d fps", nbframes);
ts = now;
nbframes = 0;
}
render(renderer, texture);
break;
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_EXPOSED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
render(renderer, texture_empty ? NULL : texture);
break;
}
break;
case SDL_KEYDOWN: {
SDL_bool ctrl = SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL);
SDL_bool shift = SDL_GetModState() & (KMOD_LSHIFT | KMOD_RSHIFT);
SDL_bool repeat = event.key.repeat;
switch (event.key.keysym.sym) {
case SDLK_x:
if (!repeat && ctrl && !shift) {
// Ctrl+x
struct size optimal_size = get_optimal_window_size(window, frame_size);
SDL_SetWindowSize(window, optimal_size.width, optimal_size.height);
}
break;
case SDLK_f:
if (!repeat && ctrl && !shift) {
// Ctrl+f
Uint32 new_mode = fullscreen ? 0 : SDL_WINDOW_FULLSCREEN_DESKTOP;
if (!SDL_SetWindowFullscreen(window, new_mode)) {
fullscreen = !fullscreen;
render(renderer, texture_empty ? NULL : texture);
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Could not switch fullscreen mode: %s", SDL_GetError());
}
}
break;
}
break;
}
}
}
event_loop();
screen_quit:
SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "quit...");
SDL_DestroyTexture(texture);
screen_finally_destroy_renderer:
@ -429,6 +534,11 @@ screen_finally_destroy_renderer:
//SDL_DestroyRenderer(renderer);
screen_finally_destroy_window:
//SDL_DestroyWindow(window);
screen_finally_stop_and_join_controller:
controller_stop(&controller);
controller_join(&controller);
screen_finally_destroy_controller:
controller_destroy(&controller);
screen_finally_stop_decoder:
SDLNet_TCP_Close(device_socket);
// kill the server before decoder_join() to wake up the decoder

@ -11,4 +11,4 @@ size_t xstrncpy(char *dest, const char *src, size_t n);
// join tokens by sep into dst
// returns the number of chars actually written (max n-1) if no trucation
// occurred, or n if truncated
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t);
size_t xstrjoin(char *dst, const char *const tokens[], char sep, size_t n);

@ -0,0 +1,94 @@
#include <assert.h>
#include "controlevent.h"
static void test_control_event_queue_empty() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(control_event_queue_is_empty(&queue));
struct control_event dummy_event;
SDL_bool push_ok = control_event_queue_push(&queue, &dummy_event);
assert(push_ok);
assert(!control_event_queue_is_empty(&queue));
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(control_event_queue_is_empty(&queue));
SDL_bool take_empty_ok = control_event_queue_take(&queue, &dummy_event);
assert(!take_empty_ok); // the queue is empty
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_full() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
assert(!control_event_queue_is_full(&queue));
struct control_event dummy_event;
// fill the queue
while (control_event_queue_push(&queue, &dummy_event));
SDL_bool take_ok = control_event_queue_take(&queue, &dummy_event);
assert(take_ok);
assert(!control_event_queue_is_full(&queue));
control_event_queue_destroy(&queue);
}
static void test_control_event_queue_push_take() {
struct control_event_queue queue;
SDL_bool init_ok = control_event_queue_init(&queue);
assert(init_ok);
struct control_event event = {
.type = CONTROL_EVENT_TYPE_KEYCODE,
.keycode_event = {
.action = AKEY_EVENT_ACTION_DOWN,
.keycode = AKEYCODE_ENTER,
.metastate = AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON,
},
};
SDL_bool push1_ok = control_event_queue_push(&queue, &event);
assert(push1_ok);
event = (struct control_event) {
.type = CONTROL_EVENT_TYPE_TEXT,
.text_event = {
.text = "abc",
},
};
SDL_bool push2_ok = control_event_queue_push(&queue, &event);
assert(push2_ok);
// overwrite event
SDL_bool take1_ok = control_event_queue_take(&queue, &event);
assert(take1_ok);
assert(event.type == CONTROL_EVENT_TYPE_KEYCODE);
assert(event.keycode_event.action == AKEY_EVENT_ACTION_DOWN);
assert(event.keycode_event.keycode == AKEYCODE_ENTER);
assert(event.keycode_event.metastate == (AMETA_CTRL_LEFT_ON | AMETA_CTRL_ON));
// overwrite event
SDL_bool take2_ok = control_event_queue_take(&queue, &event);
assert(take2_ok);
assert(event.type == CONTROL_EVENT_TYPE_TEXT);
assert(!strcmp(event.text_event.text, "abc"));
control_event_queue_destroy(&queue);
}
int main() {
test_control_event_queue_empty();
test_control_event_queue_full();
test_control_event_queue_push_take();
return 0;
}

@ -1,9 +1,12 @@
.PHONY: jar push run clean
.PHONY: jar push run clean compile compiletests test
SRC_DIR := src
GEN_DIR := gen
CLS_DIR := classes
CLS_DEX := classes.dex
TEST_SRC_DIR := tests
TEST_CLS_DIR := test_classes
TEST_LIBS := /usr/share/java/junit4.jar:/usr/share/java/hamcrest-core.jar
BUILD_TOOLS := $(ANDROID_HOME)/build-tools/26.0.2
AIDL := $(BUILD_TOOLS)/aidl
@ -17,15 +20,24 @@ ANDROID_JAR := $(ANDROID_HOME)/platforms/android-26/android.jar
AIDL_SRC := android/view/IRotationWatcher.aidl
SRC := com/genymobile/scrcpy/ScrCpyServer.java \
com/genymobile/scrcpy/ControlEvent.java \
com/genymobile/scrcpy/ControlEventReader.java \
com/genymobile/scrcpy/DesktopConnection.java \
com/genymobile/scrcpy/DeviceUtil.java \
com/genymobile/scrcpy/EventController.java \
com/genymobile/scrcpy/ScreenInfo.java \
com/genymobile/scrcpy/ScreenStreamer.java \
com/genymobile/scrcpy/ScreenStreamerSession.java \
com/genymobile/scrcpy/wrappers/DisplayManager.java \
com/genymobile/scrcpy/wrappers/InputManager.java \
com/genymobile/scrcpy/wrappers/ServiceManager.java \
com/genymobile/scrcpy/wrappers/WindowManager.java \
TEST_SRC := com/genymobile/scrcpy/ControlEventReaderTest.java \
# generate classnames from filepath
TEST_CLS := $(subst /,.,$(basename $(TEST_SRC)))
JAR := scrcpy-server.jar
MAIN := com.genymobile.scrcpy.ScrCpyServer
@ -35,6 +47,7 @@ SRC_CLS := $(SRC:%.java=$(CLS_DIR)/%.class)
CLS := $(AIDL_CLS) $(SRC_CLS)
ALL_JAVA := $(AIDL_GEN) $(addprefix $(SRC_DIR)/,$(SRC))
ALL_TESTS := $(addprefix $(TEST_SRC_DIR)/,$(TEST_SRC))
jar: $(JAR)
@ -42,13 +55,16 @@ $(AIDL_GEN): $(GEN_DIR)/%.java : $(SRC_DIR)/%.aidl
mkdir -p $(GEN_DIR)
"$(AIDL)" -o$(GEN_DIR) $(SRC_DIR)/$(AIDL_SRC)
$(JAR): $(ALL_JAVA)
@mkdir -p $(CLS_DIR)
compile: $(ALL_JAVA)
@mkdir -p "$(CLS_DIR)"
javac -source 1.7 -target 1.7 \
-cp "$(ANDROID_JAR)" \
-d "$(CLS_DIR)" -sourcepath $(SRC_DIR):$(GEN_DIR) \
$(ALL_JAVA)
$(JAR): $(ALL_JAVA)
# we cannot track easily class dependencies, so execute compile only when jar is outdated
+$(MAKE) compile
"$(DX)" --dex --output=$(CLS_DEX) $(CLS_DIR)
jar cvf $(JAR) classes.dex
@ -59,4 +75,12 @@ run: push
adb shell "CLASSPATH=/data/local/tmp/$(JAR) app_process /system/bin $(MAIN)"
clean:
rm -rf $(CLS_DEX) $(CLS_DIR) $(GEN_DIR) $(JAR)
rm -rf $(CLS_DEX) $(CLS_DIR) $(GEN_DIR) $(JAR) $(TEST_CLS_DIR)
compiletests: compile $(ALL_TESTS)
@mkdir -p "$(TEST_CLS_DIR)"
javac -cp "$(TEST_LIBS):$(ANDROID_JAR):$(CLS_DIR)" -d "$(TEST_CLS_DIR)" -sourcepath "$(TEST_SRC_DIR)" $(ALL_TESTS)
test:
+$(MAKE) compiletests
java -cp "$(TEST_LIBS):$(ANDROID_JAR):$(CLS_DIR):$(TEST_CLS_DIR)" org.junit.runner.JUnitCore $(TEST_CLS)

@ -0,0 +1,102 @@
package com.genymobile.scrcpy;
/**
* Union of all supported event types, identified by their {@code type}.
*/
public class ControlEvent {
public static final int TYPE_KEYCODE = 0;
public static final int TYPE_TEXT = 1;
public static final int TYPE_MOUSE = 2;
public static final int TYPE_SCROLL = 3;
private int type;
private String text;
private int metaState; // KeyEvent.META_*
private int action; // KeyEvent.ACTION_* or MotionEvent.ACTION_*
private int keycode; // KeyEvent.KEYCODE_*
private int buttons; // MotionEvent.BUTTON_*
private int x;
private int y;
private int hScroll;
private int vScroll;
private ControlEvent() {
}
public static ControlEvent createKeycodeControlEvent(int action, int keycode, int metaState) {
ControlEvent event = new ControlEvent();
event.type = TYPE_KEYCODE;
event.action = action;
event.keycode = keycode;
event.metaState = metaState;
return event;
}
public static ControlEvent createTextControlEvent(String text) {
ControlEvent event = new ControlEvent();
event.type = TYPE_TEXT;
event.text = text;
return event;
}
public static ControlEvent createMotionControlEvent(int action, int buttons, int x, int y) {
ControlEvent event = new ControlEvent();
event.type = TYPE_MOUSE;
event.action = action;
event.buttons = buttons;
event.x = x;
event.y = y;
return event;
}
public static ControlEvent createScrollControlEvent(int x, int y, int hScroll, int vScroll) {
ControlEvent event = new ControlEvent();
event.type = TYPE_SCROLL;
event.x = x;
event.y = y;
event.hScroll = hScroll;
event.vScroll = vScroll;
return event;
}
public int getType() {
return type;
}
public String getText() {
return text;
}
public int getMetaState() {
return metaState;
}
public int getAction() {
return action;
}
public int getKeycode() {
return keycode;
}
public int getButtons() {
return buttons;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getHScroll() {
return hScroll;
}
public int getVScroll() {
return vScroll;
}
}

@ -0,0 +1,99 @@
package com.genymobile.scrcpy;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class ControlEventReader {
private static final int KEYCODE_PAYLOAD_LENGTH = 9;
private static final int MOUSE_PAYLOAD_LENGTH = 13;
private static final int SCROLL_PAYLOAD_LENGTH = 16;
private final byte[] rawBuffer = new byte[128];
private final ByteBuffer buffer = ByteBuffer.wrap(rawBuffer);
private final byte[] textBuffer = new byte[32];
public ControlEventReader() {
// invariant: the buffer is always in "get" mode
buffer.limit(0);
}
public boolean isFull() {
return buffer.remaining() == rawBuffer.length;
}
public boolean readFrom(InputStream input) throws IOException {
if (isFull()) {
throw new IllegalStateException("Buffer full, call next() to consume");
}
buffer.compact();
int head = buffer.position();
int r = input.read(rawBuffer, head, rawBuffer.length - head);
if (r == -1) {
return false;
}
buffer.position(head + r);
buffer.flip();
return true;
}
public ControlEvent next() {
if (!buffer.hasRemaining()) {
return null;
}
int savedPosition = buffer.position();
int type = buffer.get();
switch (type) {
case ControlEvent.TYPE_KEYCODE: {
if (buffer.remaining() < KEYCODE_PAYLOAD_LENGTH) {
break;
}
int action = buffer.get() & 0xff; // unsigned
int keycode = buffer.getInt();
int metaState = buffer.getInt();
return ControlEvent.createKeycodeControlEvent(action, keycode, metaState);
}
case ControlEvent.TYPE_TEXT: {
if (buffer.remaining() < 1) {
break;
}
int len = buffer.get() & 0xff; // unsigned
if (buffer.remaining() < len) {
break;
}
buffer.get(textBuffer, 0, len);
String text = new String(textBuffer, 0, len, StandardCharsets.UTF_8);
return ControlEvent.createTextControlEvent(text);
}
case ControlEvent.TYPE_MOUSE: {
if (buffer.remaining() < MOUSE_PAYLOAD_LENGTH) {
break;
}
int action = buffer.get() & 0xff; // unsigned
int buttons = buffer.getInt();
int x = buffer.getInt();
int y = buffer.getInt();
return ControlEvent.createMotionControlEvent(action, buttons, x, y);
}
case ControlEvent.TYPE_SCROLL: {
if (buffer.remaining() < SCROLL_PAYLOAD_LENGTH) {
break;
}
int x = buffer.getInt();
int y = buffer.getInt();
int hscroll = buffer.getInt();
int vscroll = buffer.getInt();
return ControlEvent.createScrollControlEvent(x, y, hscroll, vscroll);
}
default:
Ln.w("Unknown event type: " + type);
}
// failure, reset savedPosition
buffer.position(savedPosition);
return null;
}
}

@ -5,6 +5,8 @@ import android.net.LocalSocketAddress;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class DesktopConnection implements Closeable {
@ -14,9 +16,15 @@ public class DesktopConnection implements Closeable {
private static final String SOCKET_NAME = "scrcpy";
private final LocalSocket socket;
private final InputStream inputStream;
private final OutputStream outputStream;
private final ControlEventReader reader = new ControlEventReader();
private DesktopConnection(LocalSocket socket) throws IOException {
this.socket = socket;
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
}
private static LocalSocket connect(String abstractName) throws IOException {
@ -27,8 +35,9 @@ public class DesktopConnection implements Closeable {
public static DesktopConnection open(String deviceName, int width, int height) throws IOException {
LocalSocket socket = connect(SOCKET_NAME);
send(socket, deviceName, width, height);
return new DesktopConnection(socket);
DesktopConnection connection = new DesktopConnection(socket);
connection.send(deviceName, width, height);
return connection;
}
public void close() throws IOException {
@ -37,7 +46,7 @@ public class DesktopConnection implements Closeable {
socket.close();
}
private static void send(LocalSocket socket, String deviceName, int width, int height) throws IOException {
private void send(String deviceName, int width, int height) throws IOException {
assert width < 0x10000 : "width may not be stored on 16 bits";
assert height < 0x10000 : "height may not be stored on 16 bits";
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
@ -51,11 +60,20 @@ public class DesktopConnection implements Closeable {
buffer[DEVICE_NAME_FIELD_LENGTH + 1] = (byte) width;
buffer[DEVICE_NAME_FIELD_LENGTH + 2] = (byte) (height >> 8);
buffer[DEVICE_NAME_FIELD_LENGTH + 3] = (byte) height;
socket.getOutputStream().write(buffer, 0, buffer.length);
outputStream.write(buffer, 0, buffer.length);
}
public void sendVideoStream(byte[] videoStreamBuffer, int len) throws IOException {
socket.getOutputStream().write(videoStreamBuffer, 0, len);
outputStream.write(videoStreamBuffer, 0, len);
}
public ControlEvent receiveControlEvent() throws IOException {
ControlEvent event = reader.next();
while (event == null) {
reader.readFrom(inputStream);
event = reader.next();
}
return event;
}
}

@ -3,6 +3,7 @@ package com.genymobile.scrcpy;
import android.os.Build;
import android.view.IRotationWatcher;
import com.genymobile.scrcpy.wrappers.InputManager;
import com.genymobile.scrcpy.wrappers.ServiceManager;
public class DeviceUtil {
@ -20,4 +21,8 @@ public class DeviceUtil {
public static String getDeviceName() {
return Build.MODEL;
}
public static InputManager getInputManager() {
return serviceManager.getInputManager();
}
}

@ -0,0 +1,127 @@
package com.genymobile.scrcpy;
import android.os.SystemClock;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import com.genymobile.scrcpy.wrappers.InputManager;
import java.io.IOException;
public class EventController {
private final InputManager inputManager;
private final DesktopConnection connection;
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
private long lastMouseDown;
private final MotionEvent.PointerProperties[] pointerProperties = { new MotionEvent.PointerProperties() };
private final MotionEvent.PointerCoords[] pointerCoords = { new MotionEvent.PointerCoords() };
public EventController(DesktopConnection connection) {
this.connection = connection;
inputManager = DeviceUtil.getInputManager();
initPointer();
}
private void initPointer() {
MotionEvent.PointerProperties props = pointerProperties[0];
props.id = 0;
props.toolType = MotionEvent.TOOL_TYPE_MOUSE;
MotionEvent.PointerCoords coords = pointerCoords[0];
coords.orientation = 0;
coords.pressure = 1;
coords.size = 1;
coords.toolMajor = 1;
coords.toolMinor = 1;
coords.touchMajor = 1;
coords.touchMinor = 1;
}
private void setPointerCoords(int x, int y) {
MotionEvent.PointerCoords coords = pointerCoords[0];
coords.x = x;
coords.y = y;
}
private void setScroll(int hScroll, int vScroll) {
MotionEvent.PointerCoords coords = pointerCoords[0];
coords.setAxisValue(MotionEvent.AXIS_SCROLL, hScroll);
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
}
public void control() throws IOException {
while (handleEvent());
}
private boolean handleEvent() throws IOException {
ControlEvent controlEvent = connection.receiveControlEvent();
if (controlEvent == null) {
return false;
}
switch (controlEvent.getType()) {
case ControlEvent.TYPE_KEYCODE:
injectKeycode(controlEvent.getAction(), controlEvent.getKeycode(), controlEvent.getMetaState());
break;
case ControlEvent.TYPE_TEXT:
injectText(controlEvent.getText());
break;
case ControlEvent.TYPE_MOUSE:
injectMouse(controlEvent.getAction(), controlEvent.getButtons(), controlEvent.getX(), controlEvent.getY());
break;
case ControlEvent.TYPE_SCROLL:
injectScroll(controlEvent.getButtons(), controlEvent.getX(), controlEvent.getY(), controlEvent.getHScroll(), controlEvent.getVScroll());
}
return true;
}
private boolean injectKeycode(int action, int keycode, int metaState) {
return injectKeyEvent(action, keycode, 0, metaState);
}
private boolean injectText(String text) {
KeyEvent[] events = charMap.getEvents(text.toCharArray());
if (events == null) {
return false;
}
for (KeyEvent event : events) {
if (!injectEvent(event)) {
return false;
}
}
return true;
}
private boolean injectMouse(int action, int buttons, int x, int y) {
long now = SystemClock.uptimeMillis();
if (action == MotionEvent.ACTION_DOWN) {
lastMouseDown = now;
}
setPointerCoords(x, y);
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, action, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
return injectEvent(event);
}
private boolean injectScroll(int buttons, int x, int y, int hScroll, int vScroll) {
long now = SystemClock.uptimeMillis();
setPointerCoords(x, y);
setScroll(hScroll, vScroll);
MotionEvent event = MotionEvent.obtain(lastMouseDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, 0, 0, InputDevice.SOURCE_MOUSE, 0);
return injectEvent(event);
}
private boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState) {
long now = SystemClock.uptimeMillis();
KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, InputDevice.SOURCE_KEYBOARD);
return injectEvent(event);
}
private boolean injectEvent(InputEvent event) {
return inputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}
}

@ -6,13 +6,16 @@ public class ScrCpyServer {
private static final String TAG = "scrcpy";
public static void scrcpy() throws IOException {
private static void scrcpy() throws IOException {
String deviceName = DeviceUtil.getDeviceName();
ScreenInfo initialScreenInfo = DeviceUtil.getScreenInfo();
int width = initialScreenInfo.getLogicalWidth();
int height = initialScreenInfo.getLogicalHeight();
try (DesktopConnection connection = DesktopConnection.open(deviceName, width, height)) {
try {
// asynchronous
startEventController(connection);
// synchronous
new ScreenStreamer(connection).streamScreen();
} catch (IOException e) {
Ln.e("Screen streaming interrupted", e);
@ -20,6 +23,19 @@ public class ScrCpyServer {
}
}
private static void startEventController(final DesktopConnection connection) {
new Thread(new Runnable() {
@Override
public void run() {
try {
new EventController(connection).control();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public static void main(String... args) throws Exception {
try {
scrcpy();

@ -0,0 +1,34 @@
package com.genymobile.scrcpy.wrappers;
import android.os.IInterface;
import android.view.InputEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class InputManager {
public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0;
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
private final IInterface manager;
private final Method injectInputEventMethod;
public InputManager(IInterface manager) {
this.manager = manager;
try {
injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
public boolean injectInputEvent(InputEvent inputEvent, int mode) {
try {
return (Boolean) injectInputEventMethod.invoke(manager, inputEvent, mode);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
}

@ -33,4 +33,8 @@ public class ServiceManager {
public DisplayManager getDisplayManager() {
return new DisplayManager(getService("display", "android.hardware.display.IDisplayManager"));
}
public InputManager getInputManager() {
return new InputManager(getService("input", "android.hardware.input.IInputManager"));
}
}

@ -0,0 +1,151 @@
package com.genymobile.scrcpy;
import android.view.KeyEvent;
import android.view.MotionEvent;
import org.junit.Assert;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ControlEventReaderTest {
@Test
public void testParseKeycodeEvent() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@Test
public void testParseTextEvent() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_TEXT);
byte[] text = "testé".getBytes(StandardCharsets.UTF_8);
dos.writeByte(text.length);
dos.write("testé".getBytes(StandardCharsets.UTF_8));
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_TEXT, event.getType());
Assert.assertEquals("testé", event.getText());
}
@Test
public void testParseMouseEvent() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@Test
public void testMultiEvents() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlEvent.TYPE_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN);
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(KeyEvent.META_CTRL_ON);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
@Test
public void testPartialEvents() throws IOException {
ControlEventReader reader = new ControlEventReader();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(ControlEvent.TYPE_KEYCODE);
dos.writeByte(KeyEvent.ACTION_UP);
dos.writeInt(KeyEvent.KEYCODE_ENTER);
dos.writeInt(KeyEvent.META_CTRL_ON);
dos.writeByte(ControlEvent.TYPE_KEYCODE);
dos.writeByte(MotionEvent.ACTION_DOWN);
byte[] packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
ControlEvent event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
Assert.assertEquals(KeyEvent.ACTION_UP, event.getAction());
Assert.assertEquals(KeyEvent.KEYCODE_ENTER, event.getKeycode());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
event = reader.next();
Assert.assertNull(event); // the event is not complete
bos.reset();
dos.writeInt(MotionEvent.BUTTON_PRIMARY);
dos.writeInt(KeyEvent.META_CTRL_ON);
packet = bos.toByteArray();
reader.readFrom(new ByteArrayInputStream(packet));
// the event is now complete
event = reader.next();
Assert.assertEquals(ControlEvent.TYPE_KEYCODE, event.getType());
Assert.assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
Assert.assertEquals(MotionEvent.BUTTON_PRIMARY, event.getKeycode());
Assert.assertEquals(KeyEvent.META_CTRL_ON, event.getMetaState());
}
}
Loading…
Cancel
Save