mobile-mcp-ai 2.3.6__py3-none-any.whl → 2.3.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mobile_mcp/core/basic_tools_lite.py +120 -21
- {mobile_mcp_ai-2.3.6.dist-info → mobile_mcp_ai-2.3.8.dist-info}/METADATA +1 -1
- {mobile_mcp_ai-2.3.6.dist-info → mobile_mcp_ai-2.3.8.dist-info}/RECORD +7 -7
- {mobile_mcp_ai-2.3.6.dist-info → mobile_mcp_ai-2.3.8.dist-info}/LICENSE +0 -0
- {mobile_mcp_ai-2.3.6.dist-info → mobile_mcp_ai-2.3.8.dist-info}/WHEEL +0 -0
- {mobile_mcp_ai-2.3.6.dist-info → mobile_mcp_ai-2.3.8.dist-info}/entry_points.txt +0 -0
- {mobile_mcp_ai-2.3.6.dist-info → mobile_mcp_ai-2.3.8.dist-info}/top_level.txt +0 -0
|
@@ -652,7 +652,14 @@ class BasicMobileToolsLite:
|
|
|
652
652
|
# ==================== 输入操作 ====================
|
|
653
653
|
|
|
654
654
|
def input_text_by_id(self, resource_id: str, text: str) -> Dict:
|
|
655
|
-
"""通过 resource-id 输入文本
|
|
655
|
+
"""通过 resource-id 输入文本
|
|
656
|
+
|
|
657
|
+
优化策略:
|
|
658
|
+
1. 先用 resourceId 定位
|
|
659
|
+
2. 如果只有 1 个元素 → 直接输入
|
|
660
|
+
3. 如果有多个相同 ID(>5个说明 ID 不可靠)→ 改用 EditText 类型定位
|
|
661
|
+
4. 多个 EditText 时选择最靠上的(搜索框通常在顶部)
|
|
662
|
+
"""
|
|
656
663
|
try:
|
|
657
664
|
if self._is_ios():
|
|
658
665
|
ios_client = self._get_ios_client()
|
|
@@ -667,13 +674,70 @@ class BasicMobileToolsLite:
|
|
|
667
674
|
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
668
675
|
return {"success": False, "message": f"❌ 输入框不存在: {resource_id}"}
|
|
669
676
|
else:
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
677
|
+
elements = self.client.u2(resourceId=resource_id)
|
|
678
|
+
|
|
679
|
+
# 检查是否存在
|
|
680
|
+
if elements.exists(timeout=0.5):
|
|
681
|
+
count = elements.count
|
|
682
|
+
|
|
683
|
+
# 只有 1 个元素,直接输入
|
|
684
|
+
if count == 1:
|
|
685
|
+
elements.set_text(text)
|
|
686
|
+
time.sleep(0.3)
|
|
687
|
+
self._record_operation('input', element=resource_id, ref=resource_id, text=text)
|
|
688
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
689
|
+
|
|
690
|
+
# 多个相同 ID(<=5个),尝试智能选择
|
|
691
|
+
if count <= 5:
|
|
692
|
+
for i in range(count):
|
|
693
|
+
try:
|
|
694
|
+
elem = elements[i]
|
|
695
|
+
info = elem.info
|
|
696
|
+
# 优先选择可编辑的
|
|
697
|
+
if info.get('editable') or info.get('focusable'):
|
|
698
|
+
elem.set_text(text)
|
|
699
|
+
time.sleep(0.3)
|
|
700
|
+
self._record_operation('input', element=resource_id, ref=resource_id, text=text)
|
|
701
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
702
|
+
except:
|
|
703
|
+
continue
|
|
704
|
+
# 没找到可编辑的,用第一个
|
|
705
|
+
elements[0].set_text(text)
|
|
706
|
+
time.sleep(0.3)
|
|
707
|
+
self._record_operation('input', element=resource_id, ref=resource_id, text=text)
|
|
708
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}'"}
|
|
709
|
+
|
|
710
|
+
# ID 不可靠(不存在或太多),改用 EditText 类型定位
|
|
711
|
+
edit_texts = self.client.u2(className='android.widget.EditText')
|
|
712
|
+
if edit_texts.exists(timeout=0.5):
|
|
713
|
+
et_count = edit_texts.count
|
|
714
|
+
if et_count == 1:
|
|
715
|
+
edit_texts.set_text(text)
|
|
716
|
+
time.sleep(0.3)
|
|
717
|
+
self._record_operation('input', element='EditText', ref='EditText', text=text)
|
|
718
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}' (通过 EditText 定位)"}
|
|
719
|
+
|
|
720
|
+
# 多个 EditText,选择最靠上的
|
|
721
|
+
best_elem = None
|
|
722
|
+
min_top = 9999
|
|
723
|
+
for i in range(et_count):
|
|
724
|
+
try:
|
|
725
|
+
elem = edit_texts[i]
|
|
726
|
+
top = elem.info.get('bounds', {}).get('top', 9999)
|
|
727
|
+
if top < min_top:
|
|
728
|
+
min_top = top
|
|
729
|
+
best_elem = elem
|
|
730
|
+
except:
|
|
731
|
+
continue
|
|
732
|
+
|
|
733
|
+
if best_elem:
|
|
734
|
+
best_elem.set_text(text)
|
|
735
|
+
time.sleep(0.3)
|
|
736
|
+
self._record_operation('input', element='EditText', ref='EditText', text=text)
|
|
737
|
+
return {"success": True, "message": f"✅ 输入成功: '{text}' (通过 EditText 定位,选择最顶部的)"}
|
|
738
|
+
|
|
676
739
|
return {"success": False, "message": f"❌ 输入框不存在: {resource_id}"}
|
|
740
|
+
|
|
677
741
|
except Exception as e:
|
|
678
742
|
return {"success": False, "message": f"❌ 输入失败: {e}"}
|
|
679
743
|
|
|
@@ -941,9 +1005,10 @@ class BasicMobileToolsLite:
|
|
|
941
1005
|
"""智能关闭弹窗
|
|
942
1006
|
|
|
943
1007
|
策略:
|
|
944
|
-
1. 从控件树找可能的关闭按钮(clickable=true
|
|
945
|
-
2.
|
|
946
|
-
3.
|
|
1008
|
+
1. 从控件树找可能的关闭按钮(clickable=true,尺寸小)
|
|
1009
|
+
2. 搜索范围:右上角、左上角、正下方中间、右下角、左下角
|
|
1010
|
+
3. 如果找到,计算中心点并点击
|
|
1011
|
+
4. 如果没找到,返回需要视觉识别的提示
|
|
947
1012
|
"""
|
|
948
1013
|
try:
|
|
949
1014
|
# 获取屏幕尺寸
|
|
@@ -979,14 +1044,44 @@ class BasicMobileToolsLite:
|
|
|
979
1044
|
center_x = (x1 + x2) // 2
|
|
980
1045
|
center_y = (y1 + y2) // 2
|
|
981
1046
|
|
|
982
|
-
# 关闭按钮特征:尺寸小(
|
|
983
|
-
if
|
|
984
|
-
#
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1047
|
+
# 关闭按钮特征:尺寸小(20-120px)
|
|
1048
|
+
if 20 <= width <= 120 and 20 <= height <= 120:
|
|
1049
|
+
# 计算位置得分,支持多个区域
|
|
1050
|
+
score = 0
|
|
1051
|
+
position = ""
|
|
1052
|
+
|
|
1053
|
+
# 相对位置(0-1)
|
|
1054
|
+
rel_x = center_x / screen_width
|
|
1055
|
+
rel_y = center_y / screen_height
|
|
1056
|
+
|
|
1057
|
+
# 右上角(最常见): x > 0.6, y < 0.4
|
|
1058
|
+
if rel_x > 0.6 and rel_y < 0.4:
|
|
1059
|
+
score = 1.0 + (rel_x - 0.6) * 2 + (0.4 - rel_y) * 2
|
|
1060
|
+
position = "右上角"
|
|
1061
|
+
|
|
1062
|
+
# 左上角: x < 0.4, y < 0.4
|
|
1063
|
+
elif rel_x < 0.4 and rel_y < 0.4:
|
|
1064
|
+
score = 0.9 + (0.4 - rel_x) * 2 + (0.4 - rel_y) * 2
|
|
1065
|
+
position = "左上角"
|
|
1066
|
+
|
|
1067
|
+
# 正下方中间(弹窗底部): 0.3 < x < 0.7, y > 0.5
|
|
1068
|
+
elif 0.3 < rel_x < 0.7 and rel_y > 0.5:
|
|
1069
|
+
# 越靠近中间下方得分越高
|
|
1070
|
+
center_score = 1 - abs(rel_x - 0.5) * 2
|
|
1071
|
+
score = 0.8 + center_score * 0.5 + (rel_y - 0.5) * 0.5
|
|
1072
|
+
position = "正下方"
|
|
1073
|
+
|
|
1074
|
+
# 右下角: x > 0.6, y > 0.6
|
|
1075
|
+
elif rel_x > 0.6 and rel_y > 0.6:
|
|
1076
|
+
score = 0.7 + (rel_x - 0.6) + (rel_y - 0.6)
|
|
1077
|
+
position = "右下角"
|
|
1078
|
+
|
|
1079
|
+
# 左下角: x < 0.4, y > 0.6
|
|
1080
|
+
elif rel_x < 0.4 and rel_y > 0.6:
|
|
1081
|
+
score = 0.6 + (0.4 - rel_x) + (rel_y - 0.6)
|
|
1082
|
+
position = "左下角"
|
|
1083
|
+
|
|
1084
|
+
if score > 0:
|
|
990
1085
|
close_candidates.append({
|
|
991
1086
|
'bounds': bounds,
|
|
992
1087
|
'center_x': center_x,
|
|
@@ -994,6 +1089,7 @@ class BasicMobileToolsLite:
|
|
|
994
1089
|
'width': width,
|
|
995
1090
|
'height': height,
|
|
996
1091
|
'score': score,
|
|
1092
|
+
'position': position,
|
|
997
1093
|
'resource_id': elem.get('resource_id', ''),
|
|
998
1094
|
'text': elem.get('text', '')
|
|
999
1095
|
})
|
|
@@ -1002,7 +1098,8 @@ class BasicMobileToolsLite:
|
|
|
1002
1098
|
return {
|
|
1003
1099
|
"success": False,
|
|
1004
1100
|
"message": "❌ 控件树未找到关闭按钮,请使用截图+视觉识别",
|
|
1005
|
-
"suggestion": "
|
|
1101
|
+
"suggestion": "先全屏截图让 AI 分析关闭按钮位置,再用局部截图精确定位",
|
|
1102
|
+
"screen_size": f"{screen_width}x{screen_height}"
|
|
1006
1103
|
}
|
|
1007
1104
|
|
|
1008
1105
|
# 按得分排序,取最可能的
|
|
@@ -1022,9 +1119,11 @@ class BasicMobileToolsLite:
|
|
|
1022
1119
|
|
|
1023
1120
|
return {
|
|
1024
1121
|
"success": True,
|
|
1025
|
-
"message": f"✅
|
|
1122
|
+
"message": f"✅ 点击关闭按钮 ({best['position']}): ({best['center_x']}, {best['center_y']})",
|
|
1026
1123
|
"bounds": best['bounds'],
|
|
1027
|
-
"
|
|
1124
|
+
"position": best['position'],
|
|
1125
|
+
"candidates_count": len(close_candidates),
|
|
1126
|
+
"all_positions": [c['position'] for c in close_candidates[:5]]
|
|
1028
1127
|
}
|
|
1029
1128
|
|
|
1030
1129
|
except Exception as e:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
|
|
2
2
|
mobile_mcp/config.py,sha256=yaFLAV4bc2wX0GQPtZDo7OYF9E88tXV-av41fQsJwK4,4480
|
|
3
3
|
mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
|
|
4
|
-
mobile_mcp/core/basic_tools_lite.py,sha256=
|
|
4
|
+
mobile_mcp/core/basic_tools_lite.py,sha256=Z4K9TTGR-vyvk7MzWGiE7Mwarnckqji5ToH5GpFKo5Y,64772
|
|
5
5
|
mobile_mcp/core/device_manager.py,sha256=PX3-B5bJFnKNt6C8fT7FSY8JwD-ngZ3toF88bcOV9qA,8766
|
|
6
6
|
mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
|
|
7
7
|
mobile_mcp/core/ios_client_wda.py,sha256=KudSbWTy-0l8OMQjXpsDYAiL59w7HVrw-i7ApfExJLA,18755
|
|
@@ -17,9 +17,9 @@ mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,
|
|
|
17
17
|
mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
|
|
18
18
|
mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
|
|
19
19
|
mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
|
|
20
|
-
mobile_mcp_ai-2.3.
|
|
21
|
-
mobile_mcp_ai-2.3.
|
|
22
|
-
mobile_mcp_ai-2.3.
|
|
23
|
-
mobile_mcp_ai-2.3.
|
|
24
|
-
mobile_mcp_ai-2.3.
|
|
25
|
-
mobile_mcp_ai-2.3.
|
|
20
|
+
mobile_mcp_ai-2.3.8.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
|
|
21
|
+
mobile_mcp_ai-2.3.8.dist-info/METADATA,sha256=E5wnga67GNtSj3kqS7_KDYGtt3ckgw90Vq4McIFksP0,9423
|
|
22
|
+
mobile_mcp_ai-2.3.8.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
23
|
+
mobile_mcp_ai-2.3.8.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
|
|
24
|
+
mobile_mcp_ai-2.3.8.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
|
|
25
|
+
mobile_mcp_ai-2.3.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|