@hienlh/ppm 0.21.0 → 0.22.0

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 (606) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/CLAUDE.md +29 -4
  3. package/THIRD-PARTY-NOTICES.md +1 -1
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/http-api.md +1 -1
  6. package/bun.lock +16 -1
  7. package/dist/web/assets/accounts-settings-section-BVr9-FZJ.js +1 -0
  8. package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-CdDpOwjh.js} +1 -1
  9. package/dist/web/assets/ai-settings-section-B1hQyjAd.js +1 -0
  10. package/dist/web/assets/ai-settings-section-X2UOfSaS.js +1 -0
  11. package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
  12. package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
  13. package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
  14. package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
  15. package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
  16. package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
  17. package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
  18. package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-DLoPK0LD.js} +1 -1
  19. package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
  20. package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
  21. package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
  22. package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
  23. package/dist/web/assets/branch-review-tab-G84Ojzae.js +1 -0
  24. package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
  25. package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
  26. package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
  27. package/dist/web/assets/chat-tab-DQJfjm8E.js +15 -0
  28. package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
  29. package/dist/web/assets/code-editor-D04iUPsZ.js +7 -0
  30. package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-BSccLRGT.js} +1 -1
  31. package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
  32. package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-C4ay676e.js} +2 -2
  33. package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
  34. package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
  35. package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
  36. package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-DbwEmG7o.js} +1 -1
  37. package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
  38. package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-S6LsALjX.js} +1 -1
  39. package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
  40. package/dist/web/assets/diff-viewer-3oBA7vgQ.js +4 -0
  41. package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
  42. package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
  43. package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
  44. package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-DJmaKVY0.js} +1 -1
  45. package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
  46. package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
  47. package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
  48. package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
  49. package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
  50. package/dist/web/assets/explorer-body-BQ0jyAGv.js +2 -0
  51. package/dist/web/assets/explorer-window-content-jdkWt34J.js +1 -0
  52. package/dist/web/assets/{extension-manager-section-BhwB6nqr.js → extension-manager-section-DG1guILi.js} +1 -1
  53. package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-BGSDUbdK.js} +1 -1
  54. package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
  55. package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
  56. package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
  57. package/dist/web/assets/files-settings-section-Gnyd7han.js +1 -0
  58. package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
  59. package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
  60. package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
  61. package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
  62. package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-V6Khh15V.js} +1 -1
  63. package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
  64. package/dist/web/assets/git-log-panel-Bi513qjT.js +1 -0
  65. package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
  66. package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
  67. package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
  68. package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-DnQwYSmQ.js} +7 -7
  69. package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
  70. package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
  71. package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
  72. package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
  73. package/dist/web/assets/group-chat-tab-DpczdwyV.js +1 -0
  74. package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
  75. package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
  76. package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
  77. package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
  78. package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
  79. package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
  80. package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
  81. package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
  82. package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
  83. package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
  84. package/dist/web/assets/index-BH99TIE0.css +1 -0
  85. package/dist/web/assets/index-DU68xk06.js +52 -0
  86. package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
  87. package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
  88. package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-f2HUbGDY.js} +1 -1
  89. package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
  90. package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
  91. package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
  92. package/dist/web/assets/keybindings-store-DCRsnKg5.js +1 -0
  93. package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-BpvVEw_Y.js} +1 -1
  94. package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
  95. package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
  96. package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
  97. package/dist/web/assets/markdown-renderer-Bcaf9_bA.js +1 -0
  98. package/dist/web/assets/markdown-renderer-DGJp8HC3.js +3 -0
  99. package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
  100. package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
  101. package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
  102. package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
  103. package/dist/web/assets/mobile-explorer-sheet-Bol3XAO1.js +1 -0
  104. package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
  105. package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
  106. package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
  107. package/dist/web/assets/notification-store-BXt54VIn.js +1 -0
  108. package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
  109. package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
  110. package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
  111. package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
  112. package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
  113. package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
  114. package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
  115. package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
  116. package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
  117. package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
  118. package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-DKJUA3Kq.js} +1 -1
  119. package/dist/web/assets/problems-panel-CFcoUx8h.js +1 -0
  120. package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-EKr2dqG6.js} +1 -1
  121. package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
  122. package/dist/web/assets/project-store-B87hLRSk.js +1 -0
  123. package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-B7qRVl8w.js} +1 -1
  124. package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
  125. package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
  126. package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
  127. package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
  128. package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
  129. package/dist/web/assets/remote-desktop-clipboard-notice-0WDYsyCA.js +1 -0
  130. package/dist/web/assets/remote-desktop-mobile-sheet-COYDg16H.js +2 -0
  131. package/dist/web/assets/remote-desktop-mobile-view-2DqSuS2U.js +1 -0
  132. package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-Dm_qMJMH.js} +1 -1
  133. package/dist/web/assets/remote-desktop-window-content-UodhsF7W.js +1 -0
  134. package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
  135. package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
  136. package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
  137. package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
  138. package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-bG2lYfGv.js} +1 -1
  139. package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
  140. package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
  141. package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
  142. package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
  143. package/dist/web/assets/settings-body-C06bFarv.js +2 -0
  144. package/dist/web/assets/settings-store-krC6bc79.js +2 -0
  145. package/dist/web/assets/settings-tab-34RSaASd.js +1 -0
  146. package/dist/web/assets/settings-window-content-Dr_2lmHH.js +1 -0
  147. package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
  148. package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
  149. package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
  150. package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
  151. package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-BgRH0cHD.js} +1 -1
  152. package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-DUxGslpa.js} +1 -1
  153. package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
  154. package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
  155. package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
  156. package/dist/web/assets/system-monitor-body-IZuLn7Qh.js +2 -0
  157. package/dist/web/assets/system-monitor-tab-DiSA490Z.js +1 -0
  158. package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-CEaFISgj.js} +1 -1
  159. package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-D0TN8Wrq.js} +1 -1
  160. package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
  161. package/dist/web/assets/team-member-sheet-DDPNcCjd.js +2 -0
  162. package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-DCiW_1-E.js} +1 -1
  163. package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
  164. package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-4ypP0NDY.js} +3 -3
  165. package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
  166. package/dist/web/assets/tool-cards-Dd6E7Q3k.js +9 -0
  167. package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
  168. package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
  169. package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
  170. package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
  171. package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-vrfFHuaR.js} +1 -1
  172. package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CsuZu49X.js} +1 -1
  173. package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-BIVYJBIn.js} +1 -1
  174. package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
  175. package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-BMbufhX7.js} +1 -1
  176. package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
  177. package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
  178. package/dist/web/assets/video-preview-Byj8P_jA.js +1 -0
  179. package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
  180. package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
  181. package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
  182. package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
  183. package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
  184. package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
  185. package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
  186. package/dist/web/index.html +27 -26
  187. package/dist/web/sw.js +1 -1
  188. package/docs/architecture/plugins-and-tracing.md +285 -0
  189. package/docs/lessons-learned.md +31 -0
  190. package/docs/system-architecture.md +1 -0
  191. package/package.json +2 -1
  192. package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
  193. package/packages/ext-git-graph/src/compare-view.ts +22 -3
  194. package/packages/ext-git-graph/src/extension.ts +6 -0
  195. package/packages/ext-git-graph/src/types.ts +19 -0
  196. package/packages/ext-git-graph/src/webview-html.test.ts +134 -1
  197. package/packages/ext-git-graph/src/webview-html.ts +277 -27
  198. package/scripts/gen-file-icons.ts +493 -242
  199. package/src/providers/claude-agent-sdk.ts +42 -1
  200. package/src/providers/codex-app-server/codex-history.ts +77 -23
  201. package/src/server/index.ts +42 -7
  202. package/src/server/routes/chat.ts +15 -11
  203. package/src/server/routes/git.ts +83 -3
  204. package/src/server/routes/remote-desktop.ts +7 -2
  205. package/src/server/ws/global.ts +1 -1
  206. package/src/server/ws/remote-desktop.ts +12 -5
  207. package/src/services/binary-content.ts +25 -0
  208. package/src/services/chat-search.service.ts +1 -1
  209. package/src/services/chat.service.ts +9 -0
  210. package/src/services/event-loop-lag.ts +208 -0
  211. package/src/services/file-lines.ts +114 -0
  212. package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
  213. package/src/services/file-watcher/watch-tree.ts +131 -10
  214. package/src/services/file-watcher.service.ts +73 -23
  215. package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
  216. package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
  217. package/src/services/git-hunks/git-hunks.service.ts +26 -11
  218. package/src/services/git-hunks/unified-diff.ts +15 -1
  219. package/src/services/git.service.ts +74 -24
  220. package/src/services/jsonl-transcript-parser.ts +138 -6
  221. package/src/services/log-rotate.ts +142 -0
  222. package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
  223. package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
  224. package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
  225. package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
  226. package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
  227. package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
  228. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
  229. package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
  230. package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
  231. package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
  232. package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
  233. package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
  234. package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
  235. package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
  236. package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
  237. package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
  238. package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
  239. package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
  240. package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
  241. package/src/services/supervisor.ts +47 -4
  242. package/src/shared/remote-desktop-custom-quality.ts +117 -0
  243. package/src/shared/remote-desktop-quality.ts +127 -0
  244. package/src/types/chat.ts +4 -0
  245. package/src/types/git.ts +52 -0
  246. package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
  247. package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
  248. package/src/web/components/chat/attachment-chips.tsx +30 -6
  249. package/src/web/components/chat/chat-tab.tsx +12 -1
  250. package/src/web/components/chat/message-events.tsx +273 -0
  251. package/src/web/components/chat/message-list.tsx +107 -861
  252. package/src/web/components/chat/message-markdown.tsx +34 -0
  253. package/src/web/components/chat/message-media.tsx +174 -0
  254. package/src/web/components/chat/message-user-bubble.tsx +399 -0
  255. package/src/web/components/chat/provider-selector.tsx +30 -22
  256. package/src/web/components/chat/session-history-list.tsx +2 -2
  257. package/src/web/components/chat/session-list-panel.tsx +3 -2
  258. package/src/web/components/editor/binary-diff-view.tsx +212 -0
  259. package/src/web/components/editor/diff-viewer.tsx +76 -3
  260. package/src/web/components/explorer/file-tree.tsx +235 -134
  261. package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
  262. package/src/web/components/explorer/tree-node.tsx +69 -69
  263. package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
  264. package/src/web/components/git/git-status-panel.tsx +25 -0
  265. package/src/web/components/layout/draggable-tab.tsx +4 -2
  266. package/src/web/components/layout/nav-section-rail.tsx +12 -3
  267. package/src/web/components/layout/status-bar.tsx +10 -6
  268. package/src/web/components/layout/tab-content.tsx +5 -0
  269. package/src/web/components/layout/tab-pool.tsx +1 -0
  270. package/src/web/components/layout/upgrade-button.tsx +5 -1
  271. package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
  272. package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
  273. package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
  274. package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
  275. package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
  276. package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
  277. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
  278. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
  279. package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
  280. package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
  281. package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
  282. package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
  283. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
  284. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
  285. package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
  286. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
  287. package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
  288. package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
  289. package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
  290. package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
  291. package/src/web/components/settings/theme-picker.tsx +2 -1
  292. package/src/web/components/ui/tooltip.tsx +15 -2
  293. package/src/web/hooks/use-delayed-flag.ts +35 -0
  294. package/src/web/lib/branch-review-state.ts +175 -0
  295. package/src/web/lib/file-icon-name.ts +62 -0
  296. package/src/web/lib/file-icons.generated.ts +3287 -25
  297. package/src/web/lib/file-icons.tsx +71 -73
  298. package/src/web/lib/flatten-expansions.ts +18 -5
  299. package/src/web/lib/message-ordinals.ts +23 -0
  300. package/src/web/lib/provider-logos.tsx +143 -0
  301. package/src/web/lib/tab-type-icons.ts +11 -0
  302. package/src/web/stores/file-store.ts +73 -35
  303. package/src/web/stores/panel-utils.ts +5 -0
  304. package/src/web/stores/project-framework-store.ts +82 -0
  305. package/src/web/stores/project-store.ts +7 -1
  306. package/src/web/stores/settings-store.ts +128 -0
  307. package/src/web/stores/tab-store.ts +1 -0
  308. package/src/web/stores/window-panel-persistence.ts +1 -0
  309. package/src/web/styles/file-icons.generated.css +3282 -234
  310. package/src/web/styles/globals.css +26 -0
  311. package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
  312. package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
  313. package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
  314. package/dist/web/assets/api-client-BUCo1lms.js +0 -1
  315. package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
  316. package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
  317. package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
  318. package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
  319. package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
  320. package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
  321. package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
  322. package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
  323. package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
  324. package/dist/web/assets/file-store-DMABxOkV.js +0 -1
  325. package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
  326. package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
  327. package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
  328. package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
  329. package/dist/web/assets/index-D7DbXD3G.css +0 -1
  330. package/dist/web/assets/index-ZtfxZOyb.js +0 -52
  331. package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
  332. package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
  333. package/dist/web/assets/markdown-renderer-CvL1pWeK.js +0 -3
  334. package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
  335. package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
  336. package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
  337. package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
  338. package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
  339. package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
  340. package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
  341. package/dist/web/assets/project-store-BhMbVln3.js +0 -1
  342. package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
  343. package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
  344. package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
  345. package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
  346. package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
  347. package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
  348. package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
  349. package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
  350. package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
  351. package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
  352. package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
  353. package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
  354. package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
  355. package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
  356. package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
  357. package/dist/web/assets/ws-auth-DEAJiGCR.js +0 -1
  358. /package/dist/web/assets/{abap-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
  359. /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
  360. /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
  361. /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
  362. /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
  363. /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
  364. /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
  365. /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
  366. /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
  367. /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
  368. /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
  369. /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
  370. /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
  371. /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
  372. /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
  373. /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
  374. /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
  375. /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
  376. /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
  377. /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
  378. /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
  379. /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
  380. /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
  381. /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
  382. /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
  383. /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
  384. /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
  385. /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
  386. /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
  387. /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
  388. /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
  389. /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
  390. /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
  391. /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
  392. /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
  393. /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
  394. /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
  395. /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
  396. /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
  397. /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
  398. /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
  399. /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
  400. /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
  401. /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
  402. /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
  403. /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
  404. /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
  405. /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
  406. /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
  407. /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
  408. /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
  409. /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
  410. /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
  411. /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
  412. /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
  413. /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
  414. /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
  415. /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
  416. /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
  417. /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
  418. /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
  419. /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
  420. /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
  421. /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
  422. /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
  423. /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
  424. /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
  425. /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
  426. /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
  427. /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
  428. /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
  429. /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
  430. /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
  431. /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
  432. /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
  433. /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
  434. /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
  435. /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
  436. /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
  437. /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
  438. /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
  439. /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
  440. /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
  441. /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
  442. /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
  443. /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
  444. /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
  445. /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
  446. /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
  447. /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
  448. /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
  449. /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
  450. /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
  451. /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
  452. /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
  453. /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
  454. /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
  455. /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
  456. /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
  457. /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
  458. /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
  459. /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
  460. /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
  461. /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
  462. /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
  463. /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
  464. /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
  465. /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
  466. /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
  467. /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
  468. /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
  469. /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
  470. /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
  471. /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
  472. /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
  473. /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
  474. /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
  475. /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
  476. /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
  477. /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
  478. /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
  479. /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
  480. /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
  481. /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
  482. /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
  483. /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
  484. /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
  485. /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
  486. /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
  487. /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
  488. /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
  489. /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
  490. /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
  491. /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
  492. /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
  493. /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
  494. /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
  495. /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
  496. /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
  497. /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
  498. /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
  499. /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
  500. /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
  501. /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
  502. /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
  503. /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
  504. /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
  505. /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
  506. /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
  507. /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
  508. /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
  509. /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
  510. /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
  511. /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
  512. /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
  513. /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
  514. /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
  515. /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
  516. /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
  517. /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
  518. /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
  519. /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
  520. /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
  521. /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
  522. /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
  523. /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
  524. /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
  525. /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
  526. /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
  527. /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
  528. /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
  529. /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
  530. /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
  531. /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
  532. /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
  533. /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
  534. /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
  535. /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
  536. /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
  537. /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
  538. /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
  539. /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
  540. /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
  541. /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
  542. /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
  543. /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
  544. /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
  545. /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
  546. /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
  547. /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
  548. /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
  549. /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
  550. /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
  551. /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
  552. /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
  553. /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
  554. /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
  555. /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
  556. /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
  557. /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
  558. /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
  559. /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
  560. /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
  561. /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
  562. /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
  563. /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
  564. /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
  565. /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
  566. /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
  567. /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
  568. /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
  569. /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
  570. /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
  571. /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
  572. /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
  573. /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
  574. /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
  575. /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
  576. /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
  577. /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
  578. /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
  579. /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
  580. /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
  581. /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
  582. /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
  583. /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
  584. /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
  585. /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
  586. /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
  587. /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
  588. /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
  589. /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
  590. /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
  591. /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
  592. /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
  593. /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
  594. /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
  595. /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
  596. /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
  597. /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
  598. /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
  599. /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
  600. /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
  601. /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
  602. /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
  603. /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
  604. /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
  605. /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
  606. /package/dist/web/assets/{zig-B2htezJ_.js → zig-CbaUfBQe.js} +0 -0
@@ -0,0 +1,253 @@
1
+ /**
2
+ * X11 input backend: `bun:ffi` into libXtst's XTEST extension — no helper process, matching
3
+ * `remote-desktop-input-win32.ts` (user32/SendInput) and `-darwin.ts` (CoreGraphics).
4
+ *
5
+ * Selected by `remote-desktop-input.ts` on `linux`, and it answers only for an X11 session:
6
+ * XTEST is an X server extension, so a Wayland compositor has nothing to inject into and this
7
+ * backend reports unavailable there (`remote-desktop-input-uinput.ts` covers that case).
8
+ *
9
+ * Keys go through `remote-desktop-evdev-key-map.ts` + `X11_KEYCODE_OFFSET`, never through
10
+ * `XKeysymToKeycode` — see that file for why a keysym lookup transposes a non-US keyboard.
11
+ *
12
+ * Text is harder than on the other two platforms: X11 has no "type this Unicode string" call
13
+ * (`KEYEVENTF_UNICODE` / `CGEventKeyboardSetUnicodeString` have no counterpart). So `text()`
14
+ * has two paths:
15
+ * - **Fast path** — the character already exists in the active keymap, which covers all ASCII
16
+ * on any ordinary layout. Press its real keycode, with Shift when it sits in the shifted
17
+ * slot. No mapping change, no round trip per character.
18
+ * - **Slow path** — anything else (`ệ`, `日`, an emoji). A scratch keycode with no keysyms of
19
+ * its own is temporarily bound to the character's Unicode keysym, pressed, and unbound
20
+ * again. This needs a real delay after the remap, not just `XSync`: the server applies the
21
+ * change immediately but the focused *client* only learns about it when it processes the
22
+ * `MappingNotify`, and a key pressed before then is interpreted with the old mapping.
23
+ *
24
+ * Caveat shared with macOS/Windows: injected keys enter the host's input stack *above* its
25
+ * IME, so a Telex/ibus input method running on the host will still rewrite what arrives
26
+ * (typing "World" through a Telex IME lands as "ửold"). That is the host's IME doing its job,
27
+ * not a bug here.
28
+ */
29
+ import { asPointer, getX11, type X11Connection } from "./remote-desktop-x11.ts";
30
+ import { detectLinuxSession, type LinuxSession } from "./remote-desktop-linux-session.ts";
31
+ import { codeToEvdev, MODIFIER_EVDEV_CODES, X11_KEYCODE_OFFSET } from "./remote-desktop-evdev-key-map.ts";
32
+ import { RemoteInputUnavailableError, type InputTargetRect, type RemoteInputBackend } from "./remote-desktop-input-backend.ts";
33
+
34
+ /** X core protocol button numbers. 4/5 are the wheel's two directions — X11 has no scroll
35
+ * axis, a notch *is* a button click. */
36
+ const BUTTON_LEFT = 1;
37
+ const BUTTON_RIGHT = 3;
38
+ const BUTTON_WHEEL_UP = 4;
39
+ const BUTTON_WHEEL_DOWN = 5;
40
+ /** One notch, matching `WHEEL_DELTA` on Windows — the client sends in these units. */
41
+ const WHEEL_DELTA = 120;
42
+ /** Above this a runaway client value is clamped rather than turned into thousands of clicks. */
43
+ const MAX_WHEEL_NOTCHES = 64;
44
+ /** ShiftLeft, for reaching the shifted half of the keymap in `text()`. */
45
+ const SHIFT_KEYCODE = 42 + X11_KEYCODE_OFFSET;
46
+ /** How long a client needs to process the `MappingNotify` from a scratch-keycode remap before
47
+ * a key pressed on it is read with the new mapping. `xdotool type` uses 12ms for the same
48
+ * reason; below ~8ms characters start arriving as the previous mapping on a busy host. */
49
+ const REMAP_SETTLE_MS = 12;
50
+
51
+ const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
52
+
53
+ /** The X11 keysym for a Unicode code point. Latin-1 is its own keysym range; everything else
54
+ * uses the `0x01000000 | codepoint` convention every X server understands. */
55
+ function unicodeKeysym(cp: number): number {
56
+ return (cp >= 0x20 && cp <= 0x7e) || (cp >= 0xa0 && cp <= 0xff) ? cp : 0x01000000 | cp;
57
+ }
58
+
59
+ async function connect(): Promise<X11Connection> {
60
+ const session: LinuxSession | null = detectLinuxSession();
61
+ if (session?.kind !== "x11") {
62
+ throw new RemoteInputUnavailableError("remote input needs an X11 session (Wayland uses uinput)");
63
+ }
64
+ const conn = await getX11(session);
65
+ if (!conn?.xtst || !conn.hasXTest) {
66
+ throw new RemoteInputUnavailableError("the X server has no XTEST extension (install libXtst)");
67
+ }
68
+ return conn;
69
+ }
70
+
71
+ /** Where a 0..1 fraction lands in root-window coordinates. X11 puts every monitor in one root
72
+ * coordinate space, so a per-monitor target is just an offset rect inside it. */
73
+ function toRootCoords(
74
+ conn: X11Connection, xFrac: number, yFrac: number, target: InputTargetRect | null,
75
+ ): { x: number; y: number } {
76
+ const rect = target ?? {
77
+ x: 0, y: 0,
78
+ width: conn.x11.XDisplayWidth(conn.dpy, conn.screen),
79
+ height: conn.x11.XDisplayHeight(conn.dpy, conn.screen),
80
+ };
81
+ const clamp = (v: number) => (Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0);
82
+ return {
83
+ x: Math.round(rect.x + clamp(xFrac) * Math.max(0, rect.width - 1)),
84
+ y: Math.round(rect.y + clamp(yFrac) * Math.max(0, rect.height - 1)),
85
+ };
86
+ }
87
+
88
+ async function pointer(
89
+ xFrac: number, yFrac: number, button: "left" | "right" | null, down: boolean | null,
90
+ target: InputTargetRect | null,
91
+ ): Promise<void> {
92
+ const conn = await connect();
93
+ const { x, y } = toRootCoords(conn, xFrac, yFrac, target);
94
+ conn.xtst!.XTestFakeMotionEvent(conn.dpy, conn.screen, x, y, 0);
95
+ if (button && down !== null) {
96
+ conn.xtst!.XTestFakeButtonEvent(conn.dpy, button === "left" ? BUTTON_LEFT : BUTTON_RIGHT, down ? 1 : 0, 0);
97
+ }
98
+ conn.x11.XFlush(conn.dpy);
99
+ }
100
+
101
+ /** `deltaY` in 120-unit notches, positive = away from the user (scroll up), matching the
102
+ * Windows/macOS backends. X11 turns each notch into a press+release of button 4 or 5. */
103
+ async function wheel(deltaY: number): Promise<void> {
104
+ const conn = await connect();
105
+ const notches = Math.min(MAX_WHEEL_NOTCHES, Math.round(Math.abs(deltaY) / WHEEL_DELTA));
106
+ if (notches === 0) return;
107
+ const button = deltaY > 0 ? BUTTON_WHEEL_UP : BUTTON_WHEEL_DOWN;
108
+ for (let i = 0; i < notches; i++) {
109
+ conn.xtst!.XTestFakeButtonEvent(conn.dpy, button, 1, 0);
110
+ conn.xtst!.XTestFakeButtonEvent(conn.dpy, button, 0, 0);
111
+ }
112
+ conn.x11.XFlush(conn.dpy);
113
+ }
114
+
115
+ async function key(code: string, down: boolean): Promise<boolean> {
116
+ const evdev = codeToEvdev(code);
117
+ if (evdev === null) return false;
118
+ const conn = await connect();
119
+ conn.xtst!.XTestFakeKeyEvent(conn.dpy, evdev + X11_KEYCODE_OFFSET, down ? 1 : 0, 0);
120
+ conn.x11.XFlush(conn.dpy);
121
+ return true;
122
+ }
123
+
124
+ /** keysym → the keycode that produces it and whether Shift is needed. Rebuilt per `text()`
125
+ * call rather than cached: a layout switch would otherwise leave a stale index pressing
126
+ * keycodes that now produce different characters, which is worse than one round trip. */
127
+ function buildKeysymIndex(conn: X11Connection): Map<number, { keycode: number; shift: boolean }> {
128
+ const { ffi, x11, dpy } = conn;
129
+ const index = new Map<number, { keycode: number; shift: boolean }>();
130
+ const min = new Int32Array(1), max = new Int32Array(1);
131
+ x11.XDisplayKeycodes(dpy, ffi.ptr(min), ffi.ptr(max));
132
+ const count = max[0]! - min[0]! + 1;
133
+ if (count <= 0) return index;
134
+
135
+ const perCode = new Int32Array(1);
136
+ const syms = x11.XGetKeyboardMapping(dpy, min[0]!, count, ffi.ptr(perCode));
137
+ if (!syms) return index;
138
+ const base = asPointer(Number(syms));
139
+ // Only the first two slots: they are the plain and Shift levels. Level 3/4 need AltGr, so a
140
+ // character living only there is left to the scratch-keycode path instead of guessing.
141
+ const slots = Math.min(2, perCode[0]!);
142
+ for (let i = 0; i < count; i++) {
143
+ for (let slot = 0; slot < slots; slot++) {
144
+ const keysym = Number(ffi.read.u64(base, (i * perCode[0]! + slot) * 8));
145
+ if (keysym !== 0 && !index.has(keysym)) {
146
+ index.set(keysym, { keycode: min[0]! + i, shift: slot === 1 });
147
+ }
148
+ }
149
+ }
150
+ x11.XFree(syms);
151
+ return index;
152
+ }
153
+
154
+ /** A keycode with no keysyms of its own, safe to borrow for one character. null when the
155
+ * keymap is full (then non-ASCII text is dropped rather than clobbering a real key). */
156
+ function findScratchKeycode(conn: X11Connection): number | null {
157
+ const { ffi, x11, dpy } = conn;
158
+ const min = new Int32Array(1), max = new Int32Array(1);
159
+ x11.XDisplayKeycodes(dpy, ffi.ptr(min), ffi.ptr(max));
160
+ const count = max[0]! - min[0]! + 1;
161
+ if (count <= 0) return null;
162
+ const perCode = new Int32Array(1);
163
+ const syms = x11.XGetKeyboardMapping(dpy, min[0]!, count, ffi.ptr(perCode));
164
+ if (!syms) return null;
165
+ const base = asPointer(Number(syms));
166
+ let found: number | null = null;
167
+ // Search from the top: high keycodes are where a layout is least likely to grow into.
168
+ for (let i = count - 1; i >= 0 && found === null; i--) {
169
+ let empty = true;
170
+ for (let slot = 0; slot < perCode[0]!; slot++) {
171
+ if (Number(ffi.read.u64(base, (i * perCode[0]! + slot) * 8)) !== 0) { empty = false; break; }
172
+ }
173
+ if (empty) found = min[0]! + i;
174
+ }
175
+ x11.XFree(syms);
176
+ return found;
177
+ }
178
+
179
+ /**
180
+ * One string at a time.
181
+ *
182
+ * The slow path borrows a spare keycode, and `findScratchKeycode` is deterministic — two
183
+ * overlapping calls pick the *same* one. They then interleave at the two settle sleeps, and
184
+ * whichever finishes first unbinds the keycode the other is still using, so the loser taps a
185
+ * key bound to nothing and types silence, or types the winner's character.
186
+ *
187
+ * The client makes this ordinary rather than exotic: the virtual keyboard sends one `text`
188
+ * message per keystroke and another at `compositionend`, and the slow path is exactly the
189
+ * non-ASCII input — Vietnamese, CJK — where those arrive fastest.
190
+ */
191
+ let textQueue: Promise<void> = Promise.resolve();
192
+
193
+ function text(str: string): Promise<void> {
194
+ const next = textQueue.then(() => injectText(str));
195
+ // The chain must survive a failed string, or one error would wedge every later one.
196
+ textQueue = next.catch(() => undefined);
197
+ return next;
198
+ }
199
+
200
+ async function injectText(str: string): Promise<void> {
201
+ const conn = await connect();
202
+ const { ffi, x11, xtst, dpy } = conn;
203
+ const index = buildKeysymIndex(conn);
204
+ let scratch: number | null | undefined;
205
+ const scratchSyms = new BigUint64Array(1);
206
+
207
+ const tap = (keycode: number, shift: boolean) => {
208
+ if (shift) xtst!.XTestFakeKeyEvent(dpy, SHIFT_KEYCODE, 1, 0);
209
+ xtst!.XTestFakeKeyEvent(dpy, keycode, 1, 0);
210
+ xtst!.XTestFakeKeyEvent(dpy, keycode, 0, 0);
211
+ if (shift) xtst!.XTestFakeKeyEvent(dpy, SHIFT_KEYCODE, 0, 0);
212
+ x11.XFlush(dpy);
213
+ };
214
+
215
+ try {
216
+ for (const ch of str) {
217
+ const cp = ch.codePointAt(0);
218
+ if (cp === undefined) continue;
219
+ const hit = index.get(unicodeKeysym(cp));
220
+ if (hit) { tap(hit.keycode, hit.shift); continue; }
221
+
222
+ if (scratch === undefined) scratch = findScratchKeycode(conn);
223
+ if (scratch === null) continue; // no borrowable keycode — drop rather than clobber a real key
224
+ scratchSyms[0] = BigInt(unicodeKeysym(cp));
225
+ x11.XChangeKeyboardMapping(dpy, scratch, 1, ffi.ptr(scratchSyms), 1);
226
+ x11.XSync(dpy, 0);
227
+ await sleep(REMAP_SETTLE_MS);
228
+ tap(scratch, false);
229
+ await sleep(REMAP_SETTLE_MS);
230
+ }
231
+ } finally {
232
+ // Always hand the scratch keycode back, including after a throw mid-string: leaving a
233
+ // Unicode keysym bound to it would make that key type a stray character forever.
234
+ if (typeof scratch === "number") {
235
+ scratchSyms[0] = 0n;
236
+ x11.XChangeKeyboardMapping(dpy, scratch, 1, ffi.ptr(scratchSyms), 1);
237
+ x11.XSync(dpy, 0);
238
+ }
239
+ }
240
+ }
241
+
242
+ async function releaseAllModifiers(): Promise<void> {
243
+ const conn = await connect();
244
+ for (const evdev of MODIFIER_EVDEV_CODES) {
245
+ conn.xtst!.XTestFakeKeyEvent(conn.dpy, evdev + X11_KEYCODE_OFFSET, 0, 0);
246
+ }
247
+ conn.x11.XFlush(conn.dpy);
248
+ }
249
+
250
+ export const x11InputBackend: RemoteInputBackend = {
251
+ id: "linux-xtest",
252
+ pointer, wheel, key, text, releaseAllModifiers,
253
+ };
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Wayland input backend: two virtual devices on `/dev/uinput`, driven straight from the
3
+ * kernel's evdev interface.
4
+ *
5
+ * Why not XTEST: XTEST is an X server extension. A Wayland compositor is not an X server, and
6
+ * the protocol has no input-injection request at all by design — so the only way in is to be
7
+ * an input *device*. uinput events enter below the compositor, which means they also work on
8
+ * X11 (useful: it is how this backend is testable on an X11 host) and they are indistinguishable
9
+ * from a real keyboard/mouse to everything above.
10
+ *
11
+ * **Two devices, not one.** libinput classifies a device from the capabilities it declares, and
12
+ * a single device advertising both a full keyboard and absolute axes gets misread — typically as
13
+ * a touchpad, which then applies pointer acceleration and treats absolute coordinates as
14
+ * relative motion, so the pointer drifts instead of landing where it was told. A pointer device
15
+ * (absolute axes + buttons + wheel, `INPUT_PROP_DIRECT`) and a keyboard device (keys only) are
16
+ * each unambiguous.
17
+ *
18
+ * **No `text()`.** uinput carries key codes, not characters: there is no counterpart to
19
+ * `KEYEVENTF_UNICODE` (Windows), `CGEventKeyboardSetUnicodeString` (macOS) or even the
20
+ * scratch-keysym remap the X11 backend uses, because the layout that turns a key code into a
21
+ * character lives in the compositor. `text` is therefore absent, the facade's `injectText`
22
+ * returns false, and the client falls back to per-key events. Consequence: a phone soft keyboard
23
+ * that reports no key codes (most Android IMEs) cannot type on a Wayland host.
24
+ *
25
+ * **Absolute coordinates cover the whole desktop.** The kernel knows nothing about monitor
26
+ * layout, so 0..65535 maps to the compositor's full pointer area. Per-monitor targeting is not
27
+ * possible here the way it is under X11's single root-window coordinate space.
28
+ */
29
+ import { closeSync, openSync, writeSync } from "node:fs";
30
+ import { ALL_EVDEV_CODES, codeToEvdev, MODIFIER_EVDEV_CODES } from "./remote-desktop-evdev-key-map.ts";
31
+ import { RemoteInputUnavailableError, type InputTargetRect, type RemoteInputBackend } from "./remote-desktop-input-backend.ts";
32
+
33
+ /** ioctl request numbers, verified against `linux/uinput.h` on x86-64 rather than derived by
34
+ * hand: `_IOW` encodes the struct size, so a wrong `sizeof` yields a silently different
35
+ * request number and `ioctl` fails with EINVAL for no visible reason. */
36
+ const UI_DEV_CREATE = 0x5501;
37
+ const UI_DEV_DESTROY = 0x5502;
38
+ const UI_DEV_SETUP = 0x405c5503; // _IOW('U', 3, struct uinput_setup) sizeof 92
39
+ const UI_ABS_SETUP = 0x401c5504; // _IOW('U', 4, struct uinput_abs_setup) sizeof 28
40
+ const UI_SET_EVBIT = 0x40045564;
41
+ const UI_SET_KEYBIT = 0x40045565;
42
+ const UI_SET_RELBIT = 0x40045566;
43
+ const UI_SET_ABSBIT = 0x40045567;
44
+ const UI_SET_PROPBIT = 0x4004556e;
45
+
46
+ const EV_SYN = 0, EV_KEY = 1, EV_REL = 2, EV_ABS = 3;
47
+ const SYN_REPORT = 0;
48
+ const ABS_X = 0, ABS_Y = 1;
49
+ const REL_WHEEL = 8;
50
+ const BTN_LEFT = 0x110, BTN_RIGHT = 0x111, BTN_MIDDLE = 0x112;
51
+ const INPUT_PROP_DIRECT = 0x01;
52
+ const BUS_USB = 0x03;
53
+
54
+ /** Absolute axis range. Matches Windows `SendInput`'s 0..65535 normalisation, so both
55
+ * platforms carry the same "fraction of the surface" semantics. */
56
+ const ABS_MAX = 65535;
57
+ /** `struct input_event` on x86-64: 16-byte timeval (kernel fills it when zero) + u16 type +
58
+ * u16 code + s32 value. */
59
+ const EVENT_SIZE = 24;
60
+ /** One notch, matching `WHEEL_DELTA` on the other backends. */
61
+ const WHEEL_DELTA = 120;
62
+ const MAX_WHEEL_NOTCHES = 64;
63
+
64
+ type Ffi = typeof import("bun:ffi");
65
+ /* eslint-disable @typescript-eslint/no-explicit-any -- dlopen symbol types are per-call */
66
+ type Libc = { ioctl: any };
67
+ /* eslint-enable @typescript-eslint/no-explicit-any */
68
+
69
+ interface Devices { pointer: number; keyboard: number }
70
+
71
+ let ffiModule: Ffi | null = null;
72
+ let libc: Libc | null = null;
73
+ let devices: Devices | null = null;
74
+ let exitHookInstalled = false;
75
+
76
+ function eventBuf(entries: Array<[type: number, code: number, value: number]>): Uint8Array {
77
+ const buf = new Uint8Array(EVENT_SIZE * entries.length);
78
+ const dv = new DataView(buf.buffer);
79
+ entries.forEach(([type, code, value], i) => {
80
+ const at = i * EVENT_SIZE;
81
+ dv.setUint16(at + 16, type, true);
82
+ dv.setUint16(at + 18, code, true);
83
+ dv.setInt32(at + 20, value, true);
84
+ });
85
+ return buf;
86
+ }
87
+
88
+ /** `struct uinput_setup`: input_id (bustype/vendor/product/version) + char name[80] + u32. */
89
+ function setupBuf(name: string, product: number): Uint8Array {
90
+ const buf = new Uint8Array(92);
91
+ const dv = new DataView(buf.buffer);
92
+ dv.setUint16(0, BUS_USB, true);
93
+ dv.setUint16(2, 0x1d6b, true); // Linux Foundation — a virtual device, not a spoofed vendor
94
+ dv.setUint16(4, product, true);
95
+ dv.setUint16(6, 1, true);
96
+ buf.set(new TextEncoder().encode(name).subarray(0, 79), 8);
97
+ return buf;
98
+ }
99
+
100
+ /** `struct uinput_abs_setup`: u16 code + 2 pad + input_absinfo (value/min/max/fuzz/flat/res). */
101
+ function absSetupBuf(code: number, max: number): Uint8Array {
102
+ const buf = new Uint8Array(28);
103
+ const dv = new DataView(buf.buffer);
104
+ dv.setUint16(0, code, true);
105
+ dv.setInt32(8, 0, true); // minimum
106
+ dv.setInt32(12, max, true); // maximum
107
+ return buf;
108
+ }
109
+
110
+ async function loadLibc(): Promise<Libc> {
111
+ if (libc) return libc;
112
+ if (!ffiModule) ffiModule = await import("bun:ffi");
113
+ const { dlopen, FFIType } = ffiModule;
114
+ // `ioctl` is variadic in C, but every request here passes one integer/pointer argument, which
115
+ // the x86-64 ABI puts in the same register a fixed third parameter would use.
116
+ libc = dlopen("libc.so.6", {
117
+ ioctl: { args: [FFIType.i32, FFIType.u64, FFIType.u64], returns: FFIType.i32 },
118
+ }).symbols as Libc;
119
+ return libc;
120
+ }
121
+
122
+ function ioctlOrThrow(lib: Libc, fd: number, request: number, arg: number | bigint, what: string): void {
123
+ if (lib.ioctl(fd, BigInt(request), typeof arg === "bigint" ? arg : BigInt(arg)) < 0) {
124
+ throw new RemoteInputUnavailableError(`uinput ${what} failed (is /dev/uinput writable?)`);
125
+ }
126
+ }
127
+
128
+ function createPointerDevice(lib: Libc, ffi: Ffi): number {
129
+ const fd = openSync("/dev/uinput", "w");
130
+ ioctlOrThrow(lib, fd, UI_SET_EVBIT, EV_KEY, "SET_EVBIT EV_KEY");
131
+ for (const btn of [BTN_LEFT, BTN_RIGHT, BTN_MIDDLE]) ioctlOrThrow(lib, fd, UI_SET_KEYBIT, btn, "SET_KEYBIT");
132
+ ioctlOrThrow(lib, fd, UI_SET_EVBIT, EV_REL, "SET_EVBIT EV_REL");
133
+ ioctlOrThrow(lib, fd, UI_SET_RELBIT, REL_WHEEL, "SET_RELBIT");
134
+ ioctlOrThrow(lib, fd, UI_SET_EVBIT, EV_ABS, "SET_EVBIT EV_ABS");
135
+ // `INPUT_PROP_DIRECT` is what stops libinput reading absolute axes as touchpad motion.
136
+ ioctlOrThrow(lib, fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT, "SET_PROPBIT");
137
+ for (const axis of [ABS_X, ABS_Y]) {
138
+ ioctlOrThrow(lib, fd, UI_SET_ABSBIT, axis, "SET_ABSBIT");
139
+ const abs = absSetupBuf(axis, ABS_MAX);
140
+ ioctlOrThrow(lib, fd, UI_ABS_SETUP, BigInt(ffi.ptr(abs)), "ABS_SETUP");
141
+ }
142
+ const setup = setupBuf("PPM Remote Pointer", 0x0001);
143
+ ioctlOrThrow(lib, fd, UI_DEV_SETUP, BigInt(ffi.ptr(setup)), "DEV_SETUP");
144
+ ioctlOrThrow(lib, fd, UI_DEV_CREATE, 0, "DEV_CREATE");
145
+ return fd;
146
+ }
147
+
148
+ function createKeyboardDevice(lib: Libc, ffi: Ffi): number {
149
+ const fd = openSync("/dev/uinput", "w");
150
+ ioctlOrThrow(lib, fd, UI_SET_EVBIT, EV_KEY, "SET_EVBIT EV_KEY");
151
+ for (const code of ALL_EVDEV_CODES) ioctlOrThrow(lib, fd, UI_SET_KEYBIT, code, "SET_KEYBIT");
152
+ const setup = setupBuf("PPM Remote Keyboard", 0x0002);
153
+ ioctlOrThrow(lib, fd, UI_DEV_SETUP, BigInt(ffi.ptr(setup)), "DEV_SETUP");
154
+ ioctlOrThrow(lib, fd, UI_DEV_CREATE, 0, "DEV_CREATE");
155
+ return fd;
156
+ }
157
+
158
+ /** Create both devices once. The compositor needs a moment to notice a new device before the
159
+ * first event will be routed, so creation is awaited rather than raced with the first click. */
160
+ async function ensureDevices(): Promise<Devices> {
161
+ if (devices) return devices;
162
+ if (!ffiModule) ffiModule = await import("bun:ffi");
163
+ const lib = await loadLibc();
164
+ const created = { pointer: createPointerDevice(lib, ffiModule), keyboard: createKeyboardDevice(lib, ffiModule) };
165
+ devices = created;
166
+ if (!exitHookInstalled) {
167
+ exitHookInstalled = true;
168
+ // Without DEV_DESTROY the virtual devices outlive the process and pile up in the
169
+ // compositor's device list on every restart.
170
+ process.on("exit", destroyDevices);
171
+ }
172
+ await new Promise<void>((r) => setTimeout(r, 120));
173
+ return created;
174
+ }
175
+
176
+ /** Destroy both virtual devices now, rather than at process exit.
177
+ *
178
+ * They are process-wide, not per session: one Wayland session leaves a "PPM Remote Keyboard"
179
+ * and a "PPM Remote Pointer" in the compositor's device list for the life of the server, where
180
+ * they show up in system settings and in any tool that enumerates input devices. The next
181
+ * session recreates them — including the ~120 ms `ensureDevices` waits for the compositor to
182
+ * notice them — so the only cost is on the first event after a reconnect. */
183
+ export function releaseUinputDevices(): void {
184
+ destroyDevices();
185
+ }
186
+
187
+ function destroyDevices(): void {
188
+ if (!devices || !libc) return;
189
+ for (const fd of [devices.pointer, devices.keyboard]) {
190
+ try { libc.ioctl(fd, BigInt(UI_DEV_DESTROY), 0n); } catch { /* going away anyway */ }
191
+ try { closeSync(fd); } catch { /* ditto */ }
192
+ }
193
+ devices = null;
194
+ }
195
+
196
+ function emit(fd: number, entries: Array<[number, number, number]>): void {
197
+ const buf = eventBuf([...entries, [EV_SYN, SYN_REPORT, 0]]);
198
+ writeSync(fd, buf, 0, buf.byteLength);
199
+ }
200
+
201
+ /** `target` is accepted for interface parity but cannot be honoured: the kernel has no monitor
202
+ * layout, so absolute axes always span the compositor's whole pointer area. */
203
+ async function pointer(
204
+ xFrac: number, yFrac: number, button: "left" | "right" | null, down: boolean | null,
205
+ _target: InputTargetRect | null,
206
+ ): Promise<void> {
207
+ const dev = await ensureDevices();
208
+ const clamp = (v: number) => (Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : 0);
209
+ const events: Array<[number, number, number]> = [
210
+ [EV_ABS, ABS_X, Math.round(clamp(xFrac) * ABS_MAX)],
211
+ [EV_ABS, ABS_Y, Math.round(clamp(yFrac) * ABS_MAX)],
212
+ ];
213
+ if (button && down !== null) events.push([EV_KEY, button === "left" ? BTN_LEFT : BTN_RIGHT, down ? 1 : 0]);
214
+ emit(dev.pointer, events);
215
+ }
216
+
217
+ async function wheel(deltaY: number): Promise<void> {
218
+ const dev = await ensureDevices();
219
+ const notches = Math.round(deltaY / WHEEL_DELTA);
220
+ const clamped = Math.max(-MAX_WHEEL_NOTCHES, Math.min(MAX_WHEEL_NOTCHES, notches));
221
+ if (clamped === 0) return;
222
+ // REL_WHEEL is positive away from the user, the same sign the facade already carries.
223
+ emit(dev.pointer, [[EV_REL, REL_WHEEL, clamped]]);
224
+ }
225
+
226
+ async function key(code: string, down: boolean): Promise<boolean> {
227
+ const evdev = codeToEvdev(code);
228
+ if (evdev === null) return false;
229
+ const dev = await ensureDevices();
230
+ emit(dev.keyboard, [[EV_KEY, evdev, down ? 1 : 0]]);
231
+ return true;
232
+ }
233
+
234
+ async function releaseAllModifiers(): Promise<void> {
235
+ const dev = await ensureDevices();
236
+ emit(dev.keyboard, MODIFIER_EVDEV_CODES.map((c) => [EV_KEY, c, 0] as [number, number, number]));
237
+ }
238
+
239
+ export const uinputInputBackend: RemoteInputBackend = {
240
+ id: "linux-uinput",
241
+ pointer, wheel, key, releaseAllModifiers,
242
+ };
@@ -3,11 +3,17 @@
3
3
  * from the registry below; the session and routes never import a platform module directly.
4
4
  *
5
5
  * Every backend module is safe to *import* anywhere (FFI is loaded lazily inside each), so the
6
- * registry can hold them all statically — only the selected one ever `dlopen`s. Adding Linux =
7
- * `remote-desktop-input-linux.ts` + one line in `BACKENDS`.
6
+ * registry can hold them all statically — only the selected one ever `dlopen`s.
7
+ *
8
+ * Linux is the one platform whose backend cannot be decided by `process.platform` alone: XTEST
9
+ * is an X server extension and a Wayland compositor has no equivalent, so the choice is made
10
+ * per *session* inside `linuxInputBackend`.
8
11
  */
9
12
  import { win32InputBackend } from "./remote-desktop-input-win32.ts";
10
13
  import { darwinInputBackend } from "./remote-desktop-input-darwin.ts";
14
+ import { x11InputBackend } from "./remote-desktop-input-linux.ts";
15
+ import { uinputInputBackend, releaseUinputDevices } from "./remote-desktop-input-uinput.ts";
16
+ import { detectLinuxSession } from "./remote-desktop-linux-session.ts";
11
17
  import { RemoteInputUnavailableError, type InputTargetRect, type RemoteInputBackend } from "./remote-desktop-input-backend.ts";
12
18
 
13
19
  export { RemoteInputUnavailableError, type InputTargetRect, type RemoteInputBackend };
@@ -17,11 +23,34 @@ const BACKENDS: Partial<Record<NodeJS.Platform, RemoteInputBackend>> = {
17
23
  darwin: darwinInputBackend,
18
24
  };
19
25
 
20
- /** The backend for this host, or null when the platform has none. */
21
- export function getInputBackend(platform: NodeJS.Platform = process.platform): RemoteInputBackend | null {
26
+ /** Linux picks by session, not by platform: XTEST is an X server extension, so a Wayland
27
+ * compositor is driven as a virtual input device through uinput instead. Returns null on a
28
+ * host with no graphical session at all (a headless server, a CI container) so
29
+ * `isInputAvailable()` stays honest there. */
30
+ function linuxInputBackend(session = detectLinuxSession()): RemoteInputBackend | null {
31
+ if (!session) return null;
32
+ return session.kind === "x11" ? x11InputBackend : uinputInputBackend;
33
+ }
34
+
35
+ /** The backend for this host, or null when the platform has none. `session` is only read on
36
+ * Linux and is passed explicitly by tests — it otherwise probes the host, which a headless
37
+ * runner answers differently from a desktop. */
38
+ export function getInputBackend(
39
+ platform: NodeJS.Platform = process.platform,
40
+ session = platform === "linux" ? detectLinuxSession() : null,
41
+ ): RemoteInputBackend | null {
42
+ if (platform === "linux") return linuxInputBackend(session);
22
43
  return BACKENDS[platform] ?? null;
23
44
  }
24
45
 
46
+ /** Give back anything the injector holds between calls, for when no session needs it.
47
+ *
48
+ * Only uinput holds something: XTEST, `SendInput` and CoreGraphics own nothing between
49
+ * events, so this is a no-op everywhere else and is safe to call unconditionally. */
50
+ export function releaseRemoteInput(): void {
51
+ releaseUinputDevices();
52
+ }
53
+
25
54
  /** Sync, cheap: does this platform have an injector at all? Used on the per-event hot path to
26
55
  * drop input messages early. "Would it actually work" (OS permissions) is answered by
27
56
  * `remote-desktop-requirements.ts`. */
@@ -29,25 +58,28 @@ export function isInputAvailable(): boolean {
29
58
  return getInputBackend() !== null;
30
59
  }
31
60
 
61
+ /** Throws `RemoteInputUnavailableError`. Every caller below is `async` on purpose, so an
62
+ * unsupported platform surfaces as a *rejected promise* rather than a synchronous throw —
63
+ * the session forwards input from a WS message handler and only ever `await`s these. */
32
64
  function required(): RemoteInputBackend {
33
65
  const backend = getInputBackend();
34
66
  if (!backend) throw new RemoteInputUnavailableError();
35
67
  return backend;
36
68
  }
37
69
 
38
- export function injectPointer(
70
+ export async function injectPointer(
39
71
  xFrac: number, yFrac: number, button: "left" | "right" | null, down: boolean | null, target: InputTargetRect | null = null,
40
72
  ): Promise<void> {
41
73
  return required().pointer(xFrac, yFrac, button, down, target);
42
74
  }
43
75
 
44
- export function injectWheel(deltaY: number): Promise<void> {
76
+ export async function injectWheel(deltaY: number): Promise<void> {
45
77
  // A zero delta is a no-op on every OS — settle it here so no backend is even consulted.
46
- if (Math.round(deltaY) === 0) return Promise.resolve();
78
+ if (Math.round(deltaY) === 0) return;
47
79
  return required().wheel(deltaY);
48
80
  }
49
81
 
50
- export function injectKey(code: string, down: boolean): Promise<boolean> {
82
+ export async function injectKey(code: string, down: boolean): Promise<boolean> {
51
83
  return required().key(code, down);
52
84
  }
53
85