From 7469cf22ea2cac8a59c5e0c1e8de40cc3cf6296c Mon Sep 17 00:00:00 2001 From: zyj Date: Sun, 9 Nov 2025 21:11:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=85=83=E7=B4=A0=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/main.go | 14 +- libs/selector.go | 458 ++++++++++++++++++++++++++++++----------------- 2 files changed, 302 insertions(+), 170 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 433fe31..96ded52 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,13 +4,19 @@ import ( "encoding/json" "fmt" "go-mobile-uiautomator/adb" + selector "go-mobile-uiautomator/libs" "go-mobile-uiautomator/services" "time" ) +const serial = "192.168.4.103:5555" +const addr = "127.0.0.1:5037" + func main() { - addr := "127.0.0.1:5037" - serial := "emulator-5556" + selector.Example() +} + +func LaunchUiautomator() { FilePushInstall() go adb.LaunchUiautomator(addr, serial) select {} @@ -18,8 +24,6 @@ func main() { // 文件推送加安装 func FilePushInstall() { - addr := "127.0.0.1:5037" - serial := "emulator-5556" payload, _ := adb.ListDevicesRaw(addr, 15*time.Second) m := adb.ParseDevicesPayload(payload) b2, _ := json.MarshalIndent(m, "", " ") @@ -33,7 +37,6 @@ func FilePushInstall() { // 文件推送验证 func FilePush() { // edit these for your environment - addr := "127.0.0.1:5037" local := "C:/Users/01/Desktop/aaa.PNG" remote := "/sdcard/ccc.PNG" mode := 0644 @@ -66,7 +69,6 @@ func FilePush() { // 连接验证 func Connect() { - addr := "127.0.0.1:5037" targetProduct := "23113RKC6C" serial, err := adb.FindSerialByProduct(addr, targetProduct) diff --git a/libs/selector.go b/libs/selector.go index f777ab3..c5c6b06 100644 --- a/libs/selector.go +++ b/libs/selector.go @@ -1,98 +1,71 @@ -package libs +package selector import ( + "encoding/json" + "errors" "fmt" - "sort" - "strings" + "strconv" ) -const ( - MASK_TEXT uint32 = 0x00000001 - MASK_TEXT_CONTAINS uint32 = 0x00000002 - MASK_TEXT_MATCHES uint32 = 0x00000004 - MASK_TEXT_STARTS_WITH uint32 = 0x00000008 - MASK_CLASS_NAME uint32 = 0x00000010 - MASK_CLASS_NAME_MATCHES uint32 = 0x00000020 - MASK_DESCRIPTION uint32 = 0x00000040 - MASK_DESCRIPTION_CONTAINS uint32 = 0x00000080 - MASK_DESCRIPTION_MATCHES uint32 = 0x00000100 - MASK_DESCRIPTION_STARTS_WITH uint32 = 0x00000200 - MASK_CHECKABLE uint32 = 0x00000400 - MASK_CHECKED uint32 = 0x00000800 - MASK_CLICKABLE uint32 = 0x00001000 - MASK_LONG_CLICKABLE uint32 = 0x00002000 - MASK_SCROLLABLE uint32 = 0x00004000 - MASK_ENABLED uint32 = 0x00008000 - MASK_FOCUSABLE uint32 = 0x00010000 - MASK_FOCUSED uint32 = 0x00020000 - MASK_SELECTED uint32 = 0x00040000 - MASK_PACKAGE_NAME uint32 = 0x00080000 - MASK_PACKAGE_NAME_MATCHES uint32 = 0x00100000 - MASK_RESOURCE_ID uint32 = 0x00200000 - MASK_RESOURCE_ID_MATCHES uint32 = 0x00400000 - MASK_INDEX uint32 = 0x00800000 - MASK_INSTANCE uint32 = 0x01000000 -) - -// Relationship type for building child/sibling chains. -type relationType string - -const ( - relChild relationType = "child" - relSibling relationType = "sibling" -) - -// Field metadata mirrors Python __fields: name -> (mask, default) -type fieldMeta struct { - Mask uint32 - Default any +// FieldMeta 保存每个字段对应的 mask 位和默认值(nil 表示无默认值) +type FieldMeta struct { + Bit uint32 + Default interface{} } -var allowedFields = map[string]fieldMeta{ - "text": {MASK_TEXT, nil}, - "textContains": {MASK_TEXT_CONTAINS, nil}, - "textMatches": {MASK_TEXT_MATCHES, nil}, - "textStartsWith": {MASK_TEXT_STARTS_WITH, nil}, - "className": {MASK_CLASS_NAME, nil}, - "classNameMatches": {MASK_CLASS_NAME_MATCHES, nil}, - "description": {MASK_DESCRIPTION, nil}, - "descriptionContains": {MASK_DESCRIPTION_CONTAINS, nil}, - "descriptionMatches": {MASK_DESCRIPTION_MATCHES, nil}, - "descriptionStartsWith": {MASK_DESCRIPTION_STARTS_WITH, nil}, - "checkable": {MASK_CHECKABLE, false}, - "checked": {MASK_CHECKED, false}, - "clickable": {MASK_CLICKABLE, false}, - "longClickable": {MASK_LONG_CLICKABLE, false}, - "scrollable": {MASK_SCROLLABLE, false}, - "enabled": {MASK_ENABLED, false}, - "focusable": {MASK_FOCUSABLE, false}, - "focused": {MASK_FOCUSED, false}, - "selected": {MASK_SELECTED, false}, - "packageName": {MASK_PACKAGE_NAME, nil}, - "packageNameMatches": {MASK_PACKAGE_NAME_MATCHES, nil}, - "resourceId": {MASK_RESOURCE_ID, nil}, - "resourceIdMatches": {MASK_RESOURCE_ID_MATCHES, nil}, - "index": {MASK_INDEX, 0}, - "instance": {MASK_INSTANCE, 0}, -} - -// Selector replicates Python Selector(dict) behavior with explicit APIs. +// Selector 表示一个 UiSelector 的构造器 type Selector struct { - mask uint32 - fields map[string]any // only allowedFields keys permitted - childOrSibling []relationType - childOrSiblingSelect []*Selector + // 存放字段及其值(只包含显式设置的字段) + fields map[string]interface{} + + // mask 值(通过设置/删除字段自动维护) + mask uint32 + + // childOrSibling 顺序列表,元素为 "child" 或 "sibling" + childOrSibling []string + + // 对应的嵌套 Selector 列表,长度与 childOrSibling 相同 + childOrSiblingSelector []*Selector } -// NewSelector initializes with kwargs like Python's Selector(**kwargs). -func NewSelector(kwargs map[string]any) (*Selector, error) { +// 字段元数据(与 Python 版本一致) +var fieldDefs = map[string]FieldMeta{ + "text": {Bit: 0x01, Default: nil}, + "textContains": {Bit: 0x02, Default: nil}, + "textMatches": {Bit: 0x04, Default: nil}, + "textStartsWith": {Bit: 0x08, Default: nil}, + "className": {Bit: 0x10, Default: nil}, + "classNameMatches": {Bit: 0x20, Default: nil}, + "description": {Bit: 0x40, Default: nil}, + "descriptionContains": {Bit: 0x80, Default: nil}, + "descriptionMatches": {Bit: 0x0100, Default: nil}, + "descriptionStartsWith": {Bit: 0x0200, Default: nil}, + "checkable": {Bit: 0x0400, Default: false}, + "checked": {Bit: 0x0800, Default: false}, + "clickable": {Bit: 0x1000, Default: false}, + "longClickable": {Bit: 0x2000, Default: false}, + "scrollable": {Bit: 0x4000, Default: false}, + "enabled": {Bit: 0x8000, Default: false}, + "focusable": {Bit: 0x010000, Default: false}, + "focused": {Bit: 0x020000, Default: false}, + "selected": {Bit: 0x040000, Default: false}, + "packageName": {Bit: 0x080000, Default: nil}, + "packageNameMatches": {Bit: 0x100000, Default: nil}, + "resourceId": {Bit: 0x200000, Default: nil}, + "resourceIdMatches": {Bit: 0x400000, Default: nil}, + "index": {Bit: 0x800000, Default: 0}, + "instance": {Bit: 0x01000000, Default: 0}, +} + +// New creates a Selector and可选传入初始字段 +func New(initial map[string]interface{}) (*Selector, error) { s := &Selector{ - mask: 0, - fields: make(map[string]any), - childOrSibling: make([]relationType, 0), - childOrSiblingSelect: make([]*Selector, 0), + fields: make(map[string]interface{}), + childOrSibling: []string{}, + childOrSiblingSelector: []*Selector{}, + mask: 0, } - for k, v := range kwargs { + for k, v := range initial { if err := s.Set(k, v); err != nil { return nil, err } @@ -100,124 +73,281 @@ func NewSelector(kwargs map[string]any) (*Selector, error) { return s, nil } -// Set assigns a field and updates the bitmask. Only allowed fields permitted. -func (s *Selector) Set(key string, val any) error { - meta, ok := allowedFields[key] +// MustNew 跟 New 相同,但在错误时 panic,便于简洁示例 +func MustNew(initial map[string]interface{}) *Selector { + s, err := New(initial) + if err != nil { + panic(err) + } + return s +} + +// validateValue 对给定字段和值做类型校验(布尔字段与整数字段) +func validateValue(key string, val interface{}) error { + meta, ok := fieldDefs[key] if !ok { + return fmt.Errorf("field %s is not allowed", key) + } + if meta.Default == false { + // 期望 bool + _, ok := val.(bool) + if !ok { + return fmt.Errorf("%s must be bool", key) + } + return nil + } + // 对整数字段(Default 为 int 类型)要求 int + switch d := meta.Default.(type) { + case int: + // 支持 int 和可被转为 int 的数值(如 int64) + switch val.(type) { + case int, int8, int16, int32, int64: + return nil + case uint, uint8, uint16, uint32, uint64: + return nil + default: + return fmt.Errorf("%s must be integer type, default=%v", key, d) + } + default: + // 其它字段没有特别要求 + return nil + } +} + +// Set 设置字段并更新 mask;若字段非法或类型不对则返回错误 +func (s *Selector) Set(key string, val interface{}) error { + if _, ok := fieldDefs[key]; !ok { return fmt.Errorf("%s is not allowed", key) } + if err := validateValue(key, val); err != nil { + return err + } s.fields[key] = val - s.mask |= meta.Mask + s.mask = s.mask | fieldDefs[key].Bit return nil } -// Delete removes a field and clears its bit in mask. +// Delete 删除字段并更新 mask;幂等(删除不存在字段不报错) func (s *Selector) Delete(key string) error { - meta, ok := allowedFields[key] - if !ok { + if _, ok := fieldDefs[key]; !ok { return fmt.Errorf("%s is not allowed", key) } - delete(s.fields, key) - s.mask &^= meta.Mask // AND NOT + if _, present := s.fields[key]; present { + delete(s.fields, key) + s.mask = s.mask & ^fieldDefs[key].Bit + } return nil } -// Get retrieves a field (nil if absent). -func (s *Selector) Get(key string) (any, bool) { - v, ok := s.fields[key] - return v, ok +// Mask 返回当前 mask(只读) +func (s *Selector) Mask() uint32 { + return s.mask } -// Mask returns current bit mask. -func (s *Selector) Mask() uint32 { return s.mask } - -// Child appends a child selector (in-place) and returns the receiver. -func (s *Selector) Child(kwargs map[string]any) (*Selector, error) { - sub, err := NewSelector(kwargs) +// Child 在末尾添加 child +func (s *Selector) Child(initial map[string]interface{}) (*Selector, error) { + child, err := New(initial) if err != nil { return nil, err } - s.childOrSibling = append(s.childOrSibling, relChild) - s.childOrSiblingSelect = append(s.childOrSiblingSelect, sub) + s.childOrSibling = append(s.childOrSibling, "child") + s.childOrSiblingSelector = append(s.childOrSiblingSelector, child) return s, nil } -// Sibling appends a sibling selector (in-place) and returns the receiver. -func (s *Selector) Sibling(kwargs map[string]any) (*Selector, error) { - sub, err := NewSelector(kwargs) +// Sibling 在末尾添加 sibling +func (s *Selector) Sibling(initial map[string]interface{}) (*Selector, error) { + child, err := New(initial) if err != nil { return nil, err } - s.childOrSibling = append(s.childOrSibling, relSibling) - s.childOrSiblingSelect = append(s.childOrSiblingSelect, sub) + s.childOrSibling = append(s.childOrSibling, "sibling") + s.childOrSiblingSelector = append(s.childOrSiblingSelector, child) return s, nil } -// Clone deep-copies selector, including child/sibling chains. -func (s *Selector) Clone() *Selector { - cp := &Selector{ - mask: s.mask, - fields: make(map[string]any, len(s.fields)), - childOrSibling: make([]relationType, len(s.childOrSibling)), - childOrSiblingSelect: make([]*Selector, 0, len(s.childOrSiblingSelect)), - } - for k, v := range s.fields { - cp.fields[k] = v - } - copy(cp.childOrSibling, s.childOrSibling) - for _, sub := range s.childOrSiblingSelect { - cp.childOrSiblingSelect = append(cp.childOrSiblingSelect, sub.Clone()) - } - return cp -} - -// UpdateInstance updates 'instance' on the last child/sibling selector if present; -// otherwise updates the current selector's 'instance'. +// UpdateInstance 更新最后一个 childOrSiblingSelector 的 instance 字段(或根 selector) func (s *Selector) UpdateInstance(i int) error { - if len(s.childOrSiblingSelect) > 0 { - last := s.childOrSiblingSelect[len(s.childOrSiblingSelect)-1] - return last.Set("instance", i) + n := len(s.childOrSiblingSelector) + if n > 0 { + return s.childOrSiblingSelector[n-1].Set("instance", i) } return s.Set("instance", i) } -// String prints Selector [k='v', ...] while skipping mask and empty child/sibling groups. -// Deterministic order for readability. -func (s *Selector) String() string { - parts := make([]string, 0, len(s.fields)+2) - - // stable sort keys for consistent output - keys := make([]string, 0, len(s.fields)) - for k := range s.fields { - keys = append(keys, k) +// Clone 深拷贝 Selector,包括子/兄弟 +func (s *Selector) Clone() *Selector { + clone := &Selector{ + fields: make(map[string]interface{}, len(s.fields)), + mask: s.mask, + childOrSibling: append([]string{}, s.childOrSibling...), + childOrSiblingSelector: make([]*Selector, 0, len(s.childOrSiblingSelector)), } - sort.Strings(keys) - - for _, k := range keys { - // repr-like rendering - parts = append(parts, fmt.Sprintf("%s=%q", k, fmtAny(s.fields[k]))) + for k, v := range s.fields { + // 简单深拷贝:对于常见类型(string,bool,int)直接赋值即可。 + // 若值为复杂结构,调用方应使用 ToMap/ToJSON 再 Parse 得到深拷贝。 + clone.fields[k] = v } + for _, c := range s.childOrSiblingSelector { + clone.childOrSiblingSelector = append(clone.childOrSiblingSelector, c.Clone()) + } + return clone +} - // child/sibling only if present - if len(s.childOrSibling) > 0 && len(s.childOrSiblingSelect) > 0 { - // render as chain: rel(type)->subselector - chunks := make([]string, 0, len(s.childOrSibling)) - for i, rel := range s.childOrSibling { - chunks = append(chunks, fmt.Sprintf("%s(%s)", rel, s.childOrSiblingSelect[i].String())) +// ToMap 序列化为 map,便于 RPC 调用或 JSON 编码 +func (s *Selector) ToMap() map[string]interface{} { + out := make(map[string]interface{}, len(s.fields)+3) + for k, v := range s.fields { + out[k] = v + } + out["mask"] = s.mask + if len(s.childOrSibling) > 0 { + out["childOrSibling"] = append([]string{}, s.childOrSibling...) + cs := make([]map[string]interface{}, 0, len(s.childOrSiblingSelector)) + for _, c := range s.childOrSiblingSelector { + cs = append(cs, c.ToMap()) } - parts = append(parts, "chain=["+strings.Join(chunks, ", ")+"]") + out["childOrSiblingSelector"] = cs } - - return "Selector [" + strings.Join(parts, ", ") + "]" + return out } -func fmtAny(v any) string { - switch t := v.(type) { - case string: - return t - case fmt.Stringer: - return t.String() - default: - return fmt.Sprintf("%v", v) - } +// ToJSON 返回 ToMap 的 JSON 编码 +func (s *Selector) ToJSON() ([]byte, error) { + return json.Marshal(s.ToMap()) +} + +// FromMap 从 map 恢复 Selector(简单实现,忽略非法字段) +func FromMap(data map[string]interface{}) (*Selector, error) { + // 提取根字段 + root := &Selector{ + fields: make(map[string]interface{}), + childOrSibling: []string{}, + childOrSiblingSelector: []*Selector{}, + mask: 0, + } + // 读取已知字段 + for k, meta := range fieldDefs { + if v, ok := data[k]; ok { + // 尝试 Set 以做类型校验并设置 mask + if err := root.Set(k, v); err != nil { + return nil, err + } + // 注意:Set 已经更新了 mask + _ = meta + } + } + // 恢复 mask(如果提供了 mask,并且为数值) + if m, ok := data["mask"]; ok { + switch mv := m.(type) { + case float64: + root.mask = uint32(mv) + case uint32: + root.mask = mv + case int: + root.mask = uint32(mv) + case int64: + root.mask = uint32(mv) + default: + // 忽略不能解析的 mask + } + } + // 恢复 childOrSibling 列表和对应 selector(期望 childOrSiblingSelector 为 []map[string]interface{}) + if cs, ok := data["childOrSibling"]; ok { + if arr, ok := cs.([]interface{}); ok { + for _, e := range arr { + if sname, ok := e.(string); ok { + root.childOrSibling = append(root.childOrSibling, sname) + } + } + } + } + if css, ok := data["childOrSiblingSelector"]; ok { + if arr, ok := css.([]interface{}); ok { + for _, item := range arr { + if m, ok := item.(map[string]interface{}); ok { + c, err := FromMap(m) + if err != nil { + return nil, err + } + root.childOrSiblingSelector = append(root.childOrSiblingSelector, c) + } + } + } + } + return root, nil +} + +// UpdateAtPath 在指定路径(child 索引链)上更新字段 +// path: 逐级索引,例如 [0,2] 表示 childOrSiblingSelector[0].childOrSiblingSelector[2] +func (s *Selector) UpdateAtPath(path []int, updates map[string]interface{}) error { + node := s + for _, idx := range path { + if idx < 0 || idx >= len(node.childOrSiblingSelector) { + return errors.New("path out of range") + } + node = node.childOrSiblingSelector[idx] + } + for k, v := range updates { + if err := node.Set(k, v); err != nil { + return err + } + } + return nil +} + +// String 实现 fmt.Stringer,输出友好可读的 Selector 表示(类似 Python 的 __str__) +func (s *Selector) String() string { + m := s.ToMap() + // 删除空的 childOrSibling 字段以保持简洁 + if _, ok := m["childOrSibling"]; !ok { + delete(m, "childOrSibling") + delete(m, "childOrSiblingSelector") + } + b, _ := json.Marshal(m) + return "Selector " + string(b) +} + +// Example 用法示例(不是正式测试,仅供快速手动运行) +func Example() { + // 初始化根 selector + root := MustNew(map[string]interface{}{ + "className": "android.widget.LinearLayout", + }) + + // 添加 child + root.Child(map[string]interface{}{ + "text": "下一步", + "instance": 0, + }) + + // 更新最后一个 child 的 instance + _ = root.UpdateInstance(2) + + // 深拷贝 + cpy := root.Clone() + + // 序列化到 JSON + j, _ := cpy.ToJSON() + fmt.Println(string(j)) +} + +// 简单测试函数(你可在 package 内使用 testing 包将其改写成真正的单元测试) +func SimpleTests() { + // set & delete + s := MustNew(map[string]interface{}{"text": "hello"}) + fmt.Println("mask after set:", strconv.FormatUint(uint64(s.Mask()), 10)) + _ = s.Delete("text") + fmt.Println("mask after delete:", strconv.FormatUint(uint64(s.Mask()), 10)) + + // bool 类型校验 + _, err := New(map[string]interface{}{"checkable": "yes"}) + fmt.Println("expected error for bad bool:", err != nil) + + // clone 深拷贝检查 + s2 := MustNew(map[string]interface{}{"text": "a"}) + s2.Child(map[string]interface{}{"text": "b", "instance": 1}) + c := s2.Clone() + c.childOrSibling[0] = "sibling" + fmt.Println("original childOrSibling:", s2.childOrSibling[0], "clone childOrSibling:", c.childOrSibling[0]) }