ksaa 2025.6.8.0__py3-none-any.whl → 2025.6.23.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 (196) hide show
  1. kotonebot/backend/context/context.py +31 -25
  2. kotonebot/backend/debug/server.py +0 -2
  3. kotonebot/backend/dispatch.py +1 -17
  4. kotonebot/backend/loop.py +277 -0
  5. kotonebot/client/__init__.py +4 -1
  6. kotonebot/client/device.py +23 -21
  7. kotonebot/client/host/__init__.py +2 -1
  8. kotonebot/client/host/custom.py +26 -3
  9. kotonebot/client/host/leidian_host.py +22 -11
  10. kotonebot/client/host/mumu12_host.py +25 -3
  11. kotonebot/client/host/protocol.py +40 -18
  12. kotonebot/client/implements/__init__.py +6 -0
  13. kotonebot/client/implements/adb.py +67 -7
  14. kotonebot/client/implements/adb_raw.py +12 -5
  15. kotonebot/client/implements/remote_windows.py +20 -18
  16. kotonebot/client/implements/uiautomator2.py +8 -0
  17. kotonebot/client/implements/windows.py +38 -15
  18. kotonebot/client/protocol.py +13 -0
  19. kotonebot/client/registration.py +89 -0
  20. kotonebot/config/base_config.py +5 -1
  21. kotonebot/debug_entry.py +7 -2
  22. kotonebot/kaa/clear_logs.py +0 -3
  23. kotonebot/kaa/game_ui/common.py +4 -7
  24. kotonebot/kaa/game_ui/dialog.py +24 -4
  25. kotonebot/kaa/game_ui/idols_overview.py +28 -12
  26. kotonebot/kaa/game_ui/primary_button.py +55 -0
  27. kotonebot/kaa/game_ui/scrollable.py +0 -5
  28. kotonebot/kaa/main/cli.py +9 -4
  29. kotonebot/kaa/main/dmm_host.py +33 -7
  30. kotonebot/kaa/main/kaa.py +113 -41
  31. kotonebot/kaa/metadata.py +15 -0
  32. kotonebot/kaa/resources/__pycache__/__init__.cpython-310.pyc +0 -0
  33. kotonebot/kaa/resources/game.db +0 -0
  34. kotonebot/kaa/resources/game_ver.txt +0 -0
  35. kotonebot/kaa/resources/idol_cards/i_card-skin-kllj-3-012_0.png +0 -0
  36. kotonebot/kaa/resources/idol_cards/i_card-skin-kllj-3-012_1.png +0 -0
  37. kotonebot/kaa/resources/idol_cards/i_card-skin-ssmk-3-012_0.png +0 -0
  38. kotonebot/kaa/resources/idol_cards/i_card-skin-ssmk-3-012_1.png +0 -0
  39. kotonebot/kaa/sprites/24e99232-9434-457f-a9a0-69dd7ecf675f.png +0 -0
  40. kotonebot/kaa/sprites/2ededcf5-1d80-4e2a-9c83-2a31998331ce.png +0 -0
  41. kotonebot/kaa/sprites/3b473fe6-e147-477f-b088-9b8fb042a4f6.png +0 -0
  42. kotonebot/kaa/sprites/55f7db71-0a18-4b3d-b847-57959b8d2e32.png +0 -0
  43. kotonebot/kaa/sprites/6cd80be8-c9b3-4ba5-bf17-3ffc9b000743.png +0 -0
  44. kotonebot/kaa/sprites/74ec3510-583d-4a76-ac69-38480fbf1387.png +0 -0
  45. kotonebot/kaa/sprites/a0bd6a5f-784d-4f0a-9d66-10f4b80c8d3e.png +0 -0
  46. kotonebot/kaa/sprites/d3424d31-0502-4623-996e-f0194e5085ce.png +0 -0
  47. kotonebot/kaa/sprites/e6b45405-cd9f-4c6e-a9f1-6ec953747c65.png +0 -0
  48. kotonebot/kaa/sprites/f5c16d2f-ebc5-4617-9b96-971696af7c52.png +0 -0
  49. kotonebot/kaa/tasks/R.py +157 -135
  50. kotonebot/kaa/tasks/produce/in_purodyuusu.py +0 -4
  51. kotonebot/kaa/tasks/produce/non_lesson_actions.py +0 -3
  52. kotonebot/kaa/tasks/produce/produce.py +119 -70
  53. kotonebot/kaa/tasks/start_game.py +7 -6
  54. kotonebot/kaa/util/paths.py +6 -1
  55. kotonebot/tools/mirror.py +23 -13
  56. kotonebot/util.py +32 -0
  57. {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/METADATA +1 -1
  58. {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/RECORD +194 -179
  59. kotonebot/client/factory.py +0 -92
  60. kotonebot/kaa/sprites/38b8f119-8de1-49bf-8a5f-a44371de4569.png +0 -0
  61. /kotonebot/kaa/sprites/{321b54c8-c69b-4437-931d-655882dabeed.png → 0235ae3d-1741-4a81-8053-f60bc395ee85.png} +0 -0
  62. /kotonebot/kaa/sprites/{4d6c198a-0e34-4c80-86d1-7789b2082d64.png → 038c69e6-7cb0-4c8d-99da-b537e48a86b7.png} +0 -0
  63. /kotonebot/kaa/sprites/{4bd66b38-78d3-4a27-816b-d315039a8bb6.png → 080b3cf5-ce35-4382-9bfa-d6a12efb09b3.png} +0 -0
  64. /kotonebot/kaa/sprites/{ecd44a4a-e1ca-4a86-ae0d-2306a672613f.png → 09fb5e41-6caa-4246-a415-8317b12d8d81.png} +0 -0
  65. /kotonebot/kaa/sprites/{4f58031c-36b9-4baf-bccf-11c05d83bd19.png → 0a6c6b1e-7a80-428e-a483-1858bfb8ac59.png} +0 -0
  66. /kotonebot/kaa/sprites/{e41bb5df-164d-4760-9db6-d5d5d763bb5f.png → 0ad2f603-a3ad-4a92-a398-c411145fd8e9.png} +0 -0
  67. /kotonebot/kaa/sprites/{c1b1125f-542b-4e63-9c86-5d2e35a89a10.png → 0d4b669e-5e43-4458-b65a-5565797335ae.png} +0 -0
  68. /kotonebot/kaa/sprites/{74521b6f-de5e-4ba8-a3c9-c6ba01070498.png → 0fbcb3f6-9ca0-43c7-975b-e7c15c26c897.png} +0 -0
  69. /kotonebot/kaa/sprites/{7b3c6860-fbbe-48aa-aadf-8062fa88f1fe.png → 15465879-eb84-4ca6-ae0f-d3c5ac71e723.png} +0 -0
  70. /kotonebot/kaa/sprites/{309105d4-073a-47c9-8c5c-d593aa497723.png → 1639278d-67e0-4452-a2b5-ec4cf4011f42.png} +0 -0
  71. /kotonebot/kaa/sprites/{157dd58d-e319-4e0c-a37a-638e1a31c60b.png → 17414e9e-b046-49e0-9596-cad1f4673a9e.png} +0 -0
  72. /kotonebot/kaa/sprites/{05ff5efd-0f76-47ce-bc89-237266bc17c0.png → 17a8b0a5-5b71-4612-8a68-16c51f03c71b.png} +0 -0
  73. /kotonebot/kaa/sprites/{54bb5fcf-be61-4af8-bb19-a3c5113cf9e1.png → 1aaceb21-dc5c-4a58-96d3-d2c66b2fb357.png} +0 -0
  74. /kotonebot/kaa/sprites/{1f8f8c46-43ec-4449-be9f-2e96da11da1e.png → 1aae0ab3-f619-47e0-8bf6-3d3791f7d445.png} +0 -0
  75. /kotonebot/kaa/sprites/{b1e88fda-434a-4d06-8891-883a7beafa12.png → 1b6be6dd-2711-441a-9fa6-3281a6ad10c7.png} +0 -0
  76. /kotonebot/kaa/sprites/{ef39804e-8d2d-40f6-8d13-bd2877d1353a.png → 1bb00a74-2fdc-4973-84ae-dc1edee15f81.png} +0 -0
  77. /kotonebot/kaa/sprites/{804a4b05-1ca3-4915-bf23-a66ccdc34404.png → 1ccdcacf-4f7d-442c-8e2f-09aa7fe14e56.png} +0 -0
  78. /kotonebot/kaa/sprites/{6a7d061e-682b-42c1-97d5-121c52921e8b.png → 1d5f7454-672d-4e87-a368-ec4bb32f9b1f.png} +0 -0
  79. /kotonebot/kaa/sprites/{624555b9-31e3-46a3-8078-55619eaa40b9.png → 1f2981d8-5eb6-4332-aa9b-17f4db2d4163.png} +0 -0
  80. /kotonebot/kaa/sprites/{ff77d221-8444-4d0f-8c93-0223a97e2a98.png → 25910d8e-d57f-409f-b0be-ee68c5af4920.png} +0 -0
  81. /kotonebot/kaa/sprites/{fbd816df-7ae6-40a4-b4d4-5708f5ba4a7c.png → 264e056f-3bcc-4b16-94d4-f767c78ab6f5.png} +0 -0
  82. /kotonebot/kaa/sprites/{0bbfb5b0-f0f7-4ad9-86f7-a2adb01a6371.png → 26580772-ff20-4dcf-8496-f939e0f2890d.png} +0 -0
  83. /kotonebot/kaa/sprites/{0a430873-8c12-4ce2-a5de-ab9790c08344.png → 29cb7b0f-5684-4516-8fae-4439f7f28e08.png} +0 -0
  84. /kotonebot/kaa/sprites/{52620946-b92c-42c7-9fcc-0ff13388fd0d.png → 2d145ade-b926-4276-b872-baec4ef78308.png} +0 -0
  85. /kotonebot/kaa/sprites/{7e8164f6-a9e4-4600-b776-77b35a490933.png → 2dd34393-ee6f-4b19-8a9c-76556d1bd7d7.png} +0 -0
  86. /kotonebot/kaa/sprites/{550fc45d-22c0-4a7c-a324-2c4ff107ab39.png → 2e00ac54-d23a-4d51-82f2-829c012552a6.png} +0 -0
  87. /kotonebot/kaa/sprites/{d58d9987-b1ca-4669-a9a6-9a0fd1847fa6.png → 2f74b04f-1992-4584-8dc6-e789b081ddc3.png} +0 -0
  88. /kotonebot/kaa/sprites/{668002b6-805b-467b-a703-203fb75444f4.png → 302fde7d-de85-44c7-ba57-fe4b5f6db5fa.png} +0 -0
  89. /kotonebot/kaa/sprites/{80fb515c-4323-422d-a24c-80a3bf702f1d.png → 3cc9685d-6347-4114-9d39-7683c2dc9df6.png} +0 -0
  90. /kotonebot/kaa/sprites/{869b1771-b2ce-4872-ab75-2157c47c5b13.png → 42d8b5dc-ef27-4411-b8b5-d981e562990b.png} +0 -0
  91. /kotonebot/kaa/sprites/{ba955365-1328-4018-83a2-bfe818f8f172.png → 448ea37d-a10d-4102-ac9f-a58403ee8914.png} +0 -0
  92. /kotonebot/kaa/sprites/{111a03e6-e401-4ad5-b5dc-d5124e8f1ab0.png → 4a310bdd-0a7d-4669-9d57-6ae69bc8f022.png} +0 -0
  93. /kotonebot/kaa/sprites/{05bfd235-2077-479a-8c43-11fb49316000.png → 4b20b364-5f5f-4b34-960e-b0cfd08b1662.png} +0 -0
  94. /kotonebot/kaa/sprites/{cd4cbd7b-df80-45bb-a053-cc333a806934.png → 4e8f3e59-edfa-4523-939d-bc46988fc909.png} +0 -0
  95. /kotonebot/kaa/sprites/{67a81c23-4ce4-45e3-bfa8-1136fb14b831.png → 4eaa5db2-d624-4df4-b2e4-01f72755f1a4.png} +0 -0
  96. /kotonebot/kaa/sprites/{705d9ecf-faf8-40f4-b701-637cec822c1e.png → 5152e3fa-079c-4592-8df7-656101694df8.png} +0 -0
  97. /kotonebot/kaa/sprites/{4e21f02d-d5e1-49eb-946a-8edc74d0f1f7.png → 52f7c8b1-af88-43c5-a999-de4da3caa03a.png} +0 -0
  98. /kotonebot/kaa/sprites/{f74fc514-61f1-4068-bdf4-754fcaf8ac89.png → 534d1310-3503-46d3-8ecd-426d9bcf183e.png} +0 -0
  99. /kotonebot/kaa/sprites/{9bb1a400-935a-4c5e-838b-fca502c49aba.png → 5868ebfb-5e61-4ac6-b270-acb1e89b738f.png} +0 -0
  100. /kotonebot/kaa/sprites/{82a5e224-a161-43d4-8e15-2946480336dd.png → 58a5f51d-d1a3-480b-8dde-ecead34d9f02.png} +0 -0
  101. /kotonebot/kaa/sprites/{c1ad996a-925e-41b5-b26c-d48b8ded21d2.png → 5d3d61e9-bf13-4668-b0c7-fed730ea2ed6.png} +0 -0
  102. /kotonebot/kaa/sprites/{ad784a29-f0e2-4037-976b-45143db8441c.png → 5dd0cc39-4102-4f0f-8ad3-342bbf825977.png} +0 -0
  103. /kotonebot/kaa/sprites/{4a235eaa-46f1-4f71-b54d-ada0ade44572.png → 5ee8e9a8-b133-4ab7-a52c-e7e4e1d1ce7b.png} +0 -0
  104. /kotonebot/kaa/sprites/{09433024-3dfe-4fcb-9d67-cf442b58f31b.png → 60be53a6-98e9-4fc6-9285-85506dc0e335.png} +0 -0
  105. /kotonebot/kaa/sprites/{90a9c819-ee6b-4b3e-a0e4-ce484c6e413d.png → 6114d53e-dd69-4cee-82a7-7e43b338ff8d.png} +0 -0
  106. /kotonebot/kaa/sprites/{4c72f39e-1182-4479-867f-8207744ee4fd.png → 6156116e-56dd-4b41-a6de-ccec55dab7c8.png} +0 -0
  107. /kotonebot/kaa/sprites/{559df9b9-e74d-45af-a300-77d3424e69c6.png → 65d2a5e5-48aa-4390-bb87-bfd8e044e4f6.png} +0 -0
  108. /kotonebot/kaa/sprites/{c0fc7de5-b842-4c70-9cf2-89d833bfe7a6.png → 66172bc0-3869-4a48-a26d-0dc3fa404ab8.png} +0 -0
  109. /kotonebot/kaa/sprites/{fdfecda8-2fe0-487e-b5fe-6358274bb9ff.png → 66678caa-7b25-4ff7-8c3a-017f67279349.png} +0 -0
  110. /kotonebot/kaa/sprites/{f8cbb089-a63e-4e40-9238-26d14da2c459.png → 68838f92-5916-424e-a37e-47e0b3834eb3.png} +0 -0
  111. /kotonebot/kaa/sprites/{fb927b92-7a82-4869-8c61-581d49cd7201.png → 694d3a07-9697-4bda-984d-967779d530f7.png} +0 -0
  112. /kotonebot/kaa/sprites/{db94ffb7-a09d-4a51-b420-b930851c9db4.png → 69b022a8-e42f-4985-bd16-b4a530ad8a20.png} +0 -0
  113. /kotonebot/kaa/sprites/{13535ecd-bdf5-4ff7-aa2c-6ecb20fe9c06.png → 6aa34982-e7ab-4001-808f-8c5cf370d087.png} +0 -0
  114. /kotonebot/kaa/sprites/{572077d4-7ee2-449a-b4f0-66239b0d7aff.png → 7516c9a1-87ec-4d2f-ac5e-40d1aec27104.png} +0 -0
  115. /kotonebot/kaa/sprites/{d7f604a6-c663-44db-b1d3-c9792eb3daa4.png → 7aa2435c-da90-4d52-b831-88dbec57dde9.png} +0 -0
  116. /kotonebot/kaa/sprites/{56a8c0ca-4659-4de2-9bce-55ce86a40d75.png → 7b7dad18-a838-4267-b3c7-6ab43b8361f0.png} +0 -0
  117. /kotonebot/kaa/sprites/{1f9165e6-77ee-403e-9b76-99c114527dbb.png → 7cabdf91-2f15-4991-b165-eb9cdd557af6.png} +0 -0
  118. /kotonebot/kaa/sprites/{5ca50448-cc21-499a-a685-c5e1c4240c48.png → 7f6cb60e-6ab3-4db4-86e1-8558250062a4.png} +0 -0
  119. /kotonebot/kaa/sprites/{a82e06fb-a02e-4be5-a7b4-4d2758914fcf.png → 83acbca8-3567-4262-a1e7-50f158348d45.png} +0 -0
  120. /kotonebot/kaa/sprites/{b3d1f8d4-1393-4206-b78e-a5495d8fb930.png → 83cf48ba-d1af-4674-a52e-6bb80a626db7.png} +0 -0
  121. /kotonebot/kaa/sprites/{22d5a7d5-59a0-4137-b02e-1a7af86d4250.png → 83f17b06-2792-4035-bc90-1800279ae131.png} +0 -0
  122. /kotonebot/kaa/sprites/{1bfce48a-b151-406e-a6f5-863cb68cbbf2.png → 84bb4986-0295-4a45-9486-86e31fcdf1f1.png} +0 -0
  123. /kotonebot/kaa/sprites/{73f4204e-8281-47cc-9dce-6bb8280c4ce5.png → 8b9c115b-d4ce-492e-88cb-08bd01c43570.png} +0 -0
  124. /kotonebot/kaa/sprites/{f0786a26-81b5-4e76-b577-db70ab3fab50.png → 8e48af2b-c083-4480-a27c-c59c31a7ede1.png} +0 -0
  125. /kotonebot/kaa/sprites/{c7aa3f89-8a5e-4685-9297-4fc892bfdd81.png → 8f1e5644-1079-44b1-9d44-97bae3817bc5.png} +0 -0
  126. /kotonebot/kaa/sprites/{cc829886-23e8-4d47-b4c4-37ca20381180.png → 90def878-cd6e-46e5-9dc4-9c92dd904b80.png} +0 -0
  127. /kotonebot/kaa/sprites/{d8136469-6d73-4f82-a79c-fee83c3e93a8.png → 920a3974-4875-4d2b-bbac-caa175c03ce0.png} +0 -0
  128. /kotonebot/kaa/sprites/{c4f28132-4aba-40ba-be6c-b1a31d014635.png → 94433cf2-dd04-4b74-ae14-e5e3d945f579.png} +0 -0
  129. /kotonebot/kaa/sprites/{ad8ee599-88a8-429c-8982-99086e552dcc.png → 969eb98c-f761-4007-b8bd-2b3fb8d1f08c.png} +0 -0
  130. /kotonebot/kaa/sprites/{31a52dc8-b7fb-4ff6-a261-eb90a918603c.png → 97adee05-1586-4589-93e6-c6c3a9cdae9d.png} +0 -0
  131. /kotonebot/kaa/sprites/{02e0aa0d-4094-4bdc-8625-1a063733615b.png → 9aef1aa8-41e1-42a7-83ad-b4e212ee3976.png} +0 -0
  132. /kotonebot/kaa/sprites/{e2fae2a4-bb19-4f49-b382-47e504ec26d3.png → 9c7f1bcc-0bc8-454b-95fb-56a803030771.png} +0 -0
  133. /kotonebot/kaa/sprites/{8e8a0b57-0231-4ed6-958e-3196f7c9ca7b.png → 9cd7e1f1-13c4-471e-abee-223ffd9126cb.png} +0 -0
  134. /kotonebot/kaa/sprites/{5bf00cba-9aab-4ffe-b20b-8f5e74279276.png → 9d5dd50c-3341-41a4-a3dd-ccc00edcf201.png} +0 -0
  135. /kotonebot/kaa/sprites/{e866d8b1-4f61-45bf-8170-5229f53c047a.png → 9f99b0e9-8cc0-4877-8f8a-d26257ef5386.png} +0 -0
  136. /kotonebot/kaa/sprites/{1a3d18a4-7d91-4182-9e40-e60d73fed365.png → a1853223-d6b9-4660-af4e-91e59bdddc3c.png} +0 -0
  137. /kotonebot/kaa/sprites/{ed5d2bf3-405c-4165-88d0-9f8a4be968c6.png → a1bba683-a740-4eaf-8c37-3b1042b963b0.png} +0 -0
  138. /kotonebot/kaa/sprites/{910d307b-6461-4950-badb-509975d0b797.png → a1cf80ee-64bf-4d54-a7db-ffadf3c97026.png} +0 -0
  139. /kotonebot/kaa/sprites/{645744c4-3bbf-4ea2-8f46-4725fff874b3.png → a3b15425-0bfd-4668-a3a9-2ab64da8505f.png} +0 -0
  140. /kotonebot/kaa/sprites/{7cea265a-fca5-43d0-b2da-067c8fff4d72.png → a4075991-98da-4d06-bcde-28054cd124d2.png} +0 -0
  141. /kotonebot/kaa/sprites/{89c0e675-3fd4-456a-8ae7-3bfb248b7281.png → a7da51c2-8444-4770-a16b-b6543e40197f.png} +0 -0
  142. /kotonebot/kaa/sprites/{5be4f6c7-2baa-42f3-a5a5-99ca0830cc30.png → a8b3fdce-9e4a-495c-839e-0142f67fee69.png} +0 -0
  143. /kotonebot/kaa/sprites/{72c1804f-db8c-40fa-bc79-6533f2861a5d.png → aa3b717f-67f8-445f-a6bf-b89f7200d95b.png} +0 -0
  144. /kotonebot/kaa/sprites/{7736a171-9221-4cda-8288-908997abeff2.png → abcc709c-1f4d-4832-a063-eafbad94ac31.png} +0 -0
  145. /kotonebot/kaa/sprites/{12e8d3f1-494a-46f5-908f-f621a0c57ec6.png → ad172750-d1a6-4a25-8ce8-9483f4b5affb.png} +0 -0
  146. /kotonebot/kaa/sprites/{a33ae1c3-ac69-4783-977e-9f0f1b1a2f8d.png → aefdec38-e3b6-4762-aed8-33107686ddac.png} +0 -0
  147. /kotonebot/kaa/sprites/{22ee3f1b-ddcb-48bf-85dd-8423f85e3199.png → b01c25d2-21bf-4db9-b5b2-ec35817f1b04.png} +0 -0
  148. /kotonebot/kaa/sprites/{5edef075-4843-4dc9-840f-ae445cbe0730.png → b1e7f24e-f246-406e-9073-58e2f3e4e819.png} +0 -0
  149. /kotonebot/kaa/sprites/{a105d17d-d44c-4798-aeff-70808c91cb47.png → b5d4265c-82e0-470d-a48e-8aa4edbdef5a.png} +0 -0
  150. /kotonebot/kaa/sprites/{48c257f8-3583-49f7-b01a-ba7d251086ac.png → b65724b4-ec8f-48a9-9ab2-e7ce5b8f35b2.png} +0 -0
  151. /kotonebot/kaa/sprites/{469006bc-a011-46e4-bcde-992be888456e.png → b8aada91-eedd-4c53-99f3-f903d547235e.png} +0 -0
  152. /kotonebot/kaa/sprites/{e5d15c50-45ee-4de9-8b97-4630cbd8f01a.png → ba759205-ce99-4417-8cab-d32fe55dff4c.png} +0 -0
  153. /kotonebot/kaa/sprites/{afa0f85f-dcb7-48a1-a1ae-624456b9b65c.png → bb0ae68d-44d5-47c6-91dd-a529bc34225b.png} +0 -0
  154. /kotonebot/kaa/sprites/{79bc56c5-eae6-4389-9116-4a9e182d6bf3.png → bed9575d-ccad-4f4e-825e-47b2a9cb0925.png} +0 -0
  155. /kotonebot/kaa/sprites/{f9b2f64d-f829-4b13-9704-c5f00fb956be.png → bfec58df-2628-454f-92aa-769cad186448.png} +0 -0
  156. /kotonebot/kaa/sprites/{9c1fa1d4-2ae5-4093-abaa-cebb51135721.png → c25c7ce5-ad8d-4b2c-83eb-ae6d4871a6c3.png} +0 -0
  157. /kotonebot/kaa/sprites/{05f8f996-a38a-45d9-8ff9-720b6c4fd599.png → c3ec61aa-f62e-46fd-b359-48864ea7a150.png} +0 -0
  158. /kotonebot/kaa/sprites/{826bf80a-112e-4356-9111-4b7e692ccc82.png → c484ca57-6b19-46b5-a107-1805a7488d48.png} +0 -0
  159. /kotonebot/kaa/sprites/{cd8d3d55-f102-419e-a6fd-2ad5ecc4fd2e.png → c571e1c0-ff85-4b2e-97ed-97d33b8d92e3.png} +0 -0
  160. /kotonebot/kaa/sprites/{0decaa6a-b9a9-4d9b-829a-0fc1f67538b4.png → c5e16f75-9b5a-4853-a144-275f3d20c186.png} +0 -0
  161. /kotonebot/kaa/sprites/{d747239f-7620-4f23-9ac7-cf2d34889266.png → c5ea3dfd-6761-466e-b44c-86592ef05323.png} +0 -0
  162. /kotonebot/kaa/sprites/{695f8d57-b2c9-4a6d-9ae9-1b49ee3eb22a.png → ca2adfb8-1e88-4a31-b3cc-1f70568ce4a3.png} +0 -0
  163. /kotonebot/kaa/sprites/{333fe6d3-0be7-40fb-a2a3-364f98758f2a.png → cb24b3df-1ec3-4ffd-95f1-ca4b0dd5b7da.png} +0 -0
  164. /kotonebot/kaa/sprites/{0bab7080-9a37-4c27-9821-8df36d49b678.png → cdbbb5ba-5117-488a-8172-48cfc92c14c6.png} +0 -0
  165. /kotonebot/kaa/sprites/{a3c12040-2b11-4038-b830-215fb7b6f359.png → cf66ae12-48bc-4512-a988-0e5de337c76d.png} +0 -0
  166. /kotonebot/kaa/sprites/{994aa6a6-0189-4a79-8125-56136973b581.png → d1699baa-467b-46d1-9276-aae78fe8f589.png} +0 -0
  167. /kotonebot/kaa/sprites/{48d2f007-8c1b-43fc-bd3f-f0b10d2a0c88.png → d3e6e2e9-c5b5-463f-98f4-de72c9320d9d.png} +0 -0
  168. /kotonebot/kaa/sprites/{b2f7a430-5777-4861-8435-5419394e5ee8.png → d4c47cc7-5b3e-4c3e-8e80-78cb94e7eed9.png} +0 -0
  169. /kotonebot/kaa/sprites/{fd610de1-b023-4747-8c1b-72f89b1c5647.png → d5d854fc-1b87-43d4-b32d-66eccb66fa41.png} +0 -0
  170. /kotonebot/kaa/sprites/{c2667677-cbff-4dd4-a401-e0963e7224f4.png → d60a395b-d907-416a-b091-36bfee4948b3.png} +0 -0
  171. /kotonebot/kaa/sprites/{43d65b3f-bbae-4e29-a7bf-539379c1b072.png → dad4ff8c-07f4-4b07-abd3-5d02bbcc4562.png} +0 -0
  172. /kotonebot/kaa/sprites/{b44f5e5b-e999-4427-8a9f-f76c6485f4a5.png → dbd537c7-d1d3-4d64-9ccd-43dd4519bedd.png} +0 -0
  173. /kotonebot/kaa/sprites/{2f81e70a-f2af-420f-9b6b-6338c391911f.png → dde03640-6c3a-4b13-a783-f672816bfec4.png} +0 -0
  174. /kotonebot/kaa/sprites/{da3e7cee-d77b-4e62-b703-ed257706dd39.png → df97421e-4b74-430e-b3d1-ed9bac6918a7.png} +0 -0
  175. /kotonebot/kaa/sprites/{0439399f-5fca-43f9-a105-99342ff04d2e.png → dfb14c98-f672-4906-80bd-d2a18bdd62cd.png} +0 -0
  176. /kotonebot/kaa/sprites/{03458cf9-8ab0-4908-9a56-9c5c7e06bc9b.png → e1e0d751-9006-4fa4-a933-4c0dc5e1f3d2.png} +0 -0
  177. /kotonebot/kaa/sprites/{f65a4495-f044-44de-bf0d-c869e637d5d3.png → e394ff91-09d3-41e1-bbf5-bc42a71a623b.png} +0 -0
  178. /kotonebot/kaa/sprites/{de709296-e411-465f-86fc-5daf5ca83cef.png → e5259dfb-3a49-4cfc-9f89-b42958ce6eec.png} +0 -0
  179. /kotonebot/kaa/sprites/{371a3192-101c-45ef-9da4-2ed389770c0f.png → e5eeb9af-8003-4981-bf56-2e32e76cec5d.png} +0 -0
  180. /kotonebot/kaa/sprites/{73af305f-840f-4325-abae-b3519fba672a.png → e9c45a73-2011-4584-87b7-c4e566eba87f.png} +0 -0
  181. /kotonebot/kaa/sprites/{7a479871-547e-4098-9cdd-6c9bb9f7449a.png → eae83523-e58c-4db9-a356-4335a5da3dc8.png} +0 -0
  182. /kotonebot/kaa/sprites/{e7022542-4317-4a61-8098-f3f733d8400e.png → ed6d3e3d-0f04-4fa4-b0f9-fdbf324649f6.png} +0 -0
  183. /kotonebot/kaa/sprites/{24cd42fd-bb3b-4fc6-b40d-d3e3e36fe9f1.png → efb28e1d-7b52-4e20-aec5-117c798b7fbe.png} +0 -0
  184. /kotonebot/kaa/sprites/{6bf5556a-8f4e-41d7-8ab5-d683ad1b47a8.png → f025027f-daa0-4943-8395-5d7d44cc7f15.png} +0 -0
  185. /kotonebot/kaa/sprites/{b3e72de2-e103-49a3-b0f2-8593cff87c4e.png → f12c2b0b-8273-4197-a8c6-2a8ef2788a9b.png} +0 -0
  186. /kotonebot/kaa/sprites/{d22ca9e0-001d-43d6-a557-1c76c5a27c81.png → f528ea0e-1e55-4e41-844f-bb1db9185ef8.png} +0 -0
  187. /kotonebot/kaa/sprites/{b604e25d-680a-4e02-a012-2e593b5bc276.png → f611bbd9-c062-4989-96a4-65ff6c59673c.png} +0 -0
  188. /kotonebot/kaa/sprites/{b1d1dfab-51f5-4ae9-b718-cdf46774c0ff.png → f6bfbf6a-6e53-4563-9345-3f3756d6ea9a.png} +0 -0
  189. /kotonebot/kaa/sprites/{7344e180-251b-465a-a8cf-974477f7ad44.png → f72bd3d6-850d-4190-9775-938f474d263c.png} +0 -0
  190. /kotonebot/kaa/sprites/{96cf0633-6531-499b-b1cc-6670ab710293.png → f8eec9b2-55ca-4195-b098-a5393d02a98a.png} +0 -0
  191. /kotonebot/kaa/sprites/{92b7d830-3db5-434e-b156-0ac855db480d.png → faf069e1-f623-47c1-b879-cfa246fbc7df.png} +0 -0
  192. /kotonebot/kaa/sprites/{a00fcf77-2800-4f7d-993d-4636b36045bb.png → faf360e8-7845-4fcf-b0a6-0f5f93c050f1.png} +0 -0
  193. {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/WHEEL +0 -0
  194. {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/entry_points.txt +0 -0
  195. {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/licenses/LICENSE +0 -0
  196. {ksaa-2025.6.8.0.dist-info → ksaa-2025.6.23.0.dist-info}/top_level.txt +0 -0
@@ -2,13 +2,15 @@ import os
2
2
  import json
3
3
  import subprocess
4
4
  from functools import lru_cache
5
- from typing import Any
5
+ from typing import Any, cast
6
6
  from typing_extensions import override
7
7
 
8
8
  from kotonebot import logging
9
9
  from kotonebot.client import DeviceImpl, Device
10
+ from kotonebot.client.registration import AdbBasedImpl, create_device
11
+ from kotonebot.client.implements.adb import AdbImplConfig
10
12
  from kotonebot.util import Countdown, Interval
11
- from .protocol import HostProtocol, Instance, copy_type
13
+ from .protocol import HostProtocol, Instance, copy_type, AdbHostConfig
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
@@ -19,7 +21,7 @@ else:
19
21
  """Stub for read_reg on non-Windows platforms."""
20
22
  return default
21
23
 
22
- class Mumu12Instance(Instance):
24
+ class Mumu12Instance(Instance[AdbHostConfig]):
23
25
  @copy_type(Instance.__init__)
24
26
  def __init__(self, *args, **kwargs):
25
27
  super().__init__(*args, **kwargs)
@@ -70,6 +72,26 @@ class Mumu12Instance(Instance):
70
72
  def running(self) -> bool:
71
73
  return self.is_android_started
72
74
 
75
+ @override
76
+ def create_device(self, impl: DeviceImpl, host_config: AdbHostConfig) -> Device:
77
+ """为MuMu12模拟器实例创建 Device。"""
78
+ if self.adb_port is None:
79
+ raise ValueError("ADB port is not set and is required.")
80
+
81
+ # 为 ADB 相关的实现创建配置
82
+ if impl in ['adb', 'adb_raw', 'uiautomator2']:
83
+ config = AdbImplConfig(
84
+ addr=f'{self.adb_ip}:{self.adb_port}',
85
+ connect=True,
86
+ disconnect=True,
87
+ device_serial=self.adb_name,
88
+ timeout=host_config.timeout
89
+ )
90
+ impl = cast(AdbBasedImpl, impl) # make pylance happy
91
+ return create_device(impl, config)
92
+ else:
93
+ raise ValueError(f'Unsupported device implementation for MuMu12: {impl}')
94
+
73
95
  class Mumu12Host(HostProtocol):
74
96
  @staticmethod
75
97
  @lru_cache(maxsize=1)
@@ -1,14 +1,15 @@
1
1
  import time
2
2
  import socket
3
3
  from abc import ABC, abstractmethod
4
- from typing_extensions import ParamSpec, Concatenate
5
- from typing import Callable, TypeVar, Generic, Protocol, runtime_checkable, Type, Any
4
+ from typing import Callable, TypeVar, Protocol, Any, Generic
5
+ from dataclasses import dataclass
6
6
 
7
7
  from adbutils import adb, AdbTimeout, AdbError
8
8
  from adbutils._device import AdbDevice
9
9
 
10
10
  from kotonebot import logging
11
- from kotonebot.client import Device, create_device, DeviceImpl
11
+ from kotonebot.client import Device, DeviceImpl
12
+
12
13
  from kotonebot.util import Countdown, Interval
13
14
 
14
15
  logger = logging.getLogger(__name__)
@@ -17,6 +18,28 @@ _T = TypeVar("_T")
17
18
  def copy_type(_: _T) -> Callable[[Any], _T]:
18
19
  return lambda x: x
19
20
 
21
+ # --- 定义专用的 HostConfig 数据类 ---
22
+ @dataclass
23
+ class AdbHostConfig:
24
+ """由外部为基于 ADB 的主机提供的配置。"""
25
+ timeout: float = 180
26
+
27
+ @dataclass
28
+ class WindowsHostConfig:
29
+ """由外部为 Windows 实现提供配置。"""
30
+ window_title: str
31
+ ahk_exe_path: str
32
+
33
+ @dataclass
34
+ class RemoteWindowsHostConfig:
35
+ """由外部为远程 Windows 实现提供配置。"""
36
+ windows_host_config: WindowsHostConfig
37
+ host: str
38
+ port: int
39
+
40
+ # --- 使用泛型改造 Instance 协议 ---
41
+ T_HostConfig = TypeVar("T_HostConfig")
42
+
20
43
  def tcp_ping(host: str, port: int, timeout: float = 1.0) -> bool:
21
44
  """
22
45
  通过 TCP ping 检查主机和端口是否可达。
@@ -36,7 +59,11 @@ def tcp_ping(host: str, port: int, timeout: float = 1.0) -> bool:
36
59
  return False
37
60
 
38
61
 
39
- class Instance(ABC):
62
+ class Instance(Generic[T_HostConfig], ABC):
63
+ """
64
+ 代表一个可运行环境的实例(如一个模拟器)。
65
+ 使用泛型来约束 create_device 方法的配置参数类型。
66
+ """
40
67
  def __init__(self,
41
68
  id: str,
42
69
  name: str,
@@ -68,7 +95,7 @@ class Instance(ABC):
68
95
  启动模拟器实例。
69
96
  """
70
97
  raise NotImplementedError()
71
-
98
+
72
99
  @abstractmethod
73
100
  def stop(self):
74
101
  """
@@ -80,21 +107,16 @@ class Instance(ABC):
80
107
  def running(self) -> bool:
81
108
  raise NotImplementedError()
82
109
 
83
- def create_device(self, impl: DeviceImpl, *, timeout: float = 180) -> Device:
110
+ @abstractmethod
111
+ def create_device(self, impl: DeviceImpl, host_config: T_HostConfig) -> Device:
84
112
  """
85
- 创建 Device 实例,可用于控制模拟器系统。
86
-
87
- :return: Device 实例
113
+ 根据实现名称和类型化的主机配置创建设备。
114
+
115
+ :param impl: 设备实现的名称。
116
+ :param host_config: 一个类型化的数据对象,包含创建所需的所有外部配置。
117
+ :return: 配置好的 Device 实例。
88
118
  """
89
- if self.adb_port is None:
90
- raise ValueError("ADB port is not set and is required.")
91
- return create_device(
92
- addr=f'{self.adb_ip}:{self.adb_port}',
93
- impl=impl,
94
- device_serial=self.adb_name,
95
- connect=True,
96
- timeout=timeout
97
- )
119
+ raise NotImplementedError()
98
120
 
99
121
  def wait_available(self, timeout: float = 180):
100
122
  logger.info('Starting to wait for emulator %s(127.0.0.1:%d) to be available...', self.name, self.adb_port)
@@ -0,0 +1,6 @@
1
+ # 导入所有内置实现,以触发它们的 @register_impl 装饰器
2
+ from . import adb # noqa: F401
3
+ from . import adb_raw # noqa: F401
4
+ from . import remote_windows # noqa: F401
5
+ from . import uiautomator2 # noqa: F401
6
+ from . import windows # noqa: F401
@@ -5,16 +5,27 @@ from typing_extensions import override
5
5
  import cv2
6
6
  import numpy as np
7
7
  from cv2.typing import MatLike
8
+ from adbutils._device import AdbDevice as AdbUtilsDevice
8
9
 
9
- from ..device import Device
10
- from ..protocol import Commandable, Touchable, Screenshotable
10
+ from ..device import AndroidDevice
11
+ from ..protocol import AndroidCommandable, Touchable, Screenshotable
12
+ from ..registration import register_impl, ImplConfig
13
+ from dataclasses import dataclass
11
14
 
12
15
  logger = logging.getLogger(__name__)
13
16
 
14
- class AdbImpl(Commandable, Touchable, Screenshotable):
15
- def __init__(self, device: Device):
16
- self.device = device
17
- self.adb = device.adb
17
+ # 定义配置模型
18
+ @dataclass
19
+ class AdbImplConfig(ImplConfig):
20
+ addr: str
21
+ connect: bool = True
22
+ disconnect: bool = True
23
+ device_serial: str | None = None
24
+ timeout: float = 180
25
+
26
+ class AdbImpl(AndroidCommandable, Touchable, Screenshotable):
27
+ def __init__(self, adb_connection: AdbUtilsDevice):
28
+ self.adb = adb_connection
18
29
 
19
30
  @override
20
31
  def launch_app(self, package_name: str) -> None:
@@ -36,6 +47,10 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
36
47
  package = activity.split('/')[0]
37
48
  return package
38
49
 
50
+ def adb_shell(self, cmd: str) -> str:
51
+ """执行 ADB shell 命令"""
52
+ return cast(str, self.adb.shell(cmd))
53
+
39
54
  @override
40
55
  def detect_orientation(self):
41
56
  # 判断方向:https://stackoverflow.com/questions/10040624/check-if-device-is-landscape-via-adb
@@ -50,7 +65,9 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
50
65
  def screen_size(self) -> tuple[int, int]:
51
66
  ret = cast(str, self.adb.shell("wm size")).strip('Physical size: ')
52
67
  spiltted = tuple(map(int, ret.split("x")))
53
- landscape = self.device.orientation == 'landscape'
68
+ # 检测当前方向
69
+ orientation = self.detect_orientation()
70
+ landscape = orientation == 'landscape'
54
71
  spiltted = tuple(sorted(spiltted, reverse=landscape))
55
72
  if len(spiltted) != 2:
56
73
  raise ValueError(f"Invalid screen size: {ret}")
@@ -66,3 +83,46 @@ class AdbImpl(Commandable, Touchable, Screenshotable):
66
83
  if duration is not None:
67
84
  logger.warning("Swipe duration is not supported with AdbDevice. Ignoring duration.")
68
85
  self.adb.shell(f"input touchscreen swipe {x1} {y1} {x2} {y2}")
86
+
87
+
88
+ def _create_adb_device_base(config: AdbImplConfig, impl_class: type) -> AndroidDevice:
89
+ """
90
+ 通用的 ADB 设备创建工厂函数。
91
+ 其他任意基于 ADB 的 Impl 可以直接复用这个函数。
92
+
93
+ :param config: ADB 实现配置
94
+ :param impl_class: 实现类或工厂函数。构造函数接收 adb_connection 参数。
95
+ """
96
+ from adbutils import adb
97
+
98
+ if config.disconnect:
99
+ logger.debug('adb disconnect %s', config.addr)
100
+ adb.disconnect(config.addr)
101
+ if config.connect:
102
+ logger.debug('adb connect %s', config.addr)
103
+ result = adb.connect(config.addr)
104
+ if 'cannot connect to' in result:
105
+ raise ValueError(result)
106
+ serial = config.device_serial or config.addr
107
+ logger.debug('adb wait for %s', serial)
108
+ adb.wait_for(serial, timeout=config.timeout)
109
+ devices = adb.device_list()
110
+ logger.debug('adb device_list: %s', devices)
111
+ d = [d for d in devices if d.serial == serial]
112
+ if len(d) == 0:
113
+ raise ValueError(f"Device {config.addr} not found")
114
+ d = d[0]
115
+
116
+ device = AndroidDevice(d)
117
+ impl = impl_class(d)
118
+ device._touch = impl
119
+ device._screenshot = impl
120
+ device.commands = impl
121
+
122
+ return device
123
+
124
+
125
+ @register_impl('adb', config_model=AdbImplConfig)
126
+ def create_adb_device(config: AdbImplConfig) -> AndroidDevice:
127
+ """AdbImpl 工厂函数"""
128
+ return _create_adb_device_base(config, AdbImpl)
@@ -11,8 +11,9 @@ import numpy as np
11
11
  from cv2.typing import MatLike
12
12
  from adbutils._utils import adb_path
13
13
 
14
- from .adb import AdbImpl
15
- from ..device import Device
14
+ from .adb import AdbImpl, AdbImplConfig, _create_adb_device_base
15
+ from adbutils._device import AdbDevice as AdbUtilsDevice
16
+ from ..registration import register_impl
16
17
  from kotonebot import logging
17
18
 
18
19
  logger = logging.getLogger(__name__)
@@ -27,8 +28,8 @@ done
27
28
  """
28
29
 
29
30
  class AdbRawImpl(AdbImpl):
30
- def __init__(self, device: Device):
31
- super().__init__(device)
31
+ def __init__(self, adb_connection: AdbUtilsDevice):
32
+ super().__init__(adb_connection)
32
33
  self.__worker: Thread | None = None
33
34
  self.__process: subprocess.Popen | None = None
34
35
  self.__data: MatLike | None = None
@@ -156,4 +157,10 @@ class AdbRawImpl(AdbImpl):
156
157
  logger.verbose(f"adb raw screenshot wait time: {time.time() - start_time:.4f}s")
157
158
  data = self.__data
158
159
  self.__data = None
159
- return data
160
+ return data
161
+
162
+
163
+ @register_impl('adb_raw', config_model=AdbImplConfig)
164
+ def create_adb_raw_device(config: AdbImplConfig):
165
+ """AdbRawImpl 工厂函数"""
166
+ return _create_adb_device_base(config, AdbRawImpl)
@@ -14,6 +14,7 @@ import xmlrpc.server
14
14
  from typing import Literal, cast, Any, Tuple
15
15
  from functools import cached_property
16
16
  from threading import Thread
17
+ from dataclasses import dataclass
17
18
 
18
19
  import cv2
19
20
  import numpy as np
@@ -22,10 +23,18 @@ from cv2.typing import MatLike
22
23
  from kotonebot import logging
23
24
  from ..device import Device, WindowsDevice
24
25
  from ..protocol import Touchable, Screenshotable
25
- from .windows import WindowsImpl
26
+ from ..registration import register_impl, ImplConfig
27
+ from .windows import WindowsImpl, WindowsImplConfig, create_windows_device
26
28
 
27
29
  logger = logging.getLogger(__name__)
28
30
 
31
+ # 定义配置模型
32
+ @dataclass
33
+ class RemoteWindowsImplConfig(ImplConfig):
34
+ windows_impl_config: WindowsImplConfig
35
+ host: str = "localhost"
36
+ port: int = 8000
37
+
29
38
  def _encode_image(image: MatLike) -> str:
30
39
  """Encode an image as a base64 string."""
31
40
  success, buffer = cv2.imencode('.png', image)
@@ -48,13 +57,13 @@ class RemoteWindowsServer:
48
57
  This class wraps a WindowsImpl instance and exposes its methods via XML-RPC.
49
58
  """
50
59
 
51
- def __init__(self, host="localhost", port=8000):
60
+ def __init__(self, windows_impl_config: WindowsImplConfig, host="localhost", port=8000):
52
61
  """Initialize the server with the given host and port."""
53
62
  self.host = host
54
63
  self.port = port
55
64
  self.server = None
56
65
  self.device = WindowsDevice()
57
- self.impl = WindowsImpl(self.device)
66
+ self.impl = create_windows_device(windows_impl_config)
58
67
  self.device._screenshot = self.impl
59
68
  self.device._touch = self.impl
60
69
 
@@ -180,18 +189,11 @@ class RemoteWindowsImpl(Touchable, Screenshotable):
180
189
  raise RuntimeError(f"Failed to swipe from ({x1}, {y1}) to ({x2}, {y2})")
181
190
 
182
191
 
183
- if __name__ == "__main__":
184
- import argparse
185
-
186
- parser = argparse.ArgumentParser(description="Remote Windows XML-RPC Server")
187
- parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
188
- parser.add_argument("--port", type=int, default=8000, help="Port to bind to")
189
- args = parser.parse_args()
190
-
191
- logging.basicConfig(level=logging.INFO)
192
-
193
- server = RemoteWindowsServer(args.host, args.port)
194
- try:
195
- server.start()
196
- except KeyboardInterrupt:
197
- logger.info("Server stopped by user")
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
@@ -8,6 +8,8 @@ from cv2.typing import MatLike
8
8
  from kotonebot import logging
9
9
  from ..device import Device
10
10
  from ..protocol import Screenshotable, Commandable, Touchable
11
+ from ..registration import register_impl
12
+ from .adb import AdbImplConfig, _create_adb_device_base
11
13
 
12
14
  logger = logging.getLogger(__name__)
13
15
 
@@ -83,3 +85,9 @@ class UiAutomator2Impl(Screenshotable, Commandable, Touchable):
83
85
  滑动屏幕
84
86
  """
85
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)
@@ -2,6 +2,7 @@ from ctypes import windll
2
2
  from typing import Literal
3
3
  from importlib import resources
4
4
  from functools import cached_property
5
+ from dataclasses import dataclass
5
6
 
6
7
  import cv2
7
8
  import win32ui
@@ -10,14 +11,21 @@ import numpy as np
10
11
  from ahk import AHK, MsgBoxIcon
11
12
  from cv2.typing import MatLike
12
13
 
13
- from ..device import Device
14
+ from ..device import Device, WindowsDevice
14
15
  from ..protocol import Commandable, Touchable, Screenshotable
16
+ from ..registration import register_impl, ImplConfig
17
+
18
+ # 1. 定义配置模型
19
+ @dataclass
20
+ class WindowsImplConfig(ImplConfig):
21
+ window_title: str
22
+ ahk_exe_path: str
15
23
 
16
24
  class WindowsImpl(Touchable, Screenshotable):
17
- def __init__(self, device: Device):
25
+ def __init__(self, device: Device, window_title: str, ahk_exe_path: str):
18
26
  self.__hwnd: int | None = None
19
- # TODO: 硬编码路径
20
- self.ahk = AHK(executable_path=str(resources.files('kaa.res.bin') / 'AutoHotkey.exe'))
27
+ self.window_title = window_title
28
+ self.ahk = AHK(executable_path=ahk_exe_path)
21
29
  self.device = device
22
30
 
23
31
  # 设置 DPI aware,否则高缩放显示器上返回的坐标会错误
@@ -55,9 +63,9 @@ class WindowsImpl(Touchable, Screenshotable):
55
63
  @property
56
64
  def hwnd(self) -> int:
57
65
  if self.__hwnd is None:
58
- self.__hwnd = win32gui.FindWindow(None, 'gakumas')
66
+ self.__hwnd = win32gui.FindWindow(None, self.window_title)
59
67
  if self.__hwnd is None or self.__hwnd == 0:
60
- raise RuntimeError('Failed to find window')
68
+ raise RuntimeError(f'Failed to find window: {self.window_title}')
61
69
  return self.__hwnd
62
70
 
63
71
  def __client_rect(self) -> tuple[int, int, int, int]:
@@ -73,8 +81,8 @@ class WindowsImpl(Touchable, Screenshotable):
73
81
  return win32gui.ClientToScreen(hwnd, (x, y))
74
82
 
75
83
  def screenshot(self) -> MatLike:
76
- if not self.ahk.win_is_active('gakumas'):
77
- self.ahk.win_activate('gakumas')
84
+ if not self.ahk.win_is_active(self.window_title):
85
+ self.ahk.win_activate(self.window_title)
78
86
  hwnd = self.hwnd
79
87
 
80
88
  # TODO: 需要检查下面这些 WinAPI 的返回结果
@@ -130,7 +138,7 @@ class WindowsImpl(Touchable, Screenshotable):
130
138
  return 720, 1280
131
139
 
132
140
  def detect_orientation(self) -> None | Literal['portrait'] | Literal['landscape']:
133
- pos = self.ahk.win_get_position('gakumas')
141
+ pos = self.ahk.win_get_position(self.window_title)
134
142
  if pos is None:
135
143
  return None
136
144
  w, h = pos.width, pos.height
@@ -147,24 +155,39 @@ class WindowsImpl(Touchable, Screenshotable):
147
155
  if y == 0:
148
156
  y = 2
149
157
  x, y = int(x / self.scale_ratio), int(y / self.scale_ratio)
150
- if not self.ahk.win_is_active('gakumas'):
151
- self.ahk.win_activate('gakumas')
158
+ if not self.ahk.win_is_active(self.window_title):
159
+ self.ahk.win_activate(self.window_title)
152
160
  self.ahk.click(x, y)
153
161
 
154
162
  def swipe(self, x1: int, y1: int, x2: int, y2: int, duration: float | None = None) -> None:
155
- if not self.ahk.win_is_active('gakumas'):
156
- self.ahk.win_activate('gakumas')
163
+ if not self.ahk.win_is_active(self.window_title):
164
+ self.ahk.win_activate(self.window_title)
157
165
  x1, y1 = int(x1 / self.scale_ratio), int(y1 / self.scale_ratio)
158
166
  x2, y2 = int(x2 / self.scale_ratio), int(y2 / self.scale_ratio)
159
167
  # TODO: 这个 speed 的单位是什么?
160
168
  self.ahk.mouse_drag(x2, y2, from_position=(x1, y1), coord_mode='Client', speed=10)
161
169
 
162
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
+
163
185
  if __name__ == '__main__':
164
186
  from ..device import Device
165
- from time import sleep
166
187
  device = Device()
167
- impl = WindowsImpl(device)
188
+ # 在测试环境中直接使用默认路径
189
+ ahk_path = str(resources.files('kaa.res.bin') / 'AutoHotkey.exe')
190
+ impl = WindowsImpl(device, window_title='gakumas', ahk_exe_path=ahk_path)
168
191
  device._screenshot = impl
169
192
  device._touch = impl
170
193
  device.swipe_scaled(0.5, 0.8, 0.5, 0.2)
@@ -28,6 +28,19 @@ class Commandable(Protocol):
28
28
  def launch_app(self, package_name: str) -> None: ...
29
29
  def current_package(self) -> str | None: ...
30
30
 
31
+ @runtime_checkable
32
+ class AndroidCommandable(Protocol):
33
+ """定义 Android 平台的特定命令"""
34
+ def launch_app(self, package_name: str) -> None: ...
35
+ def current_package(self) -> str | None: ...
36
+ def adb_shell(self, cmd: str) -> str: ...
37
+
38
+ @runtime_checkable
39
+ class WindowsCommandable(Protocol):
40
+ """定义 Windows 平台的特定命令"""
41
+ def get_foreground_window(self) -> tuple[int, str]: ...
42
+ def exec_command(self, command: str) -> tuple[int, str, str]: ...
43
+
31
44
  @runtime_checkable
32
45
  class Screenshotable(Protocol):
33
46
  def __init__(self, device: 'Device'): ...
@@ -0,0 +1,89 @@
1
+ from dataclasses import dataclass
2
+ from typing import TypeVar, Callable, Dict, Type, Any, overload, Literal, cast, TYPE_CHECKING
3
+
4
+ from ..errors import KotonebotError
5
+ from .device import Device
6
+ if TYPE_CHECKING:
7
+ from .implements.adb import AdbImplConfig
8
+ from .implements.remote_windows import RemoteWindowsImplConfig
9
+ from .implements.windows import WindowsImplConfig
10
+
11
+ AdbBasedImpl = Literal['adb', 'adb_raw', 'uiautomator2']
12
+ DeviceImpl = str | AdbBasedImpl | Literal['windows', 'remote_windows']
13
+
14
+ # --- 核心类型定义 ---
15
+
16
+ class ImplRegistrationError(KotonebotError):
17
+ """与 impl 注册相关的错误"""
18
+ pass
19
+
20
+ @dataclass
21
+ class ImplConfig:
22
+ """所有设备实现配置模型的名义上的基类,便于类型约束。"""
23
+ 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,7 +3,7 @@ from typing import Generic, TypeVar, Literal
3
3
 
4
4
  from pydantic import BaseModel, ConfigDict
5
5
 
6
- from kotonebot.client.factory import DeviceImpl
6
+ from kotonebot.client import DeviceImpl
7
7
 
8
8
  T = TypeVar('T')
9
9
  BackendType = Literal['custom', 'mumu12', 'leidian', 'dmm']
@@ -44,6 +44,10 @@ class BackendConfig(ConfigBaseModel):
44
44
  """模拟器 exe 文件路径"""
45
45
  emulator_args: str = ""
46
46
  """模拟器启动时的命令行参数"""
47
+ windows_window_title: str = 'gakumas'
48
+ """Windows 截图方式的窗口标题"""
49
+ windows_ahk_path: str | None = None
50
+ """Windows 截图方式的 AutoHotkey 可执行文件路径,为 None 时使用默认路径"""
47
51
 
48
52
  class PushConfig(ConfigBaseModel):
49
53
  """推送配置。"""
kotonebot/debug_entry.py CHANGED
@@ -17,10 +17,15 @@ def run_script(script_path: str) -> None:
17
17
 
18
18
  print(f"正在运行脚本: {script_path}")
19
19
  # 运行脚本
20
- from kotonebot.backend.context import init_context
20
+ from kotonebot.backend.context import init_context, manual_context
21
+ from kotonebot.kaa.main.kaa import Kaa
21
22
  logging.basicConfig(level=logging.INFO, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
22
23
  logging.getLogger('kotonebot').setLevel(logging.DEBUG)
23
- init_context(config_type=BaseConfig)
24
+ config_path = './config.json'
25
+ kaa_instance = Kaa(config_path)
26
+ init_context(config_type=BaseConfig, target_device=kaa_instance._on_create_device())
27
+ kaa_instance._on_after_init_context()
28
+ manual_context().begin()
24
29
  runpy.run_module(module_name, run_name="__main__")
25
30
 
26
31
  def main():
@@ -28,7 +28,4 @@ def clear_logs():
28
28
  logger.info('Clearing logs done.')
29
29
 
30
30
  if __name__ == '__main__':
31
- from kotonebot.backend.context import init_context, manual_context
32
- init_context()
33
- manual_context().begin()
34
31
  clear_logs()