mirror of https://github.com/Genymobile/scrcpy
Merge branch 'Genymobile:master' into master
commit
89ffda29e7
@ -0,0 +1 @@
|
|||||||
|
/work
|
@ -0,0 +1,27 @@
|
|||||||
|
This directory (app/deps/) contains:
|
||||||
|
|
||||||
|
*.sh : shell scripts to download and build dependencies
|
||||||
|
|
||||||
|
patches/ : patches to fix dependencies (used by scripts)
|
||||||
|
|
||||||
|
work/sources/ : downloaded tarballs and extracted folders
|
||||||
|
ffmpeg-6.1.1.tar.xz
|
||||||
|
ffmpeg-6.1.1/
|
||||||
|
libusb-1.0.27.tar.gz
|
||||||
|
libusb-1.0.27/
|
||||||
|
...
|
||||||
|
work/build/ : build dirs for each dependency/version/architecture
|
||||||
|
ffmpeg-6.1.1/win32/
|
||||||
|
ffmpeg-6.1.1/win64/
|
||||||
|
libusb-1.0.27/win32/
|
||||||
|
libusb-1.0.27/win64/
|
||||||
|
...
|
||||||
|
work/install/ : install dirs for each architexture
|
||||||
|
win32/bin/
|
||||||
|
win32/include/
|
||||||
|
win32/lib/
|
||||||
|
win32/share/
|
||||||
|
win64/bin/
|
||||||
|
win64/include/
|
||||||
|
win64/lib/
|
||||||
|
win64/share/
|
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
. common
|
||||||
|
|
||||||
|
VERSION=34.0.5
|
||||||
|
FILENAME=platform-tools_r$VERSION-windows.zip
|
||||||
|
PROJECT_DIR=platform-tools-$VERSION
|
||||||
|
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
||||||
|
|
||||||
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
|
if [[ -d "$PROJECT_DIR" ]]
|
||||||
|
then
|
||||||
|
echo "$PWD/$PROJECT_DIR" found
|
||||||
|
else
|
||||||
|
get_file "https://dl.google.com/android/repository/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||||
|
mkdir -p "$PROJECT_DIR"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
ZIP_PREFIX=platform-tools
|
||||||
|
unzip "../$FILENAME" \
|
||||||
|
"$ZIP_PREFIX"/AdbWinApi.dll \
|
||||||
|
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
|
||||||
|
"$ZIP_PREFIX"/adb.exe
|
||||||
|
mv "$ZIP_PREFIX"/* .
|
||||||
|
rmdir "$ZIP_PREFIX"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR/$HOST/bin"
|
||||||
|
cd "$INSTALL_DIR/$HOST/bin"
|
||||||
|
cp -r "$SOURCES_DIR/$PROJECT_DIR"/. "$INSTALL_DIR/$HOST/bin/"
|
@ -0,0 +1,55 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# This file is intended to be sourced by other scripts, not executed
|
||||||
|
|
||||||
|
if [[ $# != 1 ]]
|
||||||
|
then
|
||||||
|
# <host>: win32 or win64
|
||||||
|
echo "Syntax: $0 <host>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOST="$1"
|
||||||
|
|
||||||
|
if [[ "$HOST" = win32 ]]
|
||||||
|
then
|
||||||
|
HOST_TRIPLET=i686-w64-mingw32
|
||||||
|
elif [[ "$HOST" = win64 ]]
|
||||||
|
then
|
||||||
|
HOST_TRIPLET=x86_64-w64-mingw32
|
||||||
|
else
|
||||||
|
echo "Unsupported host: $HOST" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
|
||||||
|
PATCHES_DIR="$PWD/patches"
|
||||||
|
|
||||||
|
WORK_DIR="$PWD/work"
|
||||||
|
SOURCES_DIR="$WORK_DIR/sources"
|
||||||
|
BUILD_DIR="$WORK_DIR/build"
|
||||||
|
INSTALL_DIR="$WORK_DIR/install"
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR" "$SOURCES_DIR" "$WORK_DIR"
|
||||||
|
|
||||||
|
checksum() {
|
||||||
|
local file="$1"
|
||||||
|
local sum="$2"
|
||||||
|
echo "$file: verifying checksum..."
|
||||||
|
echo "$sum $file" | sha256sum -c
|
||||||
|
}
|
||||||
|
|
||||||
|
get_file() {
|
||||||
|
local url="$1"
|
||||||
|
local file="$2"
|
||||||
|
local sum="$3"
|
||||||
|
if [[ -f "$file" ]]
|
||||||
|
then
|
||||||
|
echo "$file: found"
|
||||||
|
else
|
||||||
|
echo "$file: not found, downloading..."
|
||||||
|
wget "$url" -O "$file"
|
||||||
|
fi
|
||||||
|
checksum "$file" "$sum"
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
. common
|
||||||
|
|
||||||
|
VERSION=6.1.1
|
||||||
|
FILENAME=ffmpeg-$VERSION.tar.xz
|
||||||
|
PROJECT_DIR=ffmpeg-$VERSION
|
||||||
|
SHA256SUM=8684f4b00f94b85461884c3719382f1261f0d9eb3d59640a1f4ac0873616f968
|
||||||
|
|
||||||
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
|
if [[ -d "$PROJECT_DIR" ]]
|
||||||
|
then
|
||||||
|
echo "$PWD/$PROJECT_DIR" found
|
||||||
|
else
|
||||||
|
get_file "https://ffmpeg.org/releases/$FILENAME" "$FILENAME" "$SHA256SUM"
|
||||||
|
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||||
|
patch -d "$PROJECT_DIR" -p1 < "$PATCHES_DIR"/ffmpeg-6.1-fix-build.patch
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
|
||||||
|
if [[ "$HOST" = win32 ]]
|
||||||
|
then
|
||||||
|
ARCH=x86
|
||||||
|
elif [[ "$HOST" = win64 ]]
|
||||||
|
then
|
||||||
|
ARCH=x86_64
|
||||||
|
else
|
||||||
|
echo "Unsupported host: $HOST" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -static-libgcc to avoid missing libgcc_s_dw2-1.dll
|
||||||
|
# -static to avoid dynamic dependency to zlib
|
||||||
|
export CFLAGS='-static-libgcc -static'
|
||||||
|
export CXXFLAGS="$CFLAGS"
|
||||||
|
export LDFLAGS='-static-libgcc -static'
|
||||||
|
|
||||||
|
if [[ -d "$HOST" ]]
|
||||||
|
then
|
||||||
|
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||||
|
cd "$HOST"
|
||||||
|
else
|
||||||
|
mkdir "$HOST"
|
||||||
|
cd "$HOST"
|
||||||
|
|
||||||
|
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||||
|
--prefix="$INSTALL_DIR/$HOST" \
|
||||||
|
--enable-cross-compile \
|
||||||
|
--target-os=mingw32 \
|
||||||
|
--arch="$ARCH" \
|
||||||
|
--cross-prefix="${HOST_TRIPLET}-" \
|
||||||
|
--cc="${HOST_TRIPLET}-gcc" \
|
||||||
|
--extra-cflags="-O2 -fPIC" \
|
||||||
|
--enable-shared \
|
||||||
|
--disable-static \
|
||||||
|
--disable-programs \
|
||||||
|
--disable-doc \
|
||||||
|
--disable-swscale \
|
||||||
|
--disable-postproc \
|
||||||
|
--disable-avfilter \
|
||||||
|
--disable-avdevice \
|
||||||
|
--disable-network \
|
||||||
|
--disable-everything \
|
||||||
|
--enable-swresample \
|
||||||
|
--enable-decoder=h264 \
|
||||||
|
--enable-decoder=hevc \
|
||||||
|
--enable-decoder=av1 \
|
||||||
|
--enable-decoder=pcm_s16le \
|
||||||
|
--enable-decoder=opus \
|
||||||
|
--enable-decoder=aac \
|
||||||
|
--enable-decoder=flac \
|
||||||
|
--enable-decoder=png \
|
||||||
|
--enable-protocol=file \
|
||||||
|
--enable-demuxer=image2 \
|
||||||
|
--enable-parser=png \
|
||||||
|
--enable-zlib \
|
||||||
|
--enable-muxer=matroska \
|
||||||
|
--enable-muxer=mp4 \
|
||||||
|
--enable-muxer=opus \
|
||||||
|
--enable-muxer=flac \
|
||||||
|
--enable-muxer=wav \
|
||||||
|
--disable-vulkan
|
||||||
|
fi
|
||||||
|
|
||||||
|
make -j
|
||||||
|
make install
|
@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
. common
|
||||||
|
|
||||||
|
VERSION=1.0.27
|
||||||
|
FILENAME=libusb-$VERSION.tar.bz2
|
||||||
|
PROJECT_DIR=libusb-$VERSION
|
||||||
|
SHA256SUM=ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575
|
||||||
|
|
||||||
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
|
if [[ -d "$PROJECT_DIR" ]]
|
||||||
|
then
|
||||||
|
echo "$PWD/$PROJECT_DIR" found
|
||||||
|
else
|
||||||
|
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/libusb-$VERSION.tar.bz2" "$FILENAME" "$SHA256SUM"
|
||||||
|
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
|
||||||
|
export CFLAGS='-O2'
|
||||||
|
export CXXFLAGS="$CFLAGS"
|
||||||
|
|
||||||
|
if [[ -d "$HOST" ]]
|
||||||
|
then
|
||||||
|
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||||
|
cd "$HOST"
|
||||||
|
else
|
||||||
|
mkdir "$HOST"
|
||||||
|
cd "$HOST"
|
||||||
|
|
||||||
|
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||||
|
--prefix="$INSTALL_DIR/$HOST" \
|
||||||
|
--host="$HOST_TRIPLET" \
|
||||||
|
--enable-shared \
|
||||||
|
--disable-static
|
||||||
|
fi
|
||||||
|
|
||||||
|
make -j
|
||||||
|
make install-strip
|
@ -0,0 +1,27 @@
|
|||||||
|
From 03c80197afb324da38c9b70254231e3fdcfa68fc Mon Sep 17 00:00:00 2001
|
||||||
|
From: Romain Vimont <rom@rom1v.com>
|
||||||
|
Date: Sun, 12 Nov 2023 17:58:50 +0100
|
||||||
|
Subject: [PATCH] Fix FFmpeg 6.1 build
|
||||||
|
|
||||||
|
Build failed on tag n6.1 With --enable-decoder=av1 but without
|
||||||
|
--enable-muxer=av1.
|
||||||
|
---
|
||||||
|
libavcodec/Makefile | 2 +-
|
||||||
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||||
|
|
||||||
|
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
|
||||||
|
index 580a8d6b54..aff19b670c 100644
|
||||||
|
--- a/libavcodec/Makefile
|
||||||
|
+++ b/libavcodec/Makefile
|
||||||
|
@@ -249,7 +249,7 @@ OBJS-$(CONFIG_ATRAC3PAL_DECODER) += atrac3plusdec.o atrac3plus.o \
|
||||||
|
OBJS-$(CONFIG_ATRAC9_DECODER) += atrac9dec.o
|
||||||
|
OBJS-$(CONFIG_AURA_DECODER) += cyuv.o
|
||||||
|
OBJS-$(CONFIG_AURA2_DECODER) += aura.o
|
||||||
|
-OBJS-$(CONFIG_AV1_DECODER) += av1dec.o
|
||||||
|
+OBJS-$(CONFIG_AV1_DECODER) += av1dec.o av1_parse.o
|
||||||
|
OBJS-$(CONFIG_AV1_CUVID_DECODER) += cuviddec.o
|
||||||
|
OBJS-$(CONFIG_AV1_MEDIACODEC_DECODER) += mediacodecdec.o
|
||||||
|
OBJS-$(CONFIG_AV1_MEDIACODEC_ENCODER) += mediacodecenc.o
|
||||||
|
--
|
||||||
|
2.42.0
|
||||||
|
|
@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
DEPS_DIR=$(dirname ${BASH_SOURCE[0]})
|
||||||
|
cd "$DEPS_DIR"
|
||||||
|
. common
|
||||||
|
|
||||||
|
VERSION=2.28.5
|
||||||
|
FILENAME=SDL-$VERSION.tar.gz
|
||||||
|
PROJECT_DIR=SDL-release-$VERSION
|
||||||
|
SHA256SUM=9f0556e4a24ef5b267010038ad9e9948b62f236d5bcc4b22179f95ef62d84023
|
||||||
|
|
||||||
|
cd "$SOURCES_DIR"
|
||||||
|
|
||||||
|
if [[ -d "$PROJECT_DIR" ]]
|
||||||
|
then
|
||||||
|
echo "$PWD/$PROJECT_DIR" found
|
||||||
|
else
|
||||||
|
get_file "https://github.com/libsdl-org/SDL/archive/refs/tags/release-$VERSION.tar.gz" "$FILENAME" "$SHA256SUM"
|
||||||
|
tar xf "$FILENAME" # First level directory is "$PROJECT_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
cd "$BUILD_DIR/$PROJECT_DIR"
|
||||||
|
|
||||||
|
export CFLAGS='-O2'
|
||||||
|
export CXXFLAGS="$CFLAGS"
|
||||||
|
|
||||||
|
if [[ -d "$HOST" ]]
|
||||||
|
then
|
||||||
|
echo "'$PWD/$HOST' already exists, not reconfigured"
|
||||||
|
cd "$HOST"
|
||||||
|
else
|
||||||
|
mkdir "$HOST"
|
||||||
|
cd "$HOST"
|
||||||
|
|
||||||
|
"$SOURCES_DIR/$PROJECT_DIR"/configure \
|
||||||
|
--prefix="$INSTALL_DIR/$HOST" \
|
||||||
|
--host="$HOST_TRIPLET" \
|
||||||
|
--enable-shared \
|
||||||
|
--disable-static
|
||||||
|
fi
|
||||||
|
|
||||||
|
make -j
|
||||||
|
# There is no "make install-strip"
|
||||||
|
make install
|
||||||
|
# Strip manually
|
||||||
|
${HOST_TRIPLET}-strip "$INSTALL_DIR/$HOST/bin/SDL2.dll"
|
@ -1 +0,0 @@
|
|||||||
/data
|
|
@ -1,22 +0,0 @@
|
|||||||
PREBUILT_DATA_DIR=data
|
|
||||||
|
|
||||||
checksum() {
|
|
||||||
local file="$1"
|
|
||||||
local sum="$2"
|
|
||||||
echo "$file: verifying checksum..."
|
|
||||||
echo "$sum $file" | sha256sum -c
|
|
||||||
}
|
|
||||||
|
|
||||||
get_file() {
|
|
||||||
local url="$1"
|
|
||||||
local file="$2"
|
|
||||||
local sum="$3"
|
|
||||||
if [[ -f "$file" ]]
|
|
||||||
then
|
|
||||||
echo "$file: found"
|
|
||||||
else
|
|
||||||
echo "$file: not found, downloading..."
|
|
||||||
wget "$url" -O "$file"
|
|
||||||
fi
|
|
||||||
checksum "$file" "$sum"
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DIR"
|
|
||||||
. common
|
|
||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
|
||||||
cd "$PREBUILT_DATA_DIR"
|
|
||||||
|
|
||||||
DEP_DIR=platform-tools-34.0.5
|
|
||||||
|
|
||||||
FILENAME=platform-tools_r34.0.5-windows.zip
|
|
||||||
SHA256SUM=3f8320152704377de150418a3c4c9d07d16d80a6c0d0d8f7289c22c499e33571
|
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$DEP_DIR" found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_file "https://dl.google.com/android/repository/$FILENAME" \
|
|
||||||
"$FILENAME" "$SHA256SUM"
|
|
||||||
|
|
||||||
mkdir "$DEP_DIR"
|
|
||||||
cd "$DEP_DIR"
|
|
||||||
|
|
||||||
ZIP_PREFIX=platform-tools
|
|
||||||
unzip "../$FILENAME" \
|
|
||||||
"$ZIP_PREFIX"/AdbWinApi.dll \
|
|
||||||
"$ZIP_PREFIX"/AdbWinUsbApi.dll \
|
|
||||||
"$ZIP_PREFIX"/adb.exe
|
|
||||||
mv "$ZIP_PREFIX"/* .
|
|
||||||
rmdir "$ZIP_PREFIX"
|
|
@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DIR"
|
|
||||||
. common
|
|
||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
|
||||||
cd "$PREBUILT_DATA_DIR"
|
|
||||||
|
|
||||||
VERSION=6.1-scrcpy-3
|
|
||||||
DEP_DIR="ffmpeg-$VERSION"
|
|
||||||
|
|
||||||
FILENAME="$DEP_DIR".7z
|
|
||||||
SHA256SUM=b646d18a3d543a4e4c46881568213499f22e4454a464e1552f03f2ac9cc3a05a
|
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$DEP_DIR" found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_file "https://github.com/rom1v/scrcpy-deps/releases/download/$VERSION/$FILENAME" \
|
|
||||||
"$FILENAME" "$SHA256SUM"
|
|
||||||
|
|
||||||
mkdir "$DEP_DIR"
|
|
||||||
cd "$DEP_DIR"
|
|
||||||
|
|
||||||
ZIP_PREFIX=ffmpeg
|
|
||||||
7z x "../$FILENAME"
|
|
||||||
mv "$ZIP_PREFIX"/* .
|
|
||||||
rmdir "$ZIP_PREFIX"
|
|
@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DIR"
|
|
||||||
. common
|
|
||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
|
||||||
cd "$PREBUILT_DATA_DIR"
|
|
||||||
|
|
||||||
VERSION=1.0.26
|
|
||||||
DEP_DIR="libusb-$VERSION"
|
|
||||||
|
|
||||||
FILENAME="libusb-$VERSION-binaries.7z"
|
|
||||||
SHA256SUM=9c242696342dbde9cdc47239391f71833939bf9f7aa2bbb28cdaabe890465ec5
|
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$DEP_DIR" found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_file "https://github.com/libusb/libusb/releases/download/v$VERSION/$FILENAME" \
|
|
||||||
"$FILENAME" "$SHA256SUM"
|
|
||||||
|
|
||||||
mkdir "$DEP_DIR"
|
|
||||||
cd "$DEP_DIR"
|
|
||||||
|
|
||||||
7z x "../$FILENAME" \
|
|
||||||
"libusb-$VERSION-binaries/libusb-MinGW-Win32/" \
|
|
||||||
"libusb-$VERSION-binaries/libusb-MinGW-Win32/" \
|
|
||||||
"libusb-$VERSION-binaries/libusb-MinGW-x64/" \
|
|
||||||
"libusb-$VERSION-binaries/libusb-MinGW-x64/"
|
|
||||||
|
|
||||||
mv "libusb-$VERSION-binaries/libusb-MinGW-Win32" .
|
|
||||||
mv "libusb-$VERSION-binaries/libusb-MinGW-x64" .
|
|
||||||
rm -rf "libusb-$VERSION-binaries"
|
|
||||||
|
|
||||||
# Rename the dll to get the same library name on all platforms
|
|
||||||
mv libusb-MinGW-Win32/bin/msys-usb-1.0.dll libusb-MinGW-Win32/bin/libusb-1.0.dll
|
|
||||||
mv libusb-MinGW-x64/bin/msys-usb-1.0.dll libusb-MinGW-x64/bin/libusb-1.0.dll
|
|
@ -1,34 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
DIR=$(dirname ${BASH_SOURCE[0]})
|
|
||||||
cd "$DIR"
|
|
||||||
. common
|
|
||||||
mkdir -p "$PREBUILT_DATA_DIR"
|
|
||||||
cd "$PREBUILT_DATA_DIR"
|
|
||||||
|
|
||||||
VERSION=2.28.5
|
|
||||||
DEP_DIR="SDL2-$VERSION"
|
|
||||||
|
|
||||||
FILENAME="SDL2-devel-$VERSION-mingw.tar.gz"
|
|
||||||
SHA256SUM=3c0c655c2ebf67cad48fead72761d1601740ded30808952c3274ba223d226c21
|
|
||||||
|
|
||||||
if [[ -d "$DEP_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$DEP_DIR" found
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
get_file "https://github.com/libsdl-org/SDL/releases/download/release-$VERSION/$FILENAME" \
|
|
||||||
"$FILENAME" "$SHA256SUM"
|
|
||||||
|
|
||||||
mkdir "$DEP_DIR"
|
|
||||||
cd "$DEP_DIR"
|
|
||||||
|
|
||||||
TAR_PREFIX="$DEP_DIR" # root directory inside the tar has the same name
|
|
||||||
tar xf "../$FILENAME" --strip-components=1 \
|
|
||||||
"$TAR_PREFIX"/i686-w64-mingw32/bin/SDL2.dll \
|
|
||||||
"$TAR_PREFIX"/i686-w64-mingw32/include/ \
|
|
||||||
"$TAR_PREFIX"/i686-w64-mingw32/lib/ \
|
|
||||||
"$TAR_PREFIX"/x86_64-w64-mingw32/bin/SDL2.dll \
|
|
||||||
"$TAR_PREFIX"/x86_64-w64-mingw32/include/ \
|
|
||||||
"$TAR_PREFIX"/x86_64-w64-mingw32/lib/ \
|
|
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef SC_HID_EVENT_H
|
||||||
|
#define SC_HID_EVENT_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define SC_HID_MAX_SIZE 8
|
||||||
|
|
||||||
|
struct sc_hid_event {
|
||||||
|
uint8_t data[SC_HID_MAX_SIZE];
|
||||||
|
uint8_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,192 @@
|
|||||||
|
#include "hid_mouse.h"
|
||||||
|
|
||||||
|
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position,
|
||||||
|
// 1 byte for wheel motion
|
||||||
|
#define HID_MOUSE_EVENT_SIZE 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mouse descriptor from the specification:
|
||||||
|
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
||||||
|
*
|
||||||
|
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
||||||
|
*
|
||||||
|
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
||||||
|
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
||||||
|
* §4 Generic Desktop Page (0x01) (p26)
|
||||||
|
*/
|
||||||
|
const uint8_t SC_HID_MOUSE_REPORT_DESC[] = {
|
||||||
|
// Usage Page (Generic Desktop)
|
||||||
|
0x05, 0x01,
|
||||||
|
// Usage (Mouse)
|
||||||
|
0x09, 0x02,
|
||||||
|
|
||||||
|
// Collection (Application)
|
||||||
|
0xA1, 0x01,
|
||||||
|
|
||||||
|
// Usage (Pointer)
|
||||||
|
0x09, 0x01,
|
||||||
|
|
||||||
|
// Collection (Physical)
|
||||||
|
0xA1, 0x00,
|
||||||
|
|
||||||
|
// Usage Page (Buttons)
|
||||||
|
0x05, 0x09,
|
||||||
|
|
||||||
|
// Usage Minimum (1)
|
||||||
|
0x19, 0x01,
|
||||||
|
// Usage Maximum (5)
|
||||||
|
0x29, 0x05,
|
||||||
|
// Logical Minimum (0)
|
||||||
|
0x15, 0x00,
|
||||||
|
// Logical Maximum (1)
|
||||||
|
0x25, 0x01,
|
||||||
|
// Report Count (5)
|
||||||
|
0x95, 0x05,
|
||||||
|
// Report Size (1)
|
||||||
|
0x75, 0x01,
|
||||||
|
// Input (Data, Variable, Absolute): 5 buttons bits
|
||||||
|
0x81, 0x02,
|
||||||
|
|
||||||
|
// Report Count (1)
|
||||||
|
0x95, 0x01,
|
||||||
|
// Report Size (3)
|
||||||
|
0x75, 0x03,
|
||||||
|
// Input (Constant): 3 bits padding
|
||||||
|
0x81, 0x01,
|
||||||
|
|
||||||
|
// Usage Page (Generic Desktop)
|
||||||
|
0x05, 0x01,
|
||||||
|
// Usage (X)
|
||||||
|
0x09, 0x30,
|
||||||
|
// Usage (Y)
|
||||||
|
0x09, 0x31,
|
||||||
|
// Usage (Wheel)
|
||||||
|
0x09, 0x38,
|
||||||
|
// Local Minimum (-127)
|
||||||
|
0x15, 0x81,
|
||||||
|
// Local Maximum (127)
|
||||||
|
0x25, 0x7F,
|
||||||
|
// Report Size (8)
|
||||||
|
0x75, 0x08,
|
||||||
|
// Report Count (3)
|
||||||
|
0x95, 0x03,
|
||||||
|
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
|
||||||
|
0x81, 0x06,
|
||||||
|
|
||||||
|
// End Collection
|
||||||
|
0xC0,
|
||||||
|
|
||||||
|
// End Collection
|
||||||
|
0xC0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t SC_HID_MOUSE_REPORT_DESC_LEN =
|
||||||
|
sizeof(SC_HID_MOUSE_REPORT_DESC);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mouse HID event is 4 bytes long:
|
||||||
|
*
|
||||||
|
* - byte 0: buttons state
|
||||||
|
* - byte 1: relative x motion (signed byte from -127 to 127)
|
||||||
|
* - byte 2: relative y motion (signed byte from -127 to 127)
|
||||||
|
* - byte 3: wheel motion (-1, 0 or 1)
|
||||||
|
*
|
||||||
|
* 7 6 5 4 3 2 1 0
|
||||||
|
* +---------------+
|
||||||
|
* byte 0: |0 0 0 . . . . .| buttons state
|
||||||
|
* +---------------+
|
||||||
|
* ^ ^ ^ ^ ^
|
||||||
|
* | | | | `- left button
|
||||||
|
* | | | `--- right button
|
||||||
|
* | | `----- middle button
|
||||||
|
* | `------- button 4
|
||||||
|
* `--------- button 5
|
||||||
|
*
|
||||||
|
* +---------------+
|
||||||
|
* byte 1: |. . . . . . . .| relative x motion
|
||||||
|
* +---------------+
|
||||||
|
* byte 2: |. . . . . . . .| relative y motion
|
||||||
|
* +---------------+
|
||||||
|
* byte 3: |. . . . . . . .| wheel motion
|
||||||
|
* +---------------+
|
||||||
|
*
|
||||||
|
* As an example, here is the report for a motion of (x=5, y=-4) with left
|
||||||
|
* button pressed:
|
||||||
|
*
|
||||||
|
* +---------------+
|
||||||
|
* |0 0 0 0 0 0 0 1| left button pressed
|
||||||
|
* +---------------+
|
||||||
|
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
|
||||||
|
* +---------------+
|
||||||
|
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
|
||||||
|
* +---------------+
|
||||||
|
* |0 0 0 0 0 0 0 0| wheel motion
|
||||||
|
* +---------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
|
||||||
|
hid_event->size = HID_MOUSE_EVENT_SIZE;
|
||||||
|
// Leave hid_event->data uninitialized, it will be fully initialized by
|
||||||
|
// callers
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t
|
||||||
|
sc_hid_buttons_from_buttons_state(uint8_t buttons_state) {
|
||||||
|
uint8_t c = 0;
|
||||||
|
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
|
||||||
|
c |= 1 << 0;
|
||||||
|
}
|
||||||
|
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
|
||||||
|
c |= 1 << 1;
|
||||||
|
}
|
||||||
|
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
|
||||||
|
c |= 1 << 2;
|
||||||
|
}
|
||||||
|
if (buttons_state & SC_MOUSE_BUTTON_X1) {
|
||||||
|
c |= 1 << 3;
|
||||||
|
}
|
||||||
|
if (buttons_state & SC_MOUSE_BUTTON_X2) {
|
||||||
|
c |= 1 << 4;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
||||||
|
const struct sc_mouse_motion_event *event) {
|
||||||
|
sc_hid_mouse_event_init(hid_event);
|
||||||
|
|
||||||
|
uint8_t *data = hid_event->data;
|
||||||
|
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||||
|
data[1] = CLAMP(event->xrel, -127, 127);
|
||||||
|
data[2] = CLAMP(event->yrel, -127, 127);
|
||||||
|
data[3] = 0; // wheel coordinates only used for scrolling
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
||||||
|
const struct sc_mouse_click_event *event) {
|
||||||
|
sc_hid_mouse_event_init(hid_event);
|
||||||
|
|
||||||
|
uint8_t *data = hid_event->data;
|
||||||
|
data[0] = sc_hid_buttons_from_buttons_state(event->buttons_state);
|
||||||
|
data[1] = 0; // no x motion
|
||||||
|
data[2] = 0; // no y motion
|
||||||
|
data[3] = 0; // wheel coordinates only used for scrolling
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
||||||
|
const struct sc_mouse_scroll_event *event) {
|
||||||
|
sc_hid_mouse_event_init(hid_event);
|
||||||
|
|
||||||
|
uint8_t *data = hid_event->data;
|
||||||
|
data[0] = 0; // buttons state irrelevant (and unknown)
|
||||||
|
data[1] = 0; // no x motion
|
||||||
|
data[2] = 0; // no y motion
|
||||||
|
// In practice, vscroll is always -1, 0 or 1, but in theory other values
|
||||||
|
// are possible
|
||||||
|
data[3] = CLAMP(event->vscroll, -127, 127);
|
||||||
|
// Horizontal scrolling ignored
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef SC_HID_MOUSE_H
|
||||||
|
#define SC_HID_MOUSE_H
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "hid/hid_event.h"
|
||||||
|
#include "input_events.h"
|
||||||
|
|
||||||
|
extern const uint8_t SC_HID_MOUSE_REPORT_DESC[];
|
||||||
|
extern const size_t SC_HID_MOUSE_REPORT_DESC_LEN;
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_mouse_event_from_motion(struct sc_hid_event *hid_event,
|
||||||
|
const struct sc_mouse_motion_event *event);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_mouse_event_from_click(struct sc_hid_event *hid_event,
|
||||||
|
const struct sc_mouse_click_event *event);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_hid_mouse_event_from_scroll(struct sc_hid_event *hid_event,
|
||||||
|
const struct sc_mouse_scroll_event *event);
|
@ -0,0 +1,162 @@
|
|||||||
|
#include "keyboard_uhid.h"
|
||||||
|
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast key processor to keyboard_uhid */
|
||||||
|
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_uhid, key_processor)
|
||||||
|
|
||||||
|
/** Downcast uhid_receiver to keyboard_uhid */
|
||||||
|
#define DOWNCAST_RECEIVER(UR) \
|
||||||
|
container_of(UR, struct sc_keyboard_uhid, uhid_receiver)
|
||||||
|
|
||||||
|
#define UHID_KEYBOARD_ID 1
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_keyboard_uhid_send_input(struct sc_keyboard_uhid *kb,
|
||||||
|
const struct sc_hid_event *event) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
|
msg.uhid_input.id = UHID_KEYBOARD_ID;
|
||||||
|
|
||||||
|
assert(event->size <= SC_HID_MAX_SIZE);
|
||||||
|
memcpy(msg.uhid_input.data, event->data, event->size);
|
||||||
|
msg.uhid_input.size = event->size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(kb->controller, &msg)) {
|
||||||
|
LOGE("Could not send UHID_INPUT message (key)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_keyboard_uhid_synchronize_mod(struct sc_keyboard_uhid *kb) {
|
||||||
|
SDL_Keymod sdl_mod = SDL_GetModState();
|
||||||
|
uint16_t mod = sc_mods_state_from_sdl(sdl_mod) & (SC_MOD_CAPS | SC_MOD_NUM);
|
||||||
|
|
||||||
|
uint16_t device_mod =
|
||||||
|
atomic_load_explicit(&kb->device_mod, memory_order_relaxed);
|
||||||
|
uint16_t diff = mod ^ device_mod;
|
||||||
|
|
||||||
|
if (diff) {
|
||||||
|
// Inherently racy (the HID output reports arrive asynchronously in
|
||||||
|
// response to key presses), but will re-synchronize on next key press
|
||||||
|
// or HID output anyway
|
||||||
|
atomic_store_explicit(&kb->device_mod, mod, memory_order_relaxed);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_keyboard_event_from_mods(&hid_event, diff);
|
||||||
|
|
||||||
|
LOGV("HID keyboard state synchronized");
|
||||||
|
|
||||||
|
sc_keyboard_uhid_send_input(kb, &hid_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
|
const struct sc_key_event *event,
|
||||||
|
uint64_t ack_to_wait) {
|
||||||
|
(void) ack_to_wait;
|
||||||
|
|
||||||
|
if (event->repeat) {
|
||||||
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
|
// just ignore key repeat here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_keyboard_uhid *kb = DOWNCAST(kp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
|
||||||
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
|
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
||||||
|
if (event->scancode == SC_SCANCODE_CAPSLOCK) {
|
||||||
|
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_CAPS,
|
||||||
|
memory_order_relaxed);
|
||||||
|
} else if (event->scancode == SC_SCANCODE_NUMLOCK) {
|
||||||
|
atomic_fetch_xor_explicit(&kb->device_mod, SC_MOD_NUM,
|
||||||
|
memory_order_relaxed);
|
||||||
|
} else {
|
||||||
|
// Synchronize modifiers (only if the scancode itself does not
|
||||||
|
// change the modifiers)
|
||||||
|
sc_keyboard_uhid_synchronize_mod(kb);
|
||||||
|
}
|
||||||
|
sc_keyboard_uhid_send_input(kb, &hid_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned
|
||||||
|
sc_keyboard_uhid_to_sc_mod(uint8_t hid_led) {
|
||||||
|
// <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
||||||
|
// (chapter 11: LED page)
|
||||||
|
unsigned mod = 0;
|
||||||
|
if (hid_led & 0x01) {
|
||||||
|
mod |= SC_MOD_NUM;
|
||||||
|
}
|
||||||
|
if (hid_led & 0x02) {
|
||||||
|
mod |= SC_MOD_CAPS;
|
||||||
|
}
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_uhid_receiver_process_output(struct sc_uhid_receiver *receiver,
|
||||||
|
const uint8_t *data, size_t len) {
|
||||||
|
// Called from the thread receiving device messages
|
||||||
|
|
||||||
|
assert(len);
|
||||||
|
|
||||||
|
// Also check at runtime (do not trust the server)
|
||||||
|
if (!len) {
|
||||||
|
LOGE("Unexpected empty HID output message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_keyboard_uhid *kb = DOWNCAST_RECEIVER(receiver);
|
||||||
|
|
||||||
|
uint8_t hid_led = data[0];
|
||||||
|
uint16_t device_mod = sc_keyboard_uhid_to_sc_mod(hid_led);
|
||||||
|
atomic_store_explicit(&kb->device_mod, device_mod, memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
|
struct sc_controller *controller,
|
||||||
|
struct sc_uhid_devices *uhid_devices) {
|
||||||
|
sc_hid_keyboard_init(&kb->hid);
|
||||||
|
|
||||||
|
kb->controller = controller;
|
||||||
|
atomic_init(&kb->device_mod, 0);
|
||||||
|
|
||||||
|
static const struct sc_key_processor_ops ops = {
|
||||||
|
.process_key = sc_key_processor_process_key,
|
||||||
|
// Never forward text input via HID (all the keys are injected
|
||||||
|
// separately)
|
||||||
|
.process_text = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clipboard synchronization is requested over the same control socket, so
|
||||||
|
// there is no need for a specific synchronization mechanism
|
||||||
|
kb->key_processor.async_paste = false;
|
||||||
|
kb->key_processor.hid = true;
|
||||||
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
|
static const struct sc_uhid_receiver_ops uhid_receiver_ops = {
|
||||||
|
.process_output = sc_uhid_receiver_process_output,
|
||||||
|
};
|
||||||
|
|
||||||
|
kb->uhid_receiver.id = UHID_KEYBOARD_ID;
|
||||||
|
kb->uhid_receiver.ops = &uhid_receiver_ops;
|
||||||
|
sc_uhid_devices_add_receiver(uhid_devices, &kb->uhid_receiver);
|
||||||
|
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
|
msg.uhid_create.id = UHID_KEYBOARD_ID;
|
||||||
|
msg.uhid_create.report_desc = SC_HID_KEYBOARD_REPORT_DESC;
|
||||||
|
msg.uhid_create.report_desc_size = SC_HID_KEYBOARD_REPORT_DESC_LEN;
|
||||||
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
LOGE("Could not send UHID_CREATE message (keyboard)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef SC_KEYBOARD_UHID_H
|
||||||
|
#define SC_KEYBOARD_UHID_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
#include "hid/hid_keyboard.h"
|
||||||
|
#include "uhid/uhid_output.h"
|
||||||
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
|
struct sc_keyboard_uhid {
|
||||||
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
|
struct sc_uhid_receiver uhid_receiver;
|
||||||
|
|
||||||
|
struct sc_hid_keyboard hid;
|
||||||
|
struct sc_controller *controller;
|
||||||
|
atomic_uint_least16_t device_mod;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_keyboard_uhid_init(struct sc_keyboard_uhid *kb,
|
||||||
|
struct sc_controller *controller,
|
||||||
|
struct sc_uhid_devices *uhid_devices);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,89 @@
|
|||||||
|
#include "mouse_uhid.h"
|
||||||
|
|
||||||
|
#include "hid/hid_mouse.h"
|
||||||
|
#include "input_events.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast mouse processor to mouse_uhid */
|
||||||
|
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_uhid, mouse_processor)
|
||||||
|
|
||||||
|
#define UHID_MOUSE_ID 2
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_uhid_send_input(struct sc_mouse_uhid *mouse,
|
||||||
|
const struct sc_hid_event *event, const char *name) {
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_INPUT;
|
||||||
|
msg.uhid_input.id = UHID_MOUSE_ID;
|
||||||
|
|
||||||
|
assert(event->size <= SC_HID_MAX_SIZE);
|
||||||
|
memcpy(msg.uhid_input.data, event->data, event->size);
|
||||||
|
msg.uhid_input.size = event->size;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(mouse->controller, &msg)) {
|
||||||
|
LOGE("Could not send UHID_INPUT message (%s)", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
|
const struct sc_mouse_motion_event *event) {
|
||||||
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_mouse_event_from_motion(&hid_event, event);
|
||||||
|
|
||||||
|
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse motion");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||||
|
const struct sc_mouse_click_event *event) {
|
||||||
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_mouse_event_from_click(&hid_event, event);
|
||||||
|
|
||||||
|
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse click");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||||
|
const struct sc_mouse_scroll_event *event) {
|
||||||
|
struct sc_mouse_uhid *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
||||||
|
|
||||||
|
sc_mouse_uhid_send_input(mouse, &hid_event, "mouse scroll");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
||||||
|
struct sc_controller *controller) {
|
||||||
|
mouse->controller = controller;
|
||||||
|
|
||||||
|
static const struct sc_mouse_processor_ops ops = {
|
||||||
|
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||||
|
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
||||||
|
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
|
||||||
|
// Touch events not supported (coordinates are not relative)
|
||||||
|
.process_touch = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
mouse->mouse_processor.ops = &ops;
|
||||||
|
|
||||||
|
mouse->mouse_processor.relative_mode = true;
|
||||||
|
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_UHID_CREATE;
|
||||||
|
msg.uhid_create.id = UHID_MOUSE_ID;
|
||||||
|
msg.uhid_create.report_desc = SC_HID_MOUSE_REPORT_DESC;
|
||||||
|
msg.uhid_create.report_desc_size = SC_HID_MOUSE_REPORT_DESC_LEN;
|
||||||
|
if (!sc_controller_push_msg(controller, &msg)) {
|
||||||
|
LOGE("Could not send UHID_CREATE message (mouse)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef SC_MOUSE_UHID_H
|
||||||
|
#define SC_MOUSE_UHID_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
#include "trait/mouse_processor.h"
|
||||||
|
|
||||||
|
struct sc_mouse_uhid {
|
||||||
|
struct sc_mouse_processor mouse_processor; // mouse processor trait
|
||||||
|
|
||||||
|
struct sc_controller *controller;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_mouse_uhid_init(struct sc_mouse_uhid *mouse,
|
||||||
|
struct sc_controller *controller);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,25 @@
|
|||||||
|
#include "uhid_output.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_uhid_devices_init(struct sc_uhid_devices *devices) {
|
||||||
|
devices->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
||||||
|
struct sc_uhid_receiver *receiver) {
|
||||||
|
assert(devices->count < SC_UHID_MAX_RECEIVERS);
|
||||||
|
devices->receivers[devices->count++] = receiver;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_uhid_receiver *
|
||||||
|
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id) {
|
||||||
|
for (size_t i = 0; i < devices->count; ++i) {
|
||||||
|
if (devices->receivers[i]->id == id) {
|
||||||
|
return devices->receivers[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
#ifndef SC_UHID_OUTPUT_H
|
||||||
|
#define SC_UHID_OUTPUT_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The communication with UHID devices is bidirectional.
|
||||||
|
*
|
||||||
|
* This component manages the registration of receivers to handle UHID output
|
||||||
|
* messages (sent from the device to the computer).
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct sc_uhid_receiver {
|
||||||
|
uint16_t id;
|
||||||
|
|
||||||
|
const struct sc_uhid_receiver_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct sc_uhid_receiver_ops {
|
||||||
|
void
|
||||||
|
(*process_output)(struct sc_uhid_receiver *receiver,
|
||||||
|
const uint8_t *data, size_t len);
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SC_UHID_MAX_RECEIVERS 1
|
||||||
|
|
||||||
|
struct sc_uhid_devices {
|
||||||
|
struct sc_uhid_receiver *receivers[SC_UHID_MAX_RECEIVERS];
|
||||||
|
unsigned count;
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_uhid_devices_init(struct sc_uhid_devices *devices);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_uhid_devices_add_receiver(struct sc_uhid_devices *devices,
|
||||||
|
struct sc_uhid_receiver *receiver);
|
||||||
|
|
||||||
|
struct sc_uhid_receiver *
|
||||||
|
sc_uhid_devices_get_receiver(struct sc_uhid_devices *devices, uint16_t id);
|
||||||
|
|
||||||
|
#endif
|
@ -1,267 +0,0 @@
|
|||||||
#include "hid_mouse.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include "input_events.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
/** Downcast mouse processor to hid_mouse */
|
|
||||||
#define DOWNCAST(MP) container_of(MP, struct sc_hid_mouse, mouse_processor)
|
|
||||||
|
|
||||||
#define HID_MOUSE_ACCESSORY_ID 2
|
|
||||||
|
|
||||||
// 1 byte for buttons + padding, 1 byte for X position, 1 byte for Y position
|
|
||||||
#define HID_MOUSE_EVENT_SIZE 4
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mouse descriptor from the specification:
|
|
||||||
* <https://www.usb.org/sites/default/files/hid1_11.pdf>
|
|
||||||
*
|
|
||||||
* Appendix E (p71): §E.10 Report Descriptor (Mouse)
|
|
||||||
*
|
|
||||||
* The usage tags (like Wheel) are listed in "HID Usage Tables":
|
|
||||||
* <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
|
|
||||||
* §4 Generic Desktop Page (0x01) (p26)
|
|
||||||
*/
|
|
||||||
static const unsigned char mouse_report_desc[] = {
|
|
||||||
// Usage Page (Generic Desktop)
|
|
||||||
0x05, 0x01,
|
|
||||||
// Usage (Mouse)
|
|
||||||
0x09, 0x02,
|
|
||||||
|
|
||||||
// Collection (Application)
|
|
||||||
0xA1, 0x01,
|
|
||||||
|
|
||||||
// Usage (Pointer)
|
|
||||||
0x09, 0x01,
|
|
||||||
|
|
||||||
// Collection (Physical)
|
|
||||||
0xA1, 0x00,
|
|
||||||
|
|
||||||
// Usage Page (Buttons)
|
|
||||||
0x05, 0x09,
|
|
||||||
|
|
||||||
// Usage Minimum (1)
|
|
||||||
0x19, 0x01,
|
|
||||||
// Usage Maximum (5)
|
|
||||||
0x29, 0x05,
|
|
||||||
// Logical Minimum (0)
|
|
||||||
0x15, 0x00,
|
|
||||||
// Logical Maximum (1)
|
|
||||||
0x25, 0x01,
|
|
||||||
// Report Count (5)
|
|
||||||
0x95, 0x05,
|
|
||||||
// Report Size (1)
|
|
||||||
0x75, 0x01,
|
|
||||||
// Input (Data, Variable, Absolute): 5 buttons bits
|
|
||||||
0x81, 0x02,
|
|
||||||
|
|
||||||
// Report Count (1)
|
|
||||||
0x95, 0x01,
|
|
||||||
// Report Size (3)
|
|
||||||
0x75, 0x03,
|
|
||||||
// Input (Constant): 3 bits padding
|
|
||||||
0x81, 0x01,
|
|
||||||
|
|
||||||
// Usage Page (Generic Desktop)
|
|
||||||
0x05, 0x01,
|
|
||||||
// Usage (X)
|
|
||||||
0x09, 0x30,
|
|
||||||
// Usage (Y)
|
|
||||||
0x09, 0x31,
|
|
||||||
// Usage (Wheel)
|
|
||||||
0x09, 0x38,
|
|
||||||
// Local Minimum (-127)
|
|
||||||
0x15, 0x81,
|
|
||||||
// Local Maximum (127)
|
|
||||||
0x25, 0x7F,
|
|
||||||
// Report Size (8)
|
|
||||||
0x75, 0x08,
|
|
||||||
// Report Count (3)
|
|
||||||
0x95, 0x03,
|
|
||||||
// Input (Data, Variable, Relative): 3 position bytes (X, Y, Wheel)
|
|
||||||
0x81, 0x06,
|
|
||||||
|
|
||||||
// End Collection
|
|
||||||
0xC0,
|
|
||||||
|
|
||||||
// End Collection
|
|
||||||
0xC0,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mouse HID event is 3 bytes long:
|
|
||||||
*
|
|
||||||
* - byte 0: buttons state
|
|
||||||
* - byte 1: relative x motion (signed byte from -127 to 127)
|
|
||||||
* - byte 2: relative y motion (signed byte from -127 to 127)
|
|
||||||
*
|
|
||||||
* 7 6 5 4 3 2 1 0
|
|
||||||
* +---------------+
|
|
||||||
* byte 0: |0 0 0 . . . . .| buttons state
|
|
||||||
* +---------------+
|
|
||||||
* ^ ^ ^ ^ ^
|
|
||||||
* | | | | `- left button
|
|
||||||
* | | | `--- right button
|
|
||||||
* | | `----- middle button
|
|
||||||
* | `------- button 4
|
|
||||||
* `--------- button 5
|
|
||||||
*
|
|
||||||
* +---------------+
|
|
||||||
* byte 1: |. . . . . . . .| relative x motion
|
|
||||||
* +---------------+
|
|
||||||
* byte 2: |. . . . . . . .| relative y motion
|
|
||||||
* +---------------+
|
|
||||||
* byte 3: |. . . . . . . .| wheel motion (-1, 0 or 1)
|
|
||||||
* +---------------+
|
|
||||||
*
|
|
||||||
* As an example, here is the report for a motion of (x=5, y=-4) with left
|
|
||||||
* button pressed:
|
|
||||||
*
|
|
||||||
* +---------------+
|
|
||||||
* |0 0 0 0 0 0 0 1| left button pressed
|
|
||||||
* +---------------+
|
|
||||||
* |0 0 0 0 0 1 0 1| horizontal motion (x = 5)
|
|
||||||
* +---------------+
|
|
||||||
* |1 1 1 1 1 1 0 0| relative y motion (y = -4)
|
|
||||||
* +---------------+
|
|
||||||
* |0 0 0 0 0 0 0 0| wheel motion
|
|
||||||
* +---------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
static bool
|
|
||||||
sc_hid_mouse_event_init(struct sc_hid_event *hid_event) {
|
|
||||||
unsigned char *buffer = calloc(1, HID_MOUSE_EVENT_SIZE);
|
|
||||||
if (!buffer) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
sc_hid_event_init(hid_event, HID_MOUSE_ACCESSORY_ID, buffer,
|
|
||||||
HID_MOUSE_EVENT_SIZE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static unsigned char
|
|
||||||
buttons_state_to_hid_buttons(uint8_t buttons_state) {
|
|
||||||
unsigned char c = 0;
|
|
||||||
if (buttons_state & SC_MOUSE_BUTTON_LEFT) {
|
|
||||||
c |= 1 << 0;
|
|
||||||
}
|
|
||||||
if (buttons_state & SC_MOUSE_BUTTON_RIGHT) {
|
|
||||||
c |= 1 << 1;
|
|
||||||
}
|
|
||||||
if (buttons_state & SC_MOUSE_BUTTON_MIDDLE) {
|
|
||||||
c |= 1 << 2;
|
|
||||||
}
|
|
||||||
if (buttons_state & SC_MOUSE_BUTTON_X1) {
|
|
||||||
c |= 1 << 3;
|
|
||||||
}
|
|
||||||
if (buttons_state & SC_MOUSE_BUTTON_X2) {
|
|
||||||
c |= 1 << 4;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
|
||||||
const struct sc_mouse_motion_event *event) {
|
|
||||||
struct sc_hid_mouse *mouse = DOWNCAST(mp);
|
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
|
||||||
if (!sc_hid_mouse_event_init(&hid_event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *buffer = hid_event.buffer;
|
|
||||||
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
|
|
||||||
buffer[1] = CLAMP(event->xrel, -127, 127);
|
|
||||||
buffer[2] = CLAMP(event->yrel, -127, 127);
|
|
||||||
buffer[3] = 0; // wheel coordinates only used for scrolling
|
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
|
|
||||||
sc_hid_event_destroy(&hid_event);
|
|
||||||
LOGW("Could not request HID event (mouse motion)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
|
||||||
const struct sc_mouse_click_event *event) {
|
|
||||||
struct sc_hid_mouse *mouse = DOWNCAST(mp);
|
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
|
||||||
if (!sc_hid_mouse_event_init(&hid_event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *buffer = hid_event.buffer;
|
|
||||||
buffer[0] = buttons_state_to_hid_buttons(event->buttons_state);
|
|
||||||
buffer[1] = 0; // no x motion
|
|
||||||
buffer[2] = 0; // no y motion
|
|
||||||
buffer[3] = 0; // wheel coordinates only used for scrolling
|
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
|
|
||||||
sc_hid_event_destroy(&hid_event);
|
|
||||||
LOGW("Could not request HID event (mouse click)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
|
||||||
const struct sc_mouse_scroll_event *event) {
|
|
||||||
struct sc_hid_mouse *mouse = DOWNCAST(mp);
|
|
||||||
|
|
||||||
struct sc_hid_event hid_event;
|
|
||||||
if (!sc_hid_mouse_event_init(&hid_event)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned char *buffer = hid_event.buffer;
|
|
||||||
buffer[0] = 0; // buttons state irrelevant (and unknown)
|
|
||||||
buffer[1] = 0; // no x motion
|
|
||||||
buffer[2] = 0; // no y motion
|
|
||||||
// In practice, vscroll is always -1, 0 or 1, but in theory other values
|
|
||||||
// are possible
|
|
||||||
buffer[3] = CLAMP(event->vscroll, -127, 127);
|
|
||||||
// Horizontal scrolling ignored
|
|
||||||
|
|
||||||
if (!sc_aoa_push_hid_event(mouse->aoa, &hid_event)) {
|
|
||||||
sc_hid_event_destroy(&hid_event);
|
|
||||||
LOGW("Could not request HID event (mouse scroll)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_hid_mouse_init(struct sc_hid_mouse *mouse, struct sc_aoa *aoa) {
|
|
||||||
mouse->aoa = aoa;
|
|
||||||
|
|
||||||
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID, mouse_report_desc,
|
|
||||||
ARRAY_LEN(mouse_report_desc));
|
|
||||||
if (!ok) {
|
|
||||||
LOGW("Register HID mouse failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct sc_mouse_processor_ops ops = {
|
|
||||||
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
|
||||||
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
|
||||||
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
|
|
||||||
// Touch events not supported (coordinates are not relative)
|
|
||||||
.process_touch = NULL,
|
|
||||||
};
|
|
||||||
|
|
||||||
mouse->mouse_processor.ops = &ops;
|
|
||||||
|
|
||||||
mouse->mouse_processor.relative_mode = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_hid_mouse_destroy(struct sc_hid_mouse *mouse) {
|
|
||||||
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
|
||||||
if (!ok) {
|
|
||||||
LOGW("Could not unregister HID mouse");
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,110 @@
|
|||||||
|
#include "keyboard_aoa.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "input_events.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast key processor to keyboard_aoa */
|
||||||
|
#define DOWNCAST(KP) container_of(KP, struct sc_keyboard_aoa, key_processor)
|
||||||
|
|
||||||
|
#define HID_KEYBOARD_ACCESSORY_ID 1
|
||||||
|
|
||||||
|
static bool
|
||||||
|
push_mod_lock_state(struct sc_keyboard_aoa *kb, uint16_t mods_state) {
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
if (!sc_hid_keyboard_event_from_mods(&hid_event, mods_state)) {
|
||||||
|
// Nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sc_aoa_push_hid_event(kb->aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||||
|
&hid_event)) {
|
||||||
|
LOGW("Could not request HID event (mod lock state)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGD("HID keyboard state synchronized");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_key_processor_process_key(struct sc_key_processor *kp,
|
||||||
|
const struct sc_key_event *event,
|
||||||
|
uint64_t ack_to_wait) {
|
||||||
|
if (event->repeat) {
|
||||||
|
// In USB HID protocol, key repeat is handled by the host (Android), so
|
||||||
|
// just ignore key repeat here.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sc_keyboard_aoa *kb = DOWNCAST(kp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
|
||||||
|
// Not all keys are supported, just ignore unsupported keys
|
||||||
|
if (sc_hid_keyboard_event_from_key(&kb->hid, &hid_event, event)) {
|
||||||
|
if (!kb->mod_lock_synchronized) {
|
||||||
|
// Inject CAPSLOCK and/or NUMLOCK if necessary to synchronize
|
||||||
|
// keyboard state
|
||||||
|
if (push_mod_lock_state(kb, event->mods_state)) {
|
||||||
|
kb->mod_lock_synchronized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ack_to_wait is != SC_SEQUENCE_INVALID, then Ctrl+v is pressed, so
|
||||||
|
// clipboard synchronization has been requested. Wait until clipboard
|
||||||
|
// synchronization is acknowledged by the server, otherwise it could
|
||||||
|
// paste the old clipboard content.
|
||||||
|
|
||||||
|
if (!sc_aoa_push_hid_event_with_ack_to_wait(kb->aoa,
|
||||||
|
HID_KEYBOARD_ACCESSORY_ID,
|
||||||
|
&hid_event,
|
||||||
|
ack_to_wait)) {
|
||||||
|
LOGW("Could not request HID event (key)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa) {
|
||||||
|
kb->aoa = aoa;
|
||||||
|
|
||||||
|
bool ok = sc_aoa_setup_hid(aoa, HID_KEYBOARD_ACCESSORY_ID,
|
||||||
|
SC_HID_KEYBOARD_REPORT_DESC,
|
||||||
|
SC_HID_KEYBOARD_REPORT_DESC_LEN);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Register HID keyboard failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sc_hid_keyboard_init(&kb->hid);
|
||||||
|
|
||||||
|
kb->mod_lock_synchronized = false;
|
||||||
|
|
||||||
|
static const struct sc_key_processor_ops ops = {
|
||||||
|
.process_key = sc_key_processor_process_key,
|
||||||
|
// Never forward text input via HID (all the keys are injected
|
||||||
|
// separately)
|
||||||
|
.process_text = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clipboard synchronization is requested over the control socket, while HID
|
||||||
|
// events are sent over AOA, so it must wait for clipboard synchronization
|
||||||
|
// to be acknowledged by the device before injecting Ctrl+v.
|
||||||
|
kb->key_processor.async_paste = true;
|
||||||
|
kb->key_processor.hid = true;
|
||||||
|
kb->key_processor.ops = &ops;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb) {
|
||||||
|
// Unregister HID keyboard so the soft keyboard shows again on Android
|
||||||
|
bool ok = sc_aoa_unregister_hid(kb->aoa, HID_KEYBOARD_ACCESSORY_ID);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not unregister HID keyboard");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef SC_KEYBOARD_AOA_H
|
||||||
|
#define SC_KEYBOARD_AOA_H
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "aoa_hid.h"
|
||||||
|
#include "hid/hid_keyboard.h"
|
||||||
|
#include "trait/key_processor.h"
|
||||||
|
|
||||||
|
struct sc_keyboard_aoa {
|
||||||
|
struct sc_key_processor key_processor; // key processor trait
|
||||||
|
|
||||||
|
struct sc_hid_keyboard hid;
|
||||||
|
struct sc_aoa *aoa;
|
||||||
|
|
||||||
|
bool mod_lock_synchronized;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_keyboard_aoa_init(struct sc_keyboard_aoa *kb, struct sc_aoa *aoa);
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_keyboard_aoa_destroy(struct sc_keyboard_aoa *kb);
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,89 @@
|
|||||||
|
#include "mouse_aoa.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "hid/hid_mouse.h"
|
||||||
|
#include "input_events.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
|
||||||
|
/** Downcast mouse processor to mouse_aoa */
|
||||||
|
#define DOWNCAST(MP) container_of(MP, struct sc_mouse_aoa, mouse_processor)
|
||||||
|
|
||||||
|
#define HID_MOUSE_ACCESSORY_ID 2
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_motion(struct sc_mouse_processor *mp,
|
||||||
|
const struct sc_mouse_motion_event *event) {
|
||||||
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_mouse_event_from_motion(&hid_event, event);
|
||||||
|
|
||||||
|
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
||||||
|
&hid_event)) {
|
||||||
|
LOGW("Could not request HID event (mouse motion)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_click(struct sc_mouse_processor *mp,
|
||||||
|
const struct sc_mouse_click_event *event) {
|
||||||
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_mouse_event_from_click(&hid_event, event);
|
||||||
|
|
||||||
|
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
||||||
|
&hid_event)) {
|
||||||
|
LOGW("Could not request HID event (mouse click)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sc_mouse_processor_process_mouse_scroll(struct sc_mouse_processor *mp,
|
||||||
|
const struct sc_mouse_scroll_event *event) {
|
||||||
|
struct sc_mouse_aoa *mouse = DOWNCAST(mp);
|
||||||
|
|
||||||
|
struct sc_hid_event hid_event;
|
||||||
|
sc_hid_mouse_event_from_scroll(&hid_event, event);
|
||||||
|
|
||||||
|
if (!sc_aoa_push_hid_event(mouse->aoa, HID_MOUSE_ACCESSORY_ID,
|
||||||
|
&hid_event)) {
|
||||||
|
LOGW("Could not request HID event (mouse scroll)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_mouse_aoa_init(struct sc_mouse_aoa *mouse, struct sc_aoa *aoa) {
|
||||||
|
mouse->aoa = aoa;
|
||||||
|
|
||||||
|
bool ok = sc_aoa_setup_hid(aoa, HID_MOUSE_ACCESSORY_ID,
|
||||||
|
SC_HID_MOUSE_REPORT_DESC,
|
||||||
|
SC_HID_MOUSE_REPORT_DESC_LEN);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Register HID mouse failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct sc_mouse_processor_ops ops = {
|
||||||
|
.process_mouse_motion = sc_mouse_processor_process_mouse_motion,
|
||||||
|
.process_mouse_click = sc_mouse_processor_process_mouse_click,
|
||||||
|
.process_mouse_scroll = sc_mouse_processor_process_mouse_scroll,
|
||||||
|
// Touch events not supported (coordinates are not relative)
|
||||||
|
.process_touch = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
mouse->mouse_processor.ops = &ops;
|
||||||
|
|
||||||
|
mouse->mouse_processor.relative_mode = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_mouse_aoa_destroy(struct sc_mouse_aoa *mouse) {
|
||||||
|
bool ok = sc_aoa_unregister_hid(mouse->aoa, HID_MOUSE_ACCESSORY_ID);
|
||||||
|
if (!ok) {
|
||||||
|
LOGW("Could not unregister HID mouse");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
#include "audiobuf.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <util/log.h>
|
||||||
|
#include <util/memory.h>
|
||||||
|
|
||||||
|
bool
|
||||||
|
sc_audiobuf_init(struct sc_audiobuf *buf, size_t sample_size,
|
||||||
|
uint32_t capacity) {
|
||||||
|
assert(sample_size);
|
||||||
|
assert(capacity);
|
||||||
|
|
||||||
|
// The actual capacity is (alloc_size - 1) so that head == tail is
|
||||||
|
// non-ambiguous
|
||||||
|
buf->alloc_size = capacity + 1;
|
||||||
|
buf->data = sc_allocarray(buf->alloc_size, sample_size);
|
||||||
|
if (!buf->data) {
|
||||||
|
LOG_OOM();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->sample_size = sample_size;
|
||||||
|
atomic_init(&buf->head, 0);
|
||||||
|
atomic_init(&buf->tail, 0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
sc_audiobuf_destroy(struct sc_audiobuf *buf) {
|
||||||
|
free(buf->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
sc_audiobuf_read(struct sc_audiobuf *buf, void *to_, uint32_t samples_count) {
|
||||||
|
assert(samples_count);
|
||||||
|
|
||||||
|
uint8_t *to = to_;
|
||||||
|
|
||||||
|
// Only the reader thread can write tail without synchronization, so
|
||||||
|
// memory_order_relaxed is sufficient
|
||||||
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_relaxed);
|
||||||
|
|
||||||
|
// The head cursor is updated after the data is written to the array
|
||||||
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_acquire);
|
||||||
|
|
||||||
|
uint32_t can_read = (buf->alloc_size + head - tail) % buf->alloc_size;
|
||||||
|
if (samples_count > can_read) {
|
||||||
|
samples_count = can_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
uint32_t right_count = buf->alloc_size - tail;
|
||||||
|
if (right_count > samples_count) {
|
||||||
|
right_count = samples_count;
|
||||||
|
}
|
||||||
|
memcpy(to,
|
||||||
|
buf->data + (tail * buf->sample_size),
|
||||||
|
right_count * buf->sample_size);
|
||||||
|
|
||||||
|
if (samples_count > right_count) {
|
||||||
|
uint32_t left_count = samples_count - right_count;
|
||||||
|
memcpy(to + (right_count * buf->sample_size),
|
||||||
|
buf->data,
|
||||||
|
left_count * buf->sample_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t new_tail = (tail + samples_count) % buf->alloc_size;
|
||||||
|
atomic_store_explicit(&buf->tail, new_tail, memory_order_release);
|
||||||
|
|
||||||
|
return samples_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
sc_audiobuf_write(struct sc_audiobuf *buf, const void *from_,
|
||||||
|
uint32_t samples_count) {
|
||||||
|
const uint8_t *from = from_;
|
||||||
|
|
||||||
|
// Only the writer thread can write head, so memory_order_relaxed is
|
||||||
|
// sufficient
|
||||||
|
uint32_t head = atomic_load_explicit(&buf->head, memory_order_relaxed);
|
||||||
|
|
||||||
|
// The tail cursor is updated after the data is consumed by the reader
|
||||||
|
uint32_t tail = atomic_load_explicit(&buf->tail, memory_order_acquire);
|
||||||
|
|
||||||
|
uint32_t can_write = (buf->alloc_size + tail - head - 1) % buf->alloc_size;
|
||||||
|
if (samples_count > can_write) {
|
||||||
|
samples_count = can_write;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t right_count = buf->alloc_size - head;
|
||||||
|
if (right_count > samples_count) {
|
||||||
|
right_count = samples_count;
|
||||||
|
}
|
||||||
|
memcpy(buf->data + (head * buf->sample_size),
|
||||||
|
from,
|
||||||
|
right_count * buf->sample_size);
|
||||||
|
|
||||||
|
if (samples_count > right_count) {
|
||||||
|
uint32_t left_count = samples_count - right_count;
|
||||||
|
memcpy(buf->data,
|
||||||
|
from + (right_count * buf->sample_size),
|
||||||
|
left_count * buf->sample_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t new_head = (head + samples_count) % buf->alloc_size;
|
||||||
|
atomic_store_explicit(&buf->head, new_head, memory_order_release);
|
||||||
|
|
||||||
|
return samples_count;
|
||||||
|
}
|
@ -1,104 +0,0 @@
|
|||||||
#include "bytebuf.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/log.h"
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size) {
|
|
||||||
assert(alloc_size);
|
|
||||||
buf->data = malloc(alloc_size);
|
|
||||||
if (!buf->data) {
|
|
||||||
LOG_OOM();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->alloc_size = alloc_size;
|
|
||||||
buf->head = 0;
|
|
||||||
buf->tail = 0;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_destroy(struct sc_bytebuf *buf) {
|
|
||||||
free(buf->data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_read(buf));
|
|
||||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
|
||||||
|
|
||||||
size_t right_limit = buf->tail < buf->head ? buf->head : buf->alloc_size;
|
|
||||||
size_t right_len = right_limit - buf->tail;
|
|
||||||
if (len < right_len) {
|
|
||||||
right_len = len;
|
|
||||||
}
|
|
||||||
memcpy(to, buf->data + buf->tail, right_len);
|
|
||||||
|
|
||||||
if (len > right_len) {
|
|
||||||
memcpy(to + right_len, buf->data, len - right_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_read(buf));
|
|
||||||
assert(buf->tail != buf->head); // the buffer could not be empty
|
|
||||||
|
|
||||||
buf->tail = (buf->tail + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_bytebuf_write_step0(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len) {
|
|
||||||
size_t right_len = buf->alloc_size - buf->head;
|
|
||||||
if (len < right_len) {
|
|
||||||
right_len = len;
|
|
||||||
}
|
|
||||||
memcpy(buf->data + buf->head, from, right_len);
|
|
||||||
|
|
||||||
if (len > right_len) {
|
|
||||||
memcpy(buf->data, from + right_len, len - right_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
|
||||||
sc_bytebuf_write_step1(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
buf->head = (buf->head + len) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len) {
|
|
||||||
assert(len);
|
|
||||||
assert(len <= sc_bytebuf_can_write(buf));
|
|
||||||
|
|
||||||
sc_bytebuf_write_step0(buf, from, len);
|
|
||||||
sc_bytebuf_write_step1(buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len) {
|
|
||||||
// *This function MUST NOT access buf->tail (even in assert()).*
|
|
||||||
// The purpose of this function is to allow a reader and a writer to access
|
|
||||||
// different parts of the buffer in parallel simultaneously. It is intended
|
|
||||||
// to be called without lock (only sc_bytebuf_commit_write() is intended to
|
|
||||||
// be called with lock held).
|
|
||||||
|
|
||||||
assert(len < buf->alloc_size - 1);
|
|
||||||
sc_bytebuf_write_step0(buf, from, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len) {
|
|
||||||
assert(len <= sc_bytebuf_can_write(buf));
|
|
||||||
sc_bytebuf_write_step1(buf, len);
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
#ifndef SC_BYTEBUF_H
|
|
||||||
#define SC_BYTEBUF_H
|
|
||||||
|
|
||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
struct sc_bytebuf {
|
|
||||||
uint8_t *data;
|
|
||||||
// The actual capacity is (allocated - 1) so that head == tail is
|
|
||||||
// non-ambiguous
|
|
||||||
size_t alloc_size;
|
|
||||||
size_t head; // writter cursor
|
|
||||||
size_t tail; // reader cursor
|
|
||||||
// empty: tail == head
|
|
||||||
// full: ((tail + 1) % alloc_size) == head
|
|
||||||
};
|
|
||||||
|
|
||||||
bool
|
|
||||||
sc_bytebuf_init(struct sc_bytebuf *buf, size_t alloc_size);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy from the bytebuf to a user-provided array
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_read_available() (it is an
|
|
||||||
* error to attempt to read more bytes than available).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->head.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_read(struct sc_bytebuf *buf, uint8_t *to, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drop len bytes from the buffer
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_read_available() (it is an
|
|
||||||
* error to attempt to skip more bytes than available).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->head.
|
|
||||||
*
|
|
||||||
* It is equivalent to call sc_bytebuf_read() to some array and discard the
|
|
||||||
* array (but this function is more efficient since there is no copy).
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_skip(struct sc_bytebuf *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the user-provided array to the bytebuf
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_write_available() (it is an
|
|
||||||
* error to write more bytes than the remaining available space).
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to write to buf->tail.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_write(struct sc_bytebuf *buf, const uint8_t *from, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the user-provided array to the bytebuf, but do not advance the cursor
|
|
||||||
*
|
|
||||||
* The caller must check that len <= sc_bytebuf_write_available() (it is an
|
|
||||||
* error to write more bytes than the remaining available space).
|
|
||||||
*
|
|
||||||
* After this function is called, the write must be committed with
|
|
||||||
* sc_bytebuf_commit_write().
|
|
||||||
*
|
|
||||||
* The purpose of this mechanism is to acquire a lock only to commit the write,
|
|
||||||
* but not to perform the actual copy.
|
|
||||||
*
|
|
||||||
* This function is guaranteed not to access buf->tail.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_prepare_write(struct sc_bytebuf *buf, const uint8_t *from,
|
|
||||||
size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit a prepared write
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
sc_bytebuf_commit_write(struct sc_bytebuf *buf, size_t len);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes which can be read
|
|
||||||
*
|
|
||||||
* It is an error to read more bytes than available.
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_can_read(struct sc_bytebuf *buf) {
|
|
||||||
return (buf->alloc_size + buf->head - buf->tail) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the number of bytes which can be written
|
|
||||||
*
|
|
||||||
* It is an error to write more bytes than available.
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_can_write(struct sc_bytebuf *buf) {
|
|
||||||
return (buf->alloc_size + buf->tail - buf->head - 1) % buf->alloc_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the actual capacity of the buffer (can_read() + can_write())
|
|
||||||
*/
|
|
||||||
static inline size_t
|
|
||||||
sc_bytebuf_capacity(struct sc_bytebuf *buf) {
|
|
||||||
return buf->alloc_size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
sc_bytebuf_destroy(struct sc_bytebuf *buf);
|
|
||||||
|
|
||||||
#endif
|
|
@ -0,0 +1,128 @@
|
|||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "util/audiobuf.h"
|
||||||
|
|
||||||
|
static void test_audiobuf_simple(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 5);
|
||||||
|
assert(w == 5);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 4);
|
||||||
|
assert(r == 4);
|
||||||
|
assert(!memcmp(data, samples, 16));
|
||||||
|
|
||||||
|
uint32_t samples2[] = {6, 7, 8};
|
||||||
|
w = sc_audiobuf_write(&buf, samples2, 3);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
uint32_t single = 9;
|
||||||
|
w = sc_audiobuf_write(&buf, &single, 1);
|
||||||
|
assert(w == 1);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, &data[4], 8);
|
||||||
|
assert(r == 5);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
|
assert(!memcmp(data, expected, 36));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_audiobuf_boundaries(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[20];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 20);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 9);
|
||||||
|
assert(r == 9);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3, 4, 5, 6, 1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected, 36));
|
||||||
|
|
||||||
|
uint32_t samples2[] = {7, 8, 9, 10, 11};
|
||||||
|
w = sc_audiobuf_write(&buf, samples2, 5);
|
||||||
|
assert(w == 5);
|
||||||
|
|
||||||
|
uint32_t single = 12;
|
||||||
|
w = sc_audiobuf_write(&buf, &single, 1);
|
||||||
|
assert(w == 1);
|
||||||
|
|
||||||
|
w = sc_audiobuf_read(&buf, NULL, 3);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
assert(sc_audiobuf_can_read(&buf) == 12);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, data, 12);
|
||||||
|
assert(r == 12);
|
||||||
|
|
||||||
|
uint32_t expected2[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
|
||||||
|
assert(!memcmp(data, expected2, 48));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_audiobuf_partial_read_write(void) {
|
||||||
|
struct sc_audiobuf buf;
|
||||||
|
uint32_t data[15];
|
||||||
|
|
||||||
|
bool ok = sc_audiobuf_init(&buf, 4, 10);
|
||||||
|
assert(ok);
|
||||||
|
|
||||||
|
uint32_t samples[] = {1, 2, 3, 4, 5, 6};
|
||||||
|
uint32_t w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 6);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 4);
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 0);
|
||||||
|
|
||||||
|
uint32_t r = sc_audiobuf_read(&buf, data, 3);
|
||||||
|
assert(r == 3);
|
||||||
|
|
||||||
|
uint32_t expected[] = {1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected, 12));
|
||||||
|
|
||||||
|
w = sc_audiobuf_write(&buf, samples, 6);
|
||||||
|
assert(w == 3);
|
||||||
|
|
||||||
|
r = sc_audiobuf_read(&buf, data, 15);
|
||||||
|
assert(r == 10);
|
||||||
|
uint32_t expected2[] = {4, 5, 6, 1, 2, 3, 4, 1, 2, 3};
|
||||||
|
assert(!memcmp(data, expected2, 12));
|
||||||
|
|
||||||
|
sc_audiobuf_destroy(&buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
(void) argc;
|
||||||
|
(void) argv;
|
||||||
|
|
||||||
|
test_audiobuf_simple();
|
||||||
|
test_audiobuf_boundaries();
|
||||||
|
test_audiobuf_partial_read_write();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,126 +0,0 @@
|
|||||||
#include "common.h"
|
|
||||||
|
|
||||||
#include <assert.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "util/bytebuf.h"
|
|
||||||
|
|
||||||
static void test_bytebuf_simple(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello", sizeof("hello") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 5);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 4);
|
|
||||||
assert(!strncmp((char *) data, "hell", 4));
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) " world", sizeof(" world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 7);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 8);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, &data[4], 8);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_bytebuf_boundaries(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 6);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 18);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 9);
|
|
||||||
assert(!strncmp((char *) data, "hello hel", 9));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 14);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 15);
|
|
||||||
|
|
||||||
sc_bytebuf_skip(&buf, 3);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 12);
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void test_bytebuf_two_steps_write(void) {
|
|
||||||
struct sc_bytebuf buf;
|
|
||||||
uint8_t data[20];
|
|
||||||
|
|
||||||
bool ok = sc_bytebuf_init(&buf, 20);
|
|
||||||
assert(ok);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 6);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "hello ", sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12); // write not committed yet
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 9);
|
|
||||||
assert(!strncmp((char *) data, "hello hel", 3));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 3);
|
|
||||||
|
|
||||||
sc_bytebuf_commit_write(&buf, sizeof("hello ") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9);
|
|
||||||
|
|
||||||
sc_bytebuf_prepare_write(&buf, (uint8_t *) "world", sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 9); // write not committed yet
|
|
||||||
|
|
||||||
sc_bytebuf_commit_write(&buf, sizeof("world") - 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 14);
|
|
||||||
|
|
||||||
sc_bytebuf_write(&buf, (uint8_t *) "!", 1);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 15);
|
|
||||||
|
|
||||||
sc_bytebuf_skip(&buf, 3);
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 12);
|
|
||||||
|
|
||||||
sc_bytebuf_read(&buf, data, 12);
|
|
||||||
data[12] = '\0';
|
|
||||||
assert(!strcmp((char *) data, "hello world!"));
|
|
||||||
assert(sc_bytebuf_can_read(&buf) == 0);
|
|
||||||
|
|
||||||
sc_bytebuf_destroy(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
(void) argc;
|
|
||||||
(void) argv;
|
|
||||||
|
|
||||||
test_bytebuf_simple();
|
|
||||||
test_bytebuf_boundaries();
|
|
||||||
test_bytebuf_two_steps_write();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
# HID/OTG
|
|
||||||
|
|
||||||
By default, _scrcpy_ injects input events at the Android API level. As an
|
|
||||||
alternative, when connected over USB, it is possible to send HID events, so that
|
|
||||||
scrcpy behaves as if it was a physical keyboard and/or mouse connected to the
|
|
||||||
Android device.
|
|
||||||
|
|
||||||
A special [OTG](#otg) mode allows to control the device without mirroring (and
|
|
||||||
without USB debugging).
|
|
||||||
|
|
||||||
|
|
||||||
## Physical keyboard simulation
|
|
||||||
|
|
||||||
By default, _scrcpy_ uses Android key or text injection. It works everywhere,
|
|
||||||
but is limited to ASCII.
|
|
||||||
|
|
||||||
Instead, it can simulate a physical USB keyboard on Android to provide a better
|
|
||||||
input experience (using [USB HID over AOAv2][hid-aoav2]): the virtual keyboard
|
|
||||||
is disabled and it works for all characters and IME.
|
|
||||||
|
|
||||||
[hid-aoav2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
|
||||||
|
|
||||||
However, it only works if the device is connected via USB.
|
|
||||||
|
|
||||||
Note: On Windows, it may only work in [OTG mode](#otg), not while mirroring (it
|
|
||||||
is not possible to open a USB device if it is already open by another process
|
|
||||||
like the _adb daemon_).
|
|
||||||
|
|
||||||
To enable this mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-keyboard
|
|
||||||
scrcpy -K # short version
|
|
||||||
```
|
|
||||||
|
|
||||||
If it fails for some reason (for example because the device is not connected via
|
|
||||||
USB), it automatically fallbacks to the default mode (with a log in the
|
|
||||||
console). This allows using the same command line options when connected over
|
|
||||||
USB and TCP/IP.
|
|
||||||
|
|
||||||
In this mode, raw key events (scancodes) are sent to the device, independently
|
|
||||||
of the host key mapping. Therefore, if your keyboard layout does not match, it
|
|
||||||
must be configured on the Android device, in Settings → System → Languages and
|
|
||||||
input → [Physical keyboard].
|
|
||||||
|
|
||||||
This settings page can be started directly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
adb shell am start -a android.settings.HARD_KEYBOARD_SETTINGS
|
|
||||||
```
|
|
||||||
|
|
||||||
However, the option is only available when the HID keyboard is enabled (or when
|
|
||||||
a physical keyboard is connected).
|
|
||||||
|
|
||||||
[Physical keyboard]: https://github.com/Genymobile/scrcpy/pull/2632#issuecomment-923756915
|
|
||||||
|
|
||||||
|
|
||||||
## Physical mouse simulation
|
|
||||||
|
|
||||||
By default, _scrcpy_ uses Android mouse events injection with absolute
|
|
||||||
coordinates. By simulating a physical mouse, a mouse pointer appears on the
|
|
||||||
Android device, and relative mouse motion, clicks and scrolls are injected.
|
|
||||||
|
|
||||||
To enable this mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --hid-mouse
|
|
||||||
scrcpy -M # short version
|
|
||||||
```
|
|
||||||
|
|
||||||
When this mode is enabled, the computer mouse is "captured" (the mouse pointer
|
|
||||||
disappears from the computer and appears on the Android device instead).
|
|
||||||
|
|
||||||
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
|
|
||||||
(disable or enable) the mouse capture. Use one of them to give the control of
|
|
||||||
the mouse back to the computer.
|
|
||||||
|
|
||||||
|
|
||||||
## OTG
|
|
||||||
|
|
||||||
It is possible to run _scrcpy_ with only physical keyboard and mouse simulation
|
|
||||||
(HID), as if the computer keyboard and mouse were plugged directly to the device
|
|
||||||
via an OTG cable.
|
|
||||||
|
|
||||||
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
|
|
||||||
|
|
||||||
This is similar to `--hid-keyboard --hid-mouse`, but without mirroring.
|
|
||||||
|
|
||||||
To enable OTG mode:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg
|
|
||||||
# Pass the serial if several USB devices are available
|
|
||||||
scrcpy --otg -s 0123456789abcdef
|
|
||||||
```
|
|
||||||
|
|
||||||
It is possible to enable only HID keyboard or HID mouse:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
scrcpy --otg --hid-keyboard # keyboard only
|
|
||||||
scrcpy --otg --hid-mouse # mouse only
|
|
||||||
scrcpy --otg --hid-keyboard --hid-mouse # keyboard and mouse
|
|
||||||
# for convenience, enable both by default
|
|
||||||
scrcpy --otg # keyboard and mouse
|
|
||||||
```
|
|
||||||
|
|
||||||
Like `--hid-keyboard` and `--hid-mouse`, it only works if the device is
|
|
||||||
connected over USB.
|
|
||||||
|
|
||||||
## HID/OTG issues on Windows
|
|
||||||
|
|
||||||
See [FAQ](/FAQ.md#hidotg-issues-on-windows).
|
|
@ -0,0 +1,136 @@
|
|||||||
|
# Keyboard
|
||||||
|
|
||||||
|
Several keyboard input modes are available:
|
||||||
|
|
||||||
|
- `--keyboard=sdk` (default)
|
||||||
|
- `--keyboard=uhid` (or `-K`): simulates a physical HID keyboard using the UHID
|
||||||
|
kernel module on the device
|
||||||
|
- `--keyboard=aoa`: simulates a physical HID keyboard using the AOAv2 protocol
|
||||||
|
- `--keyboard=disabled`
|
||||||
|
|
||||||
|
By default, `sdk` is used, but if you use scrcpy regularly, it is recommended to
|
||||||
|
use [`uhid`](#uhid) and configure the keyboard layout once and for all.
|
||||||
|
|
||||||
|
|
||||||
|
## SDK keyboard
|
||||||
|
|
||||||
|
In this mode (`--keyboard=sdk`, or if the parameter is omitted), keyboard input
|
||||||
|
events are injected at the Android API level. It works everywhere, but it is
|
||||||
|
limited to ASCII and some other characters.
|
||||||
|
|
||||||
|
Note that on some devices, an additional option must be enabled in developer
|
||||||
|
options for this keyboard mode to work. See
|
||||||
|
[prerequisites](/README.md#prerequisites).
|
||||||
|
|
||||||
|
Additional parameters (specific to `--keyboard=sdk`) described below allow to
|
||||||
|
customize the behavior.
|
||||||
|
|
||||||
|
|
||||||
|
### Text injection preference
|
||||||
|
|
||||||
|
Two kinds of [events][textevents] are generated when typing text:
|
||||||
|
- _key events_, signaling that a key is pressed or released;
|
||||||
|
- _text events_, signaling that a text has been entered.
|
||||||
|
|
||||||
|
By default, numbers and "special characters" are inserted using text events, but
|
||||||
|
letters are injected using key events, so that the keyboard behaves as expected
|
||||||
|
in games (typically for WASD keys).
|
||||||
|
|
||||||
|
But this may [cause issues][prefertext]. If you encounter such a problem, you
|
||||||
|
can inject letters as text (or just switch to [UHID](#uhid)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --prefer-text
|
||||||
|
```
|
||||||
|
|
||||||
|
(but this will break keyboard behavior in games)
|
||||||
|
|
||||||
|
On the contrary, you could force to always inject raw key events:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --raw-key-events
|
||||||
|
```
|
||||||
|
|
||||||
|
[textevents]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-text-input
|
||||||
|
[prefertext]: https://github.com/Genymobile/scrcpy/issues/650#issuecomment-512945343
|
||||||
|
|
||||||
|
|
||||||
|
### Key repeat
|
||||||
|
|
||||||
|
By default, holding a key down generates repeated key events. Ths can cause
|
||||||
|
performance problems in some games, where these events are useless anyway.
|
||||||
|
|
||||||
|
To avoid forwarding repeated key events:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --no-key-repeat
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Physical keyboard simulation
|
||||||
|
|
||||||
|
Two modes allow to simulate a physical HID keyboard on the device.
|
||||||
|
|
||||||
|
To work properly, it is necessary to configure (once and for all) the keyboard
|
||||||
|
layout on the device to match that of the computer.
|
||||||
|
|
||||||
|
The configuration page can be opened in one of the following ways:
|
||||||
|
- from the scrcpy window (when `uhid` or `aoa` is used), by pressing
|
||||||
|
<kbd>MOD</kbd>+<kbd>k</kbd> (see [shortcuts](shortcuts.md))
|
||||||
|
- from the device, in Settings → System → Languages and input → Physical
|
||||||
|
devices
|
||||||
|
- from a terminal on the computer, by executing `adb shell am start -a
|
||||||
|
android.settings.HARD_KEYBOARD_SETTINGS`
|
||||||
|
|
||||||
|
From this configuration page, it is also possible to enable or disable on-screen
|
||||||
|
keyboard.
|
||||||
|
|
||||||
|
|
||||||
|
### UHID
|
||||||
|
|
||||||
|
This mode simulates a physical HID keyboard using the [UHID] kernel module on the
|
||||||
|
device.
|
||||||
|
|
||||||
|
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
|
||||||
|
|
||||||
|
To enable UHID keyboard, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --keyboard=uhid
|
||||||
|
scrcpy -K # short version
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the keyboard layout is configured (see above), it is the best mode for
|
||||||
|
using the keyboard while mirroring:
|
||||||
|
|
||||||
|
- it works for all characters and IME (contrary to `--keyboard=sdk`)
|
||||||
|
- the on-screen keyboard can be disabled (contrary to `--keyboard=sdk`)
|
||||||
|
- it works over TCP/IP (wirelessly) (contrary to `--keyboard=aoa`)
|
||||||
|
- there are no issues on Windows (contrary to `--keyboard=aoa`)
|
||||||
|
|
||||||
|
One drawback is that it may not work on old Android versions due to permission
|
||||||
|
errors.
|
||||||
|
|
||||||
|
|
||||||
|
### AOA
|
||||||
|
|
||||||
|
This mode simulates a physical HID keyboard using the [AOAv2] protocol.
|
||||||
|
|
||||||
|
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||||
|
|
||||||
|
To enable AOA keyboard, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --keyboard=aoa
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrary to the other modes, it works at the USB level directly (so it only
|
||||||
|
works over USB).
|
||||||
|
|
||||||
|
It does not use the scrcpy server, and does not require `adb` (USB debugging).
|
||||||
|
Therefore, it is possible to control the device (but not mirror) even with USB
|
||||||
|
debugging disabled (see [OTG](otg.md)).
|
||||||
|
|
||||||
|
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring
|
||||||
|
(it is not possible to open a USB device if it is already open by another
|
||||||
|
process like the _adb daemon_).
|
@ -0,0 +1,70 @@
|
|||||||
|
# Mouse
|
||||||
|
|
||||||
|
Several mouse input modes are available:
|
||||||
|
|
||||||
|
- `--mouse=sdk` (default)
|
||||||
|
- `--mouse=uhid` (or `-M`): simulates a physical HID mouse using the UHID
|
||||||
|
kernel module on the device
|
||||||
|
- `--mouse=aoa`: simulates a physical HID mouse using the AOAv2 protocol
|
||||||
|
- `--mouse=disabled`
|
||||||
|
|
||||||
|
|
||||||
|
## SDK mouse
|
||||||
|
|
||||||
|
In this mode (`--mouse=sdk`, or if the parameter is omitted), mouse input events
|
||||||
|
are injected at the Android API level with absolute coordinates.
|
||||||
|
|
||||||
|
Note that on some devices, an additional option must be enabled in developer
|
||||||
|
options for this mouse mode to work. See
|
||||||
|
[prerequisites](/README.md#prerequisites).
|
||||||
|
|
||||||
|
|
||||||
|
## Physical mouse simulation
|
||||||
|
|
||||||
|
Two modes allow to simulate a physical HID mouse on the device.
|
||||||
|
|
||||||
|
In these modes, the computer mouse is "captured": the mouse pointer disappears
|
||||||
|
from the computer and appears on the Android device instead.
|
||||||
|
|
||||||
|
Special capture keys, either <kbd>Alt</kbd> or <kbd>Super</kbd>, toggle
|
||||||
|
(disable or enable) the mouse capture. Use one of them to give the control of
|
||||||
|
the mouse back to the computer.
|
||||||
|
|
||||||
|
|
||||||
|
### UHID
|
||||||
|
|
||||||
|
This mode simulates a physical HID mouse using the [UHID] kernel module on the
|
||||||
|
device.
|
||||||
|
|
||||||
|
[UHID]: https://kernel.org/doc/Documentation/hid/uhid.txt
|
||||||
|
|
||||||
|
To enable UHID mouse, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --mouse=uhid
|
||||||
|
scrcpy -M # short version
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### AOA
|
||||||
|
|
||||||
|
This mode simulates a physical HID mouse using the [AOAv2] protocol.
|
||||||
|
|
||||||
|
[AOAv2]: https://source.android.com/devices/accessories/aoa2#hid-support
|
||||||
|
|
||||||
|
To enable AOA mouse, use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --mouse=aoa
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrary to the other modes, it works at the USB level directly (so it only
|
||||||
|
works over USB).
|
||||||
|
|
||||||
|
It does not use the scrcpy server, and does not require `adb` (USB debugging).
|
||||||
|
Therefore, it is possible to control the device (but not mirror) even with USB
|
||||||
|
debugging disabled (see [OTG](otg.md)).
|
||||||
|
|
||||||
|
Note: On Windows, it may only work in [OTG mode](otg.md), not while mirroring
|
||||||
|
(it is not possible to open a USB device if it is already open by another
|
||||||
|
process like the _adb daemon_).
|
@ -0,0 +1,37 @@
|
|||||||
|
# OTG
|
||||||
|
|
||||||
|
By default, _scrcpy_ injects input events at the Android API level. As an
|
||||||
|
alternative, when connected over USB, it is possible to send HID events, so that
|
||||||
|
scrcpy behaves as if it was a physical keyboard and/or mouse connected to the
|
||||||
|
Android device.
|
||||||
|
|
||||||
|
A special mode allows to control the device without mirroring, using AOA
|
||||||
|
[keyboard](keyboard.md#aoa) and [mouse](mouse.md#aoa). Therefore, it is possible
|
||||||
|
to run _scrcpy_ with only physical keyboard and mouse simulation (HID), as if
|
||||||
|
the computer keyboard and mouse were plugged directly to the device via an OTG
|
||||||
|
cable.
|
||||||
|
|
||||||
|
In this mode, `adb` (USB debugging) is not necessary, and mirroring is disabled.
|
||||||
|
|
||||||
|
This is similar to `--keyboard=aoa --mouse=aoa`, but without mirroring.
|
||||||
|
|
||||||
|
To enable OTG mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --otg
|
||||||
|
# Pass the serial if several USB devices are available
|
||||||
|
scrcpy --otg -s 0123456789abcdef
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to disable HID keyboard or HID mouse:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrcpy --otg --keyboard=disabled
|
||||||
|
scrcpy --otg --mouse=disabled
|
||||||
|
```
|
||||||
|
|
||||||
|
It only works if the device is connected over USB.
|
||||||
|
|
||||||
|
## OTG issues on Windows
|
||||||
|
|
||||||
|
See [FAQ](/FAQ.md#otg-issues-on-windows).
|
@ -1,2 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
SCRCPY_SERVER_PATH="$MESON_BUILD_ROOT/server/scrcpy-server" "$MESON_BUILD_ROOT/app/scrcpy"
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.net.LocalSocket;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
public final class ControlChannel {
|
||||||
|
private final InputStream inputStream;
|
||||||
|
private final OutputStream outputStream;
|
||||||
|
|
||||||
|
private final ControlMessageReader reader = new ControlMessageReader();
|
||||||
|
private final DeviceMessageWriter writer = new DeviceMessageWriter();
|
||||||
|
|
||||||
|
public ControlChannel(LocalSocket controlSocket) throws IOException {
|
||||||
|
this.inputStream = controlSocket.getInputStream();
|
||||||
|
this.outputStream = controlSocket.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlMessage recv() throws IOException {
|
||||||
|
ControlMessage msg = reader.next();
|
||||||
|
while (msg == null) {
|
||||||
|
reader.readFrom(inputStream);
|
||||||
|
msg = reader.next();
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(DeviceMessage msg) throws IOException {
|
||||||
|
writer.writeTo(msg, outputStream);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue