1. 背景
由于公司的办公电脑也用于深度学习的模型训练,该电脑就不能同时大型软件(连Chrome也不能多开标签),避免影响训练。于是就想把新手机“红米Note 12 Turbo”利用起来,处理一些“轻办公”的工作。
找到方案是:
- 方案1:Android系统利用
chroot
安装完整Linux系统,运行基于X11的图形化软件(例如Chrome),PC端运行X server
。 - 方案2:手机运行Android桌面模式,并投屏到PC。
由于方案1运行不流畅、不能方便利用GPU加速、软件兼容不佳等,只能放弃。方案2整理了一下,能达到比较高的实用性。
2. 原理
据说从Android 10开始,Android系统内置了桌面模式。该模式下,App可以自由拖动显示位置,并调整窗口大小(跟PC操作系统一样)。
有趣的是,“开发者选项”里开启了“模拟辅助显示设备”,就可以启用桌面模式,而且基本不影响手机正常显示模式。可以认为同一个Android手机上同时运行“手机模式”和“桌面模式”,App能在这两个模式之间显示。
最后,利用scrcpy
把“模拟辅助显示设备”投屏到PC端,就可以使用大屏幕显示“桌面模式”,并且利用鼠标键盘进行输入。
据说手机直连显示器(需要手机直接输出HDMI功能),或者无线连接Miracast,都可以显示“桌面模式”。但是手上没有相关设备,不能验证。
3. 配置
3.1. PC端软件
3.2. Android必要配置
3.3. 启动命令
在PC端的命令窗口执行以下命令即可启动。Linux Shell脚本参考如下:
#!/bin/bash
# Params
# $1: SERIAL|HOST[:PORT]
P_SERIAL=$1
PHONE_NAME="My Phone"
CMD_ADB="/opt/android-sdk/platform-tools/adb"
ADB_SERIAL=
CMD_SCRCPY="/opt/scrcpy/scrcpy"
DISPLAY_ID=0
# Log level
declare -A LOG_LEVEL=([i]=INFO [w]=WARN [e]=ERR)
# Log message
# Params
# $1: log level
# $2: message
function func_log() {
lv=${LOG_LEVEL[$1]}
if [ -z "$lv" ]; then
lv=NONE
fi
echo [$lv] $2 1>&2
return
}
function func_init() {
result=n
# Do connect
is_connected=$(func_connect)
if [ "$is_connected" == "y" ]; then
echo y
return
fi
func_log w "Connect failed. SERIAL or HOST is invalid, or need pairing"
# Do pair
read -p "Need to pair device? Enter "y" for yes, otherwise exit:" need_pair
if [ "$need_pair" != "y" ]; then
echo Exit 1>&2
echo $result
return
fi
is_paired=$(func_pair)
if [ "$is_paired" != "y" ]; then
func_log e "Pair failed, exit"
echo $result
return
else
func_log i "Pair succeed"
fi
is_connected=$(func_connect)
if [ "$is_connected" != "y" ]; then
func_log e "Connect failed, exit"
echo $result
return
else
func_log i "Connect succeed"
fi
echo y
return
}
function func_get_serialno() {
dev_serialno=$($CMD_ADB get-serialno)
if [ -z "$dev_serialno" ]; then
return
fi
echo $dev_serialno
return
}
function func_check_serialno() {
result=n
dev_serialno=$($CMD_ADB -s "$P_SERIAL" get-serialno)
if [ "$P_SERIAL" != "$dev_serialno" ]; then
echo $result
return
fi
echo y
return
}
function func_connect() {
result=n
# Get serialno of default device
if [ -z $P_SERIAL ]; then
dev_serialno=$(func_get_serialno)
if [ -z "$dev_serialno" ]; then
func_log e "No connected device"
echo $result
else
echo y
P_SERIAL=$dev_serialno
fi
return
fi
# Check if the device is connected
is_connected=$(func_check_serialno)
if [ "$is_connected" == "y" ]; then
echo y
return
fi
# Connect to the device
dev_connect=$($CMD_ADB connect "$P_SERIAL")
if [ "${dev_connect:0:9}" != "connected" ] && [ "${dev_connect:0:17}" != "already connected" ]; then
echo $result
return
fi
echo y
return
}
function func_pair() {
result=n
echo Enter "HOST[:PORT]" of the paired device:; read pair_host
echo Enter "PAIRING CODE":; read pair_code
dev_pair=$($CMD_ADB pair "$pair_host" pair_code)
if [ "${dev_pair:0:19}" != "Successfully paired" ]; then
echo $result
return
fi
echo y
return
}
function func_tips() {
echo + Tips ------------------
echo - Ctrl + Shift + O, turn ON screen of the connected device
echo - Ctrl + O, turn OFF screen of the connected device
echo + -----------------------
}
function func_start_apps() {
# --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.
# --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.
$CMD_ADB $ADB_SERIAL shell am start-activity --display $DISPLAY_ID -a com.aistra.hail.action.LAUNCH -e package cu.axel.smartdock
# $CMD_ADB $ADB_SERIAL shell am start-activity --display $DISPLAY_ID -a com.aistra.hail.action.UNFREEZE -e package cu.axel.smartdock
# sleep 2
# $CMD_ADB $ADB_SERIAL shell am start-activity --display $DISPLAY_ID -n cu.axel.smartdock/.activities.MainActivity
}
function func_stop_apps() {
$CMD_ADB $ADB_SERIAL shell am start-activity --display $DISPLAY_ID -a com.aistra.hail.action.FREEZE -e package cu.axel.smartdock
}
# main function
function func_main() {
is_init=$(func_init)
if [ "$is_init" != "y" ]; then
return
fi
# Turn on auxiliary display device
$CMD_ADB $ADB_SERIAL shell settings put global overlay_display_devices 1920x1080/193
#$CMD_ADB $ADB_SERIAL shell settings put global overlay_display_devices 1920x1008/180
# Get display-id of Simulate secondary displays
DISPLAY_ID=$($CMD_SCRCPY $ADB_SERIAL --list-displays | $CMD_ADB $ADB_SERIAL shell "grep -o 'display-id=[1-9][0-9]*' | sed 's/display-id=\([1-9][0-9]*\)/\1/'")
func_log i "Display id: $DISPLAY_ID"
# Show tips
func_tips
# Run apps
func_start_apps
# Run scrcpy
# $CMD_SCRCPY $ADB_SERIAL --display-id=$DISPLAY_ID --keyboard=uhid --mouse=sdk --no-audio --power-off-on-close --shortcut-mod="lctrl,rctrl" --stay-awake --turn-screen-off --window-title="$PHONE_NAME - Android Desktop Mode" --window-x=0 --window-y=25
$CMD_SCRCPY $ADB_SERIAL --display-id=$DISPLAY_ID --keyboard=sdk --mouse=sdk --no-audio --power-off-on-close --shortcut-mod="lctrl,rctrl" --stay-awake --turn-screen-off --window-title="$PHONE_NAME - Android Desktop Mode" --window-x=0 --window-y=25
# Stop apps
func_stop_apps
# Turn off auxiliary display device
$CMD_ADB $ADB_SERIAL shell settings put global overlay_display_devices null
}
func_main
exit
说明:
Windows的CMD批处理脚本,参考如下:
@echo off
SETLOCAL ENABLEEXTENSIONS
rem Params
rem %1: SERIAL|HOST[:PORT]
set P_SERIAL=%1
set PHONE_NAME=My Phone
set CMD_ADB="D:\tools\android-sdk\platform-tools\adb.exe"
set ADB_SERIAL=
set CMD_SCRCPY="D:\tools\scrcpy\scrcpy.exe"
set TEMP_FILE="C:\Users\%username%\AppData\Local\Temp\scrcpy_display_id_%RANDOM%.txt"
set DISPLAY_ID=0
call :func_main
goto end
rem Log message. If error, do exit.
rem Params
rem %1: log level
rem %2: message
:func_log
set lv_index=%~1
if "%lv_index%" == "i" (
set lv=INFO
) else if "%lv_index%" == "w" (
set lv=WARN
) else if "%lv_index%" == "e" (
set lv=ERR
) else (
set lv=NONE
)
echo [%lv%] %~2
goto :eof
:func_init
set %~1=n
rem Do connect
call :func_connect is_connected
if "%is_connected%" == "y" (
set %~1=y
set ADB_SERIAL=-s %P_SERIAL%
goto :eof
)
call :func_log w,"Connect failed. SERIAL or HOST is invalid, or need pairing"
rem Do pair
set /p need_pair=Need to pair device? Enter "y" for yes, otherwise exit:
if "%need_pair%" neq "y" (
echo Exit
goto :eof
)
call :func_pair is_paired
if "%is_paired%" neq "y" (
call :func_log e,"Pair failed, exit"
goto :eof
) else (
call :func_log i,"Pair succeed"
)
call :func_connect is_connected
if "%is_connected%" neq "y" (
call :func_log e,"Connect failed, exit"
goto :eof
) else (
call :func_log i,"Connect succeed"
)
set ADB_SERIAL=-s %P_SERIAL%
set %~1=y
goto :eof
:func_get_serialno
for /f "delims=" %%i in ('%CMD_ADB% get-serialno') do set dev_serialno=%%i
if "%dev_serialno%" == "" (
goto :eof
)
set %~1=%dev_serialno%
goto :eof
:func_check_serialno
set %~1=n
for /f "delims=" %%i in ('%CMD_ADB% -s %P_SERIAL% get-serialno') do set dev_serialno=%%i
if "%P_SERIAL%" neq "%dev_serialno%" (
goto :eof
)
set %~1=y
goto :eof
:func_connect
set %~1=n
rem Get serialno of default device
if "%P_SERIAL%" == "" (
call :func_get_serialno dev_serialno
)
if "%P_SERIAL%%dev_serialno%" == "" (
call :func_log e,"No connected device"
goto :eof
) else if "%P_SERIAL%%dev_serialno%" == "%dev_serialno%" (
set %~1=y
set P_SERIAL=%dev_serialno%
goto :eof
)
rem Check if the device is connected
call :func_check_serialno is_connected
if "%is_connected%" == "y" (
set %~1=y
goto :eof
)
rem Connect to the device
for /f "delims=" %%i in ('%CMD_ADB% connect %P_SERIAL%') do set dev_connect=%%i
if "%dev_connect:~0,9%" == "connected" (
rem Do nothing
) else if "%dev_connect:~0,17%" == "already connected" (
rem Do nothing
) else (
goto :eof
)
set %~1=y
goto :eof
:func_pair
set %~1=n
echo Enter "HOST[:PORT]" of the paired device: & set /p pair_host=
echo Enter "PAIRING CODE": & set /p pair_code=
for /f "delims=" %%i in ('%CMD_ADB% pair %pair_host% %pair_code%') do set dev_pair=%%i
if "%dev_pair:~0,19%" neq "Successfully paired" (
goto :eof
)
set %~1=y
goto :eof
:func_tips
echo + Tips ------------------
echo - Ctrl + Shift + O, turn ON screen of the connected device
echo - Ctrl + O, turn OFF screen of the connected device
echo + -----------------------
goto :eof
:func_start_apps
rem --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.
rem --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.
%CMD_ADB% %ADB_SERIAL% shell am start-activity --display %DISPLAY_ID% -a com.aistra.hail.action.LAUNCH -e package cu.axel.smartdock
rem %CMD_ADB% %ADB_SERIAL% shell am start-activity --display %DISPLAY_ID% -a com.aistra.hail.action.UNFREEZE -e package cu.axel.smartdock
rem timeout /T 2
rem %CMD_ADB% %ADB_SERIAL% shell am start-activity --display %DISPLAY_ID% -n cu.axel.smartdock/.activities.MainActivity
goto :eof
:func_stop_apps
%CMD_ADB% %ADB_SERIAL% shell am start-activity --display %DISPLAY_ID% -a com.aistra.hail.action.FREEZE -e package cu.axel.smartdock
goto :eof
rem main function
:func_main
call :func_init is_init
if "%is_init%" neq "y" (
goto :eof
)
call :func_log i,"Connected device: %P_SERIAL%"
rem Turn on auxiliary display device
rem %CMD_ADB% shell settings put global overlay_display_devices 1920x1080/180
%CMD_ADB% %ADB_SERIAL% shell settings put global overlay_display_devices 1920x1008/180
rem Get display id of auxiliary display device
%CMD_SCRCPY% %ADB_SERIAL% --list-displays | %CMD_ADB% %ADB_SERIAL% shell "grep -o 'display-id=[1-9][0-9]*' | sed 's/display-id=\([1-9][0-9]*\)/\1/'" > %TEMP_FILE%
set /p DISPLAY_ID=<%TEMP_FILE%
del %TEMP_FILE%
call :func_log i,"Display id: %DISPLAY_ID%"
rem Show tips
call :func_tips
rem Run apps
call :func_start_apps
rem Run scrcpy
rem %CMD_SCRCPY% --display-id=%DISPLAY_ID% --keyboard=sdk --mouse=sdk --no-audio --power-off-on-close --shortcut-mod="lctrl,rctrl" --stay-awake --turn-screen-off --window-title="%PHONE_NAME% - Android Desktop Mode" --window-x=0 --window-y=25
%CMD_SCRCPY% %ADB_SERIAL% --display-id=%DISPLAY_ID% --keyboard=sdk --mouse=sdk --no-audio --power-off-on-close --shortcut-mod="lctrl,rctrl" --stay-awake --turn-screen-off --window-title="%PHONE_NAME% - Android Desktop Mode" --window-x=0 --window-y=25
rem Stop apps
call :func_stop_apps
rem Turn off auxiliary display device
%CMD_ADB% %ADB_SERIAL% shell settings put global overlay_display_devices null
goto :eof
:end