railmux 0.3.2.dev2__tar.gz → 0.3.2.dev5__tar.gz

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 (192) hide show
  1. {railmux-0.3.2.dev2/src/railmux.egg-info → railmux-0.3.2.dev5}/PKG-INFO +1 -1
  2. railmux-0.3.2.dev5/src/railmux/__init__.py +1 -0
  3. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/diagnostics.py +4 -0
  4. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/discovery.py +2 -2
  5. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/fast_display_client.py +32 -22
  6. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/fast_display_input.py +5 -2
  7. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/favorites.py +2 -2
  8. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/legacy_sessions.py +2 -0
  9. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/mux/tmux_backend.py +2 -0
  10. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/renames.py +2 -2
  11. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/runtime_config.py +4 -0
  12. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/self_update.py +2 -0
  13. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/session_index.py +1 -1
  14. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/settings.py +1 -1
  15. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/tmux_binding_manager.py +106 -3
  16. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/tmux_ctl.py +407 -4
  17. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/tool_panes.py +2 -0
  18. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/app.py +157 -4
  19. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5/src/railmux.egg-info}/PKG-INFO +1 -1
  20. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux.egg-info/SOURCES.txt +1 -0
  21. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_fast_full_window.py +50 -17
  22. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_platform.py +106 -0
  23. railmux-0.3.2.dev5/tests/test_termux_local_touch.py +120 -0
  24. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tmux_binding_manager.py +81 -4
  25. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tmux_ctl.py +121 -0
  26. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tmux_integration.py +150 -0
  27. railmux-0.3.2.dev2/src/railmux/__init__.py +0 -1
  28. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/LICENSE +0 -0
  29. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/MANIFEST.in +0 -0
  30. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/README.md +0 -0
  31. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/pyproject.toml +0 -0
  32. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/setup.cfg +0 -0
  33. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/__main__.py +0 -0
  34. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/atomic_file.py +0 -0
  35. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/background_index.py +0 -0
  36. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/cli.py +0 -0
  37. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/codex_index.py +0 -0
  38. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/config.py +0 -0
  39. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/config_cli.py +0 -0
  40. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/display_transport.py +0 -0
  41. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/fast_display_history.py +0 -0
  42. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/fast_display_protocol.py +0 -0
  43. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/fast_display_server.py +0 -0
  44. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/fuzzy.py +0 -0
  45. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/help_workspace.py +0 -0
  46. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/launcher.py +0 -0
  47. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/local_clipboard.py +0 -0
  48. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/local_open.py +0 -0
  49. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/models.py +0 -0
  50. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/modes.py +0 -0
  51. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/mouse_manager.py +0 -0
  52. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/mux/__init__.py +0 -0
  53. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/mux/backend.py +0 -0
  54. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/orphan_marker.py +0 -0
  55. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/pane_surface.py +0 -0
  56. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/path_codec.py +0 -0
  57. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/__init__.py +0 -0
  58. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/config_paths.py +0 -0
  59. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/console.py +0 -0
  60. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/file_security.py +0 -0
  61. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/filelock.py +0 -0
  62. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/multiplex.py +0 -0
  63. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/process.py +0 -0
  64. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/platform/runtime_paths.py +0 -0
  65. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/remote_config.py +0 -0
  66. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/restart_state.py +0 -0
  67. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/scroll_agent.py +0 -0
  68. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/scroll_manager.py +0 -0
  69. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/selection_isolation.py +0 -0
  70. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/session_cache.py +0 -0
  71. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/setting_contracts.py +0 -0
  72. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ssh_args.py +0 -0
  73. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ssh_compat.py +0 -0
  74. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ssh_display_diagnostics.py +0 -0
  75. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ssh_doctor.py +0 -0
  76. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/system_deps.py +0 -0
  77. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/terminal_emulator.py +0 -0
  78. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/terminal_status.py +0 -0
  79. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/tmux_health.py +0 -0
  80. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/tmux_server.py +0 -0
  81. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/transcript.py +0 -0
  82. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/__init__.py +0 -0
  83. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/_widgets.py +0 -0
  84. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/keymap.py +0 -0
  85. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/modals.py +0 -0
  86. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/projects_pane.py +0 -0
  87. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/running_pane.py +0 -0
  88. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/sessions_pane.py +0 -0
  89. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/sidebar.py +0 -0
  90. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/statusbar.py +0 -0
  91. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/ui/workspace.py +0 -0
  92. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/__init__.py +0 -0
  93. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/backend.py +0 -0
  94. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/client.py +0 -0
  95. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/compositor.py +0 -0
  96. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/conpty.py +0 -0
  97. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/daemon.py +0 -0
  98. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/history_viewer.py +0 -0
  99. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/ipc.py +0 -0
  100. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/session_store.py +0 -0
  101. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux/winlocal/virtual_screen.py +0 -0
  102. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux.egg-info/dependency_links.txt +0 -0
  103. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux.egg-info/entry_points.txt +0 -0
  104. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux.egg-info/requires.txt +0 -0
  105. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/src/railmux.egg-info/top_level.txt +0 -0
  106. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_agent_workspace.py +0 -0
  107. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_atomic_file.py +0 -0
  108. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_background_index.py +0 -0
  109. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_cli.py +0 -0
  110. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_codex_delete.py +0 -0
  111. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_codex_index.py +0 -0
  112. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_codex_lineage.py +0 -0
  113. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_codex_transcript.py +0 -0
  114. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_config.py +0 -0
  115. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_config_cli.py +0 -0
  116. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_denested_transport_benchmark.py +0 -0
  117. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_diagnostics.py +0 -0
  118. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_discovery.py +0 -0
  119. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_display_transport.py +0 -0
  120. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_focus_highlight.py +0 -0
  121. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_fullscreen_binding.py +0 -0
  122. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_fuzzy.py +0 -0
  123. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_help_agent.py +0 -0
  124. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_help_workspace.py +0 -0
  125. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_history_cleanup.py +0 -0
  126. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_keymap.py +0 -0
  127. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_launcher.py +0 -0
  128. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_layout_profile.py +0 -0
  129. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_legacy_sessions.py +0 -0
  130. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_local_clipboard.py +0 -0
  131. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_local_open.py +0 -0
  132. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_modals.py +0 -0
  133. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_mode_selection.py +0 -0
  134. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_modes.py +0 -0
  135. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_mouse_manager.py +0 -0
  136. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_mux_backend.py +0 -0
  137. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_new_project.py +0 -0
  138. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_options.py +0 -0
  139. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_orphan_recovery.py +0 -0
  140. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_pane_surface.py +0 -0
  141. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_path_codec.py +0 -0
  142. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_projects_pane.py +0 -0
  143. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_release_notes.py +0 -0
  144. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_release_workflow.py +0 -0
  145. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_remote_config.py +0 -0
  146. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_renames.py +0 -0
  147. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_restart_state.py +0 -0
  148. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_running_pane.py +0 -0
  149. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_running_sort.py +0 -0
  150. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_runtime_config.py +0 -0
  151. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_scroll_agent.py +0 -0
  152. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_scroll_manager.py +0 -0
  153. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_selection_isolation.py +0 -0
  154. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_self_update.py +0 -0
  155. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_session_cache.py +0 -0
  156. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_session_index.py +0 -0
  157. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_sessions_pane.py +0 -0
  158. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_setting_contracts.py +0 -0
  159. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_settings.py +0 -0
  160. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_sidebar.py +0 -0
  161. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_soft_quit.py +0 -0
  162. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_ssh_args.py +0 -0
  163. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_ssh_compat.py +0 -0
  164. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_ssh_display_benchmark.py +0 -0
  165. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_ssh_display_diagnostics.py +0 -0
  166. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_ssh_doctor.py +0 -0
  167. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_status.py +0 -0
  168. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_statusbar.py +0 -0
  169. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_system_deps.py +0 -0
  170. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_terminal_size.py +0 -0
  171. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_terminal_status.py +0 -0
  172. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tmux_health.py +0 -0
  173. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tmux_server.py +0 -0
  174. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tmux_status.py +0 -0
  175. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_tool_panes.py +0 -0
  176. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_transcript.py +0 -0
  177. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_web_demo_recorder.py +0 -0
  178. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_wheel_smoke.py +0 -0
  179. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_widgets.py +0 -0
  180. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_backend.py +0 -0
  181. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_compositor.py +0 -0
  182. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_conpty.py +0 -0
  183. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_daemon.py +0 -0
  184. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_history_viewer.py +0 -0
  185. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_ipc.py +0 -0
  186. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_real_conpty.py +0 -0
  187. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_session_store.py +0 -0
  188. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_winlocal_virtual_screen.py +0 -0
  189. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tests/test_yolo_prompt.py +0 -0
  190. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tools/denested_transport_benchmark.py +0 -0
  191. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tools/release_notes.py +0 -0
  192. {railmux-0.3.2.dev2 → railmux-0.3.2.dev5}/tools/wheel_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: railmux
3
- Version: 0.3.2.dev2
3
+ Version: 0.3.2.dev5
4
4
  Summary: Terminal UI to navigate, resume, and start Claude Code and Codex sessions
5
5
  Author-email: Rightglow <zhang.taian@foxmail.com>
6
6
  License-Expression: MIT
@@ -0,0 +1 @@
1
+ __version__ = "0.3.2.dev5"
@@ -151,6 +151,8 @@ def _tool_diagnostic(
151
151
  env=environ,
152
152
  capture_output=True,
153
153
  text=True,
154
+ encoding="utf-8",
155
+ errors="replace",
154
156
  timeout=3,
155
157
  check=False,
156
158
  )
@@ -300,6 +302,8 @@ def collect_doctor_snapshot(
300
302
  env=effective_env,
301
303
  capture_output=True,
302
304
  text=True,
305
+ encoding="utf-8",
306
+ errors="replace",
303
307
  timeout=2,
304
308
  check=False,
305
309
  )
@@ -43,9 +43,9 @@ def _path_cache_file() -> Path:
43
43
 
44
44
  def _load_path_cache() -> dict[str, str]:
45
45
  try:
46
- data = json.loads(_path_cache_file().read_text())
46
+ data = json.loads(_path_cache_file().read_text(encoding="utf-8"))
47
47
  return data if isinstance(data, dict) else {}
48
- except (OSError, json.JSONDecodeError):
48
+ except (OSError, UnicodeError, json.JSONDecodeError):
49
49
  return {}
50
50
 
51
51
 
@@ -45,6 +45,7 @@ from railmux.fast_display_input import (
45
45
  SelectionSegment,
46
46
  SgrMouseEvent,
47
47
  TermuxTouchKeyboard,
48
+ TouchKeyboardAction,
48
49
  TerminalInputDecoder,
49
50
  is_termux_environment,
50
51
  page_key_direction,
@@ -223,6 +224,33 @@ def compact_status_row(screen: AppliedScreen) -> int | None:
223
224
  return None
224
225
 
225
226
 
227
+ def termux_prompt_touch_action(
228
+ touch_keyboard: TermuxTouchKeyboard,
229
+ event: SgrMouseEvent,
230
+ history: LocalHistoryView,
231
+ screen: AppliedScreen | None,
232
+ *,
233
+ now: float | None = None,
234
+ ) -> TouchKeyboardAction:
235
+ """Classify one translated Termux tap against live screen route authority."""
236
+ if screen is None:
237
+ return TouchKeyboardAction()
238
+ clicked_pane_id = history.pane_id_at_position(event.x - 1, event.y - 1)
239
+ cursor_pane_id = history.pane_id_at_position(
240
+ screen.cursor_x,
241
+ screen.cursor_y,
242
+ )
243
+ return touch_keyboard.pointer_event(
244
+ event,
245
+ clicked_pane_id=clicked_pane_id,
246
+ cursor_pane_id=cursor_pane_id,
247
+ cursor_y=screen.cursor_y,
248
+ pane_frozen=history.pane_is_frozen(clicked_pane_id),
249
+ navigation_row=compact_status_row(screen),
250
+ now=now,
251
+ )
252
+
253
+
226
254
  class ProbeError(RuntimeError):
227
255
  """A bounded, user-facing SSH display failure."""
228
256
 
@@ -2723,34 +2751,16 @@ def run(args: argparse.Namespace) -> int:
2723
2751
  latest_screen.cursor_x, latest_screen.cursor_y
2724
2752
  )
2725
2753
  )
2726
- clicked_pane_id = (
2727
- None
2728
- if latest_screen is None
2729
- else history.pane_id_at_position(part.x - 1, part.y - 1)
2730
- )
2731
- cursor_pane_id = (
2732
- None
2733
- if latest_screen is None
2734
- else history.pane_id_at_position(
2735
- latest_screen.cursor_x,
2736
- latest_screen.cursor_y,
2737
- )
2738
- )
2739
2754
  status_row = (
2740
2755
  compact_status_row(latest_screen)
2741
2756
  if latest_screen is not None
2742
2757
  else None
2743
2758
  )
2744
- touch_action = touch_keyboard.pointer_event(
2759
+ touch_action = termux_prompt_touch_action(
2760
+ touch_keyboard,
2745
2761
  part,
2746
- clicked_pane_id=clicked_pane_id,
2747
- cursor_pane_id=cursor_pane_id,
2748
- cursor_y=0 if latest_screen is None else latest_screen.cursor_y,
2749
- cursor_visible=(
2750
- False if latest_screen is None else latest_screen.cursor_visible
2751
- ),
2752
- pane_frozen=history.pane_is_frozen(clicked_pane_id),
2753
- navigation_row=status_row,
2762
+ history,
2763
+ latest_screen,
2754
2764
  now=time.monotonic(),
2755
2765
  )
2756
2766
  if touch_action.suspend_mouse:
@@ -129,7 +129,6 @@ class TermuxTouchKeyboard:
129
129
  clicked_pane_id: str | None,
130
130
  cursor_pane_id: str | None,
131
131
  cursor_y: int,
132
- cursor_visible: bool,
133
132
  pane_frozen: bool,
134
133
  navigation_row: int | None = None,
135
134
  now: float | None = None,
@@ -150,7 +149,6 @@ class TermuxTouchKeyboard:
150
149
  or self._active
151
150
  or self._close_reassert_pending
152
151
  or pane_frozen
153
- or not cursor_visible
154
152
  or clicked_pane_id is None
155
153
  or clicked_pane_id != cursor_pane_id
156
154
  or (navigation_row is not None and event.y == navigation_row)
@@ -158,6 +156,11 @@ class TermuxTouchKeyboard:
158
156
  or not self._is_plain_left_press(event)
159
157
  ):
160
158
  return TouchKeyboardAction()
159
+ # DEC cursor visibility is presentation state, not prompt authority.
160
+ # Providers may hide the hardware cursor while retaining its exact
161
+ # input coordinates. The verified live-agent route, matching cursor
162
+ # route, unfrozen viewport, and bounded row distance remain the
163
+ # fail-closed authority for yielding one Termux tap.
161
164
  requested_at = time.monotonic() if now is None else now
162
165
  # A rapid reopen supersedes the delayed ownership reassertion queued by
163
166
  # the previous close; Termux must receive this new native prompt tap.
@@ -29,10 +29,10 @@ class Favorites:
29
29
  if not self._path.is_file():
30
30
  return
31
31
  try:
32
- data = json.loads(self._path.read_text())
32
+ data = json.loads(self._path.read_text(encoding="utf-8"))
33
33
  if isinstance(data, list):
34
34
  self._ids = set(data)
35
- except (json.JSONDecodeError, OSError):
35
+ except (json.JSONDecodeError, OSError, UnicodeError):
36
36
  self._ids = set()
37
37
 
38
38
  def _save(self) -> None:
@@ -59,6 +59,8 @@ def discover(
59
59
  tmux_server.target_argv(target, "list-sessions", "-F", fmt),
60
60
  stderr=subprocess.DEVNULL,
61
61
  text=True,
62
+ encoding="utf-8",
63
+ errors="replace",
62
64
  timeout=timeout,
63
65
  env=None if env is None else dict(env),
64
66
  )
@@ -79,6 +79,8 @@ class TmuxBackend:
79
79
  stdout=subprocess.PIPE,
80
80
  stderr=subprocess.DEVNULL,
81
81
  text=True,
82
+ encoding="utf-8",
83
+ errors="replace",
82
84
  )
83
85
  except OSError:
84
86
  return None
@@ -32,8 +32,8 @@ class Renames:
32
32
  if not self._path.is_file():
33
33
  return
34
34
  try:
35
- data = json.loads(self._path.read_text())
36
- except (json.JSONDecodeError, OSError):
35
+ data = json.loads(self._path.read_text(encoding="utf-8"))
36
+ except (json.JSONDecodeError, OSError, UnicodeError):
37
37
  return
38
38
  if isinstance(data, dict):
39
39
  self._titles = {
@@ -125,6 +125,8 @@ def check_executable(
125
125
  env=None if environ is None else dict(environ),
126
126
  capture_output=True,
127
127
  text=True,
128
+ encoding="utf-8",
129
+ errors="replace",
128
130
  timeout=timeout,
129
131
  check=False,
130
132
  )
@@ -168,6 +170,8 @@ def check_utf8_locale(
168
170
  env=env,
169
171
  capture_output=True,
170
172
  text=True,
173
+ encoding="utf-8",
174
+ errors="replace",
171
175
  timeout=timeout,
172
176
  check=False,
173
177
  )
@@ -88,6 +88,8 @@ def fresh_process_version(*, timeout: float = 10.0) -> str | None:
88
88
  ],
89
89
  capture_output=True,
90
90
  text=True,
91
+ encoding="utf-8",
92
+ errors="replace",
91
93
  timeout=timeout,
92
94
  check=False,
93
95
  )
@@ -92,7 +92,7 @@ def _scan_session(project: Project, jsonl_path: Path) -> SessionMeta | None:
92
92
  last_stop_reason: str = "" # only set for assistant records
93
93
 
94
94
  try:
95
- with jsonl_path.open("r") as f:
95
+ with jsonl_path.open("r", encoding="utf-8", errors="replace") as f:
96
96
  for line in f:
97
97
  line = line.strip()
98
98
  if not line:
@@ -110,7 +110,7 @@ class Settings:
110
110
 
111
111
  def _read_document(self):
112
112
  try:
113
- text = self._path.read_text()
113
+ text = self._path.read_text(encoding="utf-8")
114
114
  except FileNotFoundError:
115
115
  return tomlkit.document()
116
116
  except (OSError, UnicodeError):
@@ -14,7 +14,7 @@ from railmux.atomic_file import atomic_write_text
14
14
  from railmux.platform.filelock import try_lock, unlock
15
15
 
16
16
 
17
- _VERSION = 7
17
+ _VERSION = 8
18
18
  _KEYS = ("F8", "F9")
19
19
  _MAX_STATE_BYTES = 64 * 1024
20
20
 
@@ -34,6 +34,7 @@ class SharedTmuxBindingManager:
34
34
  self._prefix_tab_managed = False
35
35
  self._right_click_managed = False
36
36
  self._status_click_managed = False
37
+ self._termux_tap_managed = False
37
38
  self._selection_hook_managed = False
38
39
  self._selection_hook_index: int | None = None
39
40
 
@@ -52,6 +53,11 @@ class SharedTmuxBindingManager:
52
53
  """Whether pane ranges in this instance's status bar are clickable."""
53
54
  return self._registered and self._status_click_managed
54
55
 
56
+ @property
57
+ def termux_tap_available(self) -> bool:
58
+ """Whether direct local Termux prompt taps are safely wrapped."""
59
+ return self._registered and self._termux_tap_managed
60
+
55
61
  @contextmanager
56
62
  def _locked(self) -> Iterator[None]:
57
63
  root = restart_state.runtime_state_dir()
@@ -84,7 +90,7 @@ class SharedTmuxBindingManager:
84
90
  except (OSError, ValueError, json.JSONDecodeError):
85
91
  return None
86
92
  if (not isinstance(raw, dict)
87
- or raw.get("version") not in {1, 2, 3, 4, 5, 6, _VERSION}):
93
+ or raw.get("version") not in {1, 2, 3, 4, 5, 6, 7, _VERSION}):
88
94
  return None
89
95
  token = raw.get("token")
90
96
  phase = raw.get("phase")
@@ -96,6 +102,8 @@ class SharedTmuxBindingManager:
96
102
  right_click_managed = raw.get("right_click_managed")
97
103
  status_click_backup = raw.get("status_click_backup")
98
104
  status_click_managed = raw.get("status_click_managed")
105
+ termux_tap_backup = raw.get("termux_tap_backup")
106
+ termux_tap_managed = raw.get("termux_tap_managed")
99
107
  selection_hook_managed = raw.get("selection_hook_managed")
100
108
  selection_hook_index = raw.get("selection_hook_index")
101
109
  prefix_valid = (
@@ -116,6 +124,12 @@ class SharedTmuxBindingManager:
116
124
  and all(value is None or isinstance(value, str)
117
125
  for value in status_click_backup.values())
118
126
  )
127
+ termux_tap_valid = (
128
+ isinstance(termux_tap_backup, dict)
129
+ and set(termux_tap_backup) == {"MouseDown1Pane"}
130
+ and all(value is None or isinstance(value, str)
131
+ for value in termux_tap_backup.values())
132
+ )
119
133
  if (not isinstance(token, str) or not token or len(token) > 64
120
134
  or phase not in {"installing", "active"}
121
135
  or not isinstance(owners, dict) or len(owners) > 1024
@@ -140,7 +154,10 @@ class SharedTmuxBindingManager:
140
154
  and selection_hook_index is not None)))
141
155
  or (raw["version"] >= 5
142
156
  and (not status_click_valid
143
- or not isinstance(status_click_managed, bool)))):
157
+ or not isinstance(status_click_managed, bool)))
158
+ or (raw["version"] >= 8
159
+ and (not termux_tap_valid
160
+ or not isinstance(termux_tap_managed, bool)))):
144
161
  return None
145
162
  return raw
146
163
 
@@ -265,6 +282,21 @@ class SharedTmuxBindingManager:
265
282
  # v7 replaces Mode/Layout's ordinary m/F8 keystrokes
266
283
  # with edit- and modal-safe internal function keys.
267
284
  upgraded = True
285
+ if state["version"] < 8:
286
+ termux_tap_backup = (
287
+ tmux_ctl.prepare_root_termux_tap_binding()
288
+ )
289
+ # v8 adds a stock-left-click-only Termux prompt
290
+ # handoff. Persist its exact original before the
291
+ # server-global root-table binding is replaced.
292
+ state["termux_tap_managed"] = (
293
+ termux_tap_backup is not None
294
+ )
295
+ state["termux_tap_backup"] = (
296
+ termux_tap_backup
297
+ or {"MouseDown1Pane": None}
298
+ )
299
+ upgraded = True
268
300
  if upgraded:
269
301
  state["version"] = _VERSION
270
302
  state["phase"] = "installing"
@@ -325,6 +357,21 @@ class SharedTmuxBindingManager:
325
357
  token,
326
358
  )
327
359
  )
360
+ if state["termux_tap_managed"]:
361
+ current_tap = (
362
+ tmux_ctl.read_root_termux_tap_binding()
363
+ ["MouseDown1Pane"]
364
+ )
365
+ safe = (
366
+ safe
367
+ and tmux_ctl.root_termux_tap_binding_is_original_or_owned(
368
+ current_tap,
369
+ state["termux_tap_backup"].get(
370
+ "MouseDown1Pane"
371
+ ),
372
+ token,
373
+ )
374
+ )
328
375
  if not safe:
329
376
  if state["owners"]:
330
377
  return False
@@ -342,6 +389,9 @@ class SharedTmuxBindingManager:
342
389
  if state["status_click_managed"]:
343
390
  tmux_ctl.restore_root_status_click_binding(
344
391
  state["status_click_backup"], token=token)
392
+ if state["termux_tap_managed"]:
393
+ tmux_ctl.restore_root_termux_tap_binding(
394
+ state["termux_tap_backup"], token=token)
345
395
  self._remove_state()
346
396
  state = None
347
397
  else:
@@ -381,6 +431,14 @@ class SharedTmuxBindingManager:
381
431
  state["status_click_managed"] = False
382
432
  if not self._save(state):
383
433
  return False
434
+ if (state["termux_tap_managed"]
435
+ and not tmux_ctl.set_root_termux_tap_forwarding(
436
+ state["termux_tap_backup"], token)):
437
+ tmux_ctl.restore_root_termux_tap_binding(
438
+ state["termux_tap_backup"], token=token)
439
+ state["termux_tap_managed"] = False
440
+ if not self._save(state):
441
+ return False
384
442
  state["phase"] = "active"
385
443
  elif not (
386
444
  tmux_ctl.root_function_bindings_owned_by(token)
@@ -402,6 +460,10 @@ class SharedTmuxBindingManager:
402
460
  or tmux_ctl.root_status_click_binding_owned_by(
403
461
  token)
404
462
  )
463
+ and (
464
+ not state["termux_tap_managed"]
465
+ or tmux_ctl.root_termux_tap_binding_owned_by(token)
466
+ )
405
467
  ):
406
468
  if state["owners"]:
407
469
  return False
@@ -419,6 +481,9 @@ class SharedTmuxBindingManager:
419
481
  if state["status_click_managed"]:
420
482
  tmux_ctl.restore_root_status_click_binding(
421
483
  state["status_click_backup"], token=token)
484
+ if state["termux_tap_managed"]:
485
+ tmux_ctl.restore_root_termux_tap_binding(
486
+ state["termux_tap_backup"], token=token)
422
487
  self._remove_state()
423
488
  state = None
424
489
  if state is not None:
@@ -451,6 +516,8 @@ class SharedTmuxBindingManager:
451
516
  state["selection_hook_index"])
452
517
  self._status_click_managed = (
453
518
  state["status_click_managed"])
519
+ self._termux_tap_managed = (
520
+ state["termux_tap_managed"])
454
521
  return True
455
522
 
456
523
  backup = tmux_ctl.prepare_root_function_bindings()
@@ -460,6 +527,8 @@ class SharedTmuxBindingManager:
460
527
  selection_hook_index = tmux_ctl.prepare_selection_mode_hook()
461
528
  status_click_backup = (
462
529
  tmux_ctl.prepare_root_status_click_binding())
530
+ termux_tap_backup = (
531
+ tmux_ctl.prepare_root_termux_tap_binding())
463
532
  if backup is None:
464
533
  return False
465
534
  token = secrets.token_hex(8)
@@ -479,6 +548,9 @@ class SharedTmuxBindingManager:
479
548
  "status_click_managed": status_click_backup is not None,
480
549
  "status_click_backup": (
481
550
  status_click_backup or {"MouseDown1Status": None}),
551
+ "termux_tap_managed": termux_tap_backup is not None,
552
+ "termux_tap_backup": (
553
+ termux_tap_backup or {"MouseDown1Pane": None}),
482
554
  }
483
555
  if not self._save(state):
484
556
  return False
@@ -550,6 +622,29 @@ class SharedTmuxBindingManager:
550
622
  state["selection_hook_index"], token)
551
623
  self._remove_state()
552
624
  return False
625
+ if (state["termux_tap_managed"]
626
+ and not tmux_ctl.set_root_termux_tap_forwarding(
627
+ state["termux_tap_backup"], token)):
628
+ tmux_ctl.restore_root_termux_tap_binding(
629
+ state["termux_tap_backup"], token=token)
630
+ state["termux_tap_managed"] = False
631
+ if not self._save(state):
632
+ tmux_ctl.restore_root_function_bindings(
633
+ backup, token=token)
634
+ if state["prefix_tab_managed"]:
635
+ tmux_ctl.restore_prefix_target_binding(
636
+ state["prefix_tab_backup"], token=token)
637
+ if state["right_click_managed"]:
638
+ tmux_ctl.restore_root_right_click_binding(
639
+ state["right_click_backup"], token=token)
640
+ if state["selection_hook_managed"]:
641
+ tmux_ctl.restore_selection_mode_hook(
642
+ state["selection_hook_index"], token)
643
+ if state["status_click_managed"]:
644
+ tmux_ctl.restore_root_status_click_binding(
645
+ state["status_click_backup"], token=token)
646
+ self._remove_state()
647
+ return False
553
648
  state["phase"] = "active"
554
649
  if not self._save(state) or not self._set_controller():
555
650
  tmux_ctl.restore_root_function_bindings(
@@ -566,6 +661,9 @@ class SharedTmuxBindingManager:
566
661
  if state["status_click_managed"]:
567
662
  tmux_ctl.restore_root_status_click_binding(
568
663
  state["status_click_backup"], token=token)
664
+ if state["termux_tap_managed"]:
665
+ tmux_ctl.restore_root_termux_tap_binding(
666
+ state["termux_tap_backup"], token=token)
569
667
  self._remove_state()
570
668
  return False
571
669
  self._registered = True
@@ -574,6 +672,7 @@ class SharedTmuxBindingManager:
574
672
  self._selection_hook_managed = state["selection_hook_managed"]
575
673
  self._selection_hook_index = state["selection_hook_index"]
576
674
  self._status_click_managed = state["status_click_managed"]
675
+ self._termux_tap_managed = state["termux_tap_managed"]
577
676
  return True
578
677
  except OSError:
579
678
  return False
@@ -627,6 +726,9 @@ class SharedTmuxBindingManager:
627
726
  if state["status_click_managed"]:
628
727
  tmux_ctl.restore_root_status_click_binding(
629
728
  state["status_click_backup"], token=state["token"])
729
+ if state["termux_tap_managed"]:
730
+ tmux_ctl.restore_root_termux_tap_binding(
731
+ state["termux_tap_backup"], token=state["token"])
630
732
  self._remove_state()
631
733
  except OSError:
632
734
  pass
@@ -641,5 +743,6 @@ class SharedTmuxBindingManager:
641
743
  self._prefix_tab_managed = False
642
744
  self._right_click_managed = False
643
745
  self._status_click_managed = False
746
+ self._termux_tap_managed = False
644
747
  self._selection_hook_managed = False
645
748
  self._selection_hook_index = None