ksaa 2025.6.23.0__py3-none-any.whl → 2025.6.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. kotonebot/backend/context/context.py +0 -2
  2. kotonebot/client/__init__.py +1 -2
  3. kotonebot/client/device.py +155 -4
  4. kotonebot/client/host/adb_common.py +94 -0
  5. kotonebot/client/host/custom.py +27 -21
  6. kotonebot/client/host/leidian_host.py +14 -23
  7. kotonebot/client/host/mumu12_host.py +70 -24
  8. kotonebot/client/host/protocol.py +4 -1
  9. kotonebot/client/host/windows_common.py +55 -0
  10. kotonebot/client/implements/__init__.py +2 -1
  11. kotonebot/client/implements/adb.py +1 -44
  12. kotonebot/client/implements/adb_raw.py +2 -9
  13. kotonebot/client/implements/nemu_ipc/__init__.py +8 -0
  14. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +280 -0
  15. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -0
  16. kotonebot/client/implements/remote_windows.py +8 -14
  17. kotonebot/client/implements/uiautomator2.py +5 -16
  18. kotonebot/client/implements/windows.py +5 -36
  19. kotonebot/client/registration.py +2 -67
  20. kotonebot/config/base_config.py +4 -2
  21. kotonebot/debug_entry.py +4 -2
  22. kotonebot/errors.py +7 -0
  23. kotonebot/kaa/main/dmm_host.py +13 -32
  24. kotonebot/kaa/main/gr.py +114 -27
  25. kotonebot/kaa/main/kaa.py +30 -3
  26. kotonebot/kaa/metadata.py +32 -0
  27. kotonebot/kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
  28. kotonebot/kaa/tasks/R.py +132 -132
  29. {ksaa-2025.6.23.0.dist-info → ksaa-2025.6.28.0.dist-info}/METADATA +4 -1
  30. {ksaa-2025.6.23.0.dist-info → ksaa-2025.6.28.0.dist-info}/RECORD +167 -162
  31. /kotonebot/kaa/sprites/{7f6cb60e-6ab3-4db4-86e1-8558250062a4.png → 019273c4-22ce-486f-8a7d-dbc78f13dcac.png} +0 -0
  32. /kotonebot/kaa/sprites/{bb0ae68d-44d5-47c6-91dd-a529bc34225b.png → 03e34ef7-36b5-4ddc-9545-5b70a2eba72e.png} +0 -0
  33. /kotonebot/kaa/sprites/{faf360e8-7845-4fcf-b0a6-0f5f93c050f1.png → 06ea773b-e2b3-40d4-82f6-b119ce9ca00b.png} +0 -0
  34. /kotonebot/kaa/sprites/{2f74b04f-1992-4584-8dc6-e789b081ddc3.png → 0769f8f4-1c47-4bad-a783-bba5e20250bf.png} +0 -0
  35. /kotonebot/kaa/sprites/{58a5f51d-d1a3-480b-8dde-ecead34d9f02.png → 08946997-4e9e-4242-95e1-e988fa96b919.png} +0 -0
  36. /kotonebot/kaa/sprites/{694d3a07-9697-4bda-984d-967779d530f7.png → 0d60ce10-20ce-4980-8172-b6bac08d6258.png} +0 -0
  37. /kotonebot/kaa/sprites/{1b6be6dd-2711-441a-9fa6-3281a6ad10c7.png → 0f40352f-6443-406e-8e2c-c40d5cfc6c59.png} +0 -0
  38. /kotonebot/kaa/sprites/{94433cf2-dd04-4b74-ae14-e5e3d945f579.png → 0f6d7f89-bdb2-4235-9b6b-e65cb73a4167.png} +0 -0
  39. /kotonebot/kaa/sprites/{9f99b0e9-8cc0-4877-8f8a-d26257ef5386.png → 14d09c44-3897-4c81-b615-19c35d0f3793.png} +0 -0
  40. /kotonebot/kaa/sprites/{b5d4265c-82e0-470d-a48e-8aa4edbdef5a.png → 15191dec-7e33-4721-8068-15c11978e885.png} +0 -0
  41. /kotonebot/kaa/sprites/{42d8b5dc-ef27-4411-b8b5-d981e562990b.png → 15f8f31e-65ce-408d-8556-b799bcefc9d4.png} +0 -0
  42. /kotonebot/kaa/sprites/{d1699baa-467b-46d1-9276-aae78fe8f589.png → 20b01af5-907b-4d49-9f5b-a3700ca9c0c3.png} +0 -0
  43. /kotonebot/kaa/sprites/{faf069e1-f623-47c1-b879-cfa246fbc7df.png → 242c088b-bc73-4d11-aa31-ee7c71477fb4.png} +0 -0
  44. /kotonebot/kaa/sprites/{c25c7ce5-ad8d-4b2c-83eb-ae6d4871a6c3.png → 2f2a7951-a30a-4835-b9f6-3205cfdd2f05.png} +0 -0
  45. /kotonebot/kaa/sprites/{aefdec38-e3b6-4762-aed8-33107686ddac.png → 3031397e-3d12-4f23-8377-006eca5d2627.png} +0 -0
  46. /kotonebot/kaa/sprites/{ed6d3e3d-0f04-4fa4-b0f9-fdbf324649f6.png → 312dbd82-5457-474a-91ce-511f0e4e9c80.png} +0 -0
  47. /kotonebot/kaa/sprites/{c571e1c0-ff85-4b2e-97ed-97d33b8d92e3.png → 31851ad1-df39-4a4f-9ae7-19c1d1c6b51a.png} +0 -0
  48. /kotonebot/kaa/sprites/{c484ca57-6b19-46b5-a107-1805a7488d48.png → 338b699a-d556-4b80-a577-123442c3db45.png} +0 -0
  49. /kotonebot/kaa/sprites/{920a3974-4875-4d2b-bbac-caa175c03ce0.png → 35319890-98e3-4f75-b969-6b8576ef668d.png} +0 -0
  50. /kotonebot/kaa/sprites/{f8eec9b2-55ca-4195-b098-a5393d02a98a.png → 3536f829-70d3-48f9-8fd4-b65eab7502f3.png} +0 -0
  51. /kotonebot/kaa/sprites/{97adee05-1586-4589-93e6-c6c3a9cdae9d.png → 35516b69-a4f4-4ad4-8af4-35043475ee90.png} +0 -0
  52. /kotonebot/kaa/sprites/{c5ea3dfd-6761-466e-b44c-86592ef05323.png → 36d71d33-09c9-4be8-b184-ccf40f700333.png} +0 -0
  53. /kotonebot/kaa/sprites/{dbd537c7-d1d3-4d64-9ccd-43dd4519bedd.png → 379d5084-637f-45e8-8bd8-789eaa919821.png} +0 -0
  54. /kotonebot/kaa/sprites/{29cb7b0f-5684-4516-8fae-4439f7f28e08.png → 3851303a-e3a0-46eb-aa7e-b47fb41946f7.png} +0 -0
  55. /kotonebot/kaa/sprites/{25910d8e-d57f-409f-b0be-ee68c5af4920.png → 3d1aa368-e198-478f-a47f-49818efbaec8.png} +0 -0
  56. /kotonebot/kaa/sprites/{cb24b3df-1ec3-4ffd-95f1-ca4b0dd5b7da.png → 3f5907a9-7fe4-4f46-8e46-a96ac7fa3923.png} +0 -0
  57. /kotonebot/kaa/sprites/{c5e16f75-9b5a-4853-a144-275f3d20c186.png → 40c0f296-d29e-4e05-aaff-a212339b742e.png} +0 -0
  58. /kotonebot/kaa/sprites/{5d3d61e9-bf13-4668-b0c7-fed730ea2ed6.png → 43e72796-a644-4d9b-a817-8add011d9a75.png} +0 -0
  59. /kotonebot/kaa/sprites/{9c7f1bcc-0bc8-454b-95fb-56a803030771.png → 469c2b41-4171-4902-acc5-da0c807886ab.png} +0 -0
  60. /kotonebot/kaa/sprites/{65d2a5e5-48aa-4390-bb87-bfd8e044e4f6.png → 497e7930-f423-457a-8168-b1a1532bb1e9.png} +0 -0
  61. /kotonebot/kaa/sprites/{0ad2f603-a3ad-4a92-a398-c411145fd8e9.png → 49a43af3-17e3-4434-ba4d-519775ccaea9.png} +0 -0
  62. /kotonebot/kaa/sprites/{bfec58df-2628-454f-92aa-769cad186448.png → 49b9a298-e847-4322-99a2-c0588c3542fe.png} +0 -0
  63. /kotonebot/kaa/sprites/{a3b15425-0bfd-4668-a3a9-2ab64da8505f.png → 4a1902aa-01db-46bb-bea5-88ac62c82562.png} +0 -0
  64. /kotonebot/kaa/sprites/{66172bc0-3869-4a48-a26d-0dc3fa404ab8.png → 4a96a728-6303-4ae3-8e0c-9f1e8e5f86a7.png} +0 -0
  65. /kotonebot/kaa/sprites/{8b9c115b-d4ce-492e-88cb-08bd01c43570.png → 4b7c4221-a679-47f6-929d-0ef514894b27.png} +0 -0
  66. /kotonebot/kaa/sprites/{0d4b669e-5e43-4458-b65a-5565797335ae.png → 4d355e36-9169-43e4-bd58-2836ee3e56e0.png} +0 -0
  67. /kotonebot/kaa/sprites/{969eb98c-f761-4007-b8bd-2b3fb8d1f08c.png → 4eb97d68-cd47-4100-9c3b-35b5c86b4bdf.png} +0 -0
  68. /kotonebot/kaa/sprites/{eae83523-e58c-4db9-a356-4335a5da3dc8.png → 515a2ff0-36b8-4f2f-9902-e6c1b9796586.png} +0 -0
  69. /kotonebot/kaa/sprites/{9aef1aa8-41e1-42a7-83ad-b4e212ee3976.png → 51a3ed29-92b9-4429-9e50-783d1b8dc6a7.png} +0 -0
  70. /kotonebot/kaa/sprites/{83f17b06-2792-4035-bc90-1800279ae131.png → 52d3438b-afae-4c7a-abe5-7ed530c7558c.png} +0 -0
  71. /kotonebot/kaa/sprites/{9d5dd50c-3341-41a4-a3dd-ccc00edcf201.png → 540ee3b5-ca8f-4091-80a6-ce774d95e5f7.png} +0 -0
  72. /kotonebot/kaa/sprites/{17a8b0a5-5b71-4612-8a68-16c51f03c71b.png → 54a15298-ecaf-4cc5-b8a2-5b0d8f8add41.png} +0 -0
  73. /kotonebot/kaa/sprites/{7cabdf91-2f15-4991-b165-eb9cdd557af6.png → 554c0ff6-0a05-419d-85f9-5ced260d0d2e.png} +0 -0
  74. /kotonebot/kaa/sprites/{bed9575d-ccad-4f4e-825e-47b2a9cb0925.png → 55e4ef75-ba8e-4230-819c-450cf14b08cd.png} +0 -0
  75. /kotonebot/kaa/sprites/{4e8f3e59-edfa-4523-939d-bc46988fc909.png → 5824ef5f-6776-402c-b78c-ae79cc4cc878.png} +0 -0
  76. /kotonebot/kaa/sprites/{1ccdcacf-4f7d-442c-8e2f-09aa7fe14e56.png → 5a393483-c631-47fd-805c-c460c23b8f9e.png} +0 -0
  77. /kotonebot/kaa/sprites/{ba759205-ce99-4417-8cab-d32fe55dff4c.png → 5b75cae4-a853-402c-acec-edbfef94342a.png} +0 -0
  78. /kotonebot/kaa/sprites/{a1853223-d6b9-4660-af4e-91e59bdddc3c.png → 5c9ae62c-488f-4109-a4d2-d2385c32043d.png} +0 -0
  79. /kotonebot/kaa/sprites/{f72bd3d6-850d-4190-9775-938f474d263c.png → 5cdd71be-c8cd-4310-b6f4-5e97ddbeb46e.png} +0 -0
  80. /kotonebot/kaa/sprites/{4eaa5db2-d624-4df4-b2e4-01f72755f1a4.png → 5d5cb13b-c1fd-4808-b352-b21291a9ff0b.png} +0 -0
  81. /kotonebot/kaa/sprites/{52f7c8b1-af88-43c5-a999-de4da3caa03a.png → 5d6c4462-3d7c-42eb-ab3e-195ebf845a09.png} +0 -0
  82. /kotonebot/kaa/sprites/{7aa2435c-da90-4d52-b831-88dbec57dde9.png → 5d94e628-82ff-4531-b70a-b13297491bea.png} +0 -0
  83. /kotonebot/kaa/sprites/{0fbcb3f6-9ca0-43c7-975b-e7c15c26c897.png → 5dc2dca3-48bd-4eee-af89-1a1d30593bd5.png} +0 -0
  84. /kotonebot/kaa/sprites/{f611bbd9-c062-4989-96a4-65ff6c59673c.png → 5f0c2a57-efd0-4094-9f71-6f6fe97316f6.png} +0 -0
  85. /kotonebot/kaa/sprites/{d5d854fc-1b87-43d4-b32d-66eccb66fa41.png → 610348f2-6262-4cd1-8fc3-e7470f85580c.png} +0 -0
  86. /kotonebot/kaa/sprites/{a8b3fdce-9e4a-495c-839e-0142f67fee69.png → 613e9b10-b2eb-44ea-b6d9-9310c0e1bdcf.png} +0 -0
  87. /kotonebot/kaa/sprites/{aa3b717f-67f8-445f-a6bf-b89f7200d95b.png → 618b21ad-ab8b-436a-8eb8-bd320a198c9f.png} +0 -0
  88. /kotonebot/kaa/sprites/{7516c9a1-87ec-4d2f-ac5e-40d1aec27104.png → 61a09df6-7443-436c-9ef0-1d5a1cae1509.png} +0 -0
  89. /kotonebot/kaa/sprites/{0235ae3d-1741-4a81-8053-f60bc395ee85.png → 64be274e-e8ad-4938-84c1-5d8aabcd10d8.png} +0 -0
  90. /kotonebot/kaa/sprites/{b01c25d2-21bf-4db9-b5b2-ec35817f1b04.png → 671b4a55-023a-434d-b29a-e844fe528e6b.png} +0 -0
  91. /kotonebot/kaa/sprites/{302fde7d-de85-44c7-ba57-fe4b5f6db5fa.png → 6e5e9cf9-80b0-4241-a222-ff50dbd89a0c.png} +0 -0
  92. /kotonebot/kaa/sprites/{080b3cf5-ce35-4382-9bfa-d6a12efb09b3.png → 6ef59649-bbb3-42b6-a6b0-eac4f35a3da4.png} +0 -0
  93. /kotonebot/kaa/sprites/{15465879-eb84-4ca6-ae0f-d3c5ac71e723.png → 75a2093d-8743-427e-8d24-ae9a69fa14c1.png} +0 -0
  94. /kotonebot/kaa/sprites/{e5eeb9af-8003-4981-bf56-2e32e76cec5d.png → 76bde19a-4b1b-4e52-86d9-f56be7529c71.png} +0 -0
  95. /kotonebot/kaa/sprites/{1bb00a74-2fdc-4973-84ae-dc1edee15f81.png → 77d06f4d-8214-4306-9dc5-035c720f7256.png} +0 -0
  96. /kotonebot/kaa/sprites/{efb28e1d-7b52-4e20-aec5-117c798b7fbe.png → 78b642d3-9a2c-4c17-8872-3cc78211f4a0.png} +0 -0
  97. /kotonebot/kaa/sprites/{cdbbb5ba-5117-488a-8172-48cfc92c14c6.png → 7a9d1c39-b306-4cb0-be2f-7a5963099949.png} +0 -0
  98. /kotonebot/kaa/sprites/{f6bfbf6a-6e53-4563-9345-3f3756d6ea9a.png → 7ddda828-c8b0-4331-864f-99b85b7c5557.png} +0 -0
  99. /kotonebot/kaa/sprites/{b1e7f24e-f246-406e-9073-58e2f3e4e819.png → 81399fd9-f254-4d0e-9775-ad451c237a4e.png} +0 -0
  100. /kotonebot/kaa/sprites/{5152e3fa-079c-4592-8df7-656101694df8.png → 81526e97-1b8d-433f-a5c9-907e4e8c85cd.png} +0 -0
  101. /kotonebot/kaa/sprites/{09fb5e41-6caa-4246-a415-8317b12d8d81.png → 818e2370-8fb9-4e6c-ab78-58e86d6525b5.png} +0 -0
  102. /kotonebot/kaa/sprites/{0a6c6b1e-7a80-428e-a483-1858bfb8ac59.png → 8431cc52-0041-4425-808c-dcd4d196506c.png} +0 -0
  103. /kotonebot/kaa/sprites/{6aa34982-e7ab-4001-808f-8c5cf370d087.png → 85a49ff3-6075-4797-8b7f-35851a843698.png} +0 -0
  104. /kotonebot/kaa/sprites/{f528ea0e-1e55-4e41-844f-bb1db9185ef8.png → 86062e91-d6c1-44cf-9f8c-26a8b9dd8686.png} +0 -0
  105. /kotonebot/kaa/sprites/{26580772-ff20-4dcf-8496-f939e0f2890d.png → 860c3d98-e263-482f-95fb-4fc9c472e8b8.png} +0 -0
  106. /kotonebot/kaa/sprites/{a1bba683-a740-4eaf-8c37-3b1042b963b0.png → 8669ce44-dec7-47c2-aafd-3f2fbfd1d083.png} +0 -0
  107. /kotonebot/kaa/sprites/{8e48af2b-c083-4480-a27c-c59c31a7ede1.png → 86b15801-c932-4034-bed8-fd76b0f65916.png} +0 -0
  108. /kotonebot/kaa/sprites/{3cc9685d-6347-4114-9d39-7683c2dc9df6.png → 893b015d-6894-4e74-880e-d22859a25662.png} +0 -0
  109. /kotonebot/kaa/sprites/{66678caa-7b25-4ff7-8c3a-017f67279349.png → 8c0fa5fa-cebb-4ae7-bd1c-b542c9cbf694.png} +0 -0
  110. /kotonebot/kaa/sprites/{e9c45a73-2011-4584-87b7-c4e566eba87f.png → 913a1d17-54fe-4c8f-ac50-ef7f39600b86.png} +0 -0
  111. /kotonebot/kaa/sprites/{d60a395b-d907-416a-b091-36bfee4948b3.png → 964c6b90-f526-4199-9523-d8124373a56b.png} +0 -0
  112. /kotonebot/kaa/sprites/{69b022a8-e42f-4985-bd16-b4a530ad8a20.png → 97c281a9-ebb0-4f54-b644-b7ed7dd65f81.png} +0 -0
  113. /kotonebot/kaa/sprites/{b65724b4-ec8f-48a9-9ab2-e7ce5b8f35b2.png → 998fc84a-af71-4fa5-8aa4-e0bfeb9c53ea.png} +0 -0
  114. /kotonebot/kaa/sprites/{b8aada91-eedd-4c53-99f3-f903d547235e.png → 9cbf7f6e-fe77-463b-9c60-495dc823510a.png} +0 -0
  115. /kotonebot/kaa/sprites/{038c69e6-7cb0-4c8d-99da-b537e48a86b7.png → 9ce7d48e-295d-47b2-96eb-f3ce697c1b37.png} +0 -0
  116. /kotonebot/kaa/sprites/{cf66ae12-48bc-4512-a988-0e5de337c76d.png → 9dcca89b-c92b-4708-8f19-facd40d09a65.png} +0 -0
  117. /kotonebot/kaa/sprites/{264e056f-3bcc-4b16-94d4-f767c78ab6f5.png → 9e369992-ea8c-4c15-88d3-1344319978b0.png} +0 -0
  118. /kotonebot/kaa/sprites/{1aaceb21-dc5c-4a58-96d3-d2c66b2fb357.png → 9f9003ba-468f-406f-a82f-1b7a23ef347c.png} +0 -0
  119. /kotonebot/kaa/sprites/{1639278d-67e0-4452-a2b5-ec4cf4011f42.png → a2bf4e11-fbc3-4172-bc0b-b16f8f59e68c.png} +0 -0
  120. /kotonebot/kaa/sprites/{4a310bdd-0a7d-4669-9d57-6ae69bc8f022.png → a546e254-0490-43df-a291-a9a5c2b4c34a.png} +0 -0
  121. /kotonebot/kaa/sprites/{abcc709c-1f4d-4832-a063-eafbad94ac31.png → aacd229c-d307-4a20-a388-84d8f4afb4a5.png} +0 -0
  122. /kotonebot/kaa/sprites/{84bb4986-0295-4a45-9486-86e31fcdf1f1.png → b003bef5-53db-4032-b1bd-d5075de365a8.png} +0 -0
  123. /kotonebot/kaa/sprites/{d4c47cc7-5b3e-4c3e-8e80-78cb94e7eed9.png → b641a122-4734-43e1-b22f-468464a0cff1.png} +0 -0
  124. /kotonebot/kaa/sprites/{83acbca8-3567-4262-a1e7-50f158348d45.png → bc7303f3-43e9-4ece-9206-5f9c779b813b.png} +0 -0
  125. /kotonebot/kaa/sprites/{534d1310-3503-46d3-8ecd-426d9bcf183e.png → bd7a6754-fffc-4315-987f-5ba32d95b697.png} +0 -0
  126. /kotonebot/kaa/sprites/{8f1e5644-1079-44b1-9d44-97bae3817bc5.png → c39f2006-442b-4ab7-99bb-3dfa0673f05d.png} +0 -0
  127. /kotonebot/kaa/sprites/{f025027f-daa0-4943-8395-5d7d44cc7f15.png → c70f4634-6821-4480-a615-b5f9cdc578c1.png} +0 -0
  128. /kotonebot/kaa/sprites/{a7da51c2-8444-4770-a16b-b6543e40197f.png → c774098e-d782-455c-80a9-5bad3713129d.png} +0 -0
  129. /kotonebot/kaa/sprites/{60be53a6-98e9-4fc6-9285-85506dc0e335.png → c7e3736a-0e0f-474a-9386-f58292a45b92.png} +0 -0
  130. /kotonebot/kaa/sprites/{d3e6e2e9-c5b5-463f-98f4-de72c9320d9d.png → c90b582c-8570-48b4-98c0-62d5b067ca2c.png} +0 -0
  131. /kotonebot/kaa/sprites/{5868ebfb-5e61-4ac6-b270-acb1e89b738f.png → c9d3699b-c8b1-4294-9b48-103df4ab6c65.png} +0 -0
  132. /kotonebot/kaa/sprites/{17414e9e-b046-49e0-9596-cad1f4673a9e.png → cc9483c3-c895-4dcc-b3f2-0934ba1ad42e.png} +0 -0
  133. /kotonebot/kaa/sprites/{dad4ff8c-07f4-4b07-abd3-5d02bbcc4562.png → cec49286-3d48-4fe0-8911-6a1b17d1ac20.png} +0 -0
  134. /kotonebot/kaa/sprites/{7b7dad18-a838-4267-b3c7-6ab43b8361f0.png → cef78712-d9ae-4d6e-8d7d-d047d48fbb98.png} +0 -0
  135. /kotonebot/kaa/sprites/{ad172750-d1a6-4a25-8ce8-9483f4b5affb.png → d06fd859-4678-4593-b096-c86b38810cb7.png} +0 -0
  136. /kotonebot/kaa/sprites/{448ea37d-a10d-4102-ac9f-a58403ee8914.png → d19c37ff-d5c1-4316-8424-e69ccb5c2523.png} +0 -0
  137. /kotonebot/kaa/sprites/{ca2adfb8-1e88-4a31-b3cc-1f70568ce4a3.png → d26b60f7-d15c-4563-bedc-dd6e7e4078b2.png} +0 -0
  138. /kotonebot/kaa/sprites/{a1cf80ee-64bf-4d54-a7db-ffadf3c97026.png → d26bf52c-cb1b-4ebf-9c03-ff3f2d245959.png} +0 -0
  139. /kotonebot/kaa/sprites/{2dd34393-ee6f-4b19-8a9c-76556d1bd7d7.png → d5fd6c73-4ad8-47b5-89be-c7b137a9950f.png} +0 -0
  140. /kotonebot/kaa/sprites/{4b20b364-5f5f-4b34-960e-b0cfd08b1662.png → d696062b-9b58-4af1-835e-2cf5a0a0fbeb.png} +0 -0
  141. /kotonebot/kaa/sprites/{e1e0d751-9006-4fa4-a933-4c0dc5e1f3d2.png → da7a329e-339e-4ee7-8775-bca505a1980d.png} +0 -0
  142. /kotonebot/kaa/sprites/{dde03640-6c3a-4b13-a783-f672816bfec4.png → dedb8499-5881-4add-914f-ff0049285dae.png} +0 -0
  143. /kotonebot/kaa/sprites/{83cf48ba-d1af-4674-a52e-6bb80a626db7.png → dedea21b-f21d-4621-a213-70ad03faec58.png} +0 -0
  144. /kotonebot/kaa/sprites/{e394ff91-09d3-41e1-bbf5-bc42a71a623b.png → dff74cce-ba2d-44aa-b924-183259080a32.png} +0 -0
  145. /kotonebot/kaa/sprites/{f12c2b0b-8273-4197-a8c6-2a8ef2788a9b.png → e0246b0d-f0cf-49d4-8423-7da46e75a575.png} +0 -0
  146. /kotonebot/kaa/sprites/{1f2981d8-5eb6-4332-aa9b-17f4db2d4163.png → e1b6b32b-c797-4f1d-aa2b-e7320083a9a6.png} +0 -0
  147. /kotonebot/kaa/sprites/{6114d53e-dd69-4cee-82a7-7e43b338ff8d.png → e23e3f19-03f6-4507-9128-115191272b90.png} +0 -0
  148. /kotonebot/kaa/sprites/{90def878-cd6e-46e5-9dc4-9c92dd904b80.png → e2eb3bae-6451-40b9-bbff-90f11dca1904.png} +0 -0
  149. /kotonebot/kaa/sprites/{1d5f7454-672d-4e87-a368-ec4bb32f9b1f.png → e46e1b6d-6702-46bf-a07e-aec8d183453f.png} +0 -0
  150. /kotonebot/kaa/sprites/{e5259dfb-3a49-4cfc-9f89-b42958ce6eec.png → e5297036-9934-44a4-a761-a6a49ae06e3e.png} +0 -0
  151. /kotonebot/kaa/sprites/{2d145ade-b926-4276-b872-baec4ef78308.png → e7da5988-0e83-44ad-888b-246365473f9e.png} +0 -0
  152. /kotonebot/kaa/sprites/{c3ec61aa-f62e-46fd-b359-48864ea7a150.png → eb8d208a-dbcc-43ca-a8ac-098e1005ebfa.png} +0 -0
  153. /kotonebot/kaa/sprites/{9cd7e1f1-13c4-471e-abee-223ffd9126cb.png → ebe4ce64-98a9-40f2-9fde-3d5cdae673c7.png} +0 -0
  154. /kotonebot/kaa/sprites/{5ee8e9a8-b133-4ab7-a52c-e7e4e1d1ce7b.png → ef2ad3df-3f89-4a48-8fc3-c70a8adc1044.png} +0 -0
  155. /kotonebot/kaa/sprites/{68838f92-5916-424e-a37e-47e0b3834eb3.png → f05e410d-15b8-4831-bcc1-4cd8af07c882.png} +0 -0
  156. /kotonebot/kaa/sprites/{2e00ac54-d23a-4d51-82f2-829c012552a6.png → f1fac13a-84f3-42bd-9b52-e0ba90bc5255.png} +0 -0
  157. /kotonebot/kaa/sprites/{5dd0cc39-4102-4f0f-8ad3-342bbf825977.png → f7c34ab6-c165-48b7-ad5a-0b60613ce1bc.png} +0 -0
  158. /kotonebot/kaa/sprites/{dfb14c98-f672-4906-80bd-d2a18bdd62cd.png → fa80b14e-953f-44bd-a4ad-99aefd276fd7.png} +0 -0
  159. /kotonebot/kaa/sprites/{a4075991-98da-4d06-bcde-28054cd124d2.png → fe35655e-701b-4081-8df8-c17a7252acef.png} +0 -0
  160. /kotonebot/kaa/sprites/{df97421e-4b74-430e-b3d1-ed9bac6918a7.png → fe853ac7-934e-4b4a-bcc7-b986e057dfcf.png} +0 -0
  161. /kotonebot/kaa/sprites/{1aae0ab3-f619-47e0-8bf6-3d3791f7d445.png → ffda716c-2903-4ec7-8c59-aab5b8ad10bb.png} +0 -0
  162. /kotonebot/kaa/sprites/{6156116e-56dd-4b41-a6de-ccec55dab7c8.png → fff33200-c947-49f2-ab86-3ac98937fa36.png} +0 -0
  163. /kotonebot/kaa/{clear_logs.py → tasks/clear_logs.py} +0 -0
  164. {ksaa-2025.6.23.0.dist-info → ksaa-2025.6.28.0.dist-info}/WHEEL +0 -0
  165. {ksaa-2025.6.23.0.dist-info → ksaa-2025.6.28.0.dist-info}/entry_points.txt +0 -0
  166. {ksaa-2025.6.23.0.dist-info → ksaa-2025.6.28.0.dist-info}/licenses/LICENSE +0 -0
  167. {ksaa-2025.6.23.0.dist-info → ksaa-2025.6.28.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,327 @@
1
+ import os
2
+ import ctypes
3
+ import logging
4
+ import time
5
+ from dataclasses import dataclass
6
+ from time import sleep
7
+ from typing import Literal
8
+ from typing_extensions import override
9
+
10
+ import cv2
11
+ import numpy as np
12
+ from cv2.typing import MatLike
13
+
14
+ from ...device import AndroidDevice, Device
15
+ from ...protocol import Touchable, Screenshotable
16
+ from ...registration import ImplConfig
17
+ from .external_renderer_ipc import ExternalRendererIpc
18
+ from kotonebot.errors import KotonebotError
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class NemuIpcIncompatible(Exception):
24
+ """MuMu12 版本过低或 dll 不兼容"""
25
+ pass
26
+
27
+
28
+ class NemuIpcError(KotonebotError):
29
+ """调用 IPC 过程中发生错误"""
30
+ pass
31
+
32
+
33
+ @dataclass
34
+ class NemuIpcImplConfig(ImplConfig):
35
+ """nemu_ipc 能力的配置模型。"""
36
+ nemu_folder: str
37
+ r"""MuMu12 根目录(如 F:\Apps\Netease\MuMuPlayer-12.0)。"""
38
+ instance_id: int
39
+ """模拟器实例 ID。"""
40
+ display_id: int | None = 0
41
+ """目标显示器 ID,默认为 0(主显示器)。若为 None 且设置了 target_package_name,则自动获取对应的 display_id。"""
42
+ target_package_name: str | None = None
43
+ """目标应用包名,用于自动获取 display_id。"""
44
+ app_index: int = 0
45
+ """多开应用索引,传给 get_display_id 方法。"""
46
+ wait_package_timeout: float = 60 # 单位秒,-1 表示永远等待,0 表示不等待,立即抛出异常
47
+ wait_package_interval: float = 0.1 # 单位秒
48
+
49
+
50
+ class NemuIpcImpl(Touchable, Screenshotable):
51
+ """
52
+ 利用 MuMu12 提供的 external_renderer_ipc.dll 进行截图与触摸控制。
53
+ """
54
+
55
+ def __init__(self, config: NemuIpcImplConfig):
56
+ self.config = config
57
+ self.__width: int = 0
58
+ self.__height: int = 0
59
+ self.__connected: bool = False
60
+ self._connect_id: int = 0
61
+ self.nemu_folder = config.nemu_folder
62
+
63
+ # --------------------------- DLL 封装 ---------------------------
64
+ self._ipc = ExternalRendererIpc(config.nemu_folder)
65
+ logger.info("ExternalRendererIpc initialized and DLL loaded")
66
+
67
+ @property
68
+ def width(self) -> int:
69
+ """
70
+ 屏幕宽度。
71
+
72
+ 若为 0,表示未连接或未获取到分辨率。
73
+ """
74
+ return self.__width
75
+
76
+ @property
77
+ def height(self) -> int:
78
+ """
79
+ 屏幕高度。
80
+
81
+ 若为 0,表示未连接或未获取到分辨率。
82
+ """
83
+ return self.__height
84
+
85
+ @property
86
+ def connected(self) -> bool:
87
+ """是否已连接。"""
88
+ return self.__connected
89
+
90
+ # ------------------------------------------------------------------
91
+ # 基础控制
92
+ # ------------------------------------------------------------------
93
+
94
+ def _ensure_connected(self) -> None:
95
+ if not self.__connected:
96
+ self.connect()
97
+
98
+ def _get_display_id(self) -> int:
99
+ """获取有效的 display_id。"""
100
+ # 如果配置中直接指定了 display_id,直接返回
101
+ if self.config.display_id is not None:
102
+ return self.config.display_id
103
+
104
+ # 如果设置了 target_package_name,实时获取 display_id
105
+ if self.config.target_package_name:
106
+ self._ensure_connected()
107
+
108
+ timeout = self.config.wait_package_timeout
109
+ interval = self.config.wait_package_interval
110
+ if timeout == -1:
111
+ timeout = float('inf')
112
+ start_time = time.time()
113
+ while True:
114
+ display_id = self._ipc.get_display_id(
115
+ self._connect_id,
116
+ self.config.target_package_name,
117
+ self.config.app_index
118
+ )
119
+ if display_id >= 0:
120
+ return display_id
121
+ elif display_id == -1:
122
+ # 可以继续等
123
+ pass
124
+ else:
125
+ # 未知错误
126
+ raise NemuIpcError(f"Failed to get display_id for package '{self.config.target_package_name}', error code={display_id}")
127
+ if time.time() - start_time > timeout:
128
+ break
129
+ sleep(interval)
130
+
131
+ raise NemuIpcError(f"Failed to get display_id for package '{self.config.target_package_name}' within {timeout}s")
132
+
133
+ # 如果都没有设置,抛出错误
134
+ raise NemuIpcError("display_id is None and target_package_name is not set. Please set display_id or target_package_name in config.")
135
+
136
+ def connect(self) -> None:
137
+ """连接模拟器。"""
138
+ if self.__connected:
139
+ return
140
+
141
+ connect_id = self._ipc.connect(self.nemu_folder, self.config.instance_id)
142
+ if connect_id == 0:
143
+ raise NemuIpcError("nemu_connect failed, please check if the emulator is running and the instance ID is correct.")
144
+
145
+ self._connect_id = connect_id
146
+ self.__connected = True
147
+ logger.debug("NemuIpc connected, connect_id=%d", connect_id)
148
+
149
+ def disconnect(self) -> None:
150
+ """断开连接。"""
151
+ if not self.__connected:
152
+ return
153
+ self._ipc.disconnect(self._connect_id)
154
+ self.__connected = False
155
+ self._connect_id = 0
156
+ logger.debug("NemuIpc disconnected.")
157
+
158
+ # ------------------------------------------------------------------
159
+ # Screenshotable 接口实现
160
+ # ------------------------------------------------------------------
161
+ @property
162
+ def screen_size(self) -> tuple[int, int]:
163
+ """获取屏幕分辨率。"""
164
+ if self.__width == 0 or self.__height == 0:
165
+ self._refresh_resolution()
166
+ if self.__width == 0 or self.__height == 0:
167
+ raise NemuIpcError("Screen resolution not obtained, please connect to the emulator first.")
168
+ return self.__width, self.__height
169
+
170
+ @override
171
+ def detect_orientation(self):
172
+ return self.get_display_orientation(self._get_display_id())
173
+
174
+ def get_display_orientation(self, display_id: int = 0) -> Literal['portrait', 'landscape'] | None:
175
+ """获取指定显示屏的方向。"""
176
+ width, height = self.query_resolution(display_id)
177
+ if width > height:
178
+ return "landscape"
179
+ if height > width:
180
+ return "portrait"
181
+ return None
182
+
183
+ @override
184
+ def screenshot(self) -> MatLike:
185
+ self._ensure_connected()
186
+
187
+ # 必须每次都更新分辨率,因为屏幕可能会旋转
188
+ self._refresh_resolution()
189
+
190
+ length = self.__width * self.__height * 4 # RGBA
191
+ buf_type = ctypes.c_ubyte * length
192
+ buffer = buf_type()
193
+
194
+ w_ptr = ctypes.pointer(ctypes.c_int(self.__width))
195
+ h_ptr = ctypes.pointer(ctypes.c_int(self.__height))
196
+
197
+ ret = self._ipc.capture_display(
198
+ self._connect_id,
199
+ self._get_display_id(),
200
+ length,
201
+ ctypes.cast(w_ptr, ctypes.c_void_p),
202
+ ctypes.cast(h_ptr, ctypes.c_void_p),
203
+ ctypes.cast(buffer, ctypes.c_void_p),
204
+ )
205
+ if ret != 0:
206
+ raise NemuIpcError(f"nemu_capture_display screenshot failed, error code={ret}")
207
+
208
+ # 读入并转换数据
209
+ img = np.ctypeslib.as_array(buffer).reshape((self.__height, self.__width, 4))
210
+ # RGBA -> BGR
211
+ img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
212
+ cv2.flip(img, 0, dst=img)
213
+ return img
214
+
215
+ # --------------------------- 内部工具 -----------------------------
216
+
217
+ def _refresh_resolution(self) -> None:
218
+ """刷新分辨率信息。"""
219
+ display_id = self._get_display_id()
220
+ self.__width, self.__height = self.query_resolution(display_id)
221
+
222
+ def query_resolution(self, display_id: int = 0) -> tuple[int, int]:
223
+ """
224
+ 查询指定显示屏的分辨率。
225
+
226
+ :param display_id: 显示屏 ID。
227
+ :return: 分辨率 (width, height)。
228
+ :raise NemuIpcError: 查询失败。
229
+ """
230
+ self._ensure_connected()
231
+
232
+ w_ptr = ctypes.pointer(ctypes.c_int(0))
233
+ h_ptr = ctypes.pointer(ctypes.c_int(0))
234
+ ret = self._ipc.capture_display(
235
+ self._connect_id,
236
+ display_id,
237
+ 0,
238
+ ctypes.cast(w_ptr, ctypes.c_void_p),
239
+ ctypes.cast(h_ptr, ctypes.c_void_p),
240
+ ctypes.c_void_p(),
241
+ )
242
+ if ret != 0:
243
+ raise NemuIpcError(f"Call nemu_capture_display failed. Return value={ret}")
244
+
245
+ return w_ptr.contents.value, h_ptr.contents.value
246
+
247
+ # ------------------------------------------------------------------
248
+ # Touchable 接口实现
249
+ # ------------------------------------------------------------------
250
+ def __convert_pos(self, x: int, y: int) -> tuple[int, int]:
251
+ # Android 显示屏有两套坐标:逻辑坐标与物理坐标。
252
+ # 逻辑坐标原点始终是画面左上角,而物理坐标原点则始终是显示屏的左上角。
253
+ # 如果屏幕画面旋转,会导致两个坐标的原点不同,坐标也不同。
254
+ # ========
255
+ # 这里传给 MuMu 的是逻辑坐标,ExternalRendererIpc DLL 内部会
256
+ # 自动判断旋转,并转换为物理坐标。但是这部分有个 bug:
257
+ # 旋转没有考虑到多显示器,只是以主显示器为准,若两个显示器旋转不一致,
258
+ # 会导致错误地转换坐标。因此需要在 Python 层面 workaround 这个问题。
259
+ # 通过判断主显示器与当前显示器的旋转,将坐标进行预转换,抵消 DLL 层的错误转换。
260
+ display_id = self._get_display_id()
261
+ if display_id == 0:
262
+ return x, y
263
+ else:
264
+ primary = self.get_display_orientation(0)
265
+ primary_size = self.query_resolution(0)
266
+ current = self.get_display_orientation(display_id)
267
+ if primary == current:
268
+ return x, y
269
+ else:
270
+ # 如果旋转不一致,视为顺时针旋转了 90°
271
+ # 因此我们要提前逆时针旋转 90°
272
+ self._refresh_resolution()
273
+ x, y = y, primary_size[1] - x
274
+ return x, y
275
+
276
+ @override
277
+ def click(self, x: int, y: int) -> None:
278
+ self._ensure_connected()
279
+ display_id = self._get_display_id()
280
+ x, y = self.__convert_pos(x, y)
281
+ self._ipc.input_touch_down(self._connect_id, display_id, x, y)
282
+ sleep(0.01)
283
+ self._ipc.input_touch_up(self._connect_id, display_id)
284
+
285
+ @override
286
+ def swipe(
287
+ self,
288
+ x1: int,
289
+ y1: int,
290
+ x2: int,
291
+ y2: int,
292
+ duration: float | None = None,
293
+ ) -> None:
294
+ self._ensure_connected()
295
+
296
+ duration = duration or 0.3
297
+ steps = max(int(duration / 0.01), 2)
298
+ display_id = self._get_display_id()
299
+ x1, y1 = self.__convert_pos(x1, y1)
300
+ x2, y2 = self.__convert_pos(x2, y2)
301
+
302
+ xs = np.linspace(x1, x2, steps, dtype=int)
303
+ ys = np.linspace(y1, y2, steps, dtype=int)
304
+
305
+ # 按下第一点
306
+ self._ipc.input_touch_down(self._connect_id, display_id, xs[0], ys[0])
307
+ sleep(0.01)
308
+ # 中间移动
309
+ for px, py in zip(xs[1:-1], ys[1:-1]):
310
+ self._ipc.input_touch_down(self._connect_id, display_id, px, py)
311
+ sleep(0.01)
312
+
313
+ # 最终抬起
314
+ self._ipc.input_touch_up(self._connect_id, display_id)
315
+ sleep(0.01)
316
+
317
+ if __name__ == '__main__':
318
+ nemu = NemuIpcImpl(NemuIpcImplConfig(
319
+ r'F:\Apps\Netease\MuMuPlayer-12.0', 0, None,
320
+ target_package_name='com.android.chrome',
321
+ ))
322
+ nemu.connect()
323
+ # while True:
324
+ # nemu.click(0, 0)
325
+ nemu.click(100, 100)
326
+ nemu.click(100*3, 100)
327
+ nemu.click(100*3, 100*3)
@@ -23,8 +23,8 @@ from cv2.typing import MatLike
23
23
  from kotonebot import logging
24
24
  from ..device import Device, WindowsDevice
25
25
  from ..protocol import Touchable, Screenshotable
26
- from ..registration import register_impl, ImplConfig
27
- from .windows import WindowsImpl, WindowsImplConfig, create_windows_device
26
+ from ..registration import ImplConfig
27
+ from .windows import WindowsImpl, WindowsImplConfig
28
28
 
29
29
  logger = logging.getLogger(__name__)
30
30
 
@@ -63,7 +63,11 @@ class RemoteWindowsServer:
63
63
  self.port = port
64
64
  self.server = None
65
65
  self.device = WindowsDevice()
66
- self.impl = create_windows_device(windows_impl_config)
66
+ self.impl = WindowsImpl(
67
+ WindowsDevice(),
68
+ ahk_exe_path=windows_impl_config.ahk_exe_path,
69
+ window_title=windows_impl_config.window_title
70
+ )
67
71
  self.device._screenshot = self.impl
68
72
  self.device._touch = self.impl
69
73
 
@@ -186,14 +190,4 @@ class RemoteWindowsImpl(Touchable, Screenshotable):
186
190
  def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
187
191
  """Swipe from (x1, y1) to (x2, y2) on the remote server."""
188
192
  if not self.proxy.swipe(x1, y1, x2, y2, duration):
189
- raise RuntimeError(f"Failed to swipe from ({x1}, {y1}) to ({x2}, {y2})")
190
-
191
-
192
- # 编写并注册创建函数
193
- @register_impl('remote_windows', config_model=RemoteWindowsImplConfig)
194
- def create_remote_windows_device(config: RemoteWindowsImplConfig) -> Device:
195
- device = WindowsDevice()
196
- remote_impl = RemoteWindowsImpl(device, config.host, config.port)
197
- device._touch = remote_impl
198
- device._screenshot = remote_impl
199
- return device
193
+ raise RuntimeError(f"Failed to swipe from ({x1}, {y1}) to ({x2}, {y2})")
@@ -4,21 +4,19 @@ from typing import Literal
4
4
  import numpy as np
5
5
  import uiautomator2 as u2
6
6
  from cv2.typing import MatLike
7
+ from adbutils._device import AdbDevice as AdbUtilsDevice
7
8
 
8
9
  from kotonebot import logging
9
10
  from ..device import Device
10
11
  from ..protocol import Screenshotable, Commandable, Touchable
11
- from ..registration import register_impl
12
- from .adb import AdbImplConfig, _create_adb_device_base
13
12
 
14
13
  logger = logging.getLogger(__name__)
15
14
 
16
15
  SCREENSHOT_INTERVAL = 0.2
17
16
 
18
17
  class UiAutomator2Impl(Screenshotable, Commandable, Touchable):
19
- def __init__(self, device: Device):
20
- self.device = device
21
- self.u2_client = u2.Device(device.adb.serial)
18
+ def __init__(self, adb_connection: AdbUtilsDevice):
19
+ self.u2_client = u2.Device(adb_connection.serial)
22
20
  self.__last_screenshot_time = 0
23
21
 
24
22
  def screenshot(self) -> MatLike:
@@ -40,10 +38,7 @@ class UiAutomator2Impl(Screenshotable, Commandable, Touchable):
40
38
  def screen_size(self) -> tuple[int, int]:
41
39
  info = self.u2_client.info
42
40
  sizes = info['displayWidth'], info['displayHeight']
43
- if self.device.orientation == 'landscape':
44
- return (max(sizes), min(sizes))
45
- else:
46
- return (min(sizes), max(sizes))
41
+ return sizes
47
42
 
48
43
  def detect_orientation(self) -> Literal['portrait', 'landscape'] | None:
49
44
  """
@@ -84,10 +79,4 @@ class UiAutomator2Impl(Screenshotable, Commandable, Touchable):
84
79
  """
85
80
  滑动屏幕
86
81
  """
87
- self.u2_client.swipe(x1, y1, x2, y2, duration=duration or 0.1)
88
-
89
-
90
- @register_impl('uiautomator2', config_model=AdbImplConfig)
91
- def create_uiautomator2_device(config: AdbImplConfig) -> Device:
92
- """UiAutomator2Impl 工厂函数"""
93
- return _create_adb_device_base(config, UiAutomator2Impl)
82
+ self.u2_client.swipe(x1, y1, x2, y2, duration=duration or 0.1)
@@ -13,7 +13,7 @@ from cv2.typing import MatLike
13
13
 
14
14
  from ..device import Device, WindowsDevice
15
15
  from ..protocol import Commandable, Touchable, Screenshotable
16
- from ..registration import register_impl, ImplConfig
16
+ from ..registration import ImplConfig
17
17
 
18
18
  # 1. 定义配置模型
19
19
  @dataclass
@@ -51,15 +51,6 @@ class WindowsImpl(Touchable, Screenshotable):
51
51
  # 将点击坐标设置为相对 Client
52
52
  self.ahk.set_coord_mode('Mouse', 'Client')
53
53
 
54
- @cached_property
55
- def scale_ratio(self) -> float:
56
- """
57
- 缩放比例。截图与模拟输入前都会根据这个比例缩放。
58
- """
59
- left, _, right, _ = self.__client_rect()
60
- w = right - left
61
- return 720 / w
62
-
63
54
  @property
64
55
  def hwnd(self) -> int:
65
56
  if self.__hwnd is None:
@@ -124,18 +115,14 @@ class WindowsImpl(Touchable, Screenshotable):
124
115
 
125
116
  # 将 RGBA 转换为 RGB
126
117
  cropped_im = cv2.cvtColor(cropped_im, cv2.COLOR_RGBA2RGB)
127
- # 缩放
128
- cropped_im = cv2.resize(cropped_im, None, fx=self.scale_ratio, fy=self.scale_ratio)
129
118
  return cropped_im
130
119
 
131
120
  @property
132
121
  def screen_size(self) -> tuple[int, int]:
133
- # 因为截图和点击的坐标都被缩放了,
134
- # 因此这里只要返回固定值即可
135
- if self.device.orientation == 'landscape':
136
- return 1280, 720
137
- else:
138
- return 720, 1280
122
+ left, top, right, bot = self.__client_rect()
123
+ w = right - left
124
+ h = bot - top
125
+ return w, h
139
126
 
140
127
  def detect_orientation(self) -> None | Literal['portrait'] | Literal['landscape']:
141
128
  pos = self.ahk.win_get_position(self.window_title)
@@ -154,7 +141,6 @@ class WindowsImpl(Touchable, Screenshotable):
154
141
  x = 2
155
142
  if y == 0:
156
143
  y = 2
157
- x, y = int(x / self.scale_ratio), int(y / self.scale_ratio)
158
144
  if not self.ahk.win_is_active(self.window_title):
159
145
  self.ahk.win_activate(self.window_title)
160
146
  self.ahk.click(x, y)
@@ -162,26 +148,9 @@ class WindowsImpl(Touchable, Screenshotable):
162
148
  def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
163
149
  if not self.ahk.win_is_active(self.window_title):
164
150
  self.ahk.win_activate(self.window_title)
165
- x1, y1 = int(x1 / self.scale_ratio), int(y1 / self.scale_ratio)
166
- x2, y2 = int(x2 / self.scale_ratio), int(y2 / self.scale_ratio)
167
151
  # TODO: 这个 speed 的单位是什么?
168
152
  self.ahk.mouse_drag(x2, y2, from_position=(x1, y1), coord_mode='Client', speed=10)
169
153
 
170
-
171
- # 3. 编写并注册创建函数
172
- @register_impl('windows', config_model=WindowsImplConfig)
173
- def create_windows_device(config: WindowsImplConfig) -> Device:
174
- device = WindowsDevice()
175
- impl = WindowsImpl(
176
- device,
177
- window_title=config.window_title,
178
- ahk_exe_path=config.ahk_exe_path
179
- )
180
- device._touch = impl
181
- device._screenshot = impl
182
- return device
183
-
184
-
185
154
  if __name__ == '__main__':
186
155
  from ..device import Device
187
156
  device = Device()
@@ -7,9 +7,10 @@ if TYPE_CHECKING:
7
7
  from .implements.adb import AdbImplConfig
8
8
  from .implements.remote_windows import RemoteWindowsImplConfig
9
9
  from .implements.windows import WindowsImplConfig
10
+ from .implements.nemu_ipc import NemuIpcImplConfig
10
11
 
11
12
  AdbBasedImpl = Literal['adb', 'adb_raw', 'uiautomator2']
12
- DeviceImpl = str | AdbBasedImpl | Literal['windows', 'remote_windows']
13
+ DeviceImpl = str | AdbBasedImpl | Literal['windows', 'remote_windows', 'nemu_ipc']
13
14
 
14
15
  # --- 核心类型定义 ---
15
16
 
@@ -21,69 +22,3 @@ class ImplRegistrationError(KotonebotError):
21
22
  class ImplConfig:
22
23
  """所有设备实现配置模型的名义上的基类,便于类型约束。"""
23
24
  pass
24
-
25
- T_Config = TypeVar("T_Config", bound=ImplConfig)
26
-
27
- # 定义两种创建者函数类型
28
- CreatorWithConfig = Callable[[Any], Device]
29
- CreatorWithoutConfig = Callable[[], Device]
30
-
31
- # --- 底层 API: 公开的注册表 ---
32
-
33
- # 注册表结构: {'impl_name': (创建函数, 配置模型类 或 None)}
34
- DEVICE_CREATORS: Dict[str, tuple[Callable[..., Device], Type[ImplConfig] | None]] = {}
35
-
36
-
37
- def register_impl(name: str, config_model: Type[ImplConfig] | None = None) -> Callable[..., Any]:
38
- """
39
- 一个统一的装饰器,用于向 DEVICE_CREATORS 注册表中注册一个设备实现。
40
-
41
- :param name: 实现的名称 (e.g., 'windows', 'adb')
42
- :param config_model: (可选) 与该实现关联的 dataclass 配置模型
43
- """
44
- def decorator(creator_func: Callable[..., Device]) -> Callable[..., Device]:
45
- if name in DEVICE_CREATORS:
46
- raise ImplRegistrationError(f"实现 '{name}' 已被注册。")
47
- DEVICE_CREATORS[name] = (creator_func, config_model)
48
- return creator_func
49
- return decorator
50
-
51
-
52
- # --- 高层 API: 带 overload 的便利函数 ---
53
-
54
- # 为需要配置的已知 impl 提供 overload
55
- @overload
56
- def create_device(impl_name: Literal['windows'], config: 'WindowsImplConfig') -> Device: ...
57
-
58
- @overload
59
- def create_device(impl_name: Literal['remote_windows'], config: 'RemoteWindowsImplConfig') -> Device: ...
60
-
61
- @overload
62
- def create_device(impl_name: AdbBasedImpl, config: 'AdbImplConfig') -> Device: ...
63
-
64
- # 函数的实际实现
65
- def create_device(impl_name: DeviceImpl, config: ImplConfig | None = None) -> Device:
66
- """
67
- 根据名称和可选的配置对象,统一创建设备。
68
- """
69
- creator_tuple = DEVICE_CREATORS.get(impl_name)
70
- if not creator_tuple:
71
- raise ImplRegistrationError(f"未找到名为 '{impl_name}' 的实现。")
72
-
73
- creator_func, registered_config_model = creator_tuple
74
-
75
- # 情况 A: 实现需要配置
76
- if registered_config_model is not None:
77
- creator_with_config = cast(CreatorWithConfig, creator_func)
78
- if config is None:
79
- raise ValueError(f"实现 '{impl_name}' 需要一个配置对象,但传入的是 None。")
80
- if not isinstance(config, registered_config_model):
81
- raise TypeError(f"为 '{impl_name}' 传入的配置类型错误,应为 '{registered_config_model.__name__}',实际为 '{type(config).__name__}'。")
82
- return creator_with_config(config)
83
-
84
- # 情况 B: 实现无需配置
85
- else:
86
- creator_without_config = cast(CreatorWithoutConfig, creator_func)
87
- if config is not None:
88
- print(f"提示:实现 '{impl_name}' 无需配置,但你提供了一个配置对象,它将被忽略。")
89
- return creator_without_config()
@@ -3,10 +3,10 @@ from typing import Generic, TypeVar, Literal
3
3
 
4
4
  from pydantic import BaseModel, ConfigDict
5
5
 
6
- from kotonebot.client import DeviceImpl
7
6
 
8
7
  T = TypeVar('T')
9
8
  BackendType = Literal['custom', 'mumu12', 'leidian', 'dmm']
9
+ DeviceRecipes = Literal['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
10
10
 
11
11
  class ConfigBaseModel(BaseModel):
12
12
  model_config = ConfigDict(use_attribute_docstrings=True)
@@ -27,7 +27,7 @@ class BackendConfig(ConfigBaseModel):
27
27
  雷电模拟器需要设置正确的模拟器名,否则 自动启动模拟器 功能将无法正常工作。
28
28
  其他功能不受影响。
29
29
  """
30
- screenshot_impl: DeviceImpl = 'adb'
30
+ screenshot_impl: DeviceRecipes = 'adb'
31
31
  """
32
32
  截图方法。暂时推荐使用【adb】截图方式。
33
33
 
@@ -48,6 +48,8 @@ class BackendConfig(ConfigBaseModel):
48
48
  """Windows 截图方式的窗口标题"""
49
49
  windows_ahk_path: str | None = None
50
50
  """Windows 截图方式的 AutoHotkey 可执行文件路径,为 None 时使用默认路径"""
51
+ mumu_background_mode: bool = False
52
+ """MuMu12 模拟器后台保活模式"""
51
53
 
52
54
  class PushConfig(ConfigBaseModel):
53
55
  """推送配置。"""
kotonebot/debug_entry.py CHANGED
@@ -1,3 +1,5 @@
1
+ import sys
2
+ sys.path.append('./projects')
1
3
  import runpy
2
4
  import logging
3
5
  import argparse
@@ -12,14 +14,14 @@ def run_script(script_path: str) -> None:
12
14
  Args:
13
15
  script_path: Python 脚本的路径
14
16
  """
17
+ logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
15
18
  # 获取模块名
16
- module_name = script_path.strip('.py').replace('\\', '/').strip('/').replace('/', '.')
19
+ module_name = script_path.strip('.py').lstrip('projects/').replace('\\', '/').strip('/').replace('/', '.')
17
20
 
18
21
  print(f"正在运行脚本: {script_path}")
19
22
  # 运行脚本
20
23
  from kotonebot.backend.context import init_context, manual_context
21
24
  from kotonebot.kaa.main.kaa import Kaa
22
- logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
23
25
  logging.getLogger('kotonebot').setLevel(logging.DEBUG)
24
26
  config_path = './config.json'
25
27
  kaa_instance = Kaa(config_path)
kotonebot/errors.py CHANGED
@@ -24,3 +24,10 @@ class TaskNotFoundError(KotonebotError):
24
24
  def __init__(self, task_id: str):
25
25
  self.task_id = task_id
26
26
  super().__init__(f'Task "{task_id}" not found.')
27
+
28
+ class UnscalableResolutionError(KotonebotError):
29
+ def __init__(self, target_resolution: tuple[int, int], screen_size: tuple[int, int]):
30
+ self.target_resolution = target_resolution
31
+ self.screen_size = screen_size
32
+ super().__init__(f'Cannot scale to target resolution {target_resolution}. '
33
+ f'Screen size: {screen_size}')