├── .idea
├── .gitignore
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── mumuApi.iml
├── README.md
├── mumu
├── __pycache__
│ ├── config.cpython-38.pyc
│ ├── constant.cpython-38.pyc
│ ├── control.cpython-38.pyc
│ ├── mumu.cpython-38.pyc
│ └── utils.cpython-38.pyc
├── api
│ ├── adb
│ │ └── Adb.py
│ ├── core
│ │ ├── Core.py
│ │ ├── __pycache__
│ │ │ ├── Core.cpython-38.pyc
│ │ │ ├── app.cpython-38.pyc
│ │ │ ├── performance.cpython-38.pyc
│ │ │ ├── power.cpython-38.pyc
│ │ │ ├── shortcut.cpython-38.pyc
│ │ │ ├── simulation.cpython-38.pyc
│ │ │ └── window.cpython-38.pyc
│ │ ├── app.py
│ │ ├── performance.py
│ │ ├── power.py
│ │ ├── shortcut.py
│ │ ├── simulation.py
│ │ └── window.py
│ ├── develop
│ │ ├── __pycache__
│ │ │ └── androidevent.cpython-38.pyc
│ │ └── androidevent.py
│ ├── driver
│ │ ├── Driver.py
│ │ ├── __pycache__
│ │ │ ├── Driver.cpython-38.pyc
│ │ │ └── bridge.cpython-38.pyc
│ │ └── bridge.py
│ ├── network
│ │ ├── Network.py
│ │ └── __pycache__
│ │ │ └── Network.cpython-38.pyc
│ ├── permission
│ │ ├── Permission.py
│ │ ├── __pycache__
│ │ │ ├── Permission.cpython-38.pyc
│ │ │ └── root.cpython-38.pyc
│ │ └── root.py
│ ├── screen
│ │ ├── __pycache__
│ │ │ └── screen.cpython-38.pyc
│ │ ├── gui.py
│ │ └── screen.py
│ └── setting
│ │ ├── __pycache__
│ │ └── setting.cpython-38.pyc
│ │ └── setting.py
├── config.py
├── constant.py
├── control.py
├── mumu.py
└── utils.py
├── requirements.txt
└── test.py
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
32 |
33 |
34 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/mumuApi.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mumu模拟器Python API
2 |
3 | - [Mumu模拟器Python API](#mumu模拟器python-api)
4 | - [项目介绍](#项目介绍)
5 | - [如何使用?](#如何使用)
6 | - [设置MuMuManager路径](#设置mumumanager路径)
7 | - [模拟器索引说明](#模拟器索引说明)
8 | - [选择模拟器](#选择模拟器)
9 | - [举个例子](#举个例子)
10 | - [注意](#注意)
11 | - [API类](#api类)
12 | - [驱动类(driver)](#驱动类driver)
13 | - [网络桥接驱动(bride)](#网络桥接驱动bride)
14 | - [安装桥接驱动(install)](#安装桥接驱动install)
15 | - [卸载桥接驱动(uninstall)](#卸载桥接驱动uninstall)
16 | - [权限类(permission)](#权限类permission)
17 | - [\* ROOT权限(root)](#-root权限root)
18 | - [开启ROOT权限(enable)](#开启root权限enable)
19 | - [关闭ROOT权限(disable)](#关闭root权限disable)
20 | - [电源类(power)](#电源类power)
21 | - [启动模拟器(start)](#启动模拟器start)
22 | - [关闭模拟器(shutdown|stop)](#关闭模拟器shutdownstop)
23 | - [重启模拟器(restart|reboot)](#重启模拟器restartreboot)
24 | - [窗口类(window)](#窗口类window)
25 | - [显示窗口(show)](#显示窗口show)
26 | - [隐藏窗口(hidden)](#隐藏窗口hidden)
27 | - [调整窗口大小或位置(layout)](#调整窗口大小或位置layout)
28 | - [应用类(app)](#应用类app)
29 | - [安装一个应用(install)](#安装一个应用install)
30 | - [卸载一个应用(uninstall)](#卸载一个应用uninstall)
31 | - [启动模拟器里的应用(launch)](#启动模拟器里的应用launch)
32 | - [关闭模拟器里的应用(close)](#关闭模拟器里的应用close)
33 | - [\* 判断应用是否存在(exists)](#-判断应用是否存在exists)
34 | - [\* 判断应用是否不存在(doesntExists)](#-判断应用是否不存在doesntexists)
35 | - [获取已经安装的应用列表(get\_installed)](#获取已经安装的应用列表get_installed)
36 | - [\* 获取应用状态(state)](#-获取应用状态state)
37 | - [核心类(core)](#核心类core)
38 | - [创建模拟器(create)](#创建模拟器create)
39 | - [克隆模拟器(clone)](#克隆模拟器clone)
40 | - [删除模拟器(delete)](#删除模拟器delete)
41 | - [重命名模拟器(rename)](#重命名模拟器rename)
42 | - [备份模拟器(export)](#备份模拟器export)
43 | - [导入模拟器(import\_)](#导入模拟器import_)
44 | - [限制CPU使用率(limit\_cpu)](#限制cpu使用率limit_cpu)
45 | - [安卓事件类(androidEvent)](#安卓事件类androidevent)
46 | - [屏幕旋转(rotate)](#屏幕旋转rotate)
47 | - [返回主页(go\_home)](#返回主页go_home)
48 | - [返回(back)](#返回back)
49 | - [窗口置顶(top\_most)](#窗口置顶top_most)
50 | - [窗口全屏(fullscreen)](#窗口全屏fullscreen)
51 | - [摇一摇(shake)](#摇一摇shake)
52 | - [截图(screenshot)](#截图screenshot)
53 | - [音量增加(volume\_up)](#音量增加volume_up)
54 | - [音量减少(volume\_down)](#音量减少volume_down)
55 | - [音量静音(volume\_mute)](#音量静音volume_mute)
56 | - [任务键(go\_task)](#任务键go_task)
57 | - [修改虚拟定位(location)](#修改虚拟定位location)
58 | - [修改重力感应(gyro)](#修改重力感应gyro)
59 | - [快捷方式类(shortcut)](#快捷方式类shortcut)
60 | - [创建桌面快捷方式(create)](#创建桌面快捷方式create)
61 | - [删除桌面快捷方式(delete)](#删除桌面快捷方式delete)
62 | - [机型类(simulation)](#机型类simulation)
63 | - [修改MAC地址(mac\_address)](#修改mac地址mac_address)
64 | - [修改IMEI(imei)](#修改imeiimei)
65 | - [修改IMSI(imsi)](#修改imsiimsi)
66 | - [修改Android ID(android\_id)](#修改android-idandroid_id)
67 | - [设置模拟器设备型号(model)](#设置模拟器设备型号model)
68 | - [设置模拟器主板品牌(brand)](#设置模拟器主板品牌brand)
69 | - [设置模拟器硬件制造商(solution)](#设置模拟器硬件制造商solution)
70 | - [设置模拟器手机号码(phone\_number)](#设置模拟器手机号码phone_number)
71 | - [设置模拟器GPU型号(gpu\_model)](#设置模拟器gpu型号gpu_model)
72 | - [配置类(setting)](#配置类setting)
73 | - [获取模拟器配置所有配置项(all)](#获取模拟器配置所有配置项all)
74 | - [获取一个或多个配置项(get)](#获取一个或多个配置项get)
75 | - [修改一个或多个配置(set)](#修改一个或多个配置set)
76 | - [根据JSON文件内容修改配置(set\_by\_json)](#根据json文件内容修改配置set_by_json)
77 | - [\*判断某个配置是否等于某个值(equal)](#判断某个配置是否等于某个值equal)
78 | - [\*判断某个配置是否不等于某个值(not\_equal)](#判断某个配置是否不等于某个值not_equal)
79 | - [\*判断某个配置等于某个值时,修改为另一个值(equal\_then\_set)](#判断某个配置等于某个值时修改为另一个值equal_then_set)
80 | - [\*判断某个配置不等于某个值时,修改为另一个值(not\_equal\_then\_set)](#判断某个配置不等于某个值时修改为另一个值not_equal_then_set)
81 | - [\*屏幕类(screen)](#屏幕类screen)
82 | - [调整模拟器分辨率(resolution)](#调整模拟器分辨率resolution)
83 | - [设置为手机分辨率(resolution\_mobile)](#设置为手机分辨率resolution_mobile)
84 | - [设置为平板分辨率(resolution\_tablet)](#设置为平板分辨率resolution_tablet)
85 | - [设置为超宽屏分辨率(resolution\_ultrawide)](#设置为超宽屏分辨率resolution_ultrawide)
86 | - [调整模拟器DPI(dpi)](#调整模拟器dpidpi)
87 | - [调整模拟器亮度(brightness)](#调整模拟器亮度brightness)
88 | - [调整模拟器最大帧率(max\_frame\_rate)](#调整模拟器最大帧率max_frame_rate)
89 | - [设置动态调整帧率(dynamic\_adjust\_frame\_rate)](#设置动态调整帧率dynamic_adjust_frame_rate)
90 | - [设置垂直同步(vertical\_sync)](#设置垂直同步vertical_sync)
91 | - [显示帧率(show\_frame\_rate)](#显示帧率show_frame_rate)
92 | - [设置窗口自动旋转(window\_auto\_rotate)](#设置窗口自动旋转window_auto_rotate)
93 | - [性能类(performance)](#性能类performance)
94 | - [设置CPU和内存(set)](#设置cpu和内存set)
95 | - [设置CPU个数(cpu)](#设置cpu个数cpu)
96 | - [设置内存大小(memory)](#设置内存大小memory)
97 | - [设置强制使用独立显卡(force\_discrete\_graphics)](#设置强制使用独立显卡force_discrete_graphics)
98 | - [显存使用策略(renderer\_strategy)](#显存使用策略renderer_strategy)
99 | - [设置磁盘类型(disk\_readonly)](#设置磁盘类型disk_readonly)
100 | - [\*网络类(network)](#网络类network)
101 | - [获取所有可被桥接的网卡(get\_bridge\_card)](#获取所有可被桥接的网卡get_bridge_card)
102 | - [设置网络桥接模式(bridge)](#设置网络桥接模式bridge)
103 | - [设置网络为NAT模式(nat)](#设置网络为nat模式nat)
104 | - [设置桥接模式IP设置方式为DHCP(bridge\_dhcp)](#设置桥接模式ip设置方式为dhcpbridge_dhcp)
105 | - [设置桥接模式IP设置方式为静态(bridge\_static)](#设置桥接模式ip设置方式为静态bridge_static)
106 | - [ADB类(adb)](#adb类adb)
107 | - [获取模拟器的ADB连接信息(get\_connect\_info)](#获取模拟器的adb连接信息get_connect_info)
108 | - [点击屏幕(click)](#点击屏幕click)
109 | - [滑动屏幕(swipe)](#滑动屏幕swipe)
110 | - [文本输入(input\_text)](#文本输入input_text)
111 | - [模拟按键(key\_event)](#模拟按键key_event)
112 | - [上传文件(push)](#上传文件push)
113 | - [上传文件到Download目录(push\_download)](#上传文件到download目录push_download)
114 | - [下载文件(pull)](#下载文件pull)
115 | - [清理应用数据(clear)](#清理应用数据clear)
116 | - [GUI自动化类(auto)](#gui自动化类auto)
117 | - [处理模拟器实时帧(create\_handle)](#处理模拟器实时帧create_handle)
118 | - [保存模拟器实时帧(save)](#保存模拟器实时帧save)
119 | - [在帧中查找第一个图片(locateOnScreen)](#在帧中查找第一个图片locateonscreen)
120 | - [在帧中查找第一张图片的中心点(locateCenterOnScreen)](#在帧中查找第一张图片的中心点locatecenteronscreen)
121 | - [在帧中查找所有图片(locateAllOnScreen)](#在帧中查找所有图片locateallonscreen)
122 | - [获取Box的中心点(center)](#获取box的中心点center)
123 | - [实战举例](#实战举例)
124 | - [支持本项目](#支持本项目)
125 |
126 | ## 项目介绍
127 |
128 | 该项目是基于MuMu提供的``MuMuManager.exe``实现的Python API,可以通过Python代码控制MuMu模拟器的大量操作。
129 |
130 | 该项目要求你已经安装了MuMu模拟器,且Mumu模拟器版本`>=4.0.0`以上。
131 |
132 | ## 如何使用?
133 |
134 | 将本项目安装到您的Python环境中
135 |
136 | 导入模块
137 |
138 | ```python
139 | from mumu.mumu import Mumu
140 | ```
141 |
142 | ### 设置MuMuManager路径
143 |
144 | 如果你的Mumu模拟器不是使用``默认路径``安装,则需要在创建Mumu对象时传入MuMuManager的路径。
145 | 默认路径``C:\Program Files\Netease\MuMu Player 12\shell\MuMuManager.exe``
146 |
147 | ```python
148 | from mumu.mumu import Mumu
149 |
150 | Mumu(r'your_path').select(1)
151 | ```
152 |
153 | ### 模拟器索引说明
154 |
155 | 在Mumu模拟器种,操作模拟器通过索引,索引可以在创建时指定,也可以创建时自动分配,如果你不知道你的模拟器索引是多少,可以打开“Mumu多开器”,找到你的模拟器,最前面那个数字就是你的模拟器索引。
156 |
157 | ### 选择模拟器
158 |
159 | 如果指定的操作需要操作模拟器,可以通过``select``方法选择模拟器
160 |
161 | ```python
162 | from mumu.mumu import Mumu
163 |
164 | mumu = Mumu().select(1)
165 | ```
166 |
167 | 对于选择多个模拟器时可以这样,一下4种方式都是等价的,均可以选择1、2、3号模拟器
168 |
169 | ```python
170 | from mumu.mumu import Mumu
171 |
172 | # 第一种
173 | mumu = Mumu().select([1, 2, 3])
174 | # 第二种
175 | mumu = Mumu().select(1, 2, 3)
176 | # 第三种
177 | mumu = Mumu().select((1, 2, 3))
178 | # 也可以混合使用
179 | mumu = Mumu().select([1, 2], 3)
180 | ```
181 |
182 | 如果希望选择所有模拟器,可以使用以下两种方法
183 |
184 | ```python
185 | from mumu.mumu import Mumu
186 |
187 | # 第一种
188 | mumu = Mumu().select() # 当select什么都不传时,默认选择所有模拟器
189 | # 第二种:通过all方法选择所有模拟器
190 | mumu = Mumu().all()
191 | ```
192 |
193 | ### 举个例子
194 |
195 | 我希望开启索引为1的模拟器的root权限,然后启动它。
196 |
197 | ```python
198 | from mumu.mumu import Mumu
199 |
200 | # 选择索引为1的模拟器
201 | mumu = Mumu().select(1)
202 | # 开启Root权限
203 | mumu.permission.root.enable()
204 | # 启动模拟器
205 | mumu.power.start()
206 | ```
207 |
208 | ### 注意
209 |
210 | 带`*`的类是本项目提供的`超类`,并不是MuMu模拟器的API原生提供。
211 |
212 | ## API类
213 |
214 | 本项目提供了多个操作类,可以通过这些类实现对模拟器的控制。
215 |
216 | ### 驱动类(driver)
217 |
218 | 说明:具官方文档,目前仅支持网络桥接驱动。
219 |
220 | #### 网络桥接驱动(bride)
221 |
222 | ##### 安装桥接驱动(install)
223 |
224 | 说明:安装桥接驱动需要管理员权限
225 |
226 | ```python
227 | Mumu().driver.bridge.install()
228 | ```
229 |
230 | ##### 卸载桥接驱动(uninstall)
231 |
232 | 说明:卸载桥接驱动需要管理员权限
233 |
234 | ```python
235 | Mumu().driver.bridge.uninstall()
236 | ```
237 |
238 | ### 权限类(permission)
239 |
240 | #### * ROOT权限(root)
241 |
242 | ##### 开启ROOT权限(enable)
243 |
244 | 说明:开启ROOT权限需要在模拟器关机状态下进行
245 |
246 | ```python
247 | Mumu().select('your_index').permission.root.enable()
248 | ```
249 |
250 | ##### 关闭ROOT权限(disable)
251 |
252 | 说明:关闭ROOT权限需要在模拟器关机状态下进行
253 |
254 | ```python
255 | Mumu().select('your_index').permission.root.disable()
256 | ```
257 |
258 | ### 电源类(power)
259 |
260 | #### 启动模拟器(start)
261 |
262 | ```python
263 | Mumu().select('your_index').power.start()
264 | ```
265 |
266 | 启动完成后打开指定包名的应用
267 |
268 | ```python
269 | Mumu().select('your_index').power.start('com.tencent.mobileqq')
270 | ```
271 |
272 | #### 关闭模拟器(shutdown|stop)
273 |
274 | ```python
275 | Mumu().select('your_index').power.shutdown()
276 | # or
277 | Mumu().select('your_index').power.stop()
278 | ```
279 |
280 | #### 重启模拟器(restart|reboot)
281 |
282 | ```python
283 | Mumu().select('your_index').power.restart()
284 | # or
285 | Mumu().select('your_index').power.reboot()
286 | ```
287 |
288 | ### 窗口类(window)
289 |
290 | #### 显示窗口(show)
291 |
292 | ```python
293 | Mumu().select('your_index').window.show()
294 | ```
295 |
296 | #### 隐藏窗口(hidden)
297 |
298 | ```python
299 | Mumu().select('your_index').window.hidden()
300 | ```
301 |
302 | #### 调整窗口大小或位置(layout)
303 |
304 | 该方法可以接受4个参数,分别是x坐标、y坐标、宽度、高度
305 |
306 | ```python
307 | Mumu().select('your_index').window.layout(0, 0, 800, 600)
308 | ```
309 |
310 | 如果只希望调整窗口的大小
311 |
312 | ```python
313 | Mumu().select('your_index').window.layout(None, None, 1080, 1920)
314 | ```
315 |
316 | 也可以只调整一个参数
317 |
318 | ```python
319 | Mumu().select('your_index').window.layout(300, None, None, None)
320 | # or
321 | Mumu().select('your_index').window.layout(None, None, 1080, None)
322 | ```
323 |
324 | ### 应用类(app)
325 |
326 | #### 安装一个应用(install)
327 |
328 | 该方法接受一个参数,即apk文件的路径,当apk路径无法访问时,会抛出``FileNotFoundError``异常
329 |
330 | ```python
331 | Mumu().select('your_index').app.install(r'C:\test.apk')
332 | ```
333 |
334 | #### 卸载一个应用(uninstall)
335 |
336 | 该方法接受一个参数,即应用的包名
337 |
338 | ```python
339 | Mumu().select('your_index').app.uninstall('com.miHoYo.Yuanshen')
340 | ```
341 |
342 | #### 启动模拟器里的应用(launch)
343 |
344 | 该方法接受一个参数,即应用的包名
345 |
346 | ```python
347 | # 启动原神
348 | Mumu().select('your_index').app.launch('com.miHoYo.Yuanshen')
349 | ```
350 |
351 | #### 关闭模拟器里的应用(close)
352 |
353 | 该方法接受一个参数,即应用的包名
354 |
355 | ```python
356 | # 关闭原神
357 | Mumu().select('your_index').app.close('com.miHoYo.Yuanshen')
358 | ```
359 |
360 | #### * 判断应用是否存在(exists)
361 |
362 | 该方法接受一个参数,即应用的包名
363 |
364 | ```python
365 | # 判断原神是否存在
366 | if (Mumu().select('your_index').app.exists('com.miHoYo.Yuanshen')):
367 | print('原神已安装')
368 | else:
369 | print('原神未安装')
370 | ```
371 |
372 | #### * 判断应用是否不存在(doesntExists)
373 |
374 | 该方法接受一个参数,即应用的包名
375 |
376 | ```python
377 | # 判断原神是否不存在
378 | if (Mumu().select('your_index').app.doesntExists('com.miHoYo.Yuanshen')):
379 | print('原神未安装')
380 | else:
381 | print('原神已安装')
382 | ```
383 |
384 | #### 获取已经安装的应用列表(get_installed)
385 |
386 | ```python
387 | # 获取已经安装的应用列表
388 | Mumu().select(1).app.get_installed()
389 | ```
390 |
391 | 返回一个列表,当没有安装任何应用时,返回一个空列表
392 |
393 | ```python
394 | [
395 | {
396 | 'package': 'com.miHoYo.Yuanshen', # 包名
397 | 'app_name': '原神', # 应用名称
398 | 'version': '4.1.8' # 版本号
399 | },
400 | ]
401 | ```
402 |
403 | #### * 获取应用状态(state)
404 |
405 | 该方法接受一个参数,即应用的包名,返回一个字符串,`running`表示应用正在运行,`stopped`表示应用未运行,`not_installed`表示应用未安装
406 |
407 | ```python
408 | # 获取原神的状态
409 | Mumu().select('your_index').app.state('com.miHoYo.Yuanshen')
410 | ```
411 |
412 | ### 核心类(core)
413 |
414 | #### 创建模拟器(create)
415 |
416 | 该方法接受一个参数,即模拟器的名称,返回一个list包含所创建的模拟器索引
417 |
418 | 举例:创建一个模拟器
419 |
420 | ```python
421 | Mumu().core.create()
422 | ```
423 |
424 | 举例:创建5个模拟器
425 |
426 | ```python
427 | Mumu().core.create(5)
428 | ```
429 |
430 | 举例:创建一个索引为``10``的模拟器
431 |
432 | ```python
433 | Mumu().select(10).core.create()
434 | ```
435 |
436 | 从索引3开始创建5个模拟器
437 |
438 | ```python
439 | Mumu().select(3).core.create(5)
440 | ```
441 |
442 | 从索引 3,20 开始,分别创建10次模拟器,索引依次递增(即创建3-12,20-29索引的模拟器)
443 |
444 | ```python
445 | Mumu().select(3, 20).core.create(10)
446 | ```
447 |
448 | #### 克隆模拟器(clone)
449 |
450 | 该方法接受一个参数,即为克隆的数量,但是调用该方法前需要先选择一个模拟器,返回一个list包含所创建的模拟器索引
451 |
452 | 举例:克隆索引为2的模拟器
453 |
454 | ```python
455 | Mumu().select(2).core.clone()
456 | ```
457 |
458 | 举例:复制索引为 2,4,6 的模拟器
459 |
460 | ```python
461 | Mumu().select(2, 4, 6).core.clone()
462 | ```
463 |
464 | 举例:复制索引为2的模拟器,复制10次
465 |
466 | ```python
467 | Mumu().select(2).core.clone(10)
468 | ```
469 |
470 | 举例:复制所有模拟器
471 |
472 | ```python
473 | Mumu().all().core.clone()
474 | ```
475 |
476 | #### 删除模拟器(delete)
477 |
478 | 该方法不需要传入参数,但是调用该方法前需要先选择一个模拟器,返回bool值,表示是否删除成功
479 |
480 | 举例:删除索引为2的模拟器
481 |
482 | ```python
483 | if Mumu().select(2).core.delete():
484 | print('删除成功')
485 | ```
486 |
487 | 举例:删除索引为2,4,6的模拟器
488 |
489 | ```python
490 | Mumu().select(2, 4, 6).core.delete()
491 | ```
492 |
493 | 举例:删除所有模拟器(危险操作)
494 |
495 | ```python
496 | Mumu().all().core.delete()
497 | ```
498 |
499 | #### 重命名模拟器(rename)
500 |
501 | 该方法接受一个参数,即为新的模拟器名称,但是调用该方法前需要先选择一个模拟器,返回bool值,表示是否重命名成功。
502 |
503 | 举例:重命名索引为2的模拟器为“测试”
504 |
505 | ```python
506 | if Mumu().select(2).core.rename('测试'):
507 | print('重命名成功')
508 | ```
509 |
510 | 举例:重命名索引为2,4,6的模拟器为“测试”
511 |
512 | ```python
513 | Mumu().select(2, 4, 6).core.rename('测试')
514 | ```
515 |
516 | 举例:重命名所有的模拟器为“测试”
517 |
518 | ```python
519 | Mumu().all().core.rename('测试')
520 | ```
521 |
522 | #### 备份模拟器(export)
523 |
524 | 该方法接受三个参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否备份成功。
525 |
526 | | 参数 | 说明 |
527 | |------|-----------|
528 | | dir | 备份的路径 |
529 | | name | 备份的名称 |
530 | | zip | 是否使用zip压缩 |
531 |
532 | 举例:备份索引为2的模拟器到C盘目录 backup 下,名称为 test.mumudata,以非压缩的格式
533 |
534 | ```python
535 | if Mumu().select(2).core.export(r'C:\backup', 'test'):
536 | print('备份成功')
537 | ```
538 |
539 | 举例:备份索引为2,4,6的模拟器到C盘目录 backup 下,文件名基于 test 自动加后缀,以非压缩的格式
540 |
541 | ```python
542 | Mumu().select(2, 4, 6).core.export(r'C:\backup', 'test')
543 | ```
544 |
545 | 举例:备份所有的模拟器到C盘目录 backup 下,文件名基于 test 自动加后缀,以压缩的格式
546 |
547 | ```python
548 | Mumu().all().core.export(r'C:\backup', 'test', True)
549 | ```
550 |
551 | #### 导入模拟器(import_)
552 |
553 | 该方法接受一个参数,即为备份文件的路径,如果传入一个列表,则表示导入多个,调用该方法前需要先选择一个模拟器,返回bool值,表示是否导入成功。
554 |
555 | 举例:导入C盘下的 test.mumudata 并创建模拟器
556 |
557 | ```python
558 | if Mumu().select(2).core.import_(r'C:\test.mumudata'):
559 | print('导入成功')
560 | ```
561 |
562 | 举例:导入C盘下的 test.mumudata 并创建模拟器,导入10次
563 |
564 | ```python
565 | Mumu().select(2).core.import_(r'C:\test.mumudata', 10)
566 | ```
567 |
568 | 举例:导入C盘下的 test.mumudata 和D盘下的 test2.mumudata 并创建模拟器,分别导入10次
569 |
570 | ```python
571 | Mumu().select(2).core.import_([r'C:\test.mumudata', r'D:\test2.mumudata'], 10)
572 | ```
573 |
574 | #### 限制CPU使用率(limit_cpu)
575 |
576 | 该方法接受一个参数,即为CPU使用率,调用该方法前需要先选择一个模拟器,返回bool值,表示是否设置成功。
577 |
578 | 举例:在索引为2的模拟器中限制CPU为50%
579 |
580 | ```python
581 | Mumu().select(2).core.limit_cpu(50)
582 | ```
583 |
584 | 举例:在索引为2,4,6的模拟器中限制CPU为50%
585 |
586 | ```python
587 | Mumu().select(2, 4, 6).core.limit_cpu(50)
588 | ```
589 |
590 | 举例:在所有模拟器中限制CPU为50%
591 |
592 | ```python
593 | Mumu().all().core.limit_cpu(50)
594 | ```
595 |
596 | ### 安卓事件类(androidEvent)
597 |
598 | 该类提供了安卓事件操作,可以通过该类实现对模拟器的操作。
599 |
600 | 调用类的所有方法前需要先选择一个模拟器,返回bool值,表示是否操作成功。
601 | 方法未特殊说明时,均无需传入参数。
602 |
603 | #### 屏幕旋转(rotate)
604 |
605 | ```python
606 | Mumu().select(1).androidEvent.rotate()
607 | ```
608 |
609 | #### 返回主页(go_home)
610 |
611 | ```python
612 | Mumu().select(1).androidEvent.go_home()
613 | ```
614 |
615 | #### 返回(back)
616 |
617 | ```python
618 | Mumu().select(1).androidEvent.go_back()
619 | ```
620 |
621 | #### 窗口置顶(top_most)
622 |
623 | ```python
624 | Mumu().select(1).androidEvent.top_most()
625 | ```
626 |
627 | #### 窗口全屏(fullscreen)
628 |
629 | ```python
630 | Mumu().select(1).androidEvent.fullscreen()
631 | ```
632 |
633 | #### 摇一摇(shake)
634 |
635 | ```python
636 | Mumu().select(1).androidEvent.shake()
637 | ```
638 |
639 | #### 截图(screenshot)
640 |
641 | ```python
642 | Mumu().select(1).androidEvent.screenshot()
643 | ```
644 |
645 | #### 音量增加(volume_up)
646 |
647 | ```python
648 | Mumu().select(1).androidEvent.volume_up()
649 | ```
650 |
651 | #### 音量减少(volume_down)
652 |
653 | ```python
654 | Mumu().select(1).androidEvent.volume_down()
655 | ```
656 |
657 | #### 音量静音(volume_mute)
658 |
659 | ```python
660 | Mumu().select(1).androidEvent.volume_mute()
661 | ```
662 |
663 | #### 任务键(go_task)
664 |
665 | ```python
666 | Mumu().select(1).androidEvent.go_task()
667 | ```
668 |
669 | #### 修改虚拟定位(location)
670 |
671 | 该方法接受两个参数,分别是经度和纬度
672 |
673 | 举例:在索引为2的模拟器中修改虚拟定位为经度114.1,纬度-23
674 |
675 | ```python
676 | Mumu().select(2).androidEvent.location(114.1, -23)
677 | ```
678 |
679 | 举例: 在索引为2,4,6的模拟器中修改虚拟定位为经度114.1,纬度-23
680 |
681 | ```python
682 | Mumu().select(2, 4, 6).androidEvent.location(114.1, -23)
683 | ```
684 |
685 | 举例:在所有模拟器中修改虚拟定位为经度114.1,纬度-23
686 |
687 | ```python
688 | Mumu().all().androidEvent.location(114.1, -23)
689 | ```
690 |
691 | #### 修改重力感应(gyro)
692 |
693 | 该方法接受三个参数,分别是x轴、y轴、z轴
694 |
695 | 举例:在索引为2的模拟器中修改重力感应X=40,Y=20,Z=30
696 |
697 | ```python
698 | Mumu().select(2).androidEvent.gyro(40, 20, 30)
699 | ```
700 |
701 | 举例:在索引为2,4,6的模拟器中修改重力感应X=40,Y=20,Z=30
702 |
703 | ```python
704 | Mumu().select(2, 4, 6).androidEvent.gyro(40, 20, 30)
705 | ```
706 |
707 | 举例:在所有模拟器中修改重力感应X=40,Y=20,Z=30
708 |
709 | ```python
710 | Mumu().all().androidEvent.gyro(40, 20, 30)
711 | ```
712 |
713 | ### 快捷方式类(shortcut)
714 |
715 | #### 创建桌面快捷方式(create)
716 |
717 | 该方法接受三个参数,分别是快捷方式名称、快捷方式图标路径、应用包名
718 |
719 | 举例:在桌面创建索引为2的模拟器的快捷方式 test,图标用 C 盘的 test.ico,自动启动原神
720 |
721 | ```python
722 | Mumu().select(2).shortcut.create('test', r'C:\test.ico', 'com.miHoYo.Yuanshen')
723 | ```
724 |
725 | 举例:在桌面创建索引为2,4,6的模拟器的快捷方式 test,图标用 C 盘的 test.ico,自动启动原神
726 |
727 | ```python
728 | Mumu().select(2, 4, 6).shortcut.create('test', r'C:\test.ico', 'com.miHoYo.Yuanshen')
729 | ```
730 |
731 | 举例:在所有模拟器中创建快捷方式 test,图标用 C 盘的 test.ico,自动启动原神
732 |
733 | ```python
734 | Mumu().all().shortcut.create('test', r'C:\test.ico', 'com.miHoYo.Yuanshen')
735 | ```
736 |
737 | #### 删除桌面快捷方式(delete)
738 |
739 | 该方法无需传入参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否删除成功。
740 |
741 | 举例:删除索引为2的模拟器的快捷方式
742 |
743 | ```python
744 | Mumu().select(2).shortcut.delete()
745 | ```
746 |
747 | 举例:删除索引为2,4,6的模拟器的快捷方式
748 |
749 | ```python
750 | Mumu().select(2, 4, 6).shortcut.delete()
751 | ```
752 |
753 | 举例:删除所有模拟器的快捷方式
754 |
755 | ```python
756 | Mumu().all().shortcut.delete()
757 | ```
758 |
759 | ### 机型类(simulation)
760 |
761 | 该类提供了模拟器机型操作。这玩意非常的鸡肋!!
762 |
763 | #### 修改MAC地址(mac_address)
764 |
765 | 该方法接受一个参数,即为新的MAC地址,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
766 |
767 | ```python
768 | Mumu().select(1).simulation.mac_address('00:11:22:33:44:55')
769 | ```
770 |
771 | 举例:为索引为1的模拟器随机生成一个MAC地址,两种方式均可
772 |
773 | ```python
774 | from mumu.constant import MacAddress
775 |
776 | # 第一种:传入一个MAC地址
777 | Mumu().select(1).simulation.mac_address(MacAddress.random())
778 |
779 | # 第二种:当不传入参数时,表示随机生成一个MAC地址
780 | Mumu().select(1).simulation.mac_address()
781 | ```
782 |
783 | #### 修改IMEI(imei)
784 |
785 | 安卓12不允许应用获取IMEI
786 |
787 | 该方法接受一个参数,即为新的IMEI,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
788 |
789 | ```python
790 | Mumu().select(1).simulation.imei('123456789012345')
791 | ```
792 |
793 | 举例:为索引为1的模拟器随机生成一个IMEI,两种方式均可
794 |
795 | ```python
796 | from mumu.constant import IMEI
797 |
798 | # 第一种:传入一个IMEI
799 | Mumu().select(1).simulation.imei(IMEI.random())
800 |
801 | # 第二种:当不传入参数时,表示随机生成一个IMEI
802 | Mumu().select(1).simulation.imei()
803 | ```
804 |
805 | #### 修改IMSI(imsi)
806 |
807 | 安卓12不允许应用获取IMSI
808 |
809 | 该方法接受一个参数,即为新的IMSI,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
810 |
811 | ```python
812 | Mumu().select(1).simulation.imsi('460000000000000')
813 | ```
814 |
815 | 举例:为索引为1的模拟器随机生成一个IMSI,两种方式均可
816 |
817 | ```python
818 | from mumu.constant import IMSI
819 |
820 | # 第一种:传入一个IMSI
821 | Mumu().select(1).simulation.imsi(IMSI.random())
822 |
823 | # 第二种:当不传入参数时,表示随机生成一个IMSI
824 | Mumu().select(1).simulation.imsi()
825 | ```
826 |
827 | #### 修改Android ID(android_id)
828 |
829 | 该方法接受一个参数,即为新的Android ID,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
830 |
831 | ```python
832 | Mumu().select(1).simulation.android_id('1234567890123456')
833 | ```
834 |
835 | 举例:为索引为1的模拟器随机生成一个Android ID,两种方式均可
836 |
837 | ```python
838 | from mumu.constant import AndroidID
839 |
840 | # 第一种:传入一个Android ID
841 | Mumu().select(1).simulation.android_id(AndroidID.random())
842 |
843 | # 第二种:当不传入参数时,表示随机生成一个Android ID
844 | Mumu().select(1).simulation.android_id()
845 | ```
846 |
847 | #### 设置模拟器设备型号(model)
848 |
849 | 该方法接受一个参数,即为新的设备型号,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
850 |
851 | ```python
852 | Mumu().select(1).simulation.model('MI 10')
853 | ```
854 |
855 | #### 设置模拟器主板品牌(brand)
856 |
857 | 该方法接受一个参数,即为新的主板型号,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
858 |
859 | ```python
860 | Mumu().select(1).simulation.brand('Xiaomi')
861 | ```
862 |
863 | #### 设置模拟器硬件制造商(solution)
864 |
865 | 该方法接受一个参数,即为新的硬件型号,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
866 |
867 | ```python
868 | Mumu().select(1).simulation.solution('qcom')
869 | ```
870 |
871 | #### 设置模拟器手机号码(phone_number)
872 |
873 | 安卓12不允许应用获取手机号码
874 |
875 | 该方法接受一个参数,即为新的手机号码,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
876 |
877 | ```python
878 | Mumu().select(1).simulation.phone_number('18888888888')
879 | ```
880 |
881 | 举例:随机设置一个手机号码,两种方式均可
882 |
883 | ```python
884 | from mumu.constant import PhoneNumber
885 |
886 | # 第一种:传入一个手机号码
887 | Mumu().select(1).simulation.phone_number(PhoneNumber.random())
888 |
889 | # 第二种:当不传入参数时,表示随机生成一个手机号码
890 | Mumu().select(1).simulation.phone_number()
891 | ```
892 |
893 | #### 设置模拟器GPU型号(gpu_model)
894 |
895 | 该方法提供4个参数,选填一个即可。
896 |
897 | 举例:设置索引为1的模拟器的GPU型号为GeForce RTX 3090
898 |
899 | ```python
900 | Mumu().select(1).simulation.gpu_model('GeForce RTX 3090')
901 | ```
902 |
903 | 举例:设置索引为1的模拟器的GPU型号为高配
904 |
905 | ```python
906 | Mumu().select(1).simulation.gpu_model(top_model=True)
907 | ```
908 |
909 | 举例:设置索引为1的模拟器的GPU型号为低配
910 |
911 | ```python
912 | Mumu().select(1).simulation.gpu_model(low_model=True)
913 | ```
914 |
915 | 举例:设置索引为1的模拟器的GPU型号为中配
916 |
917 | ```python
918 | Memu().select(1).simulation.gpu_model(middle_model=True)
919 | ```
920 |
921 | ### 配置类(setting)
922 |
923 | 该类提供了模拟器配置文件操作。
924 |
925 | 在配置类中,不选择任何模拟器即操作全局配置(默认值)
926 |
927 | #### 获取模拟器配置所有配置项(all)
928 |
929 | 该方法提供一个参数`all_writable`,当传入`True`时,表示获取所有可写的配置项,当传入`False`时,表示获取所有配置项,返回一个字典,表示所有配置项的键值对。
930 |
931 | 举例:获取所有配置项
932 |
933 | ```python
934 | Mumu().select(1).setting.all()
935 | ```
936 |
937 | 举例:获取所有可写的配置项
938 |
939 | ```python
940 | Mumu().select(1).setting.all(True)
941 | ```
942 |
943 | #### 获取一个或多个配置项(get)
944 |
945 | 该方法接受一个或多个参数,表示要获取的配置项,当获取单个配置项时,返回一个字符串,当获取多个配置项时,返回一个字典,表示所有配置项的键值对。
946 |
947 | 举例:获取指定一个或多个配置(返回字符串)
948 |
949 | ```python
950 | value = Mumu().select(1).setting.get('window_size_fixed')
951 | ```
952 |
953 | 举例:获取多个配置项(返回字典)
954 |
955 | ```python
956 | dict_val = Mumu().select(1).setting.get('window_size_fixed', 'window_save_rect')
957 | ```
958 |
959 | #### 修改一个或多个配置(set)
960 |
961 | 该方法接受一个或多个参数,表示要设置的配置项,返回一个bool值,表示是否设置成功。
962 |
963 | 当传入参数的值为`None`时,表示还原默认值。
964 |
965 | 如果遇到需要写的配置键包含`.`或者`-`时,将`.`替换为两个`_`,`-`替换为三个`_`。
966 |
967 | 举例:修改索引为2的模拟器的配置 window_size_fixed 的值为 true
968 |
969 | ```python
970 | Mumu().select(2).setting.set(window_size_fixed=True)
971 | ```
972 |
973 | 举例:修改索引为2的模拟器的配置 window_size_fixed 的值为 false,配置 window_save_rect 的值为 true
974 |
975 | ```python
976 | Mumu().select(2).setting.set(window_size_fixed=False, window_save_rect=True)
977 | ```
978 |
979 | 修改索引为2的模拟器的还原配置 window_size_fixed 的值(将使用默认值)
980 |
981 | ```python
982 | Mumu().select(2).setting.set(window_size_fixed=None)
983 | ```
984 |
985 | #### 根据JSON文件内容修改配置(set_by_json)
986 |
987 | 该方法接受一个参数,即为JSON文件的路径,返回一个bool值,表示是否设置成功。
988 |
989 | 举例:
990 |
991 | 一个 utf8 格式 test.json 文件在C盘下,文件内容如下:
992 |
993 | ```json
994 | {
995 | "window_save_rect": "true",
996 | "window_size_fixed": "false"
997 | }
998 | ```
999 |
1000 | 修改索引为2的模拟器的配置,通过JSON文件方式修改。
1001 |
1002 | ```python
1003 | Mumu().select(2).setting.set_by_json(r'C:\test.json')
1004 | ```
1005 |
1006 | #### *判断某个配置是否等于某个值(equal)
1007 |
1008 | 该方法接受两个参数,分别是配置项和值,返回一个bool值,表示是否相等。
1009 |
1010 | 举例:判断索引为2的模拟器的配置 window_size_fixed 是否等于 true
1011 |
1012 | ```python
1013 | if Mumu().select(2).setting.equal('window_size_fixed', True):
1014 | print('相等')
1015 | else:
1016 | print('不相等')
1017 | ```
1018 |
1019 | #### *判断某个配置是否不等于某个值(not_equal)
1020 |
1021 | 该方法接受两个参数,分别是配置项和值,返回一个bool值,表示是否不相等。
1022 |
1023 | 举例:判断索引为2的模拟器的配置 window_size_fixed 是否不等于 true
1024 |
1025 | ```python
1026 | if Mumu().select(2).setting.not_equal('window_size_fixed', True):
1027 | print('不相等')
1028 | else:
1029 | print('相等')
1030 | ```
1031 |
1032 | #### *判断某个配置等于某个值时,修改为另一个值(equal_then_set)
1033 |
1034 | 该方法接受三个参数,分别是配置项、值和新值,返回一个bool值,表示是否修改成功。
1035 |
1036 | 举例:判断索引为2的模拟器的配置 window_size_fixed 是否等于 true,如果相等则修改为 false
1037 |
1038 | ```python
1039 | Mumu().select(2).setting.equal_then_set('window_size_fixed', True, False)
1040 | ```
1041 |
1042 | #### *判断某个配置不等于某个值时,修改为另一个值(not_equal_then_set)
1043 |
1044 | 该方法接受三个参数,分别是配置项、值和新值,返回一个bool值,表示是否修改成功。
1045 |
1046 | 当不传入新值时,将自动设置`值`
1047 |
1048 | 举例:判断索引为2的模拟器的配置 window_size_fixed 是否不等于 true,如果不相等则修改为 true
1049 |
1050 | ```python
1051 | Mumu().select(2).setting.not_equal_then_set('window_size_fixed', True, True)
1052 |
1053 | # or
1054 |
1055 | Mumu().select(2).setting.not_equal_then_set('window_size_fixed', True)
1056 | ```
1057 |
1058 |
1059 |
1060 | ### *屏幕类(screen)
1061 |
1062 | 该类提供了模拟器屏幕操作。
1063 |
1064 | #### 调整模拟器分辨率(resolution)
1065 |
1066 | 该方法接受两个参数,分别是宽度和高度,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1067 |
1068 | 举例:修改索引为2的模拟器的分辨率为 800x600
1069 |
1070 | ```python
1071 | Mumu().select(2).screen.resolution(800, 600)
1072 | ```
1073 |
1074 | #### 设置为手机分辨率(resolution_mobile)
1075 |
1076 | 该方法不需要传入参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1077 |
1078 | 该方法调整的分辨率为 1080x1920 DPI 为 480
1079 |
1080 | 举例:设置索引为2的模拟器的分辨率为手机分辨率
1081 |
1082 | ```python
1083 | Mumu().select(2).screen.resolution_mobile()
1084 | ```
1085 |
1086 | #### 设置为平板分辨率(resolution_tablet)
1087 |
1088 | 该方法不需要传入参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1089 |
1090 | 该方法调整的分辨率为 1920x1080 DPI 为 280
1091 |
1092 | 举例:设置索引为2的模拟器的分辨率为平板分辨率
1093 |
1094 | ```python
1095 | Mumu().select(2).screen.resolution_tablet()
1096 | ```
1097 |
1098 | #### 设置为超宽屏分辨率(resolution_ultrawide)
1099 |
1100 | 该方法不需要传入参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1101 |
1102 | 该方法调整的分辨率为 3200x1440 DPI 为 400
1103 |
1104 | 举例:设置索引为2的模拟器的分辨率为超宽屏分辨率
1105 |
1106 | ```python
1107 | Mumu().select(2).screen.resolution_ultrawide()
1108 | ```
1109 |
1110 | #### 调整模拟器DPI(dpi)
1111 |
1112 | 该方法接受一个参数,即为新的DPI,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1113 |
1114 | 举例:修改索引为2的模拟器的DPI为 240
1115 |
1116 | ```python
1117 | Mumu().select(2).screen.dpi(240)
1118 | ```
1119 |
1120 | #### 调整模拟器亮度(brightness)
1121 |
1122 | 该方法接受一个参数,即为新的亮度,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1123 |
1124 | 参数范围为0-100
1125 |
1126 | 举例:修改索引为2的模拟器的亮度为 50
1127 |
1128 | ```python
1129 | Mumu().select(2).screen.brightness(50)
1130 | ```
1131 |
1132 | #### 调整模拟器最大帧率(max_frame_rate)
1133 |
1134 | 该方法接受一个参数,即为新的最大帧率,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1135 |
1136 | 参数范围为1-240
1137 |
1138 | 举例:修改索引为2的模拟器的最大帧率为 60
1139 |
1140 | ```python
1141 | Mumu().select(2).screen.max_frame_rate(60)
1142 | # or
1143 | Mumu().select(2).screen.max_frame_rate() # 缺省值为60
1144 | ```
1145 |
1146 | #### 设置动态调整帧率(dynamic_adjust_frame_rate)
1147 |
1148 | 该方法接受两个参数,分别是`enable`和`dynamic_low_frame_rate_limit`,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1149 |
1150 | ----
1151 |
1152 | | 参数 | 类型 | 说明 |
1153 | |------------------------------|------|--------------------------|
1154 | | enable | bool | 是否启用动态调整帧率 |
1155 | | dynamic_low_frame_rate_limit | int | 模拟器不是操作主窗口时,降低至的帧率,默认:15 |
1156 |
1157 | ----
1158 |
1159 | 举例:启用索引为2的模拟器的动态调整帧率,降低至15帧
1160 |
1161 | ```python
1162 | Mumu().select(2).screen.dynamic_adjust_frame_rate(True, 15)
1163 | ```
1164 |
1165 | 举例:禁用索引为2的模拟器的动态调整帧率
1166 |
1167 | ```python
1168 | Mumu().select(2).screen.dynamic_adjust_frame_rate(False)
1169 | ```
1170 |
1171 | #### 设置垂直同步(vertical_sync)
1172 |
1173 | 该方法接受一个参数,即为`enable`,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1174 |
1175 | 举例:启用索引为2的模拟器的垂直同步
1176 |
1177 | ```python
1178 | Mumu().select(2).screen.vertical_sync(True)
1179 | ```
1180 |
1181 | 举例:禁用索引为2的模拟器的垂直同步
1182 |
1183 | ```python
1184 | Mumu().select(2).screen.vertical_sync(False)
1185 | ```
1186 |
1187 | #### 显示帧率(show_frame_rate)
1188 |
1189 | 该方法接受一个参数,即为`enable`,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1190 |
1191 | 举例:启用索引为2的模拟器的显示帧率
1192 |
1193 | ```python
1194 | Mumu().select(2).screen.show_frame_rate(True)
1195 | ```
1196 |
1197 | 举例:禁用索引为2的模拟器的显示帧率
1198 |
1199 | ```python
1200 | Mumu().select(2).screen.show_frame_rate(False)
1201 | ```
1202 |
1203 | #### 设置窗口自动旋转(window_auto_rotate)
1204 |
1205 | 该方法接受一个参数,即为`enable`,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1206 |
1207 | 设置后,模拟器窗口会根据运行的应用自动旋转
1208 |
1209 | 举例:启用索引为2的模拟器的窗口自动旋转
1210 |
1211 | ```python
1212 | Mumu().select(2).screen.window_auto_rotate(True)
1213 | ```
1214 |
1215 | 举例:禁用索引为2的模拟器的窗口自动旋转
1216 |
1217 | ```python
1218 | Mumu().select(2).screen.window_auto_rotate(False)
1219 | ```
1220 |
1221 | ### 性能类(performance)
1222 |
1223 | 该类提供了模拟器性能操作。
1224 |
1225 | 该类的所有操作需要重启模拟器后生效。
1226 |
1227 | #### 设置CPU和内存(set)
1228 |
1229 | 该方法接受两个参数,分别是CPU个数和内存大小,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1230 |
1231 | CPU个数范围为1-16
1232 |
1233 | 内存大小范围为1-16,单位为GB,不支持小数
1234 |
1235 | 举例:设置索引为2的模拟器的CPU为4核,内存为4GB
1236 |
1237 | ```python
1238 | Mumu().select(2).performance.set(4, 4)
1239 | ```
1240 |
1241 | #### 设置CPU个数(cpu)
1242 |
1243 | 该方法接受一个参数,即为CPU个数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1244 |
1245 | CPU个数范围为1-16
1246 |
1247 | 举例:设置索引为2的模拟器的CPU为4核
1248 |
1249 | ```python
1250 | Mumu().select(2).performance.cpu(4)
1251 | ```
1252 |
1253 | #### 设置内存大小(memory)
1254 |
1255 | 该方法接受一个参数,即为内存大小,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1256 |
1257 | 内存大小范围为1-16,单位为GB,不支持小数
1258 |
1259 | 举例:设置索引为2的模拟器的内存为4GB
1260 |
1261 | ```python
1262 | Mumu().select(2).performance.memory(4)
1263 | ```
1264 |
1265 | #### 设置强制使用独立显卡(force_discrete_graphics)
1266 |
1267 | 该方法接受一个参数,即为`enable`,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1268 |
1269 | 举例:启用索引为2的模拟器的强制使用独立显卡
1270 |
1271 | ```python
1272 | Mumu().select(2).performance.force_discrete_graphics(True)
1273 | ```
1274 |
1275 | 举例:禁用索引为2的模拟器的强制使用独立显卡
1276 |
1277 | ```python
1278 | Mumu().select(2).performance.force_discrete_graphics(False)
1279 | ```
1280 |
1281 | #### 显存使用策略(renderer_strategy)
1282 |
1283 | 该方法接受三个参数,选择一个即可。
1284 |
1285 | 举例:设置索引为2的模拟器的显存使用策略为自动
1286 |
1287 | ```python
1288 | Mumu().select(2).performance.renderer_strategy(auto=True)
1289 | # or
1290 | Mumu().select(2).performance.renderer_strategy()
1291 | ```
1292 |
1293 | 举例:设置索引为2的模拟器的显存使用策略为资源占用更小
1294 |
1295 | ```python
1296 | Mumu().select(2).performance.renderer_strategy(perf=True)
1297 | ```
1298 |
1299 | 举例:设置索引为2的模拟器的显存使用策略为画面表现更好
1300 |
1301 | ```python
1302 | Mumu().select(2).performance.renderer_strategy(dis=True)
1303 | ```
1304 |
1305 | #### 设置磁盘类型(disk_readonly)
1306 |
1307 | 该方法接受一个参数,即为`enable`,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1308 |
1309 | `enable`缺省值为`True`,开启时,磁盘类型为(只读系统盘,官方推荐),关闭时,磁盘类型为(可写系统盘)
1310 |
1311 | 本方法提供一个助手方法`disk_writable`,用于设置磁盘类型为可写系统盘。
1312 |
1313 | 举例:设置索引为2的模拟器的磁盘类型为只读系统盘。
1314 |
1315 | ```python
1316 | Mumu().select(2).performance.disk_readonly(True)
1317 |
1318 | # or
1319 |
1320 | Mumu().select(2).performance.disk_readonly()
1321 | ```
1322 |
1323 | 举例:设置索引为2的模拟器的磁盘类型为可写系统盘
1324 |
1325 | ```python
1326 | Mumu().select(2).performance.disk_readonly(False)
1327 |
1328 | # or
1329 |
1330 | Mumu().select(2).performance.disk_writable()
1331 | ```
1332 |
1333 | ### *网络类(network)
1334 |
1335 | 该类提供了模拟器网络操作。
1336 |
1337 | #### 获取所有可被桥接的网卡(get_bridge_card)
1338 |
1339 | 该方法返回一个列表,表示所有可被桥接的网卡。
1340 |
1341 | 举例:获取所有可被桥接的网卡
1342 |
1343 | ```python
1344 | Mumu().select(1).network.get_bridge_card()
1345 | ```
1346 |
1347 | 示例返回:
1348 |
1349 | ```python
1350 | ['Realtek Gaming GbE Family Controller', 'Sangfor SSL VPN CS Support System VNIC', 'Microsoft KM-TEST 环回适配器',
1351 | 'Intel(R) Wi-Fi 6E AX211 160MHz']
1352 | ```
1353 |
1354 | #### 设置网络桥接模式(bridge)
1355 |
1356 | 该方法接受两个参数,分别代表是否启用和网卡名称,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1357 |
1358 | 该方法需要安装“桥接驱动”才能使用。
1359 |
1360 | 举例:启用索引为2的模拟器的网络桥接模式,网卡名称为`Realtek Gaming GbE Family Controller`
1361 |
1362 | ```python
1363 | Mumu().select(2).network.bridge(True, 'Realtek Gaming GbE Family Controller')
1364 | ```
1365 |
1366 | 举例:禁用索引为2的模拟器的网络桥接模式
1367 |
1368 | ```python
1369 | Mumu().select(2).network.bridge(False)
1370 | ```
1371 |
1372 | #### 设置网络为NAT模式(nat)
1373 |
1374 | 该方法无需传入参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1375 |
1376 | 举例:设置索引为2的模拟器的网络为NAT模式
1377 |
1378 | ```python
1379 | Mumu().select(2).network.nat()
1380 | ```
1381 |
1382 | #### 设置桥接模式IP设置方式为DHCP(bridge_dhcp)
1383 |
1384 | 该方法无需传入参数,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1385 |
1386 | 举例:设置索引为2的模拟器的桥接模式IP设置方式为DHCP
1387 |
1388 | ```python
1389 | Mumu().select(2).network.bridge_dhcp()
1390 | ```
1391 |
1392 | #### 设置桥接模式IP设置方式为静态(bridge_static)
1393 |
1394 | 该方法接受5个参数,分别是IP地址、子网掩码、网关、DNS1、DNS2,调用该方法前需要先选择一个模拟器,返回bool值,表示是否修改成功。
1395 |
1396 | 当不填写DNS时,默认DNS1为`8.8.8.8`,DNS2为`114.114.114.114`
1397 |
1398 | 举例:设置索引为2的模拟器的桥接模式IP设置方式为静态,IP地址为`192.168.10.10`,子网掩码为`255.255.255.0`
1399 | ,网关为`192.168.10.1`
1400 |
1401 | ```python
1402 | Mumu().select(2).network.bridge_static('192.168.10.10', '255.255.255.0', '192.168.10.1')
1403 | ```
1404 |
1405 | ### ADB类(adb)
1406 |
1407 | 该类提供了ADB操作。
1408 |
1409 | #### 获取模拟器的ADB连接信息(get_connect_info)
1410 |
1411 | 该无需传入参数,调用该方法前需要先选择一个模拟器,当选择一个模拟器时返回一个元组,包含IP和端口,当选择多个模拟器时返回一个字典,键为索引,值为IP和端口。
1412 |
1413 | 如果选择的模拟器无法获取到ADB连接信息,将返回`None,None`
1414 |
1415 | 举例:获取索引为2的模拟器的ADB连接信息
1416 |
1417 | ```python
1418 | adb_ipaddr, adb_port = Mumu().select(2).adb.get_connect_info()
1419 | ```
1420 |
1421 | 举例:获取索引为2,4,6的模拟器的ADB连接信息
1422 |
1423 | ```python
1424 | adb_info = Mumu().select(2, 4, 6).adb.get_connect_info()
1425 | ```
1426 |
1427 | 返回示例
1428 |
1429 | ```python
1430 | {'2': ('172.30.20.123', 16416), '4': (None, None), '6': (None, None)}
1431 | ```
1432 |
1433 | #### 点击屏幕(click)
1434 |
1435 | 该方法接受两个参数,分别是X坐标和Y坐标,调用该方法前需要先选择一个模拟器,返回bool值,表示是否点击成功。
1436 |
1437 | 举例:点击索引为2的模拟器的坐标为(100, 100)的位置
1438 |
1439 | ```python
1440 | Mumu().select(2).adb.click(100, 100)
1441 | ```
1442 |
1443 | 举例:点击索引为2,4,6的模拟器的坐标为(100, 100)的位置
1444 |
1445 | ```python
1446 | Mumu().select(2, 4, 6).adb.click(100, 100)
1447 | ```
1448 |
1449 | #### 滑动屏幕(swipe)
1450 |
1451 | 该方法提供5个参数,分别是起始X坐标、起始Y坐标、结束X坐标、结束Y坐标和滑动时间,调用该方法前需要先选择一个模拟器,返回bool值,表示是否滑动成功。
1452 |
1453 | 举例:滑动索引为2的模拟器的坐标为(100, 100)到(200, 200)的位置,时间为500ms
1454 |
1455 | ```python
1456 | Mumu().select(2).adb.swipe(100, 100, 200, 200, 500)
1457 | # or
1458 | Mumu().select(2).adb.swipe(100, 100, 200, 200) # 缺省值为500ms
1459 | ```
1460 |
1461 | 举例:滑动索引为2,4,6的模拟器的坐标为(100, 100)到(200, 200)的位置,时间为500ms
1462 |
1463 | ```python
1464 | Mumu().select(2, 4, 6).adb.swipe(100, 100, 200, 200, 500)
1465 | ```
1466 |
1467 | #### 文本输入(input_text)
1468 |
1469 | 该方法接受一个参数,即为要输入的文本,调用该方法前需要先选择一个模拟器,返回bool值,表示是否输入成功。
1470 |
1471 | 举例:在索引为2的模拟器中输入文本`Hello World`
1472 |
1473 | ```python
1474 | Mumu().select(2).adb.input_text('Hello World')
1475 | ```
1476 |
1477 | 举例:在索引为2,4,6的模拟器中输入文本`Hello World`
1478 |
1479 | ```python
1480 | Mumu().select(2, 4, 6).adb.input_text('Hello World')
1481 | ```
1482 |
1483 | #### 模拟按键(key_event)
1484 |
1485 | 该方法接受一个参数,即为要模拟的按键,调用该方法前需要先选择一个模拟器,返回bool值,表示是否模拟成功。
1486 |
1487 | 举例:在索引为2的模拟器中模拟按键`HOME`
1488 |
1489 | ```python
1490 | Mumu().select(2).adb.key_event(3)
1491 | ```
1492 |
1493 | 举例:通过本项目提供的按键常量模拟按键`HOME`
1494 |
1495 | ```python
1496 | from mumu.constant import AndroidKey
1497 |
1498 | Mumu().select(2).adb.key_event(AndroidKey.KEYCODE_HOME)
1499 | ```
1500 |
1501 | 举例:在索引为2,4,6的模拟器中模拟音量+
1502 |
1503 | ```python
1504 | from mumu.constant import AndroidKey
1505 |
1506 | Mumu().select(2, 4, 6).adb.key_event(AndroidKey.KEYCODE_VOLUME_UP)
1507 | ```
1508 |
1509 | #### 上传文件(push)
1510 |
1511 | 该方法接受两个参数,分别是本地文件路径和模拟器文件路径,调用该方法前需要先选择一个模拟器,返回bool值,表示是否上传成功。
1512 |
1513 | 当上传失败时,会触发一个`warning`警告。
1514 |
1515 | 举例:上传索引为2的模拟器的本地文件`C:\test.txt`到模拟器的`/sdcard/test.txt`
1516 |
1517 | ```python
1518 | Mumu().select(2).adb.push(r'C:\test.txt', '/sdcard/test.txt')
1519 | ```
1520 |
1521 | 举例:上传索引为2,4,6的模拟器的本地文件`C:\test.txt`到模拟器的`/sdcard/test.txt`
1522 |
1523 | ```python
1524 | Mumu().select(2, 4, 6).adb.push(r'C:\test.txt', '/sdcard/test.txt')
1525 | ```
1526 |
1527 | #### 上传文件到Download目录(push_download)
1528 |
1529 | 该方法提供两个参数,分别是本地文件路径和模拟器文件名,调用该方法前需要先选择一个模拟器,返回bool值,表示是否上传成功。
1530 |
1531 | 当`模拟器文件名`为`None`时,将自动使用本地文件名。
1532 |
1533 | 举例:上传索引为2的模拟器的本地文件`C:\test.txt`到模拟器的`Download`目录
1534 |
1535 | ```python
1536 | Mumu().select(2).adb.push_download(r'C:\test.txt')
1537 | ```
1538 |
1539 | 举例:上传索引为2,4,6的模拟器的本地文件`C:\test.txt`到模拟器的`Download`目录,重命名为:`test1.txt`
1540 |
1541 | ```python
1542 | Mumu().select(2, 4, 6).adb.push_download(r'C:\test.txt', 'test1.txt')
1543 | ```
1544 |
1545 | #### 下载文件(pull)
1546 |
1547 | 该方法提供两个参数,分别是模拟器文件路径和本地文件路径,调用该方法前需要先选择一个模拟器,返回bool值,表示是否下载成功。
1548 |
1549 | 当下载失败时,会触发一个`warning`警告。
1550 |
1551 | 举例:下载索引为2的模拟器的模拟器文件`/sdcard/test.txt`到本地的`C:\test.txt`
1552 |
1553 | ```python
1554 | Mumu().select(2).adb.pull('/sdcard/test.txt', r'C:\test.txt')
1555 | ```
1556 |
1557 | #### 清理应用数据(clear)
1558 |
1559 | 该方法接受一个参数,即为应用包名,调用该方法前需要先选择一个模拟器,返回bool值,表示是否清理成功。
1560 |
1561 | 如果包名错误会抛出异常
1562 |
1563 | 举例:清理索引为2的模拟器的应用数据
1564 |
1565 | ```python
1566 | Mumu().select(2).adb.clear('com.miHoYo.Yuanshen')
1567 | ```
1568 |
1569 | ### GUI自动化类(auto)
1570 |
1571 | 本类提供了模拟器的GUI自动化操作,例如:通过图片定位、坐标定位、文字定位实现自动化操作。
1572 |
1573 | 先决条件:需要安装`opencv-python`库,可以通过`pip install opencv-python`安装。
1574 |
1575 | 如果希望使用本项目自带的投屏功能,需要安装`scrcpy`,可以通过`pip install scrcpy-client`安装。
1576 |
1577 | #### 处理模拟器实时帧(create_handle)
1578 |
1579 | 该方法接受一个参数`handle`,传入一个方法,用于处理模拟器的帧。
1580 |
1581 | 该方法基于`scrcpy`实现,如果您的应用对该软件有限制,请勿使用。
1582 |
1583 | 使用该方法,会自动创建`2`个子线程,一个用于接收模拟器的帧,一个用于处理帧。
1584 |
1585 | `handle`方法包含两个参数`frame`和`mumu`,分别表示帧和当前模拟器对象。
1586 |
1587 | `frame`为`numpy`数组,可以直接使用`opencv`的方法处理。
1588 | `mumu`为当前模拟器对象,仅选中了当前模拟器。
1589 |
1590 | 举例:处理索引为2的模拟器的帧
1591 |
1592 | ```python
1593 | def handle(frame, mumu):
1594 | # do something
1595 | print('接收到模拟器:', mumu.core.utils.get_vm_id(), '的帧')
1596 |
1597 |
1598 | Mumu().select(2).auto.create_handle(handle)
1599 | ```
1600 |
1601 | 举例:处理索引为2,4,6的模拟器的帧
1602 |
1603 | ```python
1604 | def handle(frame, mumu):
1605 | # do something
1606 | print('接收到模拟器:', mumu.core.utils.get_vm_id(), '的帧')
1607 |
1608 |
1609 | Mumu().select(2, 4, 6).auto.create_handle(handle)
1610 | ```
1611 |
1612 | #### 保存模拟器实时帧(save)
1613 |
1614 | 该方法接受两个参数,分别为`frame`和`path`,用于保存模拟器的帧。
1615 |
1616 | 举例:保存帧到`C:\test.png`
1617 |
1618 | ```python
1619 | def handle(frame, mumu):
1620 | mumu.auto.save(frame, r'C:\test.png')
1621 |
1622 |
1623 | Mumu().select(2).auto.create_handle(handle)
1624 | ```
1625 |
1626 | 举例:保存索引为3的模拟器的帧到`C:\test.png`
1627 |
1628 | ```python
1629 | def handle(frame, mumu):
1630 | if mumu.core.utils.get_vm_id() == '3':
1631 | mumu.auto.save(frame, r'C:\test.png')
1632 |
1633 |
1634 | Mumu().select(2, 3).auto.create_handle(handle)
1635 | ```
1636 |
1637 | #### 在帧中查找第一个图片(locateOnScreen)
1638 |
1639 | 该方法接受4个参数,分别为`haystack`、`needle`、`confidence`和`grayscale`,用于在帧中查找图片。
1640 |
1641 | `confidence`为相似度,范围为0-1,缺省值为0.8
1642 |
1643 | `grayscale`为是否使用灰度查找,缺省值为`False
1644 |
1645 | 举例:在帧中查找图片`C:\test.png`
1646 |
1647 | ```python
1648 | def handle(frame, mumu):
1649 | # do something
1650 | print('接收到模拟器:', mumu.core.utils.get_vm_id(), '的帧')
1651 | pos = mumu.auto.locateOnScreen(frame, r'C:\test.png')
1652 | if pos:
1653 | print('找到图片:', pos)
1654 | else:
1655 | print('未找到图片')
1656 | ```
1657 |
1658 | #### 在帧中查找第一张图片的中心点(locateCenterOnScreen)
1659 |
1660 | 该方法接受4个参数,分别为`haystack`、`needle`、`confidence`和`grayscale`,用于在帧中查找图片。
1661 |
1662 | `confidence`为相似度,范围为0-1,缺省值为0.8
1663 |
1664 | `grayscale`为是否使用灰度查找,缺省值为`False
1665 |
1666 | ```python
1667 | def handle(frame, mumu):
1668 | # do something
1669 | print('接收到模拟器:', mumu.core.utils.get_vm_id(), '的帧')
1670 | pos = mumu.auto.locateCenterOnScreen(frame, r'C:\test.png')
1671 | if pos:
1672 | print('找到图片中心点:', pos)
1673 | else:
1674 | print('未找到图片')
1675 |
1676 | ```
1677 |
1678 | #### 在帧中查找所有图片(locateAllOnScreen)
1679 |
1680 | 该方法接受4个参数,分别为`haystack`、`needle`、`confidence`和`grayscale`,用于在帧中查找图片。
1681 |
1682 | `confidence`为相似度,范围为0-1,缺省值为0.8
1683 | `grayscale`为是否使用灰度查找,缺省值为`False
1684 |
1685 | ```python
1686 | def handle(frame, mumu):
1687 | # do something
1688 | print('接收到模拟器:', mumu.core.utils.get_vm_id(), '的帧')
1689 | pos = mumu.auto.locateAllOnScreen(frame, r'C:\test.png')
1690 | if pos:
1691 | print('找到图片:', pos)
1692 | else:
1693 | print('未找到图片')
1694 |
1695 | ```
1696 |
1697 | #### 获取Box的中心点(center)
1698 |
1699 | 该方法接受一个参数,即为`box`,用于获取Box的中心点,返回x和y
1700 |
1701 | `box`为一个元组,包含4个元素,分别为左上角x、左上角y、右下角x、右下角y
1702 |
1703 | ```python
1704 | def handle(frame, mumu):
1705 | # do something
1706 | print('接收到模拟器:', mumu.core.utils.get_vm_id(), '的帧')
1707 | pos = mumu.auto.locateOnScreen(frame, r'C:\test.png')
1708 | if pos:
1709 | print('找到图片:', pos)
1710 | x, y = mumu.auto.center(pos)
1711 | print('中心点:', x, y)
1712 | else:
1713 | print('未找到图片')
1714 |
1715 | ```
1716 |
1717 | #### 实战举例
1718 |
1719 | 监听模拟器3的帧,当帧中出现`test.png`时,返回`找到了`,并返回当前时间和模拟器ID,然后返回到桌面。
1720 |
1721 | ```python
1722 | def handle(frame, mumu: Mumu):
1723 | if mumu.auto.locateOnScreen(frame, './test.png', confidence=0.75, grayscale=True):
1724 | print("找到了", time.time(), '在模拟器', mumu.core.utils.get_vm_id())
1725 | mumu.androidEvent.go_home()
1726 |
1727 |
1728 | Mumu().select(3).auto.create_handle(handle)
1729 |
1730 | ```
1731 |
1732 | ## 支持本项目
1733 |
1734 | 作者:wlkjyy
1735 |
1736 | Mail:
1737 |
1738 | WeCHAT:laravel_debug
--------------------------------------------------------------------------------
/mumu/__pycache__/config.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/__pycache__/config.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/__pycache__/constant.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/__pycache__/constant.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/__pycache__/control.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/__pycache__/control.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/__pycache__/mumu.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/__pycache__/mumu.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/__pycache__/utils.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/__pycache__/utils.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/adb/Adb.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/30 上午8:40
4 | # @Author : wlkjyy
5 | # @File : Adb.py
6 | # @Software: PyCharm
7 | import json
8 | import os.path
9 | import time
10 | import warnings
11 | from typing import Union
12 |
13 |
14 | import mumu.config as config
15 |
16 |
17 | class Adb:
18 |
19 | def __init__(self, utils):
20 | self.utils = utils
21 |
22 | # __connect_list = None
23 |
24 | def get_connect_info(self):
25 | """
26 | 获取连接信息
27 | :return: 返回一个包含 ADB 连接信息的字典,或者 (None, None) 如果没有获取到信息。
28 | """
29 | self.utils.set_operate("adb")
30 | ret_code, retval = self.utils.run_command([''])
31 |
32 | if ret_code != 0:
33 | return None, None
34 |
35 | try:
36 | data = json.loads(retval)
37 | except json.JSONDecodeError:
38 | return None, None
39 |
40 | adb_info = {}
41 | for key, value in data.items():
42 | if key == "adb_host" and "adb_port" in data:
43 | return data["adb_host"], data["adb_port"]
44 | if 'errcode' in value:
45 | adb_info[key] = (None, None)
46 | else:
47 | adb_info[key] = (value.get("adb_host"), value.get("adb_port"))
48 |
49 | return adb_info if adb_info else (None, None)
50 |
51 | def click(self, x: int, y: int):
52 | """
53 | 点击(click)
54 | :param x: 横坐标
55 | :param y: 纵坐标
56 | :return:
57 | """
58 | self.utils.set_operate('adb')
59 | ret_code, retval = self.utils.run_command(['-c', 'input', 'tap', str(x), str(y)])
60 |
61 | if ret_code == 0:
62 | return True
63 |
64 | raise RuntimeError(retval)
65 |
66 | def swipe(self, from_x: int, from_y: int, to_x: int, to_y: int, duration: int = 500):
67 | """
68 | 滑动(swipe)
69 | :param from_x: 起始横坐标
70 | :param from_y: 起始纵坐标
71 | :param to_x: 终点横坐标
72 | :param to_y: 终点纵坐标
73 | :param duration: 滑动时间
74 | :return:
75 | """
76 | self.utils.set_operate('adb')
77 | ret_code, retval = self.utils.run_command(
78 | ['-c', 'input', 'swipe', str(from_x), str(from_y), str(to_x), str(to_y), str(duration)])
79 |
80 | if ret_code == 0:
81 | return True
82 |
83 | raise RuntimeError(retval)
84 |
85 | def input_text(self, text: str):
86 | """
87 | 输入(input)
88 | :param text: 输入的文本
89 | :return:
90 | """
91 | self.utils.set_operate('adb')
92 | ret_code, retval = self.utils.run_command(['-c', 'input', 'text', text])
93 |
94 | if ret_code == 0:
95 | return True
96 |
97 | raise RuntimeError(retval)
98 |
99 | def key_event(self, key: Union[int, str]):
100 | """
101 | 按键(keyevent)
102 | :param key: 键值
103 | :return:
104 | """
105 | self.utils.set_operate('adb')
106 | ret_code, retval = self.utils.run_command(['-c', 'input', 'keyevent', str(key)])
107 |
108 | if ret_code == 0:
109 | return True
110 |
111 | raise RuntimeError(retval)
112 |
113 | def __connect(self):
114 | """
115 | 获取可用的连接
116 | :return:
117 | """
118 |
119 | self.utils.set_operate("adb")
120 | ret_code, retval = self.utils.run_command([''])
121 |
122 | if ret_code != 0:
123 | return self
124 |
125 | try:
126 | data = json.loads(retval)
127 | except json.JSONDecodeError:
128 | return None, None
129 |
130 | for key, value in data.items():
131 | if key == "adb_host" and "adb_port" in data:
132 | yield data["adb_host"], data["adb_port"]
133 | return
134 |
135 | if 'errcode' in value:
136 | continue
137 | else:
138 | # connect_list.append((value.get("adb_host"), value.get("adb_port")))
139 | yield value.get("adb_host"), value.get("adb_port")
140 |
141 | return
142 |
143 | def push(self, src: str, path: str):
144 | """
145 | 传输文件(push)
146 | :param src: 源文件
147 | :param path: 目标路径
148 | :return:
149 | """
150 |
151 | if not os.path.exists(src):
152 | raise FileNotFoundError(f"File not found: {src}")
153 |
154 | if not os.path.exists(config.ADB_PATH):
155 | raise FileNotFoundError(f"adb not found in {config.ADB_PATH}")
156 |
157 | for (host, port) in self.__connect():
158 | ret_code, retval = self.utils.run_command([config.ADB_PATH, '-s', f"{host}:{port}", 'push', src, path],
159 | mumu=False)
160 |
161 | if ret_code != 0:
162 | warnings.warn(retval)
163 |
164 | return True
165 |
166 | def push_download(self, src: str, new_name: str = None):
167 | """
168 | 传输文件到Download文件夹(push)
169 | :param new_name:
170 | :param src: 源文件
171 | :param path: 目标路径
172 | :return:
173 | """
174 | if new_name:
175 | filename = new_name
176 | else:
177 | filename = os.path.basename(src)
178 |
179 | return self.push(src, f"/sdcard/Download/{filename}")
180 |
181 | def pull(self, src: str, path: str):
182 | """
183 | 传输文件(pull)
184 | :param src: 源文件
185 | :param path: 目标路径
186 | :return:
187 | """
188 |
189 | if not os.path.exists(config.ADB_PATH):
190 | raise FileNotFoundError(f"adb not found in {config.ADB_PATH}")
191 |
192 | for (host, port) in self.__connect():
193 | ret_code, retval = self.utils.run_command([config.ADB_PATH, '-s', f"{host}:{port}", 'pull', src, path],
194 | mumu=False)
195 |
196 | if ret_code != 0:
197 | warnings.warn(retval)
198 |
199 | return True
200 |
201 | def clear(self, package: str):
202 | """
203 | 清除应用数据(clear)
204 | :param package: 应用包名
205 | :return:
206 | """
207 | self.utils.set_operate('adb')
208 | ret_code, retval = self.utils.run_command(['-c', 'pm', 'clear', package])
209 |
210 | if ret_code == 0:
211 | return True
212 |
213 | raise RuntimeError(retval)
214 |
--------------------------------------------------------------------------------
/mumu/api/core/Core.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:40
4 | # @Author : wlkjyy
5 | # @File : Core.py
6 | # @Software: PyCharm
7 | import json
8 | import warnings
9 | from typing import Union
10 |
11 |
12 | class Core:
13 |
14 | def __init__(self, utils):
15 | self.utils = utils
16 |
17 | def create(self, number: int = 1) -> list:
18 | """
19 | 创建模拟器
20 | :param number: 创建数量
21 | :return:
22 | """
23 | if number <= 0:
24 | warnings.warn("The number of simulators created is less than 1")
25 | number = 1
26 |
27 | self.utils.set_operate("create")
28 |
29 | ret_code, retval = self.utils.run_command(["-n", str(number)])
30 |
31 | if ret_code != 0:
32 | raise RuntimeError(retval)
33 |
34 | create_index = []
35 |
36 | data = json.loads(retval)
37 |
38 | for row in data.keys():
39 | if data[row]["errcode"] == 0:
40 | create_index.append(int(row))
41 |
42 | return create_index
43 |
44 | def clone(self, number: int = 1) -> list:
45 | """
46 | 克隆模拟器
47 | :param number: 克隆数量
48 | :return:
49 | """
50 | if number <= 0:
51 | warnings.warn("The number of simulators created is less than 1")
52 | number = 1
53 |
54 | self.utils.set_operate("clone")
55 |
56 | ret_code, retval = self.utils.run_command(["-n", str(number)])
57 |
58 | if ret_code != 0:
59 | raise RuntimeError(retval)
60 |
61 | create_index = []
62 |
63 | data = json.loads(retval)
64 |
65 | for row in data.keys():
66 | if data[row]["errcode"] == 0:
67 | create_index.append(int(row))
68 |
69 | return create_index
70 |
71 | def delete(self) -> bool:
72 | """
73 | 删除模拟器
74 | :return:
75 | """
76 | self.utils.set_operate("delete")
77 |
78 | ret_code, retval = self.utils.run_command([])
79 |
80 | if ret_code == 0:
81 | return True
82 |
83 | raise RuntimeError(retval)
84 |
85 | def rename(self, name: str) -> bool:
86 | """
87 | 重命名模拟器
88 | :param name: 模拟器名称
89 | :return:
90 | """
91 | self.utils.set_operate("rename")
92 |
93 | ret_code, retval = self.utils.run_command(["-n", name])
94 |
95 | if ret_code == 0:
96 | return True
97 |
98 | raise RuntimeError(retval)
99 |
100 | # def export(self):
101 | # pass
102 |
103 | def export(self, dir: str, name: str, zip: bool = False) -> bool:
104 | """
105 | 导出模拟器
106 | :param dir: 备份的目录
107 | :param name: 备份文件的名称
108 | :param zip: 备份文件是否压缩
109 | :return:
110 | """
111 |
112 | self.utils.set_operate("export")
113 | args = ["-d", dir, "-n", name]
114 | if zip:
115 | args.append("--zip")
116 |
117 | ret_code, retval = self.utils.run_command(args)
118 | if ret_code == 0:
119 | return True
120 |
121 | raise RuntimeError(retval)
122 |
123 | def import_(self, path: Union[str, list], number: int = 0) -> bool:
124 | """
125 | 导入模拟器
126 | :param path:要导入的 mumudata 文件路径
127 | :param number:导入次数
128 | :return:
129 | """
130 | if number <= 0:
131 | warnings.warn("The number of simulators created is less than 1")
132 | number = 1
133 |
134 | self.utils.set_operate("import")
135 | # ret_code, retval = self.self.utils.run_command(["-p", path, "-n", str(number)])
136 |
137 | if isinstance(path, str):
138 | ret_code, retval = self.utils.run_command(["-p", path, "-n", str(number)])
139 |
140 | else:
141 | args = []
142 | for p in path:
143 | args.extend(["-p", p])
144 | args.extend(["-n", str(number)])
145 |
146 | ret_code, retval = self.utils.run_command(args)
147 |
148 | if ret_code == 0:
149 | return True
150 |
151 | raise RuntimeError(retval)
152 |
153 | def limit_cpu(self, cap: int = 100) -> bool:
154 | """
155 | 限制CPU
156 | :param cap: CPU限制,范围0-100
157 | :return:
158 | """
159 | if cap < 0 or cap > 100:
160 | raise ValueError("The value of cap must be between 0 and 100")
161 |
162 | self.utils.set_operate("control")
163 | ret_code, retval = self.utils.run_command(["tool", "downcpu", "-c", str(cap)])
164 |
165 | if ret_code == 0:
166 | return True
167 |
168 | raise RuntimeError(retval)
169 |
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/Core.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/Core.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/app.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/app.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/performance.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/performance.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/power.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/power.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/shortcut.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/shortcut.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/simulation.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/simulation.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/__pycache__/window.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/core/__pycache__/window.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/core/app.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午3:26
4 | # @Author : wlkjyy
5 | # @File : app.py
6 | # @Software: PyCharm
7 | import json
8 | import os.path
9 |
10 |
11 |
12 |
13 | class App:
14 |
15 | def __init__(self, utils):
16 | self.utils = utils
17 |
18 | def install(self, apk_path: str = None) -> bool:
19 | """
20 | 安装应用到模拟器里(install)
21 | :param apk_path: 选择要安装的应用apk文件路径(支持apk/xapk/apks后缀)
22 | :return:
23 | """
24 | if not os.path.exists(apk_path):
25 | raise FileNotFoundError(f"apk_path:{apk_path} not found")
26 |
27 | if not os.path.isfile(apk_path):
28 | raise FileNotFoundError(f"apk_path:{apk_path} is not a file")
29 | self.utils.set_operate('control')
30 |
31 | ret_code, retval = self.utils.run_command(['app', 'install', '-apk', apk_path])
32 |
33 | if ret_code == 0:
34 | return True
35 |
36 | raise RuntimeError(retval)
37 |
38 | def uninstall(self,package: str) -> bool:
39 | """
40 | 卸载应用(uninstall)
41 | :param package: 选择要卸载的应用包名
42 | :return:
43 | """
44 | self.utils.set_operate('control')
45 | ret_code, retval = self.utils.run_command(['app', 'uninstall', '-pkg', package])
46 |
47 | if ret_code == 0:
48 | return True
49 |
50 | raise RuntimeError(retval)
51 |
52 | def launch(self,package: str) -> bool:
53 | """
54 | 启动应用(launch)
55 | :param package: 选择要启动的应用包名
56 | :return:
57 | """
58 | self.utils.set_operate('control')
59 | ret_code, retval = self.utils.run_command(['app', 'launch', '-pkg', package])
60 |
61 | if ret_code == 0:
62 | return True
63 |
64 | raise RuntimeError(retval)
65 |
66 | def close(self,package: str) -> bool:
67 | """
68 | 关闭应用(close)
69 | :param package: 选择要关闭的应用包名
70 | :return:
71 | """
72 | self.utils.set_operate('control')
73 | ret_code, retval = self.utils.run_command(['app', 'close', '-pkg', package])
74 |
75 | if ret_code == 0:
76 | return True
77 |
78 | raise RuntimeError(retval)
79 |
80 | def get_installed(self):
81 | """
82 | 获取已安装的应用(get_installed)
83 | :return:
84 | """
85 | self.utils.set_operate('control')
86 | ret_code, retval = self.utils.run_command(['app', 'info','-i'])
87 |
88 | if ret_code != 0:
89 | raise RuntimeError(retval)
90 |
91 | data = json.loads(retval)
92 | installed = []
93 |
94 | for key in data.keys():
95 | if key != "active":
96 | installed.append({
97 | "package": key,
98 | "app_name": data[key]['app_name'],
99 | "version": data[key]['version']
100 | })
101 |
102 | return installed
103 |
104 |
105 | def exists(self,package: str) -> bool:
106 | """
107 | 判断应用是否存在(exists)
108 | :param package: 选择要判断的应用包名
109 | :return:
110 | """
111 | self.utils.set_operate('control')
112 | ret_code, retval = self.utils.run_command(['app', 'info', '-pkg', package])
113 |
114 | if ret_code != 0:
115 | raise RuntimeError(retval)
116 |
117 | data = json.loads(retval)
118 |
119 | return data['state'] != 'not_installed'
120 |
121 | def doesntExists(self,package: str) -> bool:
122 | """
123 | 判断应用是否不存在(doesntExists)
124 | :param package: 选择要判断的应用包名
125 | :return:
126 | """
127 | return not self.exists(package)
128 |
129 | def state(self,package: str) -> str:
130 | """
131 | 获取应用状态(state)
132 | :param package: 选择要获取的应用包名
133 | :return:
134 | """
135 | self.utils.set_operate('control')
136 | ret_code, retval = self.utils.run_command(['app', 'info', '-pkg', package])
137 |
138 | if ret_code != 0:
139 | raise RuntimeError(retval)
140 |
141 | data = json.loads(retval)
142 |
143 | return data['state']
--------------------------------------------------------------------------------
/mumu/api/core/performance.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午9:58
4 | # @Author : wlkjyy
5 | # @File : performance.py
6 | # @Software: PyCharm
7 | from mumu.api.setting.setting import Setting
8 |
9 |
10 | class Performance:
11 |
12 | def __init__(self, utils):
13 | self.utils = utils
14 |
15 | def set(self, cpu_num: int = 1, mem_gb: int = 2):
16 | """
17 | 设置模拟器性能
18 | :param cpu_num: CPU个数
19 | :param mem_gb: 内存大小
20 | :return:
21 | """
22 | cpu_num = max(1, min(16, cpu_num))
23 |
24 | return Setting(self.utils).set(
25 | performance_mode='custom',
26 | performance_cpu__custom=cpu_num,
27 | performance_mem__custom=mem_gb
28 | )
29 |
30 | def cpu(self, cpu_num: int):
31 | """
32 | 设置模拟器CPU个数
33 | :param cpu_num: CPU个数
34 | :return:
35 | """
36 | cpu_num = max(1, min(16, cpu_num))
37 |
38 | return Setting(self.utils).set(
39 | performance_mode='custom',
40 | performance_cpu__custom=cpu_num
41 | )
42 |
43 | def memory(self, mem_gb: int):
44 | """
45 | 设置模拟器内存大小
46 | :param mem_gb: 内存大小
47 | :return:
48 | """
49 | return Setting(self.utils).set(
50 | performance_mode='custom',
51 | performance_mem__custom=mem_gb
52 | )
53 |
54 | def force_discrete_graphics(self, enable: bool):
55 | """
56 | 强制使用独立显卡
57 | :param enable: 是否启用
58 | :return:
59 | """
60 | return Setting(self.utils).set(
61 | force_discrete_graphics=enable
62 | )
63 |
64 | def renderer_strategy(self, auto=True, dis=False, perf=False):
65 | """
66 | 显存使用策略
67 | :param auto: 自动调优
68 | :param dis: 画面表现更好
69 | :param perf: 资源占用更小
70 | :return:
71 | """
72 | if auto:
73 | return Setting(self.utils).set(
74 | renderer_strategy='auto'
75 | )
76 | elif dis:
77 | return Setting(self.utils).set(
78 | renderer_strategy='dis'
79 | )
80 | elif perf:
81 | return Setting(self.utils).set(
82 | renderer_strategy='perf'
83 | )
84 |
85 | def disk_readonly(self, enable: bool = True):
86 | """
87 | 是否为只读系统盘
88 | :param enable: 是否只读
89 | :return:
90 | """
91 | return Setting(self.utils).set(
92 | system_disk_readonly=enable
93 | )
94 |
95 | def disk_writable(self):
96 | """
97 | 是否为可写系统盘
98 | :param enable: 是否可写
99 | :return:
100 | """
101 | return self.disk_readonly(False)
102 |
--------------------------------------------------------------------------------
/mumu/api/core/power.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午3:05
4 | # @Author : wlkjyy
5 | # @File : power.py
6 | # @Software: PyCharm
7 |
8 |
9 |
10 | class Power:
11 |
12 | def __init__(self, utils):
13 | self.utils = utils
14 |
15 | def start(self, package: str = None) -> bool:
16 | """
17 | 启动模拟器(start)
18 | :param package: 启动时自动启动应用的应用包名
19 | :return:
20 | """
21 | self.utils.set_operate('control')
22 | args = ['launch']
23 | if package is not None:
24 | args.extend(['-pkg', package])
25 |
26 | ret_code, retval = self.utils.run_command(args)
27 | if ret_code == 0:
28 | return True
29 |
30 | raise RuntimeError(retval)
31 |
32 | def shutdown(self):
33 | """
34 | 关闭模拟器(shutdown)
35 | :return:
36 | """
37 | self.utils.set_operate('control')
38 | ret_code, retval = self.utils.run_command(['shutdown'])
39 | if ret_code == 0:
40 | return True
41 |
42 | raise RuntimeError(retval)
43 |
44 | def restart(self):
45 | """
46 | 重启模拟器(restart)
47 | :return:
48 | """
49 | self.utils.set_operate('control')
50 | ret_code, retval = self.utils.run_command(['restart'])
51 | if ret_code == 0:
52 | return True
53 |
54 | raise RuntimeError(retval)
55 |
56 | def stop(self):
57 | """
58 | 关闭一个模拟器
59 | :return:
60 | """
61 | return self.shutdown()
62 |
63 | def reboot(self):
64 | """
65 | 重启一个模拟器
66 | :return:
67 | """
68 | return self.restart()
69 |
--------------------------------------------------------------------------------
/mumu/api/core/shortcut.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午7:11
4 | # @Author : wlkjyy
5 | # @File : shortcut.py
6 | # @Software: PyCharm
7 | import os.path
8 |
9 |
10 |
11 |
12 | class Shortcut:
13 |
14 | def __init__(self, utils):
15 | self.utils = utils
16 |
17 | def create(self, name: str, icon: str, package: str) -> bool:
18 | """
19 | 创建桌面快捷方式
20 | :param name: 创建快捷方式的名称
21 | :param icon: 创建快捷方式的图标路径
22 | :param package: 创建自动启动应用的快捷方式
23 | :return:
24 | """
25 | self.utils.set_operate('control')
26 |
27 | if not os.path.exists(icon):
28 | raise FileNotFoundError(f'File not found: {icon}')
29 |
30 | ret_code, retval = self.utils.run_command(['shortcut', 'create', '-n', name, '-i', icon, '-pkg', package])
31 |
32 | if ret_code != 0:
33 | raise RuntimeError(retval)
34 |
35 | return True
36 |
37 | def delete(self) -> bool:
38 | """
39 | 删除桌面快捷方式
40 | :return:
41 | """
42 | self.utils.set_operate('control')
43 | ret_code, retval = self.utils.run_command(['shortcut', 'delete'])
44 |
45 | if ret_code != 0:
46 | raise RuntimeError(retval)
47 |
48 | return True
49 |
--------------------------------------------------------------------------------
/mumu/api/core/simulation.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午7:25
4 | # @Author : wlkjyy
5 | # @File : simulation.py
6 | # @Software: PyCharm
7 |
8 |
9 |
10 | from mumu.constant import MacAddress, IMEI, IMSI, AndroidID, PhoneNumber, GPU
11 | from mumu.api.setting.setting import Setting
12 |
13 |
14 | class Simulation:
15 |
16 | def __init__(self, utils):
17 | self.utils = utils
18 |
19 | def __action(self, sk: str, sv: str) -> bool:
20 | self.utils.set_operate('simulation')
21 | ret_code, retval = self.utils.run_command(['-sk', sk, '-sv', sv])
22 | if ret_code == 0:
23 | return True
24 | raise RuntimeError(retval)
25 |
26 | def mac_address(self, mac: str = None) -> bool:
27 | """
28 | 设置模拟器MAC地址
29 | :param mac: 设置模拟器的MAC地址
30 | :return:
31 | """
32 | if mac is None:
33 | mac = MacAddress.random()
34 |
35 | return self.__action('mac_address', mac)
36 |
37 | def imei(self, imei: str = None) -> bool:
38 | """
39 | 设置模拟器IMEI
40 | :param imei: 设置模拟器的IMEI
41 | :return:
42 | """
43 | if imei is None:
44 | imei = IMEI.random()
45 |
46 | return self.__action('imei', imei)
47 |
48 | def imsi(self, imsi: str = None) -> bool:
49 | """
50 | 设置模拟器IMSI
51 | :param imsi: 设置模拟器的IMSI
52 | :return:
53 | """
54 | if imsi is None:
55 | imsi = IMSI.random()
56 |
57 | return self.__action('imsi', imsi)
58 |
59 | def android_id(self, android_id: str = None) -> bool:
60 | """
61 | 设置模拟器Android ID
62 | :param android_id: 设置模拟器的Android ID
63 | :return:
64 | """
65 | if android_id is None:
66 | android_id = AndroidID.random()
67 |
68 | return self.__action('android_id', android_id)
69 |
70 | def model(self, model: str) -> bool:
71 | """
72 | 设置模拟器型号
73 | :param model: 设置模拟器的型号
74 | :return:
75 | """
76 | return self.__action('model', model)
77 |
78 | def brand(self, brand: str) -> bool:
79 | """
80 | 设置模拟器品牌
81 | :param brand: 设置模拟器的品牌
82 | :return:
83 | """
84 | return self.__action('brand', brand)
85 |
86 | def solution(self, solution: str) -> bool:
87 | """
88 | 设置模拟器硬件
89 | :param solution: 设置模拟器的硬件
90 | :return:
91 | """
92 | return self.__action('solution', solution)
93 |
94 | def phone_number(self, phone_number: str = None) -> bool:
95 | """
96 | 设置模拟器手机号码
97 | :param phone_number: 设置模拟器的手机号码
98 | :return:
99 | """
100 | if phone_number is None:
101 | phone_number = PhoneNumber.random()
102 |
103 | return self.__action('phone_number', phone_number)
104 |
105 | def gpu_model(self, gpu_model_name: str = "GeForce GTX 4090 Ti", top_model=False, middle_model=False,
106 | low_model=False) -> bool:
107 | """
108 | 设置模拟器GPU型号
109 | :param gpu_model_name: 自定义GPU型号
110 | :param top_model: 使用顶级GPU型号
111 | :param middle_model: 使用中级GPU型号
112 | :param low_model: 使用低级GPU型号
113 | :return:
114 | """
115 | if top_model:
116 | return Setting(self.utils).set(
117 | gpu_mode="high",
118 | gpu_model__custom=GPU.TOP_MODEL
119 | )
120 |
121 | if middle_model:
122 | return Setting(self.utils).set(
123 | gpu_mode="middle",
124 | gpu_model__custom=GPU.MIDDLE_MODEL
125 | )
126 |
127 | if low_model:
128 | return Setting(self.utils).set(
129 | gpu_mode="low",
130 | gpu_model__custom=GPU.LOW_MODEL
131 | )
132 |
133 | return Setting(self.utils).set(
134 | gpu_mode="custom",
135 | gpu_model__custom=gpu_model_name
136 | )
137 |
--------------------------------------------------------------------------------
/mumu/api/core/window.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午3:14
4 | # @Author : wlkjyy
5 | # @File : window.py
6 | # @Software: PyCharm
7 |
8 |
9 |
10 | class Window:
11 |
12 | def __init__(self, utils):
13 | self.utils = utils
14 |
15 | def show(self) -> bool:
16 | """
17 | 显示模拟器(show_window)
18 | :return:
19 | """
20 | self.utils.set_operate('control')
21 | ret_code, retval = self.utils.run_command(['show_window'])
22 |
23 | if ret_code == 0:
24 | return True
25 |
26 | raise RuntimeError(retval)
27 |
28 | def hidden(self) -> bool:
29 | """
30 | 隐藏模拟器(hide_window)
31 | :return:
32 | """
33 | self.utils.set_operate('control')
34 | ret_code, retval = self.utils.run_command(['hide_window'])
35 |
36 | if ret_code == 0:
37 | return True
38 |
39 | raise RuntimeError(retval)
40 |
41 | def layout(self, x: int=None, y: int=None, width: int=None, height: int=None) -> bool:
42 | """
43 | 设置模拟器位置和大小(layout)
44 | :param x: 选择修改窗口的X轴位置,以屏幕左上角为原点
45 | :param y: 选择修改窗口的Y轴位置,以屏幕左上角为原点
46 | :param width: 选择修改窗口的宽度
47 | :param height: 选择修改窗口的高度
48 | :return:
49 | """
50 | self.utils.set_operate('control')
51 | args = ['layout_window']
52 |
53 | if x:
54 | args.append('-px')
55 | args.append(str(x))
56 |
57 | if y:
58 | args.append('-py')
59 | args.append(str(y))
60 |
61 | if width:
62 | args.append('-width')
63 | args.append(str(width))
64 |
65 | if height:
66 | args.append('-height')
67 | args.append(str(height))
68 |
69 | if len(args) == 1:
70 | raise RuntimeError('The layout method must have at least one parameter')
71 |
72 | ret_code, retval = self.utils.run_command(args)
73 | if ret_code == 0:
74 | return True
75 |
76 | raise RuntimeError(retval)
--------------------------------------------------------------------------------
/mumu/api/develop/__pycache__/androidevent.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/develop/__pycache__/androidevent.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/develop/androidevent.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午6:34
4 | # @Author : wlkjyy
5 | # @File : develop.py
6 | # @Software: PyCharm
7 |
8 |
9 |
10 |
11 | class AndroidEvent:
12 |
13 | def __init__(self, utils):
14 | self.utils = utils
15 |
16 | def __action(self, action_name: str) -> bool:
17 | """
18 | 执行操作
19 | :param action_name: 操作名称
20 | :return:
21 | """
22 | self.utils.set_operate("control")
23 | ret_code, ret_val = self.utils.run_command(['tool', 'func', '-n', action_name])
24 | if ret_code == 0:
25 | return True
26 |
27 | raise RuntimeError(ret_val)
28 |
29 | def rotates(self) -> bool:
30 | """
31 | 屏幕旋转
32 | :return:
33 | """
34 | return self.__action("rotate")
35 |
36 | def go_home(self) -> bool:
37 | """
38 | 返回主页
39 | :return:
40 | """
41 | return self.__action("go_home")
42 |
43 | def go_back(self) -> bool:
44 | """
45 | 返回
46 | :return:
47 | """
48 | return self.__action("go_back")
49 |
50 | def top_most(self) -> bool:
51 | """
52 | 置顶
53 | :return:
54 | """
55 | return self.__action("top_most")
56 |
57 | def fullscreen(self) -> bool:
58 | """
59 | 全屏
60 | :return:
61 | """
62 | return self.__action("fullscreen")
63 |
64 | def shake(self) -> bool:
65 | """
66 | 摇一摇
67 | :return:
68 | """
69 | return self.__action("shake")
70 |
71 | def screenshot(self) -> bool:
72 | """
73 | 截图
74 | :return:
75 | """
76 | return self.__action("screenshot")
77 |
78 | def volume_up(self) -> bool:
79 | """
80 | 音量+
81 | :return:
82 | """
83 | return self.__action("volume_up")
84 |
85 | def volume_down(self) -> bool:
86 | """
87 | 音量-
88 | :return:
89 | """
90 | return self.__action("volume_down")
91 |
92 | def volume_mute(self) -> bool:
93 | """
94 | 静音
95 | :return:
96 | """
97 | return self.__action("volume_mute")
98 |
99 | def go_task(self) -> bool:
100 | """
101 | 按下安卓任务键
102 | :return:
103 | """
104 | self.utils.set_operate("adb")
105 | ret_code, ret_val = self.utils.run_command(['-c','go_task'])
106 | if ret_code == 0:
107 | return True
108 |
109 | raise RuntimeError(ret_val)
110 |
111 | def location(self, lon: float, lat: float) -> bool:
112 | """
113 | 修改虚拟定位
114 | :param lon:要修改虚拟定位的经度,-180 ~ 180 之间浮点有效
115 | :param lat: 要修改虚拟定位的纬度,-90 ~ 90 之间浮点有效
116 | :return:
117 | """
118 | if lon < -180 or lon > 180:
119 | raise ValueError("The longitude range is incorrect")
120 |
121 | if lat < -90 or lat > 90:
122 | raise ValueError("The latitude range is incorrect")
123 |
124 | self.utils.set_operate("control")
125 | ret_code, ret_val = self.utils.run_command(['tool', 'location', '-lon', str(lon), '-lat', str(lat)])
126 | if ret_code == 0:
127 | return True
128 |
129 | raise RuntimeError(ret_val)
130 |
131 | def gyro(self, x: float, y: float, z: float) -> bool:
132 | """
133 | 修改虚拟陀螺仪
134 | :param x: x轴
135 | :param y: y轴
136 | :param z: z轴
137 | :return:
138 | """
139 | self.utils.set_operate("control")
140 | ret_code, ret_val = self.utils.run_command(['tool', 'gyro', '-gx', str(x), '-gy', str(y), '-gz', str(z)])
141 | if ret_code == 0:
142 | return True
143 |
144 | raise RuntimeError(ret_val)
145 |
--------------------------------------------------------------------------------
/mumu/api/driver/Driver.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:41
4 | # @Author : wlkjyy
5 | # @File : Driver.py
6 | # @Software: PyCharm
7 | from mumu.api.driver.bridge import Bridge
8 |
9 |
10 | class Driver:
11 |
12 | def __init__(self, utils):
13 | self.utils = utils
14 |
15 | """
16 | 根据官方文档,目前仅支持“网络桥接”驱动
17 | """
18 |
19 | @property
20 | def bridge(self):
21 | """
22 | 网络桥接驱动
23 | :return:
24 | """
25 |
26 | return Bridge(self.utils)
27 |
--------------------------------------------------------------------------------
/mumu/api/driver/__pycache__/Driver.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/driver/__pycache__/Driver.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/driver/__pycache__/bridge.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/driver/__pycache__/bridge.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/driver/bridge.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:59
4 | # @Author : wlkjyy
5 | # @File : bridge.py
6 | # @Software: PyCharm
7 |
8 |
9 |
10 | class Bridge:
11 | def __init__(self, utils):
12 | self.utils = utils
13 |
14 | """
15 | 网络桥接驱动
16 | """
17 |
18 | def install(self):
19 | """
20 | 安装网卡桥接驱动
21 | :return:
22 | """
23 | self.utils.set_operate(['driver', 'install'])
24 | ret_code, ret_val = self.utils.run_command(['-n', 'lwf'])
25 | if ret_code == 0:
26 | return True
27 |
28 | raise RuntimeError(ret_val)
29 |
30 | def uninstall(self):
31 | """
32 | 卸载网卡桥接驱动
33 | :return:
34 | """
35 | self.utils.set_operate(['driver', 'uninstall'])
36 | ret_code, ret_val = self.utils.run_command([])
37 | if ret_code == 0:
38 | return True
39 |
40 | raise RuntimeError(ret_val)
41 |
--------------------------------------------------------------------------------
/mumu/api/network/Network.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:44
4 | # @Author : wlkjyy
5 | # @File : Network.py
6 | # @Software: PyCharm
7 | from mumu.api.setting.setting import Setting
8 |
9 |
10 | class Network:
11 |
12 | def __init__(self, utils):
13 | self.utils = utils
14 |
15 | def get_bridge_card(self):
16 | """
17 | 获取所有网桥适配器
18 | :return:
19 | """
20 | card = Setting().get('net_bridge_card.list')
21 | card = card[1:-1]
22 | return card.split(',')
23 |
24 | def nat(self):
25 | """
26 | 设置为NAT
27 | :return:
28 | """
29 | return Setting(self.utils).set(
30 | net_bridge_open=False
31 | )
32 |
33 | def bridge(self, enable: bool = True, net_bridge_card: str = None):
34 | """
35 | 是否启用网桥
36 | :param enable: 是否启用
37 | :return:
38 | """
39 | return Setting(self.utils).set(
40 | net_bridge_open=enable,
41 | net_bridge_card=net_bridge_card
42 | )
43 |
44 | def bridge_dhcp(self):
45 | """
46 | 设置网桥为DHCP
47 | :return:
48 | """
49 | return Setting(self.utils).set(
50 | net_bridge_ip_mode='dhcp'
51 | )
52 |
53 | def bridge_static(self, ip_addr: str, subnet_mask: str, gateway: str, dns1: str = '8.8.8.8', dns2: str='114.114.114.114'):
54 | """
55 | 设置网桥为静态
56 | :param ip_addr: ip地址
57 | :param subnet_mask: 子网掩码
58 | :param gateway: 网关
59 | :param dns1: DNS1
60 | :param dns2: DNS2
61 | :return:
62 | """
63 | return Setting(self.utils).set(
64 | net_bridge_ip_mode='static',
65 | net_bridge_ip_addr=ip_addr,
66 | net_bridge_subnet_mask=subnet_mask,
67 | net_bridge_gateway=gateway,
68 | net_bridge_dns1=dns1,
69 | net_bridge_dns2=dns2
70 | )
71 |
--------------------------------------------------------------------------------
/mumu/api/network/__pycache__/Network.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/network/__pycache__/Network.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/permission/Permission.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:42
4 | # @Author : wlkjyy
5 | # @File : Permission.py
6 | # @Software: PyCharm
7 | from mumu.api.permission.root import Root
8 |
9 |
10 | class Permission:
11 |
12 | def __init__(self, utils):
13 | self.utils = utils
14 |
15 | @property
16 | def root(self) -> Root:
17 |
18 | return Root(self.utils)
--------------------------------------------------------------------------------
/mumu/api/permission/__pycache__/Permission.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/permission/__pycache__/Permission.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/permission/__pycache__/root.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/permission/__pycache__/root.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/permission/root.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:17
4 | # @Author : wlkjyy
5 | # @File : root.py
6 | # @Software: PyCharm
7 | import mumu.config as config
8 |
9 |
10 |
11 | class Root:
12 |
13 | def __init__(self, utils):
14 | self.utils = utils
15 |
16 | def disable(self):
17 | """
18 | 关闭模拟器Root权限
19 | :return:
20 | """
21 | self.utils.set_operate("setting")
22 | ret_code, ret_val = self.utils.run_command(['-k', 'root_permission', '-val', 'false'])
23 |
24 | if ret_code != 0:
25 | raise RuntimeError(ret_val)
26 |
27 | return True
28 |
29 | def enable(self):
30 | """
31 | 启用模拟器Root权限
32 | :return:
33 | """
34 | self.utils.set_operate("setting")
35 | ret_code, ret_val = self.utils.run_command(['-k', 'root_permission', '-val', 'true'])
36 |
37 | if ret_code != 0:
38 | raise RuntimeError(ret_val)
39 |
40 | return True
41 |
--------------------------------------------------------------------------------
/mumu/api/screen/__pycache__/screen.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/screen/__pycache__/screen.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/screen/gui.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/30 下午3:06
4 | # @Author : wlkjyy
5 | # @File : gui.py
6 | # @Software: PyCharm
7 | import copy
8 | import json
9 | import time
10 |
11 | import cv2
12 | import numpy
13 | import collections
14 | from adbutils import adb
15 | import threading
16 | import mumu.config as config
17 |
18 |
19 | class Gui:
20 |
21 | def __init__(self, utils):
22 | self.utils = utils
23 |
24 | def __connect(self):
25 | """
26 | 获取可用的连接
27 | :return:
28 | """
29 |
30 | self.utils.set_operate("adb")
31 | ret_code, retval = self.utils.run_command([''])
32 | if ret_code != 0:
33 | return self
34 |
35 | try:
36 | data = json.loads(retval)
37 | except json.JSONDecodeError:
38 | return None, None
39 |
40 | for key, value in data.items():
41 | if key == "adb_host" and "adb_port" in data:
42 | yield data["adb_host"], data["adb_port"], self.utils.get_vm_id()
43 | return
44 |
45 | if 'errcode' in value:
46 | continue
47 | else:
48 | # connect_list.append((value.get("adb_host"), value.get("adb_port")))
49 | yield value.get("adb_host"), value.get("adb_port"), key
50 |
51 | return
52 |
53 | def __vm_handle_frame(self, handle, vm_id):
54 | while True:
55 | if vm_id in config.FRAME_CACHE:
56 |
57 | while True:
58 | frame = config.FRAME_CACHE[vm_id]
59 |
60 | if frame is not None:
61 | handle(frame, self.utils.get_mumu_root_object().select(vm_id))
62 | time.sleep(0.01)
63 |
64 | else:
65 | continue
66 |
67 | def create_handle(self, handle_func):
68 | """
69 | 创建帧处理函数
70 | :param handle_func:
71 | :return:
72 | """
73 | try:
74 | import scrcpy
75 | except:
76 | raise RuntimeError(
77 | "scrcpy is not installed, please install it first by running 'pip install scrcpy-client'")
78 |
79 | client_list = []
80 |
81 | for (k, v, id) in self.__connect():
82 | host = str(k) + ":" + str(v)
83 | adb.connect(host)
84 | for i in adb.device_list():
85 | if i.serial == host:
86 | client = scrcpy.Client(device=i)
87 |
88 | threading.Thread(target=self.__vm_handle_frame, args=(handle_func, id)).start()
89 |
90 | def func(vm_id):
91 | def save_frame(frame):
92 | config.FRAME_CACHE[vm_id] = frame
93 | return
94 |
95 | return save_frame
96 |
97 | client.add_listener(scrcpy.EVENT_FRAME, func(id))
98 | client_list.append(client)
99 | for cli_row in client_list:
100 | cli_row.start(threaded=True, daemon_threaded=True)
101 |
102 | def locateOnScreen(self, haystack_frame, needle_image, confidence=0.8, grayscale=None):
103 | """
104 | 在屏幕上查找图片
105 | :param haystack_frame: 屏幕帧
106 | :param needle_image: 图片
107 | :param confidence: 置信度
108 | :param grayscale: 灰度值找图
109 | :return:
110 | """
111 | needle_image = _load_cv2(needle_image)
112 | haystack_frame = _load_cv2(haystack_frame)
113 |
114 | for r in _locateAll_opencv(needle_image, haystack_frame, confidence=confidence, grayscale=None):
115 | return r
116 |
117 | return False
118 |
119 | def center(self, box):
120 | """
121 | 获取中心点
122 | :param box: Box
123 | :return:
124 | """
125 | return int(box.left + box.width / 2), int(box.top + box.height / 2)
126 |
127 | def locateCenterOnScreen(self, haystack_frame, needle_image, confidence=0.8, grayscale=None):
128 | """
129 | 在屏幕上查找图片
130 | :param haystack_frame: 屏幕帧
131 | :param needle_image: 图片
132 | :param confidence: 置信度
133 | :param grayscale: 灰度值找图
134 | :return:
135 | """
136 | needle_image = _load_cv2(needle_image)
137 | haystack_frame = _load_cv2(haystack_frame)
138 |
139 | for r in _locateAll_opencv(needle_image, haystack_frame, confidence=confidence, grayscale=None):
140 | return self.center(r)
141 |
142 | return False
143 |
144 | def locateAllOnScreen(self, haystack_frame, needle_image, confidence=0.8, grayscale=None):
145 | """
146 | 在屏幕上查找所有图片
147 | :param haystack_frame: 屏幕帧
148 | :param needle_image: 图片
149 | :param confidence: 置信度
150 | :param grayscale: 灰度值找图
151 | :return:
152 | """
153 | arr = []
154 | needle_image = _load_cv2(needle_image)
155 | haystack_frame = _load_cv2(haystack_frame)
156 |
157 | for r in _locateAll_opencv(needle_image, haystack_frame, confidence=confidence, grayscale=None):
158 | arr.append(r)
159 |
160 | return arr
161 |
162 |
163 |
164 | def save(self, frame, path):
165 | """
166 | 保存帧
167 | :param path: 路径
168 | :return:
169 | """
170 | cv2.imwrite(path, frame)
171 |
172 |
173 | GRAYSCALE_DEFAULT = True
174 | USE_IMAGE_NOT_FOUND_EXCEPTION = True
175 | Box = collections.namedtuple('Box', 'left top width height')
176 |
177 |
178 | def _load_cv2(img, grayscale=None):
179 | """
180 | TODO
181 | """
182 | # load images if given filename, or convert as needed to opencv
183 | # Alpha layer just causes failures at this point, so flatten to RGB.
184 | # RGBA: load with -1 * cv2.CV_LOAD_IMAGE_COLOR to preserve alpha
185 | # to matchTemplate, need template and image to be the same wrt having alpha
186 |
187 | if grayscale is None:
188 | grayscale = GRAYSCALE_DEFAULT
189 | if isinstance(img, str):
190 | # The function imread loads an image from the specified file and
191 | # returns it. If the image cannot be read (because of missing
192 | # file, improper permissions, unsupported or invalid format),
193 | # the function returns an empty matrix
194 | # http://docs.opencv.org/3.0-beta/modules/imgcodecs/doc/reading_and_writing_images.html
195 | if grayscale:
196 | img_cv = cv2.imread(img, cv2.IMREAD_GRAYSCALE)
197 | else:
198 | img_cv = cv2.imread(img, cv2.IMREAD_COLOR)
199 | if img_cv is None:
200 | raise IOError(
201 | "Failed to read %s because file is missing, "
202 | "has improper permissions, or is an "
203 | "unsupported or invalid format" % img
204 | )
205 | elif isinstance(img, numpy.ndarray):
206 | # don't try to convert an already-gray image to gray
207 | if grayscale and len(img.shape) == 3: # and img.shape[2] == 3:
208 | img_cv = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
209 | else:
210 | img_cv = img
211 | elif hasattr(img, 'convert'):
212 | # assume its a PIL.Image, convert to cv format
213 | img_array = numpy.array(img.convert('RGB'))
214 | img_cv = img_array[:, :, ::-1].copy() # -1 does RGB -> BGR
215 | if grayscale:
216 | img_cv = cv2.cvtColor(img_cv, cv2.COLOR_BGR2GRAY)
217 | else:
218 | raise TypeError('expected an image filename, OpenCV numpy array, or PIL image')
219 | return img_cv
220 |
221 |
222 | def _locateAll_opencv(needleImage, haystackImage, grayscale=None, limit=10000, region=None, step=1, confidence=0.999):
223 | """
224 | TODO - rewrite this
225 | faster but more memory-intensive than pure python
226 | step 2 skips every other row and column = ~3x faster but prone to miss;
227 | to compensate, the algorithm automatically reduces the confidence
228 | threshold by 5% (which helps but will not avoid all misses).
229 | limitations:
230 | - OpenCV 3.x & python 3.x not tested
231 | - RGBA images are treated as RBG (ignores alpha channel)
232 | """
233 | if grayscale is None:
234 | grayscale = GRAYSCALE_DEFAULT
235 |
236 | confidence = float(confidence)
237 |
238 | needleImage = _load_cv2(needleImage, grayscale)
239 | needleHeight, needleWidth = needleImage.shape[:2]
240 | haystackImage = _load_cv2(haystackImage, grayscale)
241 |
242 | if region:
243 | haystackImage = haystackImage[region[1]: region[1] + region[3], region[0]: region[0] + region[2]]
244 | else:
245 | region = (0, 0) # full image; these values used in the yield statement
246 | if haystackImage.shape[0] < needleImage.shape[0] or haystackImage.shape[1] < needleImage.shape[1]:
247 | # avoid semi-cryptic OpenCV error below if bad size
248 | raise ValueError('needle dimension(s) exceed the haystack image or region dimensions')
249 |
250 | if step == 2:
251 | confidence *= 0.95
252 | needleImage = needleImage[::step, ::step]
253 | haystackImage = haystackImage[::step, ::step]
254 | else:
255 | step = 1
256 |
257 | # get all matches at once, credit: https://stackoverflow.com/questions/7670112/finding-a-subimage-inside-a-numpy-image/9253805#9253805
258 | result = cv2.matchTemplate(haystackImage, needleImage, cv2.TM_CCOEFF_NORMED)
259 | match_indices = numpy.arange(result.size)[(result > confidence).flatten()]
260 | matches = numpy.unravel_index(match_indices[:limit], result.shape)
261 |
262 | if len(matches[0]) == 0:
263 | if USE_IMAGE_NOT_FOUND_EXCEPTION:
264 | # raise ImageNotFoundException('Could not locate the image (highest confidence = %.3f)' % result.max())
265 | # print('Could not locate the image (highest confidence = %.3f)' % result.max())
266 | return
267 | else:
268 | return
269 |
270 | # use a generator for API consistency:
271 | matchx = matches[1] * step + region[0] # vectorized
272 | matchy = matches[0] * step + region[1]
273 | for x, y in zip(matchx, matchy):
274 | yield Box(x, y, needleWidth, needleHeight)
275 |
--------------------------------------------------------------------------------
/mumu/api/screen/screen.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午8:54
4 | # @Author : wlkjyy
5 | # @File : screen.py.py
6 | # @Software: PyCharm
7 |
8 |
9 | from mumu.api.setting.setting import Setting
10 |
11 |
12 | class Screen:
13 |
14 | def __init__(self, utils):
15 | self.utils = utils
16 |
17 | def resolution(self, width: int, height: int):
18 | """
19 | 修改分辨率
20 | :param width:
21 | :param height:
22 | :return:
23 | """
24 | return Setting(self.utils).set(
25 | resolution_height__custom=height,
26 | resolution_width__custom=width
27 | )
28 |
29 | def resolution_mobile(self):
30 | """
31 | 设置为手机分辨率
32 | :return:
33 | """
34 | self.resolution(width=1080, height=1920)
35 | self.dpi(480)
36 |
37 | def resolution_tablet(self):
38 | """
39 | 设置为平板分辨率
40 | :return:
41 | """
42 | self.resolution(width=1920, height=1080)
43 | self.dpi(280)
44 |
45 | def resolution_ultrawide(self):
46 | """
47 | 设置为超宽屏分辨率
48 | :return:
49 | """
50 | self.resolution(width=3200, height=1440)
51 | self.dpi(400)
52 |
53 | def dpi(self, dpi: int):
54 | """
55 | 修改dpi
56 | :param dpi:
57 | :return:
58 | """
59 | return Setting(self.utils).set(
60 | resolution_dpi__custom=dpi
61 | )
62 |
63 | def brightness(self, brightness: int):
64 | """
65 | 修改模拟器亮度
66 | :param brightness: 亮度值 1-100
67 | :return:
68 | """
69 | brightness = max(1, min(100, brightness))
70 |
71 | return Setting(self.utils).set(
72 | screen_brightness=brightness
73 | )
74 |
75 | def max_frame_rate(self, frame_rate: int = 60):
76 | """
77 | 修改模拟器最大帧率
78 | :param frame_rate: 最大帧率 1-240
79 | :return:
80 | """
81 | frame_rate = max(1, min(240, frame_rate))
82 |
83 | return Setting(self.utils).set(
84 | max_frame_rate=frame_rate
85 | )
86 |
87 | def dynamic_adjust_frame_rate(self, enable: bool, dynamic_low_frame_rate_limit: int = 15):
88 | """
89 | 是否开启动态调整帧率
90 | :param enable: 是否开启
91 | :param dynamic_low_frame_rate_limit: 当主操作窗口不是本模拟器时候,降低帧率到多少
92 | :return:
93 | """
94 | return Setting(self.utils).set(
95 | dynamic_adjust_frame_rate=enable,
96 | dynamic_low_frame_rate_limit=dynamic_low_frame_rate_limit
97 | )
98 |
99 | def vertical_sync(self, enable: bool):
100 | """
101 | 是否开启垂直同步
102 | :param enable: 是否开启
103 | :return:
104 | """
105 | return Setting(self.utils).set(
106 | vertical_sync=enable
107 | )
108 |
109 | def show_frame_rate(self, enable: bool):
110 | """
111 | 是否显示帧率
112 | :param enable: 是否显示
113 | :return:
114 | """
115 | return Setting(self.utils).set(
116 | show_frame_rate=enable
117 | )
118 |
119 | def window_auto_rotate(self, enable: bool):
120 | """
121 | 是否开启窗口自动旋转
122 |
123 | 开启后,模拟器会根据程序自动旋转窗口
124 | :param enable: 是否开启
125 | :return:
126 | """
127 | return Setting(self.utils).set(
128 | window_auto_rotate=enable
129 | )
130 |
--------------------------------------------------------------------------------
/mumu/api/setting/__pycache__/setting.cpython-38.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/u-wlkjyy/mumu-python-api/7f861ceeb4bd3a7bc0d6fec73dac5d043a160f88/mumu/api/setting/__pycache__/setting.cpython-38.pyc
--------------------------------------------------------------------------------
/mumu/api/setting/setting.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午8:20
4 | # @Author : wlkjyy
5 | # @File : setting.py
6 | # @Software: PyCharm
7 | import json
8 | import os.path
9 | from typing import Union
10 |
11 |
12 | class Setting:
13 |
14 | def __init__(self, utils):
15 | self.utils = utils
16 |
17 | def all(self, all_writable: bool = False) -> dict:
18 | """
19 | 获取所有配置项
20 | :return:
21 | """
22 | self.utils.set_operate("setting")
23 | if all_writable:
24 | args = ["-aw"]
25 | else:
26 | args = ["-a"]
27 |
28 | ret_code, retval = self.utils.run_command(args)
29 | if ret_code == 0:
30 | return json.loads(retval)
31 |
32 | raise RuntimeError(retval)
33 |
34 | def get(self, *args) -> Union[dict, str]:
35 | """
36 | 获取一个或多个配置项
37 | :param args:
38 | :return:
39 | """
40 | self.utils.set_operate("setting")
41 | command_args = []
42 | for arg in args:
43 | command_args.extend(["-k", arg])
44 |
45 | ret_code, retval = self.utils.run_command(command_args)
46 | if ret_code == 0:
47 | ret = json.loads(retval)
48 | for key in ret.keys():
49 | # 类型转换
50 | val = ret[key]
51 | if val.isdigit():
52 | ret[key] = int(val)
53 | elif val.lower() == "true":
54 | ret[key] = True
55 | elif val.lower() == "false":
56 | ret[key] = False
57 |
58 | # return ret
59 | if len(args) == 1:
60 | return ret[args[0]]
61 |
62 | return ret
63 |
64 | raise RuntimeError(retval)
65 |
66 | def set(self, **kwargs) -> bool:
67 | """
68 | 设置一个或多个配置项
69 | :param kwargs:
70 | :return:
71 | """
72 | self.utils.set_operate("setting")
73 | command_args = []
74 | for key in kwargs.keys():
75 |
76 | if isinstance(kwargs[key], bool):
77 | kwargs[key] = str(kwargs[key]).lower()
78 |
79 | if kwargs[key] is None:
80 | kwargs[key] = "__null__"
81 |
82 | new_key = key
83 | if '___' in key:
84 | new_key = key.replace('___', '-')
85 |
86 | if '__' in key:
87 | new_key = key.replace('__', '.')
88 |
89 | command_args.extend(["-k", new_key, "-val", str(kwargs[key])])
90 |
91 | ret_code, retval = self.utils.run_command(command_args)
92 | if ret_code == 0:
93 | return True
94 |
95 | raise RuntimeError(retval)
96 |
97 | def set_by_json(self, file_path: str) -> bool:
98 | """
99 | 通过json文件设置配置项
100 | :param file_path: json文件路径
101 | :return:
102 | """
103 | if not os.path.exists(file_path):
104 | raise FileNotFoundError(file_path)
105 |
106 | self.utils.set_operate("setting")
107 | ret_code, retval = self.utils.run_command(["-p", file_path])
108 |
109 | if ret_code == 0:
110 | return True
111 |
112 | raise RuntimeError(retval)
113 |
114 | def equal(self, key: str, value) -> bool:
115 | """
116 | 判断配置项是否等于某个值
117 | :param key: 配置项
118 | :param value: 值
119 | :return:
120 | """
121 | return self.get(key) == value
122 |
123 | def not_equal(self, key: str, value) -> bool:
124 | """
125 | 判断配置项是否不等于某个值
126 | :param key: 配置项
127 | :param value: 值
128 | :return:
129 | """
130 | return self.get(key) != value
131 |
132 | def equal_then_set(self, key: str, value, new_value) -> bool:
133 | """
134 | 判断配置项是否等于某个值并设置新值
135 | :param key: 配置项
136 | :param value: 值
137 | :param new_value: 新值
138 | :return:
139 | """
140 | if self.equal(key, value):
141 | return self.set(**{key: new_value})
142 |
143 | return False
144 |
145 | def not_equal_then_set(self, key: str, value, new_value=None) -> bool:
146 | """
147 | 判断配置项是否不等于某个值并设置新值
148 | :param key: 配置项
149 | :param value: 值
150 | :param new_value: 新值
151 | :return:
152 | """
153 | if self.not_equal(key, value):
154 | if new_value is None:
155 | return self.set(**{key: value})
156 | return self.set(**{key: new_value})
157 |
158 | return False
159 |
--------------------------------------------------------------------------------
/mumu/config.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午2:24
4 | # @Author : wlkjyy
5 | # @File : config.py
6 | # @Software: PyCharm
7 |
8 | MUMU_PATH = None
9 | VM_INDEX = None
10 | OPERATE = None
11 | ADB_PATH = None
12 | FRAME_CACHE = {}
13 | FRAME_UPDATE_TIME = {}
14 |
--------------------------------------------------------------------------------
/mumu/constant.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/29 下午7:35
4 | # @Author : wlkjyy
5 | # @File : constant.py
6 | # @Software: PyCharm
7 | import os
8 | import random
9 |
10 |
11 | class MacAddress:
12 |
13 | @staticmethod
14 | def random() -> str:
15 | """
16 | 生成一个随机的MAC地址
17 | :return:
18 | """
19 | mac = [random.randint(0x00, 0xff) for _ in range(6)]
20 | return ':'.join(map(lambda x: "%02x" % x, mac))
21 |
22 |
23 | class IMEI:
24 |
25 | @staticmethod
26 | def random() -> str:
27 | """
28 | 生成一个随机的IMEI
29 |
30 | 来源:chatgpt-3.5
31 | :return:
32 | """
33 | # Generate TAC (first 8 digits)
34 | tac = f"{random.randint(10 ** 5, 10 ** 6 - 1):06d}"
35 |
36 | # Generate FAC (next 2 digits)
37 | fac = f"{random.randint(0, 99):02d}"
38 |
39 | # Generate SNR (next 6 digits)
40 | snr = f"{random.randint(10 ** 5, 10 ** 6 - 1):06d}"
41 |
42 | # Concatenate parts
43 | imei_base = tac + fac + snr
44 |
45 | # Calculate Check Digit (SP)
46 | def calculate_check_digit(imei):
47 | digits = list(map(int, imei))
48 | odd_digits = digits[0::2]
49 | even_digits = [sum(divmod(2 * d, 10)) + 2 * d // 10 for d in digits[1::2]]
50 | total = sum(odd_digits + even_digits)
51 | return (10 - total % 10) % 10
52 |
53 | check_digit = calculate_check_digit(imei_base)
54 |
55 | # Construct full IMEI
56 | imei = imei_base + str(check_digit)
57 |
58 | return imei
59 |
60 |
61 | class IMSI:
62 |
63 | @staticmethod
64 | def random() -> str:
65 | """
66 | 生成一个随机的IMSI
67 | :return:
68 | """
69 | mcc = random.choice(['302', '310', '334', '460']) # Example MCCs for demonstration
70 |
71 | # MNC (Mobile Network Code, 2 or 3 digits)
72 | mnc = f"{random.randint(0, 999):03d}"[:2] # Random 2-digit MNC
73 |
74 | # MSIN (Mobile Subscriber Identification Number, 10 digits)
75 | msin = f"{random.randint(0, 10 ** 10 - 1):010d}"
76 |
77 | # Concatenate parts
78 | imsi = mcc + mnc + msin
79 |
80 | return imsi
81 |
82 |
83 | class AndroidID:
84 |
85 | @staticmethod
86 | def random() -> str:
87 | """
88 | 生成一个随机的AndroidId
89 | :return:
90 | """
91 | return ''.join(random.choices('0123456789abcdef', k=16))
92 |
93 |
94 | class PhoneNumber:
95 |
96 | @staticmethod
97 | def random() -> str:
98 | """
99 | 生成一个随机的手机号
100 | :return:
101 | """
102 | prefix = random.choice(['130', '131', '132', '133', '134', '135', '136', '137', '138', '139',
103 | '147', '150', '151', '152', '153', '155', '156', '157', '158', '159',
104 | '186', '187', '188', '189', '199'])
105 |
106 | return prefix + ''.join(random.choices('0123456789', k=8))
107 |
108 |
109 | class GPU:
110 | # 顶配型号
111 | TOP_MODEL = 'Adreno (TM) 740'
112 |
113 | # 中配型号
114 | MIDDLE_MODEL = 'Adreno (TM) 640'
115 |
116 | # 低配型号
117 | LOW_MODEL = 'Adreno (TM) 530'
118 |
119 |
120 | class AndroidKey:
121 | # 菜单键
122 | KEYCODE_MENU = 1
123 |
124 | # 回到桌面
125 | KEYCODE_HOME = 3
126 |
127 | # 返回键
128 | KEYCODE_BACK = 4
129 |
130 | # 拨号键
131 | KEYCODE_CALL = 5
132 |
133 | # 挂断键
134 | KEYCODE_ENDCALL = 6
135 |
136 | # 搜索键
137 | KEYCODE_SEARCH = 84
138 |
139 | # 拍照键
140 | KEYCODE_CAMERA = 27
141 |
142 | # 相机对焦键
143 | KEYCODE_FOCUS = 80
144 |
145 | # 电源键
146 | KEYCODE_POWER = 26
147 |
148 | # 通知键
149 | KEYCODE_NOTIFICATION = 83
150 |
151 | # 禁音键
152 | KEYCODE_MUTE = 91
153 |
154 | # 扬声器静音键
155 | KEYCODE_VOLUME_MUTE = 164
156 |
157 | # 音量增加键
158 | KEYCODE_VOLUME_UP = 24
159 |
160 | # 音量减小键
161 | KEYCODE_VOLUME_DOWN = 25
162 |
163 | # 回车键
164 | KEYCODE_ENTER = 66
165 |
166 | # ESC键
167 | KEYCODE_ESCAPE = 111
168 |
169 | # 导航键
170 | KEYCODE_DPAD_CENTER = 23
171 |
172 | # 向上导航键
173 | KEYCODE_DPAD_UP = 19
174 |
175 | # 向下导航键
176 | KEYCODE_DPAD_DOWN = 20
177 |
178 | # 向左导航键
179 | KEYCODE_DPAD_LEFT = 21
180 |
181 | # 向右导航键
182 | KEYCODE_DPAD_RIGHT = 22
183 |
184 | # 退格
185 | KEYCODE_DEL = 67
186 |
187 | # TAB
188 | KEYCODE_TAB = 61
189 |
190 | # 放大键
191 | KEYCODE_ZOOM_IN = 168
192 |
193 |
--------------------------------------------------------------------------------
/mumu/control.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/28 下午10:22
4 | # @Author : wlkjyy
5 | # @File : control.py
6 | # @Software: PyCharm
7 | import os.path
8 |
9 | from mumu.utils import run_command
10 | from mumu.api.permission.root import Root
11 |
12 |
13 | class Control:
14 |
15 | __mumu_path = None
16 | __vm_index = None
17 | def __init__(self, mumu, vm_index):
18 | self.__mumu_path = mumu
19 | self.__vm_index = vm_index
20 |
21 | def start(self, package=None) -> bool:
22 | """
23 | 启动模拟器
24 | :param package: 启动完成后打开指定包名的应用
25 | :return:
26 | """
27 |
28 | if isinstance(self.__vm_index, int):
29 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
30 | else:
31 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
32 |
33 | args.append("launch")
34 | if package is not None:
35 | args.append("-pkg")
36 | args.append(package)
37 |
38 | ret_code, retval = run_command(args)
39 |
40 | if ret_code != 0:
41 | raise RuntimeError(retval)
42 |
43 | return True
44 |
45 | def shutdown(self):
46 | """
47 | 关闭模拟器
48 | :return:
49 | """
50 | if isinstance(self.__vm_index, int):
51 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
52 | else:
53 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
54 |
55 | args.append("shutdown")
56 |
57 | ret_code, retval = run_command(args)
58 |
59 | if ret_code != 0:
60 | raise RuntimeError(retval)
61 |
62 | return True
63 |
64 | def restart(self):
65 | """
66 | 重启模拟器
67 | :param package: 重启完成后打开指定包名的应用
68 | :return:
69 | """
70 | if isinstance(self.__vm_index, int):
71 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
72 | else:
73 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
74 |
75 | args.append("restart")
76 |
77 | ret_code, retval = run_command(args)
78 |
79 | if ret_code != 0:
80 | raise RuntimeError(retval)
81 |
82 | return True
83 |
84 | def show_window(self):
85 | """
86 | 显示窗口
87 | :return:
88 | """
89 | if isinstance(self.__vm_index, int):
90 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
91 | else:
92 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
93 |
94 | args.append("show_window")
95 |
96 | ret_code, retval = run_command(args)
97 |
98 | if ret_code != 0:
99 | raise RuntimeError(retval)
100 |
101 | return True
102 |
103 | def hide_window(self):
104 | """
105 | 隐藏窗口
106 | 相当于后台运行
107 | :return:
108 | """
109 | if isinstance(self.__vm_index, int):
110 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
111 | else:
112 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
113 |
114 | args.append("hide_window")
115 |
116 | ret_code, retval = run_command(args)
117 |
118 | if ret_code != 0:
119 | raise RuntimeError(retval)
120 |
121 | return True
122 |
123 | def install(self, apk: str) -> bool:
124 | """
125 | 安装应用
126 | :param apk: apk文件路径
127 | :return:
128 | """
129 | if not os.path.exists(apk):
130 | raise FileNotFoundError(apk)
131 | if isinstance(self.__vm_index, int):
132 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
133 | else:
134 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
135 |
136 | args.append("app")
137 | args.append("install")
138 | args.append("-apk")
139 | args.append(apk)
140 |
141 | ret_code, retval = run_command(args)
142 |
143 | if ret_code != 0:
144 | raise RuntimeError(retval)
145 |
146 | return True
147 |
148 | def uninstall(self, package: str) -> bool:
149 | """
150 | 卸载应用
151 | :param package: 包名
152 | :return:
153 | """
154 | if isinstance(self.__vm_index, int):
155 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
156 | else:
157 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
158 |
159 | args.append("app")
160 | args.append("uninstall")
161 | args.append("-pkg")
162 | args.append(package)
163 |
164 | ret_code, retval = run_command(args)
165 |
166 | if ret_code != 0:
167 | raise RuntimeError(retval)
168 |
169 | return True
170 |
171 | def launch(self, package: str) -> bool:
172 | """
173 | 打开应用
174 | :param package: 包名
175 | :return:
176 | """
177 | if isinstance(self.__vm_index, int):
178 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
179 | else:
180 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
181 |
182 | args.append("app")
183 | args.append("launch")
184 | args.append("-pkg")
185 | args.append(package)
186 |
187 | ret_code, retval = run_command(args)
188 |
189 | if ret_code != 0:
190 | raise RuntimeError(retval)
191 |
192 | return True
193 |
194 | def close(self, package: str) -> bool:
195 | """
196 | 关闭应用
197 | :param package: 包名
198 | :return:
199 | """
200 | if isinstance(self.__vm_index, int):
201 | args = [self.__mumu_path, "control", "-v", str(self.__vm_index)]
202 | else:
203 | args = [self.__mumu_path, "control", "-v", ",".join([str(i) for i in self.__vm_index])]
204 |
205 | args.append("app")
206 | args.append("close")
207 | args.append("-pkg")
208 | args.append(package)
209 |
210 | ret_code, retval = run_command(args)
211 |
212 | if ret_code != 0:
213 | raise RuntimeError(retval)
214 |
215 | return True
216 |
217 | def root(self):
218 | return Root(self.__mumu_path, self.__vm_index)
219 |
220 | def enable_root_permission(self):
221 | """
222 | 开启ROOT权限
223 | :return:
224 | """
225 | if isinstance(self.__vm_index, int):
226 | args = [self.__mumu_path, "setting", "-v", str(self.__vm_index)]
227 | else:
228 | args = [self.__mumu_path, "setting", "-v", ",".join([str(i) for i in self.__vm_index])]
229 |
230 | args.append("-k")
231 | args.append("root_permission")
232 | args.append("-val")
233 | args.append("true")
234 | ret_code, retval = run_command(args)
235 |
236 | if ret_code != 0:
237 | raise RuntimeError(retval)
238 |
239 | return True
240 |
241 | def disable_root_permission(self):
242 | """
243 | 关闭ROOT权限
244 | :return:
245 | """
246 | if isinstance(self.__vm_index, int):
247 | args = [self.__mumu_path, "setting", "-v", str(self.__vm_index)]
248 | else:
249 | args = [self.__mumu_path, "setting", "-v", ",".join([str(i) for i in self.__vm_index])]
250 |
251 | args.append("-k")
252 | args.append("root_permission")
253 | args.append("-val")
254 | args.append("false")
255 | ret_code, retval = run_command(args)
256 |
257 | if ret_code != 0:
258 | raise RuntimeError(retval)
259 |
260 | return True
261 |
--------------------------------------------------------------------------------
/mumu/mumu.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/28 下午9:36
4 | # @Author : wlkjyy
5 | # @File : mumu.py
6 | # @Software: PyCharm
7 | import os.path
8 | from typing import Union
9 |
10 | from mumu.api.adb.Adb import Adb
11 | from mumu.api.core.Core import Core
12 | from mumu.api.core.app import App
13 | from mumu.api.core.performance import Performance
14 | from mumu.api.core.power import Power
15 | from mumu.api.core.shortcut import Shortcut
16 | from mumu.api.core.simulation import Simulation
17 | from mumu.api.core.window import Window
18 | from mumu.api.develop.androidevent import AndroidEvent
19 | from mumu.api.driver.Driver import Driver
20 | import mumu.config as config
21 | from mumu.api.network.Network import Network
22 | from mumu.api.permission.Permission import Permission
23 | from mumu.api.screen.gui import Gui
24 | from mumu.api.screen.screen import Screen
25 | from mumu.api.setting.setting import Setting
26 | from mumu.utils import utils
27 |
28 |
29 | class Mumu:
30 | __mumu_manager = r"D:\Program Files\Netease\MuMu Player 12\shell\MuMuManager.exe"
31 |
32 | __vm_index = None
33 |
34 | def __init__(self, mumu_manager_path=None):
35 | if mumu_manager_path is not None:
36 | # 后续创建的实例未设置时使用之前设置的路径
37 | if config.MUMU_PATH is None:
38 | self.__mumu_manager = mumu_manager_path
39 | else:
40 | self.__mumu_manager = config.MUMU_PATH
41 |
42 | if not os.path.exists(self.__mumu_manager):
43 | raise RuntimeError(f"MuMuManager.exe not found in {self.__mumu_manager}")
44 |
45 | base_path = os.path.dirname(self.__mumu_manager)
46 |
47 | config.MUMU_PATH = self.__mumu_manager
48 | config.ADB_PATH = os.path.join(base_path, "adb.exe")
49 |
50 | def select(self, vm_index: Union[int, list, tuple] = None, *args):
51 | """
52 | 选择要操作的模拟器索引
53 | :param vm_index: 模拟器索引
54 | :param args: 更多的模拟器索引
55 | :return:
56 |
57 | Example:
58 | Mumu().select(1)
59 | Mumu().select(1, 2, 3)
60 | Mumu().select([1, 2, 3])
61 | Mumu().select((1, 2, 3))
62 | """
63 |
64 | if vm_index is None:
65 | self.__vm_index = 'all'
66 | return self
67 |
68 | if len(args) > 0:
69 | if isinstance(vm_index, int):
70 | vm_index = [vm_index]
71 | else:
72 | vm_index = list(vm_index)
73 |
74 | vm_index.extend(args)
75 |
76 | if isinstance(vm_index, int):
77 | self.__vm_index = str(vm_index)
78 | else:
79 | vm_index = list(set(vm_index))
80 | self.__vm_index = ",".join([str(i) for i in vm_index])
81 |
82 | return self
83 |
84 | def generate_utils(self) -> utils:
85 | return utils().set_vm_index(self.__vm_index).set_mumu_root_object(self)
86 |
87 | def all(self):
88 | """
89 | 选择所有模拟器
90 | :return:
91 | """
92 | self.__vm_index = 'all'
93 | return self
94 |
95 | @property
96 | def core(self) -> Core:
97 | """
98 | 模拟器类
99 | :return:
100 | """
101 | return Core(self.generate_utils())
102 |
103 | @property
104 | def driver(self) -> Driver:
105 | """
106 | 驱动类
107 |
108 | 已完成
109 | :return:
110 | """
111 |
112 | return Driver(self.generate_utils())
113 |
114 | @property
115 | def permission(self) -> Permission:
116 | """
117 | 权限类
118 |
119 | 已完成
120 | :return:
121 | """
122 | return Permission(self.generate_utils())
123 |
124 | @property
125 | def power(self):
126 | """
127 | 电源类
128 |
129 | 已完成
130 | :return:
131 | """
132 | return Power(self.generate_utils())
133 |
134 | @property
135 | def window(self) -> Window:
136 | """
137 | 窗口类
138 |
139 | 已完成
140 | :return:
141 | """
142 |
143 | return Window(self.generate_utils())
144 |
145 | @property
146 | def app(self) -> App:
147 | """
148 | app类
149 |
150 | 已完成
151 | :return:
152 | """
153 |
154 | return App(self.generate_utils())
155 |
156 | @property
157 | def androidEvent(self) -> AndroidEvent:
158 | """
159 | 安卓事件类
160 |
161 | 已完成
162 | :return:
163 | """
164 | return AndroidEvent(self.generate_utils())
165 |
166 | @property
167 | def shortcut(self) -> Shortcut:
168 | """
169 | 快捷方式类
170 |
171 | 已完成
172 | :return:
173 | """
174 | return Shortcut(self.generate_utils())
175 |
176 | @property
177 | def simulation(self) -> Simulation:
178 | """
179 | 机型类(这玩意很鸡肋,没什么用)
180 |
181 | 已完成
182 | :return:
183 | """
184 | return Simulation(self.generate_utils())
185 |
186 | @property
187 | def setting(self) -> Setting:
188 | """
189 | 配置类
190 | :return:
191 | """
192 |
193 | return Setting(self.generate_utils())
194 |
195 | @property
196 | def screen(self) -> Screen:
197 | """
198 | 屏幕类
199 | :return:
200 | """
201 | return Screen(self.generate_utils())
202 |
203 | @property
204 | def performance(self) -> Performance:
205 | """
206 | 性能类
207 | :return:
208 | """
209 | return Performance(self.generate_utils())
210 |
211 | @property
212 | def network(self):
213 | """
214 | 网路操作类
215 | :return:
216 | """
217 |
218 | return Network(self.generate_utils())
219 |
220 | @property
221 | def adb(self) -> Adb:
222 | """
223 | ADB类
224 | :return:
225 | """
226 | return Adb(self.generate_utils())
227 |
228 | @property
229 | def auto(self) -> Gui:
230 | """
231 | GUI自动化类
232 | :return:
233 | """
234 |
235 | try:
236 | import cv2
237 | except ImportError:
238 | raise ImportError("if you want to use autoGui class, you should install opencv-python")
239 |
240 | return Gui(self.generate_utils())
241 |
--------------------------------------------------------------------------------
/mumu/utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | # @Time : 2024/7/28 下午10:25
4 | # @Author : wlkjyy
5 | # @File : utils.py
6 | # @Software: PyCharm
7 |
8 | import subprocess
9 | from typing import Union, List
10 |
11 | import mumu.config as config
12 |
13 |
14 | class utils:
15 | __VM_INDEX = None
16 | __OPERATE = None
17 | __MUMU_ROOT_OBJECT = None
18 |
19 | def set_vm_index(self, vm_index):
20 | """
21 | 设置虚拟机索引
22 | :param vm_index:
23 | :return:
24 | """
25 | self.__VM_INDEX = vm_index
26 |
27 | return self
28 |
29 | def set_operate(self, operate: Union[str, list]):
30 | """
31 | 设置操作
32 | :param operate:
33 | :return:
34 | """
35 | self.__OPERATE = operate
36 |
37 | def set_mumu_root_object(self, mumu_root_object):
38 | """
39 | 设置mumu_root_object
40 | :param mumu_root_object:
41 | :return:
42 | """
43 | self.__MUMU_ROOT_OBJECT = mumu_root_object
44 | return self
45 |
46 | def get_mumu_root_object(self):
47 | return self.__MUMU_ROOT_OBJECT
48 |
49 | def get_vm_id(self):
50 | return self.__VM_INDEX
51 |
52 | def run_command(self, command, mumu=True):
53 | """
54 | 执行命令
55 | :param mumu:
56 | :param command:
57 | :return:
58 | """
59 | try:
60 | if mumu:
61 | command_extend = [config.MUMU_PATH]
62 |
63 | if self.__OPERATE is not None:
64 | if isinstance(self.__OPERATE, list):
65 | command_extend.extend(self.__OPERATE)
66 | else:
67 | command_extend.append(self.__OPERATE)
68 |
69 | if self.__VM_INDEX is not None:
70 | command_extend.extend(['-v', self.__VM_INDEX])
71 |
72 | command_extend.extend(command)
73 | else:
74 | command_extend = command
75 |
76 | result = subprocess.run(command_extend, shell=True, check=False, stdout=subprocess.PIPE,
77 | stderr=subprocess.PIPE,
78 | encoding='utf-8')
79 |
80 | ret_code = result.returncode
81 | retval = result.stdout
82 |
83 | # print(retval)
84 |
85 | return ret_code, retval
86 |
87 | except subprocess.CalledProcessError as e:
88 | ret_code = e.returncode
89 | retval = e.stderr
90 |
91 | return ret_code, retval
92 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | opencv-python
2 | numpy==1.24.4
3 | adbutils==0.4.7
4 | adbutils==1.2.15
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from mumu.mumu import Mumu
4 | import cv2
5 |
6 | Mumu().select(3).power.restart()
--------------------------------------------------------------------------------