ituoke/internal/device/touch.go

102 lines
3.0 KiB
Go

package device
import (
"encoding/binary"
"fmt"
"time"
)
// Touch immediately forwards an event on the persistent scrcpy connection.
// It never launches an adb shell or waits for a complete gesture.
func (s *Service) Touch(req TouchRequest) error {
if req.Action < 0 || req.Action > 3 || req.Width < 1 || req.Height < 1 || req.Width > 65535 || req.Height > 65535 || req.X < 0 || req.Y < 0 || req.X >= req.Width || req.Y >= req.Height {
return fmt.Errorf("触摸事件或画面坐标无效")
}
st := s.ensureStream()
st.mu.Lock()
defer st.mu.Unlock()
if !st.running || st.serial != req.DeviceID || st.controlConn == nil {
return fmt.Errorf("控制通道未连接,请重新开始预览")
}
if req.Action != 0 && !st.touchDown {
return nil
}
if req.Action == 0 && st.touchDown {
cancel := st.lastTouch
cancel.Action = 3
if err := st.writeTouchLocked(cancel); err != nil {
return err
}
}
if err := st.writeTouchLocked(req); err != nil {
return fmt.Errorf("发送触摸事件失败,请重新开始预览: %w", err)
}
st.lastTouch = req
st.touchDown = req.Action == 0 || req.Action == 2
return nil
}
// scrcpy 5 INJECT_TOUCH_EVENT: 32 bytes, big endian. Generic finger id=-2.
func encodeTouch(req TouchRequest) []byte {
packet := make([]byte, 32)
packet[0] = 2
packet[1] = byte(req.Action)
binary.BigEndian.PutUint64(packet[2:10], ^uint64(1))
binary.BigEndian.PutUint32(packet[10:14], uint32(req.X))
binary.BigEndian.PutUint32(packet[14:18], uint32(req.Y))
binary.BigEndian.PutUint16(packet[18:20], uint16(req.Width))
binary.BigEndian.PutUint16(packet[20:22], uint16(req.Height))
if req.Action == 0 || req.Action == 2 {
binary.BigEndian.PutUint16(packet[22:24], 65535)
}
return packet
}
func (st *previewStream) writeTouchLocked(req TouchRequest) error {
return st.writeControlLocked(encodeTouch(req))
}
func (st *previewStream) writeControlLocked(data []byte) error {
_ = st.controlConn.SetWriteDeadline(time.Now().Add(500 * time.Millisecond))
for len(data) > 0 {
n, err := st.controlConn.Write(data)
if err != nil {
return err
}
if n == 0 {
return fmt.Errorf("控制通道写入中断")
}
data = data[n:]
}
return nil
}
// SetScreenOff changes physical display power while keeping mirroring active.
func (s *Service) SetScreenOff(deviceID string, screenOff bool) error {
st := s.ensureStream()
st.lifecycle.Lock()
defer st.lifecycle.Unlock()
st.mu.Lock()
defer st.mu.Unlock()
if !st.running || st.serial != deviceID || st.controlConn == nil {
return fmt.Errorf("控制通道未连接,请重新开始预览")
}
return st.setScreenOffLocked(screenOff)
}
func (st *previewStream) setScreenOffLocked(screenOff bool) error {
if st.screenOff == screenOff {
return nil
}
// scrcpy 5 SET_DISPLAY_POWER: type=10, boolean on (not Android sleep).
on := byte(1)
if screenOff {
on = 0
}
if err := st.writeControlLocked([]byte{10, on}); err != nil {
return fmt.Errorf("调整手机屏幕失败,请重新开始预览: %w", err)
}
st.screenOff = screenOff
return nil
}