gemba 0.1.1 → 0.2.1

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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/THIRD_PARTY_NOTICES +37 -2
  3. data/assets/placeholder_boxart.png +0 -0
  4. data/bin/gemba +2 -2
  5. data/ext/gemba/extconf.rb +23 -1
  6. data/ext/gemba/gemba_ext.c +436 -2
  7. data/ext/gemba/gemba_ext.h +2 -0
  8. data/gemba.gemspec +5 -3
  9. data/lib/gemba/achievements/achievement.rb +23 -0
  10. data/lib/gemba/achievements/backend.rb +190 -0
  11. data/lib/gemba/achievements/cache.rb +70 -0
  12. data/lib/gemba/achievements/credentials_presenter.rb +142 -0
  13. data/lib/gemba/achievements/fake_backend.rb +205 -0
  14. data/lib/gemba/achievements/null_backend.rb +11 -0
  15. data/lib/gemba/achievements/offline_backend.rb +168 -0
  16. data/lib/gemba/achievements/retro_achievements/backend.rb +511 -0
  17. data/lib/gemba/achievements/retro_achievements/cli_sync_requester.rb +64 -0
  18. data/lib/gemba/achievements/retro_achievements/ping_worker.rb +28 -0
  19. data/lib/gemba/achievements/retro_achievements/unlock_retry_worker.rb +35 -0
  20. data/lib/gemba/achievements.rb +19 -0
  21. data/lib/gemba/achievements_window.rb +556 -0
  22. data/lib/gemba/app_controller.rb +1036 -0
  23. data/lib/gemba/bios.rb +54 -0
  24. data/lib/gemba/boxart_fetcher/libretro_backend.rb +39 -0
  25. data/lib/gemba/boxart_fetcher/null_backend.rb +12 -0
  26. data/lib/gemba/boxart_fetcher.rb +79 -0
  27. data/lib/gemba/bus_emitter.rb +13 -0
  28. data/lib/gemba/child_window.rb +24 -1
  29. data/lib/gemba/cli/commands/config_cmd.rb +83 -0
  30. data/lib/gemba/cli/commands/decode.rb +154 -0
  31. data/lib/gemba/cli/commands/patch.rb +78 -0
  32. data/lib/gemba/cli/commands/play.rb +78 -0
  33. data/lib/gemba/cli/commands/record.rb +114 -0
  34. data/lib/gemba/cli/commands/replay.rb +161 -0
  35. data/lib/gemba/cli/commands/retro_achievements.rb +213 -0
  36. data/lib/gemba/cli/commands/version.rb +22 -0
  37. data/lib/gemba/cli.rb +52 -364
  38. data/lib/gemba/config.rb +154 -1
  39. data/lib/gemba/data/gb_games.json +1 -0
  40. data/lib/gemba/data/gb_md5.json +1 -0
  41. data/lib/gemba/data/gba_games.json +1 -0
  42. data/lib/gemba/data/gba_md5.json +1 -0
  43. data/lib/gemba/data/gbc_games.json +1 -0
  44. data/lib/gemba/data/gbc_md5.json +1 -0
  45. data/lib/gemba/emulator_frame.rb +1084 -0
  46. data/lib/gemba/event_bus.rb +48 -0
  47. data/lib/gemba/frame_stack.rb +70 -0
  48. data/lib/gemba/game_index.rb +84 -0
  49. data/lib/gemba/game_picker_frame.rb +309 -0
  50. data/lib/gemba/gamepad_map.rb +103 -0
  51. data/lib/gemba/headless.rb +6 -5
  52. data/lib/gemba/headless_player.rb +33 -3
  53. data/lib/gemba/help_window.rb +61 -0
  54. data/lib/gemba/hotkey_map.rb +3 -1
  55. data/lib/gemba/input_recorder.rb +107 -0
  56. data/lib/gemba/input_replayer.rb +119 -0
  57. data/lib/gemba/keyboard_map.rb +90 -0
  58. data/lib/gemba/list_picker_frame.rb +271 -0
  59. data/lib/gemba/locales/en.yml +109 -5
  60. data/lib/gemba/locales/ja.yml +109 -5
  61. data/lib/gemba/main_window.rb +56 -0
  62. data/lib/gemba/modal_stack.rb +81 -0
  63. data/lib/gemba/patcher_window.rb +223 -0
  64. data/lib/gemba/platform/gb.rb +21 -0
  65. data/lib/gemba/platform/gba.rb +21 -0
  66. data/lib/gemba/platform/gbc.rb +23 -0
  67. data/lib/gemba/platform.rb +20 -0
  68. data/lib/gemba/platform_open.rb +19 -0
  69. data/lib/gemba/recorder.rb +4 -3
  70. data/lib/gemba/replay_player.rb +691 -0
  71. data/lib/gemba/rom_info.rb +57 -0
  72. data/lib/gemba/rom_info_window.rb +16 -3
  73. data/lib/gemba/rom_library.rb +106 -0
  74. data/lib/gemba/rom_overrides.rb +47 -0
  75. data/lib/gemba/rom_patcher/bps.rb +161 -0
  76. data/lib/gemba/rom_patcher/ips.rb +101 -0
  77. data/lib/gemba/rom_patcher/ups.rb +119 -0
  78. data/lib/gemba/rom_patcher.rb +109 -0
  79. data/lib/gemba/{rom_loader.rb → rom_resolver.rb} +7 -6
  80. data/lib/gemba/runtime.rb +59 -26
  81. data/lib/gemba/save_state_manager.rb +4 -7
  82. data/lib/gemba/save_state_picker.rb +17 -4
  83. data/lib/gemba/session_logger.rb +64 -0
  84. data/lib/gemba/settings/audio_tab.rb +77 -0
  85. data/lib/gemba/settings/gamepad_tab.rb +351 -0
  86. data/lib/gemba/settings/hotkeys_tab.rb +259 -0
  87. data/lib/gemba/settings/paths.rb +11 -0
  88. data/lib/gemba/settings/recording_tab.rb +83 -0
  89. data/lib/gemba/settings/save_states_tab.rb +91 -0
  90. data/lib/gemba/settings/system_tab.rb +377 -0
  91. data/lib/gemba/settings/video_tab.rb +318 -0
  92. data/lib/gemba/settings_window.rb +162 -1036
  93. data/lib/gemba/version.rb +1 -1
  94. data/lib/gemba/virtual_keyboard.rb +19 -0
  95. data/lib/gemba.rb +2 -12
  96. data/test/achievements_window/test_bulk_sync.rb +218 -0
  97. data/test/achievements_window/test_bus_events.rb +125 -0
  98. data/test/achievements_window/test_close_confirmation.rb +201 -0
  99. data/test/achievements_window/test_initial_state.rb +164 -0
  100. data/test/achievements_window/test_sorting.rb +227 -0
  101. data/test/achievements_window/test_tree_rendering.rb +133 -0
  102. data/test/fixtures/fake_bios.bin +0 -0
  103. data/test/fixtures/pong.gba +0 -0
  104. data/test/fixtures/test.gb +0 -0
  105. data/test/fixtures/test.gbc +0 -0
  106. data/test/fixtures/test_quicksave.ss +0 -0
  107. data/test/screenshots/no_focus.png +0 -0
  108. data/test/shared/teek_test_worker.rb +17 -1
  109. data/test/shared/tk_test_helper.rb +92 -4
  110. data/test/support/achievements_window_helpers.rb +18 -0
  111. data/test/support/fake_core.rb +25 -0
  112. data/test/support/fake_ra_runtime.rb +74 -0
  113. data/test/support/fake_requester.rb +78 -0
  114. data/test/support/player_helpers.rb +20 -5
  115. data/test/test_achievement.rb +32 -0
  116. data/test/{test_player.rb → test_app_controller.rb} +353 -85
  117. data/test/test_bios.rb +123 -0
  118. data/test/test_boxart_fetcher.rb +150 -0
  119. data/test/test_cli.rb +17 -265
  120. data/test/test_cli_config.rb +64 -0
  121. data/test/test_cli_decode.rb +97 -0
  122. data/test/test_cli_patch.rb +58 -0
  123. data/test/test_cli_play.rb +213 -0
  124. data/test/test_cli_ra.rb +175 -0
  125. data/test/test_cli_record.rb +69 -0
  126. data/test/test_cli_replay.rb +72 -0
  127. data/test/test_cli_sync_requester.rb +152 -0
  128. data/test/test_cli_version.rb +27 -0
  129. data/test/test_config.rb +3 -3
  130. data/test/test_config_ra.rb +69 -0
  131. data/test/test_core.rb +62 -1
  132. data/test/test_credentials_presenter.rb +192 -0
  133. data/test/test_event_bus.rb +100 -0
  134. data/test/test_fake_backend_achievements.rb +130 -0
  135. data/test/test_fake_backend_auth.rb +68 -0
  136. data/test/test_game_index.rb +77 -0
  137. data/test/test_game_picker_frame.rb +310 -0
  138. data/test/test_gamepad_map.rb +1 -3
  139. data/test/test_headless_player.rb +17 -3
  140. data/test/test_help_window.rb +82 -0
  141. data/test/test_hotkey_map.rb +22 -1
  142. data/test/test_input_recorder.rb +179 -0
  143. data/test/test_input_replay_determinism.rb +113 -0
  144. data/test/test_input_replayer.rb +162 -0
  145. data/test/test_keyboard_map.rb +1 -3
  146. data/test/test_libretro_backend.rb +41 -0
  147. data/test/test_list_picker_frame.rb +391 -0
  148. data/test/test_locale.rb +1 -1
  149. data/test/test_logging.rb +123 -0
  150. data/test/test_null_backend.rb +42 -0
  151. data/test/test_offline_backend.rb +116 -0
  152. data/test/test_overlay_renderer.rb +1 -1
  153. data/test/test_platform.rb +149 -0
  154. data/test/test_ra_backend.rb +313 -0
  155. data/test/test_ra_backend_unlock_gate.rb +56 -0
  156. data/test/test_ra_backend_unlock_retry.rb +123 -0
  157. data/test/test_recorder.rb +0 -3
  158. data/test/test_replay_player.rb +316 -0
  159. data/test/test_rom_info.rb +149 -0
  160. data/test/test_rom_overrides.rb +86 -0
  161. data/test/test_rom_patcher.rb +383 -0
  162. data/test/{test_rom_loader.rb → test_rom_resolver.rb} +25 -26
  163. data/test/test_save_state_manager.rb +2 -4
  164. data/test/test_settings_audio.rb +107 -0
  165. data/test/test_settings_hotkeys.rb +83 -66
  166. data/test/test_settings_recording.rb +49 -0
  167. data/test/test_settings_save_states.rb +97 -0
  168. data/test/test_settings_system.rb +133 -0
  169. data/test/test_settings_video.rb +450 -0
  170. data/test/test_settings_window.rb +76 -507
  171. data/test/test_tip_service.rb +6 -6
  172. data/test/test_toast_overlay.rb +1 -1
  173. data/test/test_virtual_events.rb +221 -0
  174. data/test/test_virtual_keyboard.rb +1 -1
  175. data/vendor/rcheevos/CHANGELOG.md +495 -0
  176. data/vendor/rcheevos/LICENSE +21 -0
  177. data/vendor/rcheevos/Package.swift +33 -0
  178. data/vendor/rcheevos/README.md +67 -0
  179. data/vendor/rcheevos/include/module.modulemap +70 -0
  180. data/vendor/rcheevos/include/rc_api_editor.h +296 -0
  181. data/vendor/rcheevos/include/rc_api_info.h +280 -0
  182. data/vendor/rcheevos/include/rc_api_request.h +77 -0
  183. data/vendor/rcheevos/include/rc_api_runtime.h +417 -0
  184. data/vendor/rcheevos/include/rc_api_user.h +262 -0
  185. data/vendor/rcheevos/include/rc_client.h +877 -0
  186. data/vendor/rcheevos/include/rc_client_raintegration.h +101 -0
  187. data/vendor/rcheevos/include/rc_consoles.h +138 -0
  188. data/vendor/rcheevos/include/rc_error.h +59 -0
  189. data/vendor/rcheevos/include/rc_export.h +100 -0
  190. data/vendor/rcheevos/include/rc_hash.h +200 -0
  191. data/vendor/rcheevos/include/rc_runtime.h +148 -0
  192. data/vendor/rcheevos/include/rc_runtime_types.h +452 -0
  193. data/vendor/rcheevos/include/rc_util.h +51 -0
  194. data/vendor/rcheevos/include/rcheevos.h +8 -0
  195. data/vendor/rcheevos/src/rapi/rc_api_common.c +1379 -0
  196. data/vendor/rcheevos/src/rapi/rc_api_common.h +88 -0
  197. data/vendor/rcheevos/src/rapi/rc_api_editor.c +625 -0
  198. data/vendor/rcheevos/src/rapi/rc_api_info.c +587 -0
  199. data/vendor/rcheevos/src/rapi/rc_api_runtime.c +901 -0
  200. data/vendor/rcheevos/src/rapi/rc_api_user.c +483 -0
  201. data/vendor/rcheevos/src/rc_client.c +6941 -0
  202. data/vendor/rcheevos/src/rc_client_external.c +281 -0
  203. data/vendor/rcheevos/src/rc_client_external.h +177 -0
  204. data/vendor/rcheevos/src/rc_client_external_versions.h +171 -0
  205. data/vendor/rcheevos/src/rc_client_internal.h +409 -0
  206. data/vendor/rcheevos/src/rc_client_raintegration.c +566 -0
  207. data/vendor/rcheevos/src/rc_client_raintegration_internal.h +61 -0
  208. data/vendor/rcheevos/src/rc_client_types.natvis +396 -0
  209. data/vendor/rcheevos/src/rc_compat.c +251 -0
  210. data/vendor/rcheevos/src/rc_compat.h +121 -0
  211. data/vendor/rcheevos/src/rc_libretro.c +915 -0
  212. data/vendor/rcheevos/src/rc_libretro.h +98 -0
  213. data/vendor/rcheevos/src/rc_util.c +199 -0
  214. data/vendor/rcheevos/src/rc_version.c +11 -0
  215. data/vendor/rcheevos/src/rc_version.h +32 -0
  216. data/vendor/rcheevos/src/rcheevos/alloc.c +312 -0
  217. data/vendor/rcheevos/src/rcheevos/condition.c +754 -0
  218. data/vendor/rcheevos/src/rcheevos/condset.c +777 -0
  219. data/vendor/rcheevos/src/rcheevos/consoleinfo.c +1215 -0
  220. data/vendor/rcheevos/src/rcheevos/format.c +330 -0
  221. data/vendor/rcheevos/src/rcheevos/lboard.c +287 -0
  222. data/vendor/rcheevos/src/rcheevos/memref.c +805 -0
  223. data/vendor/rcheevos/src/rcheevos/operand.c +607 -0
  224. data/vendor/rcheevos/src/rcheevos/rc_internal.h +390 -0
  225. data/vendor/rcheevos/src/rcheevos/rc_runtime_types.natvis +541 -0
  226. data/vendor/rcheevos/src/rcheevos/rc_validate.c +1406 -0
  227. data/vendor/rcheevos/src/rcheevos/rc_validate.h +18 -0
  228. data/vendor/rcheevos/src/rcheevos/richpresence.c +922 -0
  229. data/vendor/rcheevos/src/rcheevos/runtime.c +852 -0
  230. data/vendor/rcheevos/src/rcheevos/runtime_progress.c +1073 -0
  231. data/vendor/rcheevos/src/rcheevos/trigger.c +344 -0
  232. data/vendor/rcheevos/src/rcheevos/value.c +935 -0
  233. data/vendor/rcheevos/src/rhash/aes.c +480 -0
  234. data/vendor/rcheevos/src/rhash/aes.h +49 -0
  235. data/vendor/rcheevos/src/rhash/cdreader.c +838 -0
  236. data/vendor/rcheevos/src/rhash/hash.c +1402 -0
  237. data/vendor/rcheevos/src/rhash/hash_disc.c +1340 -0
  238. data/vendor/rcheevos/src/rhash/hash_encrypted.c +566 -0
  239. data/vendor/rcheevos/src/rhash/hash_rom.c +426 -0
  240. data/vendor/rcheevos/src/rhash/hash_zip.c +460 -0
  241. data/vendor/rcheevos/src/rhash/md5.c +382 -0
  242. data/vendor/rcheevos/src/rhash/md5.h +91 -0
  243. data/vendor/rcheevos/src/rhash/rc_hash_internal.h +116 -0
  244. data/vendor/rcheevos/test/libretro.h +205 -0
  245. data/vendor/rcheevos/test/rapi/test_rc_api_common.c +941 -0
  246. data/vendor/rcheevos/test/rapi/test_rc_api_editor.c +931 -0
  247. data/vendor/rcheevos/test/rapi/test_rc_api_info.c +545 -0
  248. data/vendor/rcheevos/test/rapi/test_rc_api_runtime.c +2213 -0
  249. data/vendor/rcheevos/test/rapi/test_rc_api_user.c +998 -0
  250. data/vendor/rcheevos/test/rcheevos/mock_memory.h +32 -0
  251. data/vendor/rcheevos/test/rcheevos/test_condition.c +570 -0
  252. data/vendor/rcheevos/test/rcheevos/test_condset.c +5170 -0
  253. data/vendor/rcheevos/test/rcheevos/test_consoleinfo.c +203 -0
  254. data/vendor/rcheevos/test/rcheevos/test_format.c +112 -0
  255. data/vendor/rcheevos/test/rcheevos/test_lboard.c +746 -0
  256. data/vendor/rcheevos/test/rcheevos/test_memref.c +520 -0
  257. data/vendor/rcheevos/test/rcheevos/test_operand.c +692 -0
  258. data/vendor/rcheevos/test/rcheevos/test_rc_validate.c +502 -0
  259. data/vendor/rcheevos/test/rcheevos/test_richpresence.c +1564 -0
  260. data/vendor/rcheevos/test/rcheevos/test_runtime.c +1667 -0
  261. data/vendor/rcheevos/test/rcheevos/test_runtime_progress.c +1821 -0
  262. data/vendor/rcheevos/test/rcheevos/test_timing.c +166 -0
  263. data/vendor/rcheevos/test/rcheevos/test_trigger.c +2521 -0
  264. data/vendor/rcheevos/test/rcheevos/test_value.c +870 -0
  265. data/vendor/rcheevos/test/rcheevos-test.sln +46 -0
  266. data/vendor/rcheevos/test/rcheevos-test.vcxproj +239 -0
  267. data/vendor/rcheevos/test/rcheevos-test.vcxproj.filters +335 -0
  268. data/vendor/rcheevos/test/rhash/data.c +657 -0
  269. data/vendor/rcheevos/test/rhash/data.h +32 -0
  270. data/vendor/rcheevos/test/rhash/mock_filereader.c +236 -0
  271. data/vendor/rcheevos/test/rhash/mock_filereader.h +31 -0
  272. data/vendor/rcheevos/test/rhash/test_cdreader.c +920 -0
  273. data/vendor/rcheevos/test/rhash/test_hash.c +310 -0
  274. data/vendor/rcheevos/test/rhash/test_hash_disc.c +1450 -0
  275. data/vendor/rcheevos/test/rhash/test_hash_rom.c +899 -0
  276. data/vendor/rcheevos/test/rhash/test_hash_zip.c +551 -0
  277. data/vendor/rcheevos/test/test.c +113 -0
  278. data/vendor/rcheevos/test/test_framework.h +205 -0
  279. data/vendor/rcheevos/test/test_rc_client.c +10509 -0
  280. data/vendor/rcheevos/test/test_rc_client_external.c +2197 -0
  281. data/vendor/rcheevos/test/test_rc_client_raintegration.c +441 -0
  282. data/vendor/rcheevos/test/test_rc_libretro.c +952 -0
  283. data/vendor/rcheevos/test/test_types.natvis +9 -0
  284. data/vendor/rcheevos/validator/validator.c +658 -0
  285. data/vendor/rcheevos/validator/validator.vcxproj +152 -0
  286. data/vendor/rcheevos/validator/validator.vcxproj.filters +82 -0
  287. metadata +277 -10
  288. data/lib/gemba/input_mappings.rb +0 -214
  289. data/lib/gemba/player.rb +0 -1525
@@ -8,9 +8,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
8
8
 
9
9
  def test_hotkeys_tab_exists
10
10
  assert_tk_app("hotkeys tab exists in notebook") do
11
- require "gemba/settings_window"
12
- require "gemba/hotkey_map"
13
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
11
+ require "gemba/headless"
12
+ require "gemba/headless"
13
+ sw = Gemba::SettingsWindow.new(app)
14
14
  sw.show
15
15
  app.update
16
16
 
@@ -21,9 +21,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
21
21
 
22
22
  def test_hotkey_buttons_show_default_keysyms
23
23
  assert_tk_app("hotkey buttons show default keysyms") do
24
- require "gemba/settings_window"
25
- require "gemba/hotkey_map"
26
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
24
+ require "gemba/headless"
25
+ require "gemba/headless"
26
+ sw = Gemba::SettingsWindow.new(app)
27
27
  sw.show
28
28
  app.update
29
29
 
@@ -40,9 +40,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
40
40
 
41
41
  def test_clicking_hotkey_button_enters_listen_mode
42
42
  assert_tk_app("clicking hotkey button enters listen mode") do
43
- require "gemba/settings_window"
44
- require "gemba/hotkey_map"
45
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
43
+ require "gemba/headless"
44
+ require "gemba/headless"
45
+ sw = Gemba::SettingsWindow.new(app)
46
46
  sw.show
47
47
  app.update
48
48
 
@@ -57,13 +57,13 @@ class TestMGBASettingsHotkeys < Minitest::Test
57
57
 
58
58
  def test_capture_updates_label_and_fires_callback
59
59
  assert_tk_app("capturing hotkey updates label and fires callback") do
60
- require "gemba/settings_window"
61
- require "gemba/hotkey_map"
60
+ require "gemba/headless"
61
+ require "gemba/headless"
62
62
  received_action = nil
63
63
  received_key = nil
64
- sw = Gemba::SettingsWindow.new(app, callbacks: {
65
- on_hotkey_change: proc { |a, k| received_action = a; received_key = k }
66
- })
64
+ Gemba.bus = Gemba::EventBus.new
65
+ Gemba.bus.on(:hotkey_changed) { |a, k| received_action = a; received_key = k }
66
+ sw = Gemba::SettingsWindow.new(app)
67
67
  sw.show
68
68
  app.update
69
69
 
@@ -85,9 +85,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
85
85
 
86
86
  def test_capture_enables_undo_button
87
87
  assert_tk_app("capturing hotkey enables undo button") do
88
- require "gemba/settings_window"
89
- require "gemba/hotkey_map"
90
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
88
+ require "gemba/headless"
89
+ require "gemba/headless"
90
+ sw = Gemba::SettingsWindow.new(app)
91
91
  sw.show
92
92
  app.update
93
93
 
@@ -108,12 +108,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
108
108
 
109
109
  def test_undo_fires_callback_and_disables
110
110
  assert_tk_app("undo fires on_undo_hotkeys and disables button") do
111
- require "gemba/settings_window"
112
- require "gemba/hotkey_map"
111
+ require "gemba/headless"
112
+ require "gemba/headless"
113
113
  undo_called = false
114
- sw = Gemba::SettingsWindow.new(app, callbacks: {
115
- on_undo_hotkeys: proc { undo_called = true }
116
- })
114
+ Gemba.bus = Gemba::EventBus.new
115
+ Gemba.bus.on(:undo_hotkeys) { undo_called = true }
116
+ sw = Gemba::SettingsWindow.new(app)
117
117
  sw.show
118
118
  app.update
119
119
 
@@ -135,11 +135,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
135
135
 
136
136
  def test_reset_restores_defaults
137
137
  assert_tk_app("reset restores default hotkey labels") do
138
- require "gemba/settings_window"
139
- require "gemba/hotkey_map"
138
+ require "gemba/headless"
139
+ require "gemba/headless"
140
140
  reset_called = false
141
+ Gemba.bus = Gemba::EventBus.new
142
+ Gemba.bus.on(:hotkey_reset) { reset_called = true }
141
143
  sw = Gemba::SettingsWindow.new(app, callbacks: {
142
- on_hotkey_reset: proc { reset_called = true },
143
144
  on_confirm_reset_hotkeys: -> { true },
144
145
  })
145
146
  sw.show
@@ -168,9 +169,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
168
169
 
169
170
  def test_refresh_hotkeys_updates_labels
170
171
  assert_tk_app("refresh_hotkeys updates button labels") do
171
- require "gemba/settings_window"
172
- require "gemba/hotkey_map"
173
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
172
+ require "gemba/headless"
173
+ require "gemba/headless"
174
+ sw = Gemba::SettingsWindow.new(app)
174
175
  sw.show
175
176
  app.update
176
177
 
@@ -187,9 +188,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
187
188
 
188
189
  def test_cancel_listen_restores_label
189
190
  assert_tk_app("canceling listen restores original label") do
190
- require "gemba/settings_window"
191
- require "gemba/hotkey_map"
192
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
191
+ require "gemba/headless"
192
+ require "gemba/headless"
193
+ sw = Gemba::SettingsWindow.new(app)
193
194
  sw.show
194
195
  app.update
195
196
 
@@ -210,12 +211,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
210
211
 
211
212
  def test_capture_without_listen_is_noop
212
213
  assert_tk_app("capture without listen mode is a no-op") do
213
- require "gemba/settings_window"
214
- require "gemba/hotkey_map"
214
+ require "gemba/headless"
215
+ require "gemba/headless"
215
216
  received = false
216
- sw = Gemba::SettingsWindow.new(app, callbacks: {
217
- on_hotkey_change: proc { |*| received = true }
218
- })
217
+ Gemba.bus = Gemba::EventBus.new
218
+ Gemba.bus.on(:hotkey_changed) { |*| received = true }
219
+ sw = Gemba::SettingsWindow.new(app)
219
220
  sw.show
220
221
  app.update
221
222
 
@@ -233,12 +234,13 @@ class TestMGBASettingsHotkeys < Minitest::Test
233
234
 
234
235
  def test_hotkey_rejected_when_conflicting_with_gamepad_key
235
236
  assert_tk_app("hotkey rejected when key conflicts with gamepad mapping") do
236
- require "gemba/settings_window"
237
- require "gemba/hotkey_map"
237
+ require "gemba/headless"
238
+ require "gemba/headless"
238
239
  received = false
239
240
  conflict_msg = nil
241
+ Gemba.bus = Gemba::EventBus.new
242
+ Gemba.bus.on(:hotkey_changed) { |*| received = true }
240
243
  sw = Gemba::SettingsWindow.new(app, callbacks: {
241
- on_hotkey_change: proc { |*| received = true },
242
244
  on_validate_hotkey: ->(keysym) {
243
245
  # Simulate: 'z' is GBA button A
244
246
  keysym == 'z' ? '"z" is mapped to GBA button A' : nil
@@ -265,11 +267,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
265
267
 
266
268
  def test_hotkey_accepted_when_no_conflict
267
269
  assert_tk_app("hotkey accepted when no conflict") do
268
- require "gemba/settings_window"
269
- require "gemba/hotkey_map"
270
+ require "gemba/headless"
271
+ require "gemba/headless"
270
272
  received_action = nil
273
+ Gemba.bus = Gemba::EventBus.new
274
+ Gemba.bus.on(:hotkey_changed) { |a, _| received_action = a }
271
275
  sw = Gemba::SettingsWindow.new(app, callbacks: {
272
- on_hotkey_change: proc { |a, _| received_action = a },
273
276
  on_validate_hotkey: ->(_) { nil },
274
277
  })
275
278
  sw.show
@@ -290,13 +293,13 @@ class TestMGBASettingsHotkeys < Minitest::Test
290
293
 
291
294
  def test_capture_modifier_then_key_produces_combo
292
295
  assert_tk_app("modifier + key produces combo hotkey") do
293
- require "gemba/settings_window"
294
- require "gemba/hotkey_map"
296
+ require "gemba/headless"
297
+ require "gemba/headless"
295
298
  received_action = nil
296
299
  received_hk = nil
297
- sw = Gemba::SettingsWindow.new(app, callbacks: {
298
- on_hotkey_change: proc { |a, hk| received_action = a; received_hk = hk },
299
- })
300
+ Gemba.bus = Gemba::EventBus.new
301
+ Gemba.bus.on(:hotkey_changed) { |a, hk| received_action = a; received_hk = hk }
302
+ sw = Gemba::SettingsWindow.new(app)
300
303
  sw.show
301
304
  app.update
302
305
 
@@ -319,12 +322,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
319
322
 
320
323
  def test_capture_multi_modifier_combo
321
324
  assert_tk_app("multi-modifier combo (Ctrl+Shift+S)") do
322
- require "gemba/settings_window"
323
- require "gemba/hotkey_map"
325
+ require "gemba/headless"
326
+ require "gemba/headless"
324
327
  received_hk = nil
325
- sw = Gemba::SettingsWindow.new(app, callbacks: {
326
- on_hotkey_change: proc { |_, hk| received_hk = hk },
327
- })
328
+ Gemba.bus = Gemba::EventBus.new
329
+ Gemba.bus.on(:hotkey_changed) { |_, hk| received_hk = hk }
330
+ sw = Gemba::SettingsWindow.new(app)
328
331
  sw.show
329
332
  app.update
330
333
 
@@ -344,11 +347,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
344
347
 
345
348
  def test_combo_hotkey_skips_gamepad_conflict_validation
346
349
  assert_tk_app("combo hotkey skips gamepad conflict validation") do
347
- require "gemba/settings_window"
348
- require "gemba/hotkey_map"
350
+ require "gemba/headless"
351
+ require "gemba/headless"
349
352
  received_hk = nil
353
+ Gemba.bus = Gemba::EventBus.new
354
+ Gemba.bus.on(:hotkey_changed) { |_, hk| received_hk = hk }
350
355
  sw = Gemba::SettingsWindow.new(app, callbacks: {
351
- on_hotkey_change: proc { |_, hk| received_hk = hk },
352
356
  # 'z' conflicts as a plain key, but Ctrl+z should be fine
353
357
  on_validate_hotkey: ->(key) {
354
358
  key == 'z' ? '"z" is mapped to GBA button A' : nil
@@ -370,9 +374,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
370
374
 
371
375
  def test_refresh_hotkeys_shows_combo_display_name
372
376
  assert_tk_app("refresh_hotkeys shows combo display name") do
373
- require "gemba/settings_window"
374
- require "gemba/hotkey_map"
375
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
377
+ require "gemba/headless"
378
+ require "gemba/headless"
379
+ sw = Gemba::SettingsWindow.new(app)
376
380
  sw.show
377
381
  app.update
378
382
 
@@ -387,12 +391,12 @@ class TestMGBASettingsHotkeys < Minitest::Test
387
391
 
388
392
  def test_bind_script_modifier_combo_roundtrip
389
393
  assert_tk_app("Tcl bind script round-trip with modifier+key combo") do
390
- require "gemba/settings_window"
391
- require "gemba/hotkey_map"
394
+ require "gemba/headless"
395
+ require "gemba/headless"
392
396
  received_hk = nil
393
- sw = Gemba::SettingsWindow.new(app, callbacks: {
394
- on_hotkey_change: proc { |_, hk| received_hk = hk },
395
- })
397
+ Gemba.bus = Gemba::EventBus.new
398
+ Gemba.bus.on(:hotkey_changed) { |_, hk| received_hk = hk }
399
+ sw = Gemba::SettingsWindow.new(app)
396
400
  sw.show
397
401
  app.update
398
402
 
@@ -421,9 +425,9 @@ class TestMGBASettingsHotkeys < Minitest::Test
421
425
 
422
426
  def test_record_hotkey_button_shows_default
423
427
  assert_tk_app("record hotkey button shows F10") do
424
- require "gemba/settings_window"
425
- require "gemba/hotkey_map"
426
- sw = Gemba::SettingsWindow.new(app, callbacks: {})
428
+ require "gemba/headless"
429
+ require "gemba/headless"
430
+ sw = Gemba::SettingsWindow.new(app)
427
431
  sw.show
428
432
  app.update
429
433
 
@@ -431,4 +435,17 @@ class TestMGBASettingsHotkeys < Minitest::Test
431
435
  assert_equal 'F10', text
432
436
  end
433
437
  end
438
+
439
+ def test_open_rom_hotkey_button_shows_default
440
+ assert_tk_app("open rom hotkey button shows Ctrl+O") do
441
+ require "gemba/headless"
442
+ require "gemba/headless"
443
+ sw = Gemba::SettingsWindow.new(app)
444
+ sw.show
445
+ app.update
446
+
447
+ text = app.command(Gemba::SettingsWindow::HK_ACTIONS[:open_rom], 'cget', '-text')
448
+ assert_equal 'Ctrl+O', text
449
+ end
450
+ end
434
451
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "shared/tk_test_helper"
5
+
6
+ class TestSettingsRecordingTab < Minitest::Test
7
+ include TeekTestHelper
8
+
9
+ def test_recording_tab_exists
10
+ assert_tk_app("recording tab exists in notebook") do
11
+ require "gemba/headless"
12
+ sw = Gemba::SettingsWindow.new(app)
13
+ sw.show
14
+ app.update
15
+
16
+ tabs = app.command(Gemba::Settings::Paths::NB, 'tabs')
17
+ assert_includes tabs, Gemba::Settings::RecordingTab::FRAME
18
+ end
19
+ end
20
+
21
+ def test_compression_combobox_defaults_to_1
22
+ assert_tk_app("compression combobox defaults to 1") do
23
+ require "gemba/headless"
24
+ sw = Gemba::SettingsWindow.new(app)
25
+ sw.show
26
+ app.update
27
+
28
+ assert_equal '1', app.get_variable(Gemba::Settings::RecordingTab::VAR_COMPRESSION)
29
+ end
30
+ end
31
+
32
+ def test_selecting_compression_fires_callback
33
+ assert_tk_app("selecting compression fires on_compression_change") do
34
+ require "gemba/headless"
35
+ received = nil
36
+ Gemba.bus = Gemba::EventBus.new
37
+ Gemba.bus.on(:compression_changed) { |v| received = v }
38
+ sw = Gemba::SettingsWindow.new(app)
39
+ sw.show
40
+ app.update
41
+
42
+ app.set_variable(Gemba::Settings::RecordingTab::VAR_COMPRESSION, '6')
43
+ app.command(:event, 'generate', Gemba::Settings::RecordingTab::COMPRESSION_COMBO, '<<ComboboxSelected>>')
44
+ app.update
45
+
46
+ assert_equal 6, received
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "shared/tk_test_helper"
5
+
6
+ class TestSettingsSaveStatesTab < Minitest::Test
7
+ include TeekTestHelper
8
+
9
+ def test_save_states_tab_exists
10
+ assert_tk_app("save states tab exists in notebook") do
11
+ require "gemba/headless"
12
+ sw = Gemba::SettingsWindow.new(app)
13
+ sw.show
14
+ app.update
15
+
16
+ tabs = app.command(Gemba::Settings::Paths::NB, 'tabs')
17
+ assert_includes tabs, Gemba::Settings::SaveStatesTab::FRAME
18
+ end
19
+ end
20
+
21
+ def test_quick_slot_defaults_to_1
22
+ assert_tk_app("quick slot defaults to 1") do
23
+ require "gemba/headless"
24
+ sw = Gemba::SettingsWindow.new(app)
25
+ sw.show
26
+ app.update
27
+
28
+ assert_equal '1', app.get_variable(Gemba::Settings::SaveStatesTab::VAR_QUICK_SLOT)
29
+ end
30
+ end
31
+
32
+ def test_selecting_slot_fires_callback
33
+ assert_tk_app("selecting slot fires on_quick_slot_change") do
34
+ require "gemba/headless"
35
+ received = nil
36
+ Gemba.bus = Gemba::EventBus.new
37
+ Gemba.bus.on(:quick_slot_changed) { |v| received = v }
38
+ sw = Gemba::SettingsWindow.new(app)
39
+ sw.show
40
+ app.update
41
+
42
+ app.set_variable(Gemba::Settings::SaveStatesTab::VAR_QUICK_SLOT, '5')
43
+ app.command(:event, 'generate', Gemba::Settings::SaveStatesTab::SLOT_COMBO, '<<ComboboxSelected>>')
44
+ app.update
45
+
46
+ assert_equal 5, received
47
+ end
48
+ end
49
+
50
+ def test_backup_defaults_to_on
51
+ assert_tk_app("backup checkbox defaults to on") do
52
+ require "gemba/headless"
53
+ sw = Gemba::SettingsWindow.new(app)
54
+ sw.show
55
+ app.update
56
+
57
+ assert_equal '1', app.get_variable(Gemba::Settings::SaveStatesTab::VAR_BACKUP)
58
+ end
59
+ end
60
+
61
+ def test_clicking_backup_fires_callback
62
+ assert_tk_app("clicking backup fires on_backup_change") do
63
+ require "gemba/headless"
64
+ received = nil
65
+ Gemba.bus = Gemba::EventBus.new
66
+ Gemba.bus.on(:backup_changed) { |v| received = v }
67
+ sw = Gemba::SettingsWindow.new(app)
68
+ sw.show
69
+ app.update
70
+
71
+ app.command(Gemba::Settings::SaveStatesTab::BACKUP_CHECK, 'invoke')
72
+ app.update
73
+
74
+ assert_equal false, received
75
+ end
76
+ end
77
+
78
+ def test_clicking_backup_twice_re_enables
79
+ assert_tk_app("clicking backup twice re-enables") do
80
+ require "gemba/headless"
81
+ received = nil
82
+ Gemba.bus = Gemba::EventBus.new
83
+ Gemba.bus.on(:backup_changed) { |v| received = v }
84
+ sw = Gemba::SettingsWindow.new(app)
85
+ sw.show
86
+ app.update
87
+
88
+ app.command(Gemba::Settings::SaveStatesTab::BACKUP_CHECK, 'invoke')
89
+ app.update
90
+ assert_equal false, received
91
+
92
+ app.command(Gemba::Settings::SaveStatesTab::BACKUP_CHECK, 'invoke')
93
+ app.update
94
+ assert_equal true, received
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require_relative "shared/tk_test_helper"
5
+
6
+ class TestSettingsSystem < Minitest::Test
7
+ include TeekTestHelper
8
+
9
+ FAKE_BIOS = File.expand_path("fixtures/fake_bios.bin", __dir__)
10
+
11
+ # -- tab presence -----------------------------------------------------------
12
+
13
+ def test_system_tab_exists_in_notebook
14
+ assert_tk_app("system tab exists in notebook") do
15
+ require "gemba/headless"
16
+ Gemba.bus = Gemba::EventBus.new
17
+ sw = Gemba::SettingsWindow.new(app)
18
+ sw.show
19
+ app.update
20
+
21
+ tabs = app.command(Gemba::SettingsWindow::NB, 'tabs').split
22
+ assert_includes tabs, Gemba::SettingsWindow::SYSTEM_TAB
23
+ end
24
+ end
25
+
26
+ # -- initial state ----------------------------------------------------------
27
+
28
+ def test_bios_path_empty_initially
29
+ assert_tk_app("bios path var is empty initially") do
30
+ require "gemba/headless"
31
+ Gemba.bus = Gemba::EventBus.new
32
+ sw = Gemba::SettingsWindow.new(app)
33
+ sw.show
34
+ app.update
35
+
36
+ val = app.get_variable(Gemba::SettingsWindow::VAR_BIOS_PATH)
37
+ assert_equal '', val
38
+ end
39
+ end
40
+
41
+ def test_skip_bios_unchecked_initially
42
+ assert_tk_app("skip bios checkbox is unchecked initially") do
43
+ require "gemba/headless"
44
+ Gemba.bus = Gemba::EventBus.new
45
+ sw = Gemba::SettingsWindow.new(app)
46
+ sw.show
47
+ app.update
48
+
49
+ val = app.get_variable(Gemba::SettingsWindow::VAR_SKIP_BIOS)
50
+ assert_equal '0', val
51
+ end
52
+ end
53
+
54
+ # -- skip_bios checkbox marks dirty ----------------------------------------
55
+
56
+ def test_skip_bios_toggle_marks_save_dirty
57
+ assert_tk_app("toggling skip bios enables the save button") do
58
+ require "gemba/headless"
59
+ Gemba.bus = Gemba::EventBus.new
60
+ sw = Gemba::SettingsWindow.new(app)
61
+ sw.show
62
+
63
+ # Navigate to system tab
64
+ app.command(Gemba::SettingsWindow::NB, 'select', Gemba::SettingsWindow::SYSTEM_TAB)
65
+ app.update
66
+
67
+ app.command(Gemba::Settings::SystemTab::SKIP_BIOS_CHECK, 'invoke')
68
+ app.update
69
+
70
+ state = app.command(Gemba::SettingsWindow::SAVE_BTN, :cget, '-state').to_s
71
+ assert_equal 'normal', state
72
+ end
73
+ end
74
+
75
+ # -- clear button -----------------------------------------------------------
76
+
77
+ def test_clear_button_empties_bios_path
78
+ assert_tk_app("clear button empties the bios path variable") do
79
+ require "gemba/headless"
80
+ Gemba.bus = Gemba::EventBus.new
81
+ sw = Gemba::SettingsWindow.new(app)
82
+ sw.show
83
+ app.update
84
+
85
+ # Seed a value
86
+ app.set_variable(Gemba::SettingsWindow::VAR_BIOS_PATH, 'gba_bios.bin')
87
+ app.command(Gemba::Settings::SystemTab::BIOS_CLEAR, 'invoke')
88
+ app.update
89
+
90
+ assert_equal '', app.get_variable(Gemba::SettingsWindow::VAR_BIOS_PATH)
91
+ end
92
+ end
93
+
94
+ def test_clear_button_marks_save_dirty
95
+ assert_tk_app("clear button enables the save button") do
96
+ require "gemba/headless"
97
+ Gemba.bus = Gemba::EventBus.new
98
+ sw = Gemba::SettingsWindow.new(app)
99
+ sw.show
100
+ app.update
101
+
102
+ app.command(Gemba::Settings::SystemTab::BIOS_CLEAR, 'invoke')
103
+ app.update
104
+
105
+ state = app.command(Gemba::SettingsWindow::SAVE_BTN, :cget, '-state').to_s
106
+ assert_equal 'normal', state
107
+ end
108
+ end
109
+
110
+ def test_clear_resets_status_label
111
+ assert_tk_app("clear button resets status label to not-set text") do
112
+ require "gemba/headless"
113
+ Gemba::Locale.load('en')
114
+ Gemba.bus = Gemba::EventBus.new
115
+ sw = Gemba::SettingsWindow.new(app)
116
+ sw.show
117
+ app.update
118
+
119
+ app.command(Gemba::Settings::SystemTab::BIOS_CLEAR, 'invoke')
120
+ app.update
121
+
122
+ text = app.command(Gemba::Settings::SystemTab::BIOS_STATUS, :cget, '-text').to_s
123
+ assert_includes text, 'Not set'
124
+ end
125
+ end
126
+
127
+ # -- system tab is per-game eligible ----------------------------------------
128
+
129
+ def test_system_tab_in_per_game_tabs
130
+ require "gemba/headless"
131
+ assert Gemba::SettingsWindow::PER_GAME_TABS.include?(Gemba::SettingsWindow::SYSTEM_TAB)
132
+ end
133
+ end