diff --git a/internal/driver/ioscompanion/transport/grpc.go b/internal/driver/ioscompanion/transport/grpc.go index a214135..970346a 100644 --- a/internal/driver/ioscompanion/transport/grpc.go +++ b/internal/driver/ioscompanion/transport/grpc.go @@ -73,7 +73,11 @@ func (c *grpcCompanion) SendHID(ctx context.Context, events ...HIDEvent) error { return err } for _, e := range events { - if err := stream.Send(e.event); err != nil { + message, err := hidEventToProto(e) + if err != nil { + return err + } + if err := stream.Send(message); err != nil { return err } } @@ -81,6 +85,50 @@ func (c *grpcCompanion) SendHID(ctx context.Context, events ...HIDEvent) error { return err } +// hidEventToProto encodes a neutral HID event as this companion's wire event. +// The companion measures delays and swipe durations in seconds. +func hidEventToProto(e HIDEvent) (*pb.HIDEvent, error) { + switch e.Kind { + case HIDKindTouchDown: + return touchProto(e.X, e.Y, pb.HIDEvent_DOWN), nil + case HIDKindTouchUp: + return touchProto(e.X, e.Y, pb.HIDEvent_UP), nil + case HIDKindKeyDown: + return keyProto(e.Usage, pb.HIDEvent_DOWN), nil + case HIDKindKeyUp: + return keyProto(e.Usage, pb.HIDEvent_UP), nil + case HIDKindDelay: + return &pb.HIDEvent{Event: &pb.HIDEvent_Delay{ + Delay: &pb.HIDEvent_HIDDelay{Duration: e.Milliseconds / 1000.0}, + }}, nil + case HIDKindSwipe: + return &pb.HIDEvent{Event: &pb.HIDEvent_Swipe{Swipe: &pb.HIDEvent_HIDSwipe{ + Start: &pb.Point{X: e.FromX, Y: e.FromY}, + End: &pb.Point{X: e.ToX, Y: e.ToY}, + Duration: e.Seconds, + }}}, nil + } + return nil, fmt.Errorf("unknown HID event kind %d", e.Kind) +} + +func touchProto(x, y float64, direction pb.HIDEvent_HIDDirection) *pb.HIDEvent { + return &pb.HIDEvent{Event: &pb.HIDEvent_Press{Press: &pb.HIDEvent_HIDPress{ + Direction: direction, + Action: &pb.HIDEvent_HIDPressAction{Action: &pb.HIDEvent_HIDPressAction_Touch{ + Touch: &pb.HIDEvent_HIDTouch{Point: &pb.Point{X: x, Y: y}}, + }}, + }}} +} + +func keyProto(usage uint32, direction pb.HIDEvent_HIDDirection) *pb.HIDEvent { + return &pb.HIDEvent{Event: &pb.HIDEvent_Press{Press: &pb.HIDEvent_HIDPress{ + Direction: direction, + Action: &pb.HIDEvent_HIDPressAction{Action: &pb.HIDEvent_HIDPressAction_Key{ + Key: &pb.HIDEvent_HIDKey{Keycode: uint64(usage)}, + }}, + }}} +} + func (c *grpcCompanion) Screenshot(ctx context.Context) ([]byte, string, error) { resp, err := c.client.Screenshot(ctx, &pb.ScreenshotRequest{}) if err != nil { diff --git a/internal/driver/ioscompanion/transport/hid.go b/internal/driver/ioscompanion/transport/hid.go index 3e353ae..1088dd4 100644 --- a/internal/driver/ioscompanion/transport/hid.go +++ b/internal/driver/ioscompanion/transport/hid.go @@ -1,59 +1,63 @@ package transport -import ( - pb "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb" +// HIDEventKind discriminates the neutral HID event variants. +type HIDEventKind int + +const ( + HIDKindTouchDown HIDEventKind = iota + HIDKindTouchUp + HIDKindKeyDown + HIDKindKeyUp + HIDKindDelay + HIDKindSwipe ) -// HIDEvent is one input event in a HID stream. It wraps the generated event so -// callers never import the companionpb package. Build one with a builder below. +// HIDEvent is one input event in a HID stream, expressed in transport-neutral +// terms so each companion transport encodes it for its own wire format. Build +// one with a builder below; only the fields for the event's kind are set. type HIDEvent struct { - event *pb.HIDEvent -} + Kind HIDEventKind -func touchPress(x, y float64, direction pb.HIDEvent_HIDDirection) HIDEvent { - return HIDEvent{event: &pb.HIDEvent{Event: &pb.HIDEvent_Press{Press: &pb.HIDEvent_HIDPress{ - Direction: direction, - Action: &pb.HIDEvent_HIDPressAction{Action: &pb.HIDEvent_HIDPressAction_Touch{ - Touch: &pb.HIDEvent_HIDTouch{Point: &pb.Point{X: x, Y: y}}, - }}, - }}}} -} + // X, Y is the touch point for TouchDown and TouchUp. + X, Y float64 -func keyPress(usage uint32, direction pb.HIDEvent_HIDDirection) HIDEvent { - return HIDEvent{event: &pb.HIDEvent{Event: &pb.HIDEvent_Press{Press: &pb.HIDEvent_HIDPress{ - Direction: direction, - Action: &pb.HIDEvent_HIDPressAction{Action: &pb.HIDEvent_HIDPressAction_Key{ - Key: &pb.HIDEvent_HIDKey{Keycode: uint64(usage)}, - }}, - }}}} + // Usage is the USB HID usage identifier for KeyDown and KeyUp. + Usage uint32 + + // Milliseconds is the pause length for Delay. + Milliseconds float64 + + // FromX through Seconds describe a Swipe. + FromX, FromY float64 + ToX, ToY float64 + Seconds float64 } // TouchDown presses a finger down at screen point (x, y). -func TouchDown(x, y float64) HIDEvent { return touchPress(x, y, pb.HIDEvent_DOWN) } +func TouchDown(x, y float64) HIDEvent { return HIDEvent{Kind: HIDKindTouchDown, X: x, Y: y} } // TouchUp lifts the finger at screen point (x, y). -func TouchUp(x, y float64) HIDEvent { return touchPress(x, y, pb.HIDEvent_UP) } +func TouchUp(x, y float64) HIDEvent { return HIDEvent{Kind: HIDKindTouchUp, X: x, Y: y} } // KeyDown presses the key with the given USB HID usage identifier. -func KeyDown(usage uint32) HIDEvent { return keyPress(usage, pb.HIDEvent_DOWN) } +func KeyDown(usage uint32) HIDEvent { return HIDEvent{Kind: HIDKindKeyDown, Usage: usage} } // KeyUp releases the key with the given USB HID usage identifier. -func KeyUp(usage uint32) HIDEvent { return keyPress(usage, pb.HIDEvent_UP) } +func KeyUp(usage uint32) HIDEvent { return HIDEvent{Kind: HIDKindKeyUp, Usage: usage} } -// Delay pauses the HID stream. The companion measures delays in seconds, so the -// caller's milliseconds are converted here. +// Delay pauses the HID stream for the given duration. func Delay(milliseconds float64) HIDEvent { - return HIDEvent{event: &pb.HIDEvent{Event: &pb.HIDEvent_Delay{ - Delay: &pb.HIDEvent_HIDDelay{Duration: milliseconds / 1000.0}, - }}} + return HIDEvent{Kind: HIDKindDelay, Milliseconds: milliseconds} } -// SwipeEvent drags from (fromX, fromY) to (toX, toY) over durationSeconds. The -// swipe message carries start, end, and a duration in seconds. +// SwipeEvent drags from (fromX, fromY) to (toX, toY) over durationSeconds. func SwipeEvent(fromX, fromY, toX, toY float64, durationSeconds float64) HIDEvent { - return HIDEvent{event: &pb.HIDEvent{Event: &pb.HIDEvent_Swipe{Swipe: &pb.HIDEvent_HIDSwipe{ - Start: &pb.Point{X: fromX, Y: fromY}, - End: &pb.Point{X: toX, Y: toY}, - Duration: durationSeconds, - }}}} + return HIDEvent{ + Kind: HIDKindSwipe, + FromX: fromX, + FromY: fromY, + ToX: toX, + ToY: toY, + Seconds: durationSeconds, + } } diff --git a/internal/driver/ioscompanion/transport/hid_test.go b/internal/driver/ioscompanion/transport/hid_test.go index 770ee75..18177cd 100644 --- a/internal/driver/ioscompanion/transport/hid_test.go +++ b/internal/driver/ioscompanion/transport/hid_test.go @@ -6,7 +6,31 @@ import ( pb "github.com/priyanshujain/sanderling/internal/driver/ioscompanion/companionpb" ) -func TestTouchBuilders(t *testing.T) { +func TestBuildersSetKindAndFields(t *testing.T) { + cases := []struct { + name string + event HIDEvent + want HIDEvent + }{ + {"touch down", TouchDown(12, 34), HIDEvent{Kind: HIDKindTouchDown, X: 12, Y: 34}}, + {"touch up", TouchUp(12, 34), HIDEvent{Kind: HIDKindTouchUp, X: 12, Y: 34}}, + {"key down", KeyDown(225), HIDEvent{Kind: HIDKindKeyDown, Usage: 225}}, + {"key up", KeyUp(225), HIDEvent{Kind: HIDKindKeyUp, Usage: 225}}, + {"delay", Delay(250), HIDEvent{Kind: HIDKindDelay, Milliseconds: 250}}, + {"swipe", SwipeEvent(1, 2, 3, 4, 0.5), HIDEvent{ + Kind: HIDKindSwipe, FromX: 1, FromY: 2, ToX: 3, ToY: 4, Seconds: 0.5, + }}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + if c.event != c.want { + t.Errorf("event = %+v, want %+v", c.event, c.want) + } + }) + } +} + +func TestTouchEventsToProto(t *testing.T) { cases := []struct { name string event HIDEvent @@ -17,9 +41,13 @@ func TestTouchBuilders(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - press := c.event.event.GetPress() + message, err := hidEventToProto(c.event) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + press := message.GetPress() if press == nil { - t.Fatalf("expected press event, got %#v", c.event.event.GetEvent()) + t.Fatalf("expected press event, got %#v", message.GetEvent()) } if press.GetDirection() != c.direction { t.Errorf("direction = %v, want %v", press.GetDirection(), c.direction) @@ -35,7 +63,7 @@ func TestTouchBuilders(t *testing.T) { } } -func TestKeyBuilders(t *testing.T) { +func TestKeyEventsToProto(t *testing.T) { cases := []struct { name string event HIDEvent @@ -46,7 +74,11 @@ func TestKeyBuilders(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - press := c.event.event.GetPress() + message, err := hidEventToProto(c.event) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + press := message.GetPress() if press == nil { t.Fatalf("expected press event") } @@ -64,8 +96,12 @@ func TestKeyBuilders(t *testing.T) { } } -func TestDelayConvertsToSeconds(t *testing.T) { - delay := Delay(250).event.GetDelay() +func TestDelayToProtoConvertsToSeconds(t *testing.T) { + message, err := hidEventToProto(Delay(250)) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + delay := message.GetDelay() if delay == nil { t.Fatalf("expected delay event") } @@ -74,8 +110,12 @@ func TestDelayConvertsToSeconds(t *testing.T) { } } -func TestSwipeEvent(t *testing.T) { - swipe := SwipeEvent(1, 2, 3, 4, 0.5).event.GetSwipe() +func TestSwipeEventToProto(t *testing.T) { + message, err := hidEventToProto(SwipeEvent(1, 2, 3, 4, 0.5)) + if err != nil { + t.Fatalf("hidEventToProto: %v", err) + } + swipe := message.GetSwipe() if swipe == nil { t.Fatalf("expected swipe event") } @@ -89,3 +129,9 @@ func TestSwipeEvent(t *testing.T) { t.Errorf("duration = %v, want 0.5", swipe.GetDuration()) } } + +func TestUnknownKindToProtoErrors(t *testing.T) { + if _, err := hidEventToProto(HIDEvent{Kind: HIDEventKind(99)}); err == nil { + t.Fatal("expected error for unknown event kind") + } +}