diff --git a/internal/driver/ioscompanion/transport/grpc.go b/internal/driver/ioscompanion/transport/grpc.go index 6a2227a..24a3eba 100644 --- a/internal/driver/ioscompanion/transport/grpc.go +++ b/internal/driver/ioscompanion/transport/grpc.go @@ -47,6 +47,26 @@ func (c *grpcCompanion) AccessibilityInfo(ctx context.Context) (string, error) { return resp.GetJson(), nil } +func (c *grpcCompanion) Describe(ctx context.Context) (ScreenDescription, error) { + resp, err := c.client.Describe(ctx, &pb.TargetDescriptionRequest{}) + if err != nil { + return ScreenDescription{}, err + } + return screenDescriptionFrom(resp), nil +} + +// screenDescriptionFrom extracts the point dimensions and scale from a describe +// response. The generated getters are nil-safe, so a response missing the +// nested messages yields a zero-valued ScreenDescription rather than panicking. +func screenDescriptionFrom(resp *pb.TargetDescriptionResponse) ScreenDescription { + dimensions := resp.GetTargetDescription().GetScreenDimensions() + return ScreenDescription{ + WidthPoints: int(dimensions.GetWidthPoints()), + HeightPoints: int(dimensions.GetHeightPoints()), + Scale: dimensions.GetDensity(), + } +} + func (c *grpcCompanion) SendHID(ctx context.Context, events ...HIDEvent) error { stream, err := c.client.Hid(ctx) if err != nil { diff --git a/internal/driver/ioscompanion/transport/grpc_test.go b/internal/driver/ioscompanion/transport/grpc_test.go index 05b00bf..cb76ed9 100644 --- a/internal/driver/ioscompanion/transport/grpc_test.go +++ b/internal/driver/ioscompanion/transport/grpc_test.go @@ -42,6 +42,31 @@ func TestProcessStateFromProto(t *testing.T) { } } +func TestScreenDescriptionFrom(t *testing.T) { + resp := &pb.TargetDescriptionResponse{ + TargetDescription: &pb.TargetDescription{ + ScreenDimensions: &pb.ScreenDimensions{ + Width: 828, + Height: 1792, + Density: 2, + WidthPoints: 414, + HeightPoints: 896, + }, + }, + } + got := screenDescriptionFrom(resp) + want := ScreenDescription{WidthPoints: 414, HeightPoints: 896, Scale: 2} + if got != want { + t.Errorf("screenDescriptionFrom = %+v, want %+v", got, want) + } +} + +func TestScreenDescriptionFromNilSafe(t *testing.T) { + if got := screenDescriptionFrom(&pb.TargetDescriptionResponse{}); got != (ScreenDescription{}) { + t.Errorf("screenDescriptionFrom(empty) = %+v, want zero", got) + } +} + func TestTarGzipDirectory(t *testing.T) { root := t.TempDir() bundle := filepath.Join(root, "Sample.app") diff --git a/internal/driver/ioscompanion/transport/transport.go b/internal/driver/ioscompanion/transport/transport.go index 6191130..9e229f3 100644 --- a/internal/driver/ioscompanion/transport/transport.go +++ b/internal/driver/ioscompanion/transport/transport.go @@ -16,6 +16,10 @@ type Companion interface { // raw flat-format JSON string the companion emits. AccessibilityInfo(ctx context.Context) (string, error) + // Describe reports the target's screen dimensions in points, along with + // the pixel scale when the companion supplies one. + Describe(ctx context.Context) (ScreenDescription, error) + // SendHID opens the HID stream, sends every event in order, then closes. SendHID(ctx context.Context, events ...HIDEvent) error @@ -43,6 +47,16 @@ type Companion interface { Close() error } +// ScreenDescription carries the target screen geometry. Width and Height are +// in points (the coordinate space HID events and the accessibility frames use). +// Scale is the pixel-per-point density, or 0 when the companion did not report +// one. +type ScreenDescription struct { + WidthPoints int + HeightPoints int + Scale float64 +} + // ProcessState mirrors the companion's notion of whether an app is running. type ProcessState int