@hienlh/ppm 0.18.16 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +4 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +2 -1
- package/dist/web/assets/{adaptive-context-menu-BbuRVTLn.js → adaptive-context-menu-JfuUSqbe.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-Cm4n3VXt.js → ai-resource-editor-MfdJKVM5.js} +1 -1
- package/dist/web/assets/{ai-settings-section-BEM1z7kp.js → ai-settings-section-CJE7jFsj.js} +1 -1
- package/dist/web/assets/{angular-html-BlcrLjUx.js → angular-html-VzxSzTCy.js} +1 -1
- package/dist/web/assets/{angular-ts-BUjtceAJ.js → angular-ts-CmJ4bmzM.js} +1 -1
- package/dist/web/assets/api-client-DLWwuUkJ.js +1 -0
- package/dist/web/assets/{api-client-BzWM7XPR.js → api-client-qRWu6_s5.js} +1 -1
- package/dist/web/assets/api-settings-BFuaZrNC.js +1 -0
- package/dist/web/assets/{api-settings-Cubuqjsu.js → api-settings-CPg4yGvS.js} +1 -1
- package/dist/web/assets/{api-themes-YsZgf6Xj.js → api-themes-DsAp8m7m.js} +1 -1
- package/dist/web/assets/{apl-B3g0v1pk.js → apl-do17bnVx.js} +1 -1
- package/dist/web/assets/{astro-FRnyn7cH.js → astro-ClY0e0OA.js} +1 -1
- package/dist/web/assets/{audio-preview-DWTRm4HC.js → audio-preview-Fw935Mok.js} +1 -1
- package/dist/web/assets/{blade-NLdOqUKw.js → blade-BXSdXDGq.js} +1 -1
- package/dist/web/assets/{bot-D6sx11y0.js → bot-CWZAuloY.js} +1 -1
- package/dist/web/assets/{bsl-CenHZfaU.js → bsl-MxsMKDUZ.js} +1 -1
- package/dist/web/assets/{button-Cq8yixjf.js → button-CrwgGd1y.js} +1 -1
- package/dist/web/assets/{cairo-B_WR0aiQ.js → cairo-4CIrzX_p.js} +1 -1
- package/dist/web/assets/chat-tab-bSOVoC5N.js +15 -0
- package/dist/web/assets/{chevron-down-Dp0rpHb8.js → chevron-down-D7CbB5f6.js} +1 -1
- package/dist/web/assets/chevron-right-D7Pn5agw.js +1 -0
- package/dist/web/assets/circle-4g2coECH.js +1 -0
- package/dist/web/assets/{circle-question-mark-CzR7YRc-.js → circle-question-mark-BBzFZrNh.js} +1 -1
- package/dist/web/assets/clock-DbiNiwCG.js +1 -0
- package/dist/web/assets/{cobol-DOynhWYj.js → cobol-phPOE89d.js} +1 -1
- package/dist/web/assets/code-DdKXqtf_.js +1 -0
- package/dist/web/assets/code-editor-COTa37oe.js +10 -0
- package/dist/web/assets/{coffee-Bn6dbw4P.js → coffee-Cjuukfz3.js} +1 -1
- package/dist/web/assets/{conflict-editor-Bi2RWfKQ.js → conflict-editor-Ni0PPsKu.js} +1 -1
- package/dist/web/assets/{context-menu-DfWZAxQy.js → context-menu-BEAa1qhd.js} +1 -1
- package/dist/web/assets/{copy-BewHj66E.js → copy-CbPdNp7E.js} +1 -1
- package/dist/web/assets/{cpp-CoZR9t8r.js → cpp-CT7tGBC3.js} +1 -1
- package/dist/web/assets/{createLucideIcon-D8pyde1A.js → createLucideIcon-ConhwJnb.js} +1 -1
- package/dist/web/assets/{crystal-BSheONSg.js → crystal-D8U1Qnqh.js} +1 -1
- package/dist/web/assets/{csv-preview-DxGZ1qg5.js → csv-preview-Coc2oed1.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-gwPoVSCU.js → data-grid-overlay-editor-B8WQkZDB.js} +1 -1
- package/dist/web/assets/{data-grid-types-Dq_H1IPa.js → data-grid-types-KpjaKAci.js} +1 -1
- package/dist/web/assets/{database-Y3ESDK4-.js → database-BqyeGogo.js} +1 -1
- package/dist/web/assets/{database-viewer-DSPRzZny.js → database-viewer-Dta7lSSk.js} +1 -1
- package/dist/web/assets/{dialog-BSfuiTkJ.js → dialog-DrL3WEXd.js} +1 -1
- package/dist/web/assets/{diff-viewer-YbpnNv5Y.js → diff-viewer-CT0lmDc9.js} +1 -1
- package/dist/web/assets/{dist-aDSxTR_v.js → dist-C2_dKAa_.js} +1 -1
- package/dist/web/assets/{docx-preview-DqVDOMmv.js → docx-preview-76KDw1eE.js} +1 -1
- package/dist/web/assets/{download-DG3_9xXy.js → download-DAF7SXcf.js} +1 -1
- package/dist/web/assets/{edge-DTtoslQu.js → edge-Db4ze6bw.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-CXSbSUBX.js → edit-diff-preview-DyC_-4My.js} +1 -1
- package/dist/web/assets/{elixir-CIBCZ3Zi.js → elixir-RZPZ0472.js} +1 -1
- package/dist/web/assets/{elm-BLmb7K1H.js → elm-DNZcQt_7.js} +1 -1
- package/dist/web/assets/{erb-BBY8ApRd.js → erb-DRONsZXs.js} +1 -1
- package/dist/web/assets/{erlang-D5Zb5_P0.js → erlang-DeG0-QwJ.js} +1 -1
- package/dist/web/assets/{esm-6F8v1jLL.js → esm-CZy-sSoK.js} +1 -1
- package/dist/web/assets/explorer-body-BGjR2Gbt.js +2 -0
- package/dist/web/assets/explorer-window-content-DAhHh5EQ.js +1 -0
- package/dist/web/assets/{extension-store-Bhu--fPX.js → extension-store-ClQpnxQr.js} +1 -1
- package/dist/web/assets/{extension-webview-BSh6KGd7.js → extension-webview-BW5GuBnH.js} +1 -1
- package/dist/web/assets/{external-link-B6Hl0U4X.js → external-link-ojDHcQxQ.js} +1 -1
- package/dist/web/assets/{eye-off-CUdzgmX8.js → eye-off-Rq1n2-oe.js} +1 -1
- package/dist/web/assets/{file-exclamation-point-JzhTU2TX.js → file-exclamation-point-Bpg-zDPP.js} +1 -1
- package/dist/web/assets/{file-store-6HW2Lfl4.js → file-store-28e44KP3.js} +1 -1
- package/dist/web/assets/{folder-symlink-Dx1hOk8X.js → folder-symlink-LD-mr54V.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CBdwkEUJ.js → fortran-fixed-form-CK5VRJNd.js} +1 -1
- package/dist/web/assets/{fsharp-AT4XdVYH.js → fsharp-TVVgzHWp.js} +1 -1
- package/dist/web/assets/{gdresource-D852fWBR.js → gdresource-DiUcyewI.js} +1 -1
- package/dist/web/assets/{git-commit-DWfWAyxy.js → git-commit-CfOruFrg.js} +1 -1
- package/dist/web/assets/{git-log-panel-CkTiR70w.js → git-log-panel-D1-YXZ0O.js} +1 -1
- package/dist/web/assets/{git-rebase-C6opk8gr.js → git-rebase-Bxyr24oc.js} +1 -1
- package/dist/web/assets/{glide-data-grid-BB5mCaCD.js → glide-data-grid-BSsqxe2v.js} +6 -6
- package/dist/web/assets/{glimmer-js-DLi5mWYO.js → glimmer-js-CT5aJxz_.js} +1 -1
- package/dist/web/assets/{glimmer-ts-D-scFX30.js → glimmer-ts-CRjP9Yey.js} +1 -1
- package/dist/web/assets/{glsl-dbQzF4iA.js → glsl-BX2--oI_.js} +1 -1
- package/dist/web/assets/{graphql-D1m5Wen2.js → graphql-BIQsvBxQ.js} +1 -1
- package/dist/web/assets/{group-chat-tab-BPIOdFJw.js → group-chat-tab-CMtzoaFc.js} +1 -1
- package/dist/web/assets/{hack-CqYmpO7u.js → hack-CTWk0k1m.js} +1 -1
- package/dist/web/assets/{haml-BXzKWEag.js → haml-DGh27hXc.js} +1 -1
- package/dist/web/assets/{hand-x0ejq3fS.js → hand-DKSxDa5c.js} +1 -1
- package/dist/web/assets/{handlebars-DXnrtHtq.js → handlebars-BJL1jNrG.js} +1 -1
- package/dist/web/assets/{html-DhgQJ-xL.js → html-DALyDNqU.js} +1 -1
- package/dist/web/assets/{html-derivative-xZRft-YE.js → html-derivative-B4XcU57e.js} +1 -1
- package/dist/web/assets/{http-B53mSj14.js → http-DfMo7nNu.js} +1 -1
- package/dist/web/assets/{hurl-CsomVy04.js → hurl-CGc_n2Qf.js} +1 -1
- package/dist/web/assets/{hxml-LffjWwKA.js → hxml-40zZ1t4x.js} +1 -1
- package/dist/web/assets/{image-preview-CNA6XMNS.js → image-preview-Cp3XB8UO.js} +1 -1
- package/dist/web/assets/index-BkHlMfPD.js +52 -0
- package/dist/web/assets/index-SFFv2-VM.css +2 -0
- package/dist/web/assets/{input-D0ol5jW0.js → input-DTmI80l2.js} +1 -1
- package/dist/web/assets/{jinja-CCHURl7V.js → jinja-D5zuIn9u.js} +1 -1
- package/dist/web/assets/{jison-7qBd8dnR.js → jison-Dxut9QwA.js} +1 -1
- package/dist/web/assets/{julia-BY9pnTcy.js → julia-Cu85tTPo.js} +1 -1
- package/dist/web/assets/{just-D7wRSNRr.js → just-CctM4uLa.js} +1 -1
- package/dist/web/assets/{keybindings-store-BGkB72gM.js → keybindings-store-Bi0q9x4Q.js} +1 -1
- package/dist/web/assets/keybindings-store-_KfovSz7.js +1 -0
- package/dist/web/assets/{keyboard-DBBaoQSu.js → keyboard-AdwmYDSu.js} +1 -1
- package/dist/web/assets/{latex-RgsCae55.js → latex-kpwS_CC7.js} +1 -1
- package/dist/web/assets/{lib-v9x_qXgI.js → lib-Ca7ai8xa.js} +1 -1
- package/dist/web/assets/{liquid-CnDFzvOZ.js → liquid-B8Rr0xue.js} +1 -1
- package/dist/web/assets/loader-circle-DD65e9-7.js +1 -0
- package/dist/web/assets/{lock-CNxUGpTk.js → lock-BEtt_eTO.js} +1 -1
- package/dist/web/assets/{lua-0lj5W8sm.js → lua-C_3gixwM.js} +1 -1
- package/dist/web/assets/markdown-renderer-DroTmKbJ.js +1 -0
- package/dist/web/assets/markdown-renderer-ntDi3Tx-.js +3 -0
- package/dist/web/assets/{marko-CQgqAhbV.js → marko-B28rvNL-.js} +1 -1
- package/dist/web/assets/{mdc-BkIcTiIy.js → mdc-DQ6-STFO.js} +1 -1
- package/dist/web/assets/{media-url-DDciwvLU.js → media-url-BjvbfTTl.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-pjcHKorA.js → mobile-bottom-sheet-DXn5g2LB.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BP6iU4ma.js +1 -0
- package/dist/web/assets/{monitor-CxPV_fQI.js → monitor-CXI_XpWJ.js} +1 -1
- package/dist/web/assets/{monitor-smartphone-DLZ6Q05s.js → monitor-smartphone-C1wUPOSg.js} +1 -1
- package/dist/web/assets/{nextflow-DvIW68DS.js → nextflow-B1xq2O8L.js} +1 -1
- package/dist/web/assets/{nginx-O-hft0gG.js → nginx-xsLxRGxO.js} +1 -1
- package/dist/web/assets/{nim-BxrR4HwQ.js → nim-wqcek3WO.js} +1 -1
- package/dist/web/assets/notification-store-II5Kw9rD.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BoLRYuZT.js → number-overlay-editor-1c4sgZJt.js} +1 -1
- package/dist/web/assets/{overview-panel-l3g1gT5e.js → overview-panel-Cr2xsHc6.js} +1 -1
- package/dist/web/assets/{panel-store-UOV8wrfR.js → panel-store-Dv28vz3L.js} +1 -1
- package/dist/web/assets/{pdf-preview-B2CJ_EKv.js → pdf-preview-DKeIIJTv.js} +1 -1
- package/dist/web/assets/{pencil-BlIIg6RH.js → pencil-CHKLOgLs.js} +1 -1
- package/dist/web/assets/{perl-Da-uNqmL.js → perl-BH54gN9q.js} +1 -1
- package/dist/web/assets/{php-JMu45mEx.js → php-Br5ZrEZm.js} +1 -1
- package/dist/web/assets/play-CPbVzGgq.js +1 -0
- package/dist/web/assets/{plus-BR0qewJI.js → plus-BxqD2FIZ.js} +1 -1
- package/dist/web/assets/portal-container-context-BmG4--I-.js +1 -0
- package/dist/web/assets/{postgres-viewer-23xdZYxp.js → postgres-viewer-BJPr_FYL.js} +3 -3
- package/dist/web/assets/{process-table-CUsPf7wv.js → process-table-BqTcAJ-h.js} +1 -1
- package/dist/web/assets/{project-store-DTUCS65m.js → project-store-KHjFTCxf.js} +1 -1
- package/dist/web/assets/{pug-CLzyQhia.js → pug-8VCubZ3H.js} +1 -1
- package/dist/web/assets/{qml-BHXfGB1l.js → qml-D8AF1RZ6.js} +1 -1
- package/dist/web/assets/{razor-BqetJLN3.js → razor-6v042IJW.js} +1 -1
- package/dist/web/assets/{react-B549sK_T.js → react-DHs6efqI.js} +1 -1
- package/dist/web/assets/{refresh-cw-BQU5g2CY.js → refresh-cw-BALzw7L3.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-CYZUjYTO.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-DaoB49k4.js +1 -0
- package/dist/web/assets/remote-desktop-readiness-gate--LJgsuoj.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-Cihx4PNZ.js +1 -0
- package/dist/web/assets/{rotate-ccw-CpjRo90J.js → rotate-ccw-CuvNBM80.js} +1 -1
- package/dist/web/assets/{rst-iQdJZ0BP.js → rst-mTGFo4AQ.js} +1 -1
- package/dist/web/assets/{ruby-CNwzZGdH.js → ruby-BBaOP636.js} +1 -1
- package/dist/web/assets/{run-in-terminal-CBXmVGWa.js → run-in-terminal-Zl5iIx-B.js} +1 -1
- package/dist/web/assets/{sas-BpBIT2eR.js → sas-D5QEkIH2.js} +1 -1
- package/dist/web/assets/{save-DYLiVzU8.js → save-Dxq-Zepc.js} +1 -1
- package/dist/web/assets/{scroll-area-lc0ZHORr.js → scroll-area-B5yhPpZo.js} +1 -1
- package/dist/web/assets/{scss-D1MotuEp.js → scss-C5Mu6y2u.js} +1 -1
- package/dist/web/assets/{send-C_sLFt56.js → send-Cits8q0l.js} +1 -1
- package/dist/web/assets/{send-to-chat-DJAdlvJ8.js → send-to-chat-Cv7ikFUC.js} +1 -1
- package/dist/web/assets/{settings-store-zbPkG7o-.js → settings-store-Dp4i8zeA.js} +2 -2
- package/dist/web/assets/{settings-tab-xyRpt2gq.js → settings-tab-BtMvvKAF.js} +1 -1
- package/dist/web/assets/settings-tab-UPPycGRF.js +1 -0
- package/dist/web/assets/{shaderlab-BNqmcPQi.js → shaderlab-mLnpMpN3.js} +1 -1
- package/dist/web/assets/{shallow-CmPMAPte.js → shallow-BBJ_qoEF.js} +1 -1
- package/dist/web/assets/{shellsession-BBLOVOO-.js → shellsession-qdcDfUuk.js} +1 -1
- package/dist/web/assets/{shield-check-CUI36aSc.js → shield-check-CH7F1P7m.js} +1 -1
- package/dist/web/assets/{shield-off-GdkkVhb3.js → shield-off-C9HiF0-6.js} +1 -1
- package/dist/web/assets/{soy-BpTGo9uy.js → soy-CGGf1G43.js} +1 -1
- package/dist/web/assets/{sparkles-Dh-_J2Rd.js → sparkles-BnuLH6Ck.js} +1 -1
- package/dist/web/assets/{sparql-D7A3yKob.js → sparql-tQHB50VD.js} +1 -1
- package/dist/web/assets/{sql-query-editor-BE3mLYyz.js → sql-query-editor-DZqnYIB7.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-wN5RP8sd.js → sqlite-viewer-DXutos0e.js} +1 -1
- package/dist/web/assets/{stata-B7N56ptS.js → stata-DVQNLMcR.js} +1 -1
- package/dist/web/assets/{surrealql-CTmnD-lv.js → surrealql-lu576tBi.js} +1 -1
- package/dist/web/assets/{svelte-BppwY9TR.js → svelte-CSCrOBjT.js} +1 -1
- package/dist/web/assets/{switch-BmjKoIpR.js → switch-BPol3BFC.js} +1 -1
- package/dist/web/assets/{system-monitor-body-B2M0MC6y.js → system-monitor-body-Ct8_GX_f.js} +2 -2
- package/dist/web/assets/system-monitor-tab-DC_EerdI.js +1 -0
- package/dist/web/assets/system-monitor-window-content-D25DMNpw.js +1 -0
- package/dist/web/assets/{tab-host-window-content-DKev3GJz.js → tab-host-window-content-C5kYP0-z.js} +1 -1
- package/dist/web/assets/{tab-store-DzYSsw5a.js → tab-store-DS3YlUwR.js} +1 -1
- package/dist/web/assets/{table-D5Q4ap4W.js → table-BXDyImqk.js} +1 -1
- package/dist/web/assets/team-member-sheet-DvmioeQL.js +2 -0
- package/dist/web/assets/{team-member-window-content-CK08f5cK.js → team-member-window-content-BJ_XFw8-.js} +1 -1
- package/dist/web/assets/{templ-CvTMKKra.js → templ-CPt8AS4Q.js} +1 -1
- package/dist/web/assets/{terminal-tab-DZ3QiYcs.js → terminal-tab-8bKQ3CWR.js} +3 -3
- package/dist/web/assets/{tex--c4POEUE.js → tex-BrTByT7i.js} +1 -1
- package/dist/web/assets/{text-wrap-DBvQD3kc.js → text-wrap-CzMjB6GD.js} +1 -1
- package/dist/web/assets/{tool-cards-Bw1P0MKU.js → tool-cards-pIu_cmEL.js} +4 -4
- package/dist/web/assets/{trash-2-By4VMbC4.js → trash-2-BjShZ7if.js} +1 -1
- package/dist/web/assets/{triangle-alert-CUTURFbG.js → triangle-alert-qQ6Plydp.js} +1 -1
- package/dist/web/assets/{ts-tags--kx7D-Hw.js → ts-tags-DSqEZ7A8.js} +1 -1
- package/dist/web/assets/{twig-UG9uMHGd.js → twig-DW9qc1bf.js} +1 -1
- package/dist/web/assets/{upload-BNlX8D07.js → upload-7Zs6oaK5.js} +1 -1
- package/dist/web/assets/{use-blob-url-Bdxopwwc.js → use-blob-url-DKbV7PM_.js} +1 -1
- package/dist/web/assets/{use-coarse-long-press-BmI_GVW5.js → use-coarse-long-press-DGyC48NF.js} +1 -1
- package/dist/web/assets/{use-is-mobile-B9UZNwSm.js → use-is-mobile-ClE-UcPF.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BOmd2wpe.js → use-monaco-theme-DaxAk3gB.js} +1 -1
- package/dist/web/assets/use-open-team-member-CopUuDHG.js +1 -0
- package/dist/web/assets/use-remote-desktop-display-choice-BUA_1eaH.js +1 -0
- package/dist/web/assets/{use-visual-viewport-Sfb6CZ80.js → use-visual-viewport-CCX21c9w.js} +1 -1
- package/dist/web/assets/{use-websocket-B7Xj-Q4W.js → use-websocket-CWpG7PWN.js} +1 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +291 -0
- package/dist/web/assets/{vendor-ui-CaeeFO0B.js → vendor-ui-CXJtBULr.js} +1 -1
- package/dist/web/assets/{video-preview-B0nbTGjQ.js → video-preview-C2_fdunE.js} +1 -1
- package/dist/web/assets/{vue-jjTomlbt.js → vue-CJNDL1g-.js} +1 -1
- package/dist/web/assets/{vue-html-8KEW6-I9.js → vue-html-CG5BKtq1.js} +1 -1
- package/dist/web/assets/{vue-vine-C5tohCkU.js → vue-vine-Dd5HZ7T5.js} +1 -1
- package/dist/web/assets/{window-pip-registry-FdZdGF7h.js → window-pip-registry-9H6Y-HqR.js} +1 -1
- package/dist/web/assets/{window-store-D0x_fxr0.js → window-store-CNzkeUyO.js} +1 -1
- package/dist/web/assets/x-Dzu9ecti.js +1 -0
- package/dist/web/assets/{xml-ByFFosqL.js → xml-DTOm5niz.js} +1 -1
- package/dist/web/assets/{xsl-CZUwiLkj.js → xsl-BD_kBC04.js} +1 -1
- package/dist/web/index.html +60 -60
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/system-architecture.md +19 -0
- package/package.json +1 -1
- package/scripts/check-ppm-dir-usage.sh +0 -0
- package/scripts/install.sh +0 -0
- package/scripts/release.sh +0 -0
- package/src/server/routes/remote-desktop.ts +23 -6
- package/src/server/ws/remote-desktop.ts +4 -2
- package/src/server/ws/terminal.ts +9 -3
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/media-transcode/ffmpeg-capabilities.ts +48 -7
- package/src/services/remote-desktop/remote-desktop-capture-input.ts +62 -0
- package/src/services/remote-desktop/remote-desktop-capture.ts +29 -12
- package/src/services/remote-desktop/remote-desktop-cg-key-map.ts +45 -0
- package/src/services/remote-desktop/remote-desktop-displays.ts +90 -0
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +7 -0
- package/src/services/remote-desktop/remote-desktop-input-backend.ts +36 -0
- package/src/services/remote-desktop/remote-desktop-input-darwin.ts +200 -0
- package/src/services/remote-desktop/remote-desktop-input-win32.ts +243 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +43 -193
- package/src/services/remote-desktop/remote-desktop-macos-permissions.ts +62 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +124 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +16 -5
- package/src/services/windows-process-tree.ts +351 -351
- package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +6 -3
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +12 -1
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +5 -1
- package/src/web/components/remote-desktop/remote-desktop-readiness-gate.tsx +128 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +22 -2
- package/src/web/components/remote-desktop/use-remote-desktop-available.ts +7 -24
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +10 -3
- package/src/web/components/remote-desktop/use-remote-desktop-display-choice.ts +30 -0
- package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +82 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +61 -10
- package/.rdtest2-cf.out +0 -0
- package/dist/web/assets/api-client-D1CePgQ4.js +0 -1
- package/dist/web/assets/api-settings-CNtKkHqW.js +0 -1
- package/dist/web/assets/chat-tab-C2e6Pwc2.js +0 -15
- package/dist/web/assets/chevron-right-De-Wz5Lz.js +0 -1
- package/dist/web/assets/circle-BCy0AUCo.js +0 -1
- package/dist/web/assets/clock-C1CQUQPZ.js +0 -1
- package/dist/web/assets/code-DJgYrFzm.js +0 -1
- package/dist/web/assets/code-editor-DWtv_VDo.js +0 -10
- package/dist/web/assets/explorer-body-DpQndBVw.js +0 -2
- package/dist/web/assets/explorer-window-content-m0K6Mtcg.js +0 -1
- package/dist/web/assets/index-BHdtSKzb.js +0 -52
- package/dist/web/assets/index-DN9XUQ8D.css +0 -2
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +0 -1
- package/dist/web/assets/loader-circle-DouKgIXy.js +0 -1
- package/dist/web/assets/markdown-renderer-B9wzBWm9.js +0 -3
- package/dist/web/assets/markdown-renderer-DeSV1CCQ.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-DPdxpnKh.js +0 -1
- package/dist/web/assets/notification-store-BtijuGez.js +0 -1
- package/dist/web/assets/play-DpEEBJTT.js +0 -1
- package/dist/web/assets/portal-container-context-D-495m5C.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-FTF6IBhF.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-DSwG4OSk.js +0 -1
- package/dist/web/assets/remote-desktop-stats-overlay-Bz6xZ5y9.js +0 -1
- package/dist/web/assets/remote-desktop-warning-gate-DrYySLZ5.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-Ndvezorb.js +0 -1
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +0 -1
- package/dist/web/assets/system-monitor-tab-OYca4qaf.js +0 -1
- package/dist/web/assets/system-monitor-window-content-CHz9WdTm.js +0 -1
- package/dist/web/assets/team-member-sheet-BQ738Lli.js +0 -2
- package/dist/web/assets/use-open-team-member-D9EOB8-i.js +0 -1
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +0 -291
- package/dist/web/assets/x-DiOUR_mp.js +0 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- /package/dist/web/assets/{abap-C5DFF0s3.js → abap-Cjj2olMr.js} +0 -0
- /package/dist/web/assets/{actionscript-3-B0qyRNbc.js → actionscript-3-DCkW7kWC.js} +0 -0
- /package/dist/web/assets/{ada-bAJzplug.js → ada-AEWvZXuX.js} +0 -0
- /package/dist/web/assets/{andromeeda-DUCpISy6.js → andromeeda-D7cSpME6.js} +0 -0
- /package/dist/web/assets/{apache-BhAr84Z_.js → apache-DjvX0RSe.js} +0 -0
- /package/dist/web/assets/{apex-CRv8YnNq.js → apex-70xIZ0pH.js} +0 -0
- /package/dist/web/assets/{applescript-CD-Cw8bt.js → applescript-C3qKqxmA.js} +0 -0
- /package/dist/web/assets/{ara-BUzeQYoG.js → ara-iGt2zixk.js} +0 -0
- /package/dist/web/assets/{asciidoc-CT2hTg-v.js → asciidoc-Bs2t-06W.js} +0 -0
- /package/dist/web/assets/{asm-CAu35JeY.js → asm-B-T0eqhE.js} +0 -0
- /package/dist/web/assets/{aurora-x-BeFJoIA1.js → aurora-x-D3h_9M3E.js} +0 -0
- /package/dist/web/assets/{awk-Vau-DrGN.js → awk-CZwfwpPi.js} +0 -0
- /package/dist/web/assets/{ayu-dark-CLe-OTsQ.js → ayu-dark-DulkglEA.js} +0 -0
- /package/dist/web/assets/{ayu-light-CJbQfu4M.js → ayu-light-DyNQiRpn.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-GWaKwdrx.js → ayu-mirage-CcEt4xkI.js} +0 -0
- /package/dist/web/assets/{ballerina-DWu0USI5.js → ballerina-OhQPjyHS.js} +0 -0
- /package/dist/web/assets/{bat-_07Huw7E.js → bat-DqKcJTUn.js} +0 -0
- /package/dist/web/assets/{beancount-EcvogrcF.js → beancount-BdXlMi8T.js} +0 -0
- /package/dist/web/assets/{berry-BBB87SY7.js → berry-DvgY6BkY.js} +0 -0
- /package/dist/web/assets/{bibtex-DbueS87A.js → bibtex-BSRJq0yM.js} +0 -0
- /package/dist/web/assets/{bicep-tcmXnUo4.js → bicep-DpAdumKt.js} +0 -0
- /package/dist/web/assets/{bird2-B2qwilIR.js → bird2-rlbtp8di.js} +0 -0
- /package/dist/web/assets/{c-Z58oivSt.js → c-AjOLBXIs.js} +0 -0
- /package/dist/web/assets/{c3-U40cGT9p.js → c3-DqIXz4Ia.js} +0 -0
- /package/dist/web/assets/{cadence-BFcD-LyM.js → cadence-DAJtUpOE.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-RkC9AX6J.js → catppuccin-frappe-DUPoeZfb.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-Bsi4l-nO.js → catppuccin-latte-CK3U68Ql.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B8JIdTW-.js → catppuccin-macchiato-BzteLnIX.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-oH8wwrDj.js → catppuccin-mocha-ozLd0aBR.js} +0 -0
- /package/dist/web/assets/{clarity-C3WNeRCR.js → clarity-6KqEoBfz.js} +0 -0
- /package/dist/web/assets/{clojure-CFoAz7u9.js → clojure-B_uBL5C7.js} +0 -0
- /package/dist/web/assets/{cmake-Bcsqp9yR.js → cmake-B3nxBNmj.js} +0 -0
- /package/dist/web/assets/{codeowners-DC2Z7heP.js → codeowners-DkS-51at.js} +0 -0
- /package/dist/web/assets/{codeql-BfG68Al5.js → codeql-rpr1z_mR.js} +0 -0
- /package/dist/web/assets/{common-lisp-Z5P-lKQe.js → common-lisp-BfNbv_W6.js} +0 -0
- /package/dist/web/assets/{coq-BJslqrnv.js → coq-EAW-1nw0.js} +0 -0
- /package/dist/web/assets/{csharp-DhhZTOTs.js → csharp-BE3Gn5bq.js} +0 -0
- /package/dist/web/assets/{css-DiFIpMfE.js → css-Cr27CfPY.js} +0 -0
- /package/dist/web/assets/{csv-DViLbHxI.js → csv-B5VtaPtH.js} +0 -0
- /package/dist/web/assets/{cue-CoG3mg2I.js → cue-jm6lYNrx.js} +0 -0
- /package/dist/web/assets/{cypher-jTxC8NAB.js → cypher-DXkvK-pn.js} +0 -0
- /package/dist/web/assets/{d-6hKIUAbU.js → d-CgG1_yzd.js} +0 -0
- /package/dist/web/assets/{dark-plus-D_-np2Sr.js → dark-plus-CALzcIVB.js} +0 -0
- /package/dist/web/assets/{dart-CVpxLpVn.js → dart-BEVoLlWg.js} +0 -0
- /package/dist/web/assets/{dax-BPqJ8qIY.js → dax-H0BW3Q_P.js} +0 -0
- /package/dist/web/assets/{desktop-u_y5_Hr5.js → desktop-BtYq1l_0.js} +0 -0
- /package/dist/web/assets/{diff-Duj1XMrd.js → diff-CnBYdfTg.js} +0 -0
- /package/dist/web/assets/{dist-CEZ4plgH.js → dist-CJJKpgR7.js} +0 -0
- /package/dist/web/assets/{docker-8UGSCc7N.js → docker-COBitpld.js} +0 -0
- /package/dist/web/assets/{dotenv-DVnj8kyR.js → dotenv-CG0x1m7c.js} +0 -0
- /package/dist/web/assets/{dracula-D0cEmSg7.js → dracula-CLCi47X-.js} +0 -0
- /package/dist/web/assets/{dracula-soft-DfGS5Yfr.js → dracula-soft-BMv5cCAV.js} +0 -0
- /package/dist/web/assets/{dream-maker-qG8kMGTF.js → dream-maker-1Bd4OSww.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-iVqyMLyT.js → emacs-lisp-B-2zKLn-.js} +0 -0
- /package/dist/web/assets/{everforest-dark-Cruygd5U.js → everforest-dark-BGVynCbV.js} +0 -0
- /package/dist/web/assets/{everforest-light-BiJa8RHL.js → everforest-light--rn3R7Cm.js} +0 -0
- /package/dist/web/assets/{fennel-H6XSEIoz.js → fennel-SWg9Us_u.js} +0 -0
- /package/dist/web/assets/{fish-ho_7CxX6.js → fish-B04oqgKk.js} +0 -0
- /package/dist/web/assets/{fluent-1l5ssJTF.js → fluent-BefzAGMN.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-Bo1x233A.js → fortran-free-form-alw7jMxb.js} +0 -0
- /package/dist/web/assets/{gdscript-DffFMp_k.js → gdscript-BAyhvjCx.js} +0 -0
- /package/dist/web/assets/{gdshader-CvnpAiAF.js → gdshader-Crl09X-B.js} +0 -0
- /package/dist/web/assets/{genie-2eOvAEzW.js → genie-BSIGRHu7.js} +0 -0
- /package/dist/web/assets/{gherkin-DL-5OUlV.js → gherkin-DJhSnSWH.js} +0 -0
- /package/dist/web/assets/{github-dark-default-_mDH8IMP.js → github-dark-default-FVtXjcm8.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-C48hJuLn.js → github-dark-dimmed-CzrUPgSb.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-BQ-zGGNe.js → github-dark-high-contrast-1s9rF5EB.js} +0 -0
- /package/dist/web/assets/{github-dark-DrTZprgG.js → github-dark-r8C6qwug.js} +0 -0
- /package/dist/web/assets/{github-light-BYc90Yx8.js → github-light-BvIWCkl7.js} +0 -0
- /package/dist/web/assets/{github-light-default-Daecj4J5.js → github-light-default-CVdhzrxV.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DoafCbsU.js → github-light-high-contrast-CBAf6hjF.js} +0 -0
- /package/dist/web/assets/{gleam-hUWWjmnp.js → gleam-DvL1q7aa.js} +0 -0
- /package/dist/web/assets/{gn-7PJ3R70o.js → gn-BG5NvwAf.js} +0 -0
- /package/dist/web/assets/{gnuplot-V-Pxck7b.js → gnuplot-BARnM-zG.js} +0 -0
- /package/dist/web/assets/{go-ARF6DprD.js → go-DXvjjq4q.js} +0 -0
- /package/dist/web/assets/{groovy-CKGBIPOz.js → groovy-CRylSKhr.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-Cm9wqdI3.js → gruvbox-dark-hard-BVPjnqm7.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Cmsd6dG8.js → gruvbox-dark-medium-U_PDgq0a.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-BBGlGGCz.js → gruvbox-dark-soft-CkzNtpbr.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-BCR7KmqG.js → gruvbox-light-hard-DfMmR2G8.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-CP32bga8.js → gruvbox-light-medium-BGCak-dd.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-qjxk8ps6.js → gruvbox-light-soft-DnpM3fkN.js} +0 -0
- /package/dist/web/assets/{haskell-C5DzwGRa.js → haskell-DXgGIDD_.js} +0 -0
- /package/dist/web/assets/{haxe-C8f_jCVv.js → haxe-B7qlTpRz.js} +0 -0
- /package/dist/web/assets/{hcl-DetJkf6E.js → hcl-ADWfVe_Q.js} +0 -0
- /package/dist/web/assets/{hjson-DeZhwIdo.js → hjson-BaM9gLbq.js} +0 -0
- /package/dist/web/assets/{hlsl-SMIDLRyX.js → hlsl-iqaRA4MG.js} +0 -0
- /package/dist/web/assets/{horizon-BMAVDxMx.js → horizon-BPpTnhB9.js} +0 -0
- /package/dist/web/assets/{horizon-bright-3BaV_VGD.js → horizon-bright-yBLg3QbE.js} +0 -0
- /package/dist/web/assets/{houston-BWBbEPvb.js → houston-eFOp0ySL.js} +0 -0
- /package/dist/web/assets/{hy-DcsmE-Nm.js → hy-BALeMn3i.js} +0 -0
- /package/dist/web/assets/{imba-CnkgnYMf.js → imba-CdGUm_GD.js} +0 -0
- /package/dist/web/assets/{ini-BnvTdau7.js → ini-DG7imsqe.js} +0 -0
- /package/dist/web/assets/{java-BLFACXat.js → java-D5GTT474.js} +0 -0
- /package/dist/web/assets/{javascript-BKcFWENk.js → javascript-V7WwJvRm.js} +0 -0
- /package/dist/web/assets/{json-3AJtJADf.js → json-B9FhwAgR.js} +0 -0
- /package/dist/web/assets/{json5-DLyDVlwe.js → json5--Abk7MUy.js} +0 -0
- /package/dist/web/assets/{jsonc-B5DgF1jW.js → jsonc-C8jb-Z6x.js} +0 -0
- /package/dist/web/assets/{jsonl-CV_O71a2.js → jsonl-CaJNxfkm.js} +0 -0
- /package/dist/web/assets/{jsonnet-DZfPLgYz.js → jsonnet-CPrKMSCU.js} +0 -0
- /package/dist/web/assets/{jssm-Cr1zYOsY.js → jssm-DEu-9_uo.js} +0 -0
- /package/dist/web/assets/{jsx-VN-JS2_q.js → jsx-MfAsCWDW.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-D9CcT3xg.js → kanagawa-dragon-DnbcAduF.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-DLXsB8fr.js → kanagawa-lotus-yxsY3ew8.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-weDs3Wjz.js → kanagawa-wave-C2RyvZaM.js} +0 -0
- /package/dist/web/assets/{kdl-0XF56jnT.js → kdl-CvssmvcU.js} +0 -0
- /package/dist/web/assets/{kotlin-BPkH9vzk.js → kotlin-jufO49mU.js} +0 -0
- /package/dist/web/assets/{kusto-DMVpADb9.js → kusto-BAFnylR7.js} +0 -0
- /package/dist/web/assets/{laserwave-Cl6T2cGP.js → laserwave-BgxFC4Ge.js} +0 -0
- /package/dist/web/assets/{lean-BvwzQGl1.js → lean-Bgt24V1D.js} +0 -0
- /package/dist/web/assets/{less-DI_03xkC.js → less-Bk_UaqsM.js} +0 -0
- /package/dist/web/assets/{light-plus-hIiIsQN_.js → light-plus-nMxFfInN.js} +0 -0
- /package/dist/web/assets/{llvm-B5hjjSWc.js → llvm-3_PASDOO.js} +0 -0
- /package/dist/web/assets/{log-D28raQiE.js → log-CKm0CxB1.js} +0 -0
- /package/dist/web/assets/{logo-6Cdd4ASb.js → logo-DFP1_5U6.js} +0 -0
- /package/dist/web/assets/{luau-CK9nKpOX.js → luau-BcZnoyh3.js} +0 -0
- /package/dist/web/assets/{make-CVnxKCB6.js → make-CpNSwgwT.js} +0 -0
- /package/dist/web/assets/{markdown-B9mMQzD5.js → markdown-DhU3Djpv.js} +0 -0
- /package/dist/web/assets/{material-theme-BZSs2o0j.js → material-theme-DD5L3OqF.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BeAQBzQ_.js → material-theme-darker-CG2_tD3R.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BAe0IXcs.js → material-theme-lighter-CzNkUc9Y.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CiPIf0Np.js → material-theme-ocean-BtLiAaF_.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DJBb4_XN.js → material-theme-palenight-Brvjz035.js} +0 -0
- /package/dist/web/assets/{matlab-CY0dbp_s.js → matlab-CCY0KtMD.js} +0 -0
- /package/dist/web/assets/{mdx-DU9zbF7h.js → mdx-CH6YY0aS.js} +0 -0
- /package/dist/web/assets/{mermaid-UmmjGwdh.js → mermaid-Bqh3gFI0.js} +0 -0
- /package/dist/web/assets/{min-dark-C59GkzOm.js → min-dark-BUwfTzI6.js} +0 -0
- /package/dist/web/assets/{min-light-B9QglrdQ.js → min-light-oLanXCOk.js} +0 -0
- /package/dist/web/assets/{mipsasm-Dc6Jsr_D.js → mipsasm-CTh3hPWK.js} +0 -0
- /package/dist/web/assets/{mojo-D5ij-nGW.js → mojo-DXdszZKa.js} +0 -0
- /package/dist/web/assets/{monokai-QAtE_yCP.js → monokai-TNE_tRxc.js} +0 -0
- /package/dist/web/assets/{moonbit-BMtIdiTL.js → moonbit-jrk-Gx1X.js} +0 -0
- /package/dist/web/assets/{move-BB_jMHYZ.js → move-DItn61bH.js} +0 -0
- /package/dist/web/assets/{narrat-BvvoKGVn.js → narrat-3kB4dkh0.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CqxtAsnF.js → nextflow-groovy-CSKtwETr.js} +0 -0
- /package/dist/web/assets/{night-owl-CLoE9kjt.js → night-owl-Dm91nSIq.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BwIFT5Xd.js → night-owl-light-riwrcPRg.js} +0 -0
- /package/dist/web/assets/{nix-BKlnVT9r.js → nix-sLgl9zGF.js} +0 -0
- /package/dist/web/assets/{nord-B7R1KRAK.js → nord-TWkJPI6M.js} +0 -0
- /package/dist/web/assets/{nushell-CzUme-NJ.js → nushell-CDZi4-Gf.js} +0 -0
- /package/dist/web/assets/{objective-c-D-UKKiOc.js → objective-c-DW9PboNp.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BucWn4Fx.js → objective-cpp-2XCCX6q1.js} +0 -0
- /package/dist/web/assets/{ocaml-EJCNXMGo.js → ocaml-CxPdnjrh.js} +0 -0
- /package/dist/web/assets/{odin-CEjnRU_3.js → odin-BJjlKpkM.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-Bv469NX5.js → one-dark-pro-K1KOmBZ7.js} +0 -0
- /package/dist/web/assets/{one-light-BlzO5RWI.js → one-light-DQB-vUyZ.js} +0 -0
- /package/dist/web/assets/{openscad-Czl6alLw.js → openscad-DCoLwn4j.js} +0 -0
- /package/dist/web/assets/{pascal-BI9fLvG4.js → pascal-z2ktbbWp.js} +0 -0
- /package/dist/web/assets/{pkl-ChQLsyjU.js → pkl-Dyq4-v6z.js} +0 -0
- /package/dist/web/assets/{plastic-DGL3mtdL.js → plastic-C4Rq5PnD.js} +0 -0
- /package/dist/web/assets/{plsql-BJDjqcWp.js → plsql-BCIjO63y.js} +0 -0
- /package/dist/web/assets/{po-DK5O3OpF.js → po-_kXpd_0n.js} +0 -0
- /package/dist/web/assets/{poimandres-CnuqZT4T.js → poimandres-C9XggjB3.js} +0 -0
- /package/dist/web/assets/{polar-DCsUxSsQ.js → polar-DV_1L04z.js} +0 -0
- /package/dist/web/assets/{postcss-8UjdFmis.js → postcss-BuqS91mQ.js} +0 -0
- /package/dist/web/assets/{powerquery-CAp0CGQj.js → powerquery-DK13B2Dc.js} +0 -0
- /package/dist/web/assets/{powershell-CbBsKZJP.js → powershell-tkuhhtuP.js} +0 -0
- /package/dist/web/assets/{prisma-BnNpXCJN.js → prisma-WNXfBtpr.js} +0 -0
- /package/dist/web/assets/{prolog-CC-l9E8Y.js → prolog-BzEdNKfO.js} +0 -0
- /package/dist/web/assets/{proto-CV_1k2rL.js → proto-B5meVsrA.js} +0 -0
- /package/dist/web/assets/{puppet-2D8HU2bv.js → puppet-C7h_p4r2.js} +0 -0
- /package/dist/web/assets/{purescript-BEJAORgU.js → purescript-qH1KxAs6.js} +0 -0
- /package/dist/web/assets/{python-BBdbb7nn.js → python-DeTIYd5V.js} +0 -0
- /package/dist/web/assets/{qmldir-D5ZYI4Ie.js → qmldir-4Rm5AvD3.js} +0 -0
- /package/dist/web/assets/{qss-BOJVGyXE.js → qss-YRbeNtr0.js} +0 -0
- /package/dist/web/assets/{r-B5dlEtkj.js → r-DHwHK0Jh.js} +0 -0
- /package/dist/web/assets/{racket-BUAEygJ6.js → racket-gKn4SFLQ.js} +0 -0
- /package/dist/web/assets/{raku-BG0Ss0ID.js → raku-DWTmAj3A.js} +0 -0
- /package/dist/web/assets/{red-C5yyQbtG.js → red-BFuHRZFS.js} +0 -0
- /package/dist/web/assets/{reg-DHoBWtLf.js → reg-DYoOF5tH.js} +0 -0
- /package/dist/web/assets/{regexp-DDtJrlKv.js → regexp-6rXQHD2-.js} +0 -0
- /package/dist/web/assets/{rel-CR6WJDRD.js → rel-Z_tTruCU.js} +0 -0
- /package/dist/web/assets/{riscv-2V9tC-nx.js → riscv-DfTfKuvU.js} +0 -0
- /package/dist/web/assets/{ron-QLTmqGUw.js → ron-CsUtW8gQ.js} +0 -0
- /package/dist/web/assets/{rose-pine-be0__X3A.js → rose-pine-Dng_vCo6.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-n_WQJwsW.js → rose-pine-dawn-Ds88Nezf.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-t2aLtzgB.js → rose-pine-moon-DWpzrqNz.js} +0 -0
- /package/dist/web/assets/{rosmsg-Ce1JD8vt.js → rosmsg-h2sO84oD.js} +0 -0
- /package/dist/web/assets/{rust-5fHH-ZoW.js → rust-Dx86yfCi.js} +0 -0
- /package/dist/web/assets/{sass-DPmGFiOJ.js → sass-BwwVhdT9.js} +0 -0
- /package/dist/web/assets/{scala-BRMVFbZF.js → scala-WJqNEN-w.js} +0 -0
- /package/dist/web/assets/{scheme-BMSJWevF.js → scheme-uwj-t1Rp.js} +0 -0
- /package/dist/web/assets/{sdbl-DTZ-pEHI.js → sdbl-BYy5hgEz.js} +0 -0
- /package/dist/web/assets/{shellscript-6-9yg5Cz.js → shellscript-DWTW_w2K.js} +0 -0
- /package/dist/web/assets/{slack-dark-DKJgiR7e.js → slack-dark-BvhGq5vQ.js} +0 -0
- /package/dist/web/assets/{slack-ochin-WjehVgyK.js → slack-ochin-C9g0ZRFr.js} +0 -0
- /package/dist/web/assets/{smalltalk-BChf4SN1.js → smalltalk-ZEiZ6i7U.js} +0 -0
- /package/dist/web/assets/{snazzy-light-C1WAwOx6.js → snazzy-light-29OA24q5.js} +0 -0
- /package/dist/web/assets/{solarized-dark-BxREZrGx.js → solarized-dark-Bnq9IOn_.js} +0 -0
- /package/dist/web/assets/{solarized-light-DG29B1pR.js → solarized-light-CYdI7T5g.js} +0 -0
- /package/dist/web/assets/{solidity-bDbtGwdR.js → solidity-DcuIDB-_.js} +0 -0
- /package/dist/web/assets/{splunk-Ceqx5njI.js → splunk-D5ONE91g.js} +0 -0
- /package/dist/web/assets/{sql-OwMEKbz8.js → sql-DIwJJDU-.js} +0 -0
- /package/dist/web/assets/{ssh-config-WAR3lu2k.js → ssh-config-D8717tLI.js} +0 -0
- /package/dist/web/assets/{stylus-Cjmoz3d8.js → stylus-Cm1lO4Y6.js} +0 -0
- /package/dist/web/assets/{swift-S_ZSBG1q.js → swift-X8oWpSeU.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DiUeWMSh.js → synthwave-84-DF-t1UKY.js} +0 -0
- /package/dist/web/assets/{system-verilog-RRjqPjUV.js → system-verilog-JYyHFpPf.js} +0 -0
- /package/dist/web/assets/{systemd-BD3SkXoU.js → systemd-Da-5Ma_c.js} +0 -0
- /package/dist/web/assets/{talonscript-DWefISOm.js → talonscript-CXraVPho.js} +0 -0
- /package/dist/web/assets/{tasl-DCjSS-09.js → tasl-BCmWezH9.js} +0 -0
- /package/dist/web/assets/{tcl-DTYqT_66.js → tcl-BTufzfbf.js} +0 -0
- /package/dist/web/assets/{terraform-CkbP5QnR.js → terraform-CtUA9c8W.js} +0 -0
- /package/dist/web/assets/{tokyo-night-qVSqO9xy.js → tokyo-night-BCThYXmz.js} +0 -0
- /package/dist/web/assets/{toml-D9F5Osqs.js → toml-D5YlDr6Y.js} +0 -0
- /package/dist/web/assets/{tsv-C5tpHoxm.js → tsv-CBB-P_Sl.js} +0 -0
- /package/dist/web/assets/{tsx-CMWkGtGm.js → tsx-D5B4juDp.js} +0 -0
- /package/dist/web/assets/{turtle-Cn4z3xOi.js → turtle-D8PCeJ-v.js} +0 -0
- /package/dist/web/assets/{typescript-DSyAEWsn.js → typescript-CMPnTawp.js} +0 -0
- /package/dist/web/assets/{typespec-B93Qap7y.js → typespec-CHxldxIW.js} +0 -0
- /package/dist/web/assets/{typst-DtlOmFlX.js → typst-Q8CqX5OR.js} +0 -0
- /package/dist/web/assets/{v-Dfzf-szS.js → v-Bf2hKAMu.js} +0 -0
- /package/dist/web/assets/{vala-CyiQZQ9f.js → vala-Cbete-UE.js} +0 -0
- /package/dist/web/assets/{vb-C7DGSldd.js → vb-CA-xWAcX.js} +0 -0
- /package/dist/web/assets/{verilog-kF-hClY2.js → verilog-B3wb8CfT.js} +0 -0
- /package/dist/web/assets/{vesper-BmKcADuz.js → vesper-D6r3VJq_.js} +0 -0
- /package/dist/web/assets/{vhdl-CDnb16KJ.js → vhdl-C42S_797.js} +0 -0
- /package/dist/web/assets/{viml-B8cs6Ady.js → viml-BePrIAs8.js} +0 -0
- /package/dist/web/assets/{vitesse-black-BR6o9Gto.js → vitesse-black-DRqn61Ck.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dye14-KB.js → vitesse-dark-39_8UE-w.js} +0 -0
- /package/dist/web/assets/{vitesse-light-BLpswIxf.js → vitesse-light-B1jwrxDm.js} +0 -0
- /package/dist/web/assets/{vyper-DU51Z97W.js → vyper-tDJv3imF.js} +0 -0
- /package/dist/web/assets/{wasm-CVqlJAR2.js → wasm-DR7eskZH.js} +0 -0
- /package/dist/web/assets/{wenyan-DhCa3hCn.js → wenyan-CezVfqN3.js} +0 -0
- /package/dist/web/assets/{wgsl-Bj1Ec4vv.js → wgsl-CwOcIiIG.js} +0 -0
- /package/dist/web/assets/{wikitext-C90DHNa0.js → wikitext-C5coomYc.js} +0 -0
- /package/dist/web/assets/{wit-DUgbOD7Z.js → wit-C9yyX6-U.js} +0 -0
- /package/dist/web/assets/{wolfram-BgwUqD1I.js → wolfram-BWi0rMAa.js} +0 -0
- /package/dist/web/assets/{yaml-DEQulj-8.js → yaml-CGXhKUtT.js} +0 -0
- /package/dist/web/assets/{zenscript-C7Jp7TgT.js → zenscript-Do7WLoJh.js} +0 -0
- /package/dist/web/assets/{zig-9x4qXYLg.js → zig-Cr80Qrru.js} +0 -0
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Edge forwarder — owns the public port and pipes raw TCP to the server's
|
|
3
|
-
* current loopback port.
|
|
4
|
-
*
|
|
5
|
-
* ─── CRITICAL INVARIANT: THIS PROCESS MUST NEVER SPAWN A CHILD ───
|
|
6
|
-
*
|
|
7
|
-
* On Windows a child spawned with fd stdio inherits every inheritable handle,
|
|
8
|
-
* including a listening socket. When such a descendant is orphaned it keeps the
|
|
9
|
-
* socket open, so the port stays in LISTEN under a dead PID and can never be
|
|
10
|
-
* rebound — a "zombie port". The server hits this constantly because it spawns
|
|
11
|
-
* chat/tool/MCP subprocesses; those inherit its listener and wedge its port.
|
|
12
|
-
*
|
|
13
|
-
* The edge exists precisely because it spawns nothing, so its socket cannot be
|
|
14
|
-
* inherited and its port cannot zombie. cloudflared can therefore stay pinned
|
|
15
|
-
* to it forever and the public URL stops rotating. Adding any child-process
|
|
16
|
-
* call to this file reintroduces the exact bug the edge was built to remove.
|
|
17
|
-
*
|
|
18
|
-
* Raw TCP, never HTTP: WebSocket upgrades and SSE streams pass through
|
|
19
|
-
* untouched, and forwarded requests keep the headers cloudflared set.
|
|
20
|
-
*/
|
|
21
|
-
import net from "node:net";
|
|
22
|
-
import { resolveTargetPort, _resetTargetCache } from "./edge-target-resolver.ts";
|
|
23
|
-
|
|
24
|
-
/** Delay between attempts while waiting for the server to come back. */
|
|
25
|
-
const CONNECT_RETRY_MS = 250;
|
|
26
|
-
/**
|
|
27
|
-
* How long a client connection waits for a reachable server before giving up.
|
|
28
|
-
* During an upgrade the server is briefly absent; refusing instantly would
|
|
29
|
-
* surface as a broken page rather than a slow one.
|
|
30
|
-
*/
|
|
31
|
-
const CONNECT_WINDOW_MS = 5000;
|
|
32
|
-
|
|
33
|
-
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
34
|
-
|
|
35
|
-
/** Resolve once the socket is connected; reject on the first connect error. */
|
|
36
|
-
function connectUpstream(port: number): Promise<net.Socket> {
|
|
37
|
-
return new Promise((resolvePromise, reject) => {
|
|
38
|
-
const upstream = net.connect({
|
|
39
|
-
port,
|
|
40
|
-
host: "127.0.0.1", // never taken from status.json — loopback only
|
|
41
|
-
allowHalfOpen: true,
|
|
42
|
-
});
|
|
43
|
-
const onError = (err: Error) => {
|
|
44
|
-
upstream.destroy();
|
|
45
|
-
reject(err);
|
|
46
|
-
};
|
|
47
|
-
upstream.once("error", onError);
|
|
48
|
-
upstream.once("connect", () => {
|
|
49
|
-
upstream.removeListener("error", onError);
|
|
50
|
-
resolvePromise(upstream);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Largest amount of client data held while waiting for the server. Bounds the
|
|
57
|
-
* memory a slow upload can pin during an upgrade window.
|
|
58
|
-
*/
|
|
59
|
-
const MAX_PENDING_BYTES = 8 * 1024 * 1024;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Pipe one client connection to the server.
|
|
63
|
-
*
|
|
64
|
-
* Resolving the upstream is async, so the first bytes usually arrive before
|
|
65
|
-
* there is anywhere to send them. They must be captured by a `data` listener
|
|
66
|
-
* attached synchronously on this very tick and replayed once the upstream
|
|
67
|
-
* exists.
|
|
68
|
-
*
|
|
69
|
-
* `socket.pause()` is NOT a substitute — verified against Bun 1.3.13: bytes
|
|
70
|
-
* that arrive while paused are lost, and a later `pipe()`/`resume()` does not
|
|
71
|
-
* bring them back. A socket with no `data` listener drops them outright too.
|
|
72
|
-
* Both variants silently swallowed the first request in testing.
|
|
73
|
-
*/
|
|
74
|
-
async function handleConnection(
|
|
75
|
-
client: net.Socket,
|
|
76
|
-
connectWindowMs: number,
|
|
77
|
-
): Promise<void> {
|
|
78
|
-
const pending: Buffer[] = [];
|
|
79
|
-
let pendingBytes = 0;
|
|
80
|
-
let clientEnded = false;
|
|
81
|
-
const onEarlyData = (chunk: Buffer) => {
|
|
82
|
-
pendingBytes += chunk.length;
|
|
83
|
-
if (pendingBytes > MAX_PENDING_BYTES) {
|
|
84
|
-
client.destroy();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
pending.push(chunk);
|
|
88
|
-
};
|
|
89
|
-
client.on("data", onEarlyData);
|
|
90
|
-
client.once("end", () => { clientEnded = true; });
|
|
91
|
-
|
|
92
|
-
let upstream: net.Socket | null = null;
|
|
93
|
-
let clientGone = false;
|
|
94
|
-
|
|
95
|
-
// A socket error with no listener is an unhandled 'error' event, which would
|
|
96
|
-
// take the whole edge process — and the public port — down with it.
|
|
97
|
-
const teardown = () => {
|
|
98
|
-
upstream?.destroy();
|
|
99
|
-
client.destroy();
|
|
100
|
-
};
|
|
101
|
-
client.on("error", teardown);
|
|
102
|
-
client.once("close", () => { clientGone = true; });
|
|
103
|
-
|
|
104
|
-
const deadline = Date.now() + connectWindowMs;
|
|
105
|
-
while (!clientGone && Date.now() < deadline) {
|
|
106
|
-
const port = resolveTargetPort();
|
|
107
|
-
if (port !== null) {
|
|
108
|
-
try {
|
|
109
|
-
upstream = await connectUpstream(port);
|
|
110
|
-
break;
|
|
111
|
-
} catch {
|
|
112
|
-
// Server not up yet (or moved mid-restart) — re-resolve and retry.
|
|
113
|
-
_resetTargetCache();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
await sleep(CONNECT_RETRY_MS);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (clientGone) return;
|
|
120
|
-
if (!upstream) {
|
|
121
|
-
client.destroy();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
upstream.on("error", teardown);
|
|
126
|
-
|
|
127
|
-
// Replay what arrived during the connect, then hand both directions to the
|
|
128
|
-
// stream machinery. Removing the listener and piping happen in the same tick,
|
|
129
|
-
// so no chunk can slip through the gap.
|
|
130
|
-
client.off("data", onEarlyData);
|
|
131
|
-
for (const chunk of pending) upstream.write(chunk);
|
|
132
|
-
pending.length = 0;
|
|
133
|
-
|
|
134
|
-
client.pipe(upstream);
|
|
135
|
-
upstream.pipe(client);
|
|
136
|
-
|
|
137
|
-
// The client may have half-closed while we were still connecting.
|
|
138
|
-
if (clientEnded) upstream.end();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface EdgeForwarderOptions {
|
|
142
|
-
publicPort: number;
|
|
143
|
-
host?: string;
|
|
144
|
-
/** How long a client waits for a reachable server. Defaults to 5s. */
|
|
145
|
-
connectWindowMs?: number;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Bind the public port and start forwarding. Resolves once listening so the
|
|
150
|
-
* caller can fail fast when the port is unavailable.
|
|
151
|
-
*/
|
|
152
|
-
export function startEdgeForwarder(
|
|
153
|
-
opts: EdgeForwarderOptions,
|
|
154
|
-
): Promise<net.Server> {
|
|
155
|
-
const host = opts.host ?? "0.0.0.0";
|
|
156
|
-
const connectWindowMs = opts.connectWindowMs ?? CONNECT_WINDOW_MS;
|
|
157
|
-
return new Promise((resolvePromise, reject) => {
|
|
158
|
-
// allowHalfOpen keeps the other direction alive when one side sends FIN —
|
|
159
|
-
// long-lived chat WS and SSE streams depend on it.
|
|
160
|
-
const server = net.createServer({ allowHalfOpen: true }, (client) => {
|
|
161
|
-
void handleConnection(client, connectWindowMs);
|
|
162
|
-
});
|
|
163
|
-
server.once("error", reject);
|
|
164
|
-
server.listen(opts.publicPort, host, () => {
|
|
165
|
-
server.removeListener("error", reject);
|
|
166
|
-
resolvePromise(server);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ─── Process entry ─────────────────────────────────────────────────────
|
|
172
|
-
// Mirrors the `__serve__` guard in src/server/index.ts: the supervisor
|
|
173
|
-
// re-invokes the binary as `<bin> __edge__ <publicPort> <host>`.
|
|
174
|
-
if (process.argv.includes("__edge__")) {
|
|
175
|
-
const idx = process.argv.indexOf("__edge__");
|
|
176
|
-
const publicPort = parseInt(process.argv[idx + 1] ?? "", 10);
|
|
177
|
-
const host = process.argv[idx + 2] ?? "0.0.0.0";
|
|
178
|
-
|
|
179
|
-
if (!Number.isInteger(publicPort) || publicPort <= 0 || publicPort > 65535) {
|
|
180
|
-
process.stderr.write(`[edge] Invalid public port: ${process.argv[idx + 1]}\n`);
|
|
181
|
-
process.exit(2);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
await startEdgeForwarder({ publicPort, host });
|
|
186
|
-
process.stderr.write(`[edge] Listening on ${host}:${publicPort}\n`);
|
|
187
|
-
} catch (err) {
|
|
188
|
-
process.stderr.write(`[edge] Failed to bind ${host}:${publicPort}: ${err}\n`);
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Edge forwarder — owns the public port and pipes raw TCP to the server's
|
|
3
|
+
* current loopback port.
|
|
4
|
+
*
|
|
5
|
+
* ─── CRITICAL INVARIANT: THIS PROCESS MUST NEVER SPAWN A CHILD ───
|
|
6
|
+
*
|
|
7
|
+
* On Windows a child spawned with fd stdio inherits every inheritable handle,
|
|
8
|
+
* including a listening socket. When such a descendant is orphaned it keeps the
|
|
9
|
+
* socket open, so the port stays in LISTEN under a dead PID and can never be
|
|
10
|
+
* rebound — a "zombie port". The server hits this constantly because it spawns
|
|
11
|
+
* chat/tool/MCP subprocesses; those inherit its listener and wedge its port.
|
|
12
|
+
*
|
|
13
|
+
* The edge exists precisely because it spawns nothing, so its socket cannot be
|
|
14
|
+
* inherited and its port cannot zombie. cloudflared can therefore stay pinned
|
|
15
|
+
* to it forever and the public URL stops rotating. Adding any child-process
|
|
16
|
+
* call to this file reintroduces the exact bug the edge was built to remove.
|
|
17
|
+
*
|
|
18
|
+
* Raw TCP, never HTTP: WebSocket upgrades and SSE streams pass through
|
|
19
|
+
* untouched, and forwarded requests keep the headers cloudflared set.
|
|
20
|
+
*/
|
|
21
|
+
import net from "node:net";
|
|
22
|
+
import { resolveTargetPort, _resetTargetCache } from "./edge-target-resolver.ts";
|
|
23
|
+
|
|
24
|
+
/** Delay between attempts while waiting for the server to come back. */
|
|
25
|
+
const CONNECT_RETRY_MS = 250;
|
|
26
|
+
/**
|
|
27
|
+
* How long a client connection waits for a reachable server before giving up.
|
|
28
|
+
* During an upgrade the server is briefly absent; refusing instantly would
|
|
29
|
+
* surface as a broken page rather than a slow one.
|
|
30
|
+
*/
|
|
31
|
+
const CONNECT_WINDOW_MS = 5000;
|
|
32
|
+
|
|
33
|
+
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
34
|
+
|
|
35
|
+
/** Resolve once the socket is connected; reject on the first connect error. */
|
|
36
|
+
function connectUpstream(port: number): Promise<net.Socket> {
|
|
37
|
+
return new Promise((resolvePromise, reject) => {
|
|
38
|
+
const upstream = net.connect({
|
|
39
|
+
port,
|
|
40
|
+
host: "127.0.0.1", // never taken from status.json — loopback only
|
|
41
|
+
allowHalfOpen: true,
|
|
42
|
+
});
|
|
43
|
+
const onError = (err: Error) => {
|
|
44
|
+
upstream.destroy();
|
|
45
|
+
reject(err);
|
|
46
|
+
};
|
|
47
|
+
upstream.once("error", onError);
|
|
48
|
+
upstream.once("connect", () => {
|
|
49
|
+
upstream.removeListener("error", onError);
|
|
50
|
+
resolvePromise(upstream);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Largest amount of client data held while waiting for the server. Bounds the
|
|
57
|
+
* memory a slow upload can pin during an upgrade window.
|
|
58
|
+
*/
|
|
59
|
+
const MAX_PENDING_BYTES = 8 * 1024 * 1024;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pipe one client connection to the server.
|
|
63
|
+
*
|
|
64
|
+
* Resolving the upstream is async, so the first bytes usually arrive before
|
|
65
|
+
* there is anywhere to send them. They must be captured by a `data` listener
|
|
66
|
+
* attached synchronously on this very tick and replayed once the upstream
|
|
67
|
+
* exists.
|
|
68
|
+
*
|
|
69
|
+
* `socket.pause()` is NOT a substitute — verified against Bun 1.3.13: bytes
|
|
70
|
+
* that arrive while paused are lost, and a later `pipe()`/`resume()` does not
|
|
71
|
+
* bring them back. A socket with no `data` listener drops them outright too.
|
|
72
|
+
* Both variants silently swallowed the first request in testing.
|
|
73
|
+
*/
|
|
74
|
+
async function handleConnection(
|
|
75
|
+
client: net.Socket,
|
|
76
|
+
connectWindowMs: number,
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
const pending: Buffer[] = [];
|
|
79
|
+
let pendingBytes = 0;
|
|
80
|
+
let clientEnded = false;
|
|
81
|
+
const onEarlyData = (chunk: Buffer) => {
|
|
82
|
+
pendingBytes += chunk.length;
|
|
83
|
+
if (pendingBytes > MAX_PENDING_BYTES) {
|
|
84
|
+
client.destroy();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
pending.push(chunk);
|
|
88
|
+
};
|
|
89
|
+
client.on("data", onEarlyData);
|
|
90
|
+
client.once("end", () => { clientEnded = true; });
|
|
91
|
+
|
|
92
|
+
let upstream: net.Socket | null = null;
|
|
93
|
+
let clientGone = false;
|
|
94
|
+
|
|
95
|
+
// A socket error with no listener is an unhandled 'error' event, which would
|
|
96
|
+
// take the whole edge process — and the public port — down with it.
|
|
97
|
+
const teardown = () => {
|
|
98
|
+
upstream?.destroy();
|
|
99
|
+
client.destroy();
|
|
100
|
+
};
|
|
101
|
+
client.on("error", teardown);
|
|
102
|
+
client.once("close", () => { clientGone = true; });
|
|
103
|
+
|
|
104
|
+
const deadline = Date.now() + connectWindowMs;
|
|
105
|
+
while (!clientGone && Date.now() < deadline) {
|
|
106
|
+
const port = resolveTargetPort();
|
|
107
|
+
if (port !== null) {
|
|
108
|
+
try {
|
|
109
|
+
upstream = await connectUpstream(port);
|
|
110
|
+
break;
|
|
111
|
+
} catch {
|
|
112
|
+
// Server not up yet (or moved mid-restart) — re-resolve and retry.
|
|
113
|
+
_resetTargetCache();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await sleep(CONNECT_RETRY_MS);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (clientGone) return;
|
|
120
|
+
if (!upstream) {
|
|
121
|
+
client.destroy();
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
upstream.on("error", teardown);
|
|
126
|
+
|
|
127
|
+
// Replay what arrived during the connect, then hand both directions to the
|
|
128
|
+
// stream machinery. Removing the listener and piping happen in the same tick,
|
|
129
|
+
// so no chunk can slip through the gap.
|
|
130
|
+
client.off("data", onEarlyData);
|
|
131
|
+
for (const chunk of pending) upstream.write(chunk);
|
|
132
|
+
pending.length = 0;
|
|
133
|
+
|
|
134
|
+
client.pipe(upstream);
|
|
135
|
+
upstream.pipe(client);
|
|
136
|
+
|
|
137
|
+
// The client may have half-closed while we were still connecting.
|
|
138
|
+
if (clientEnded) upstream.end();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface EdgeForwarderOptions {
|
|
142
|
+
publicPort: number;
|
|
143
|
+
host?: string;
|
|
144
|
+
/** How long a client waits for a reachable server. Defaults to 5s. */
|
|
145
|
+
connectWindowMs?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Bind the public port and start forwarding. Resolves once listening so the
|
|
150
|
+
* caller can fail fast when the port is unavailable.
|
|
151
|
+
*/
|
|
152
|
+
export function startEdgeForwarder(
|
|
153
|
+
opts: EdgeForwarderOptions,
|
|
154
|
+
): Promise<net.Server> {
|
|
155
|
+
const host = opts.host ?? "0.0.0.0";
|
|
156
|
+
const connectWindowMs = opts.connectWindowMs ?? CONNECT_WINDOW_MS;
|
|
157
|
+
return new Promise((resolvePromise, reject) => {
|
|
158
|
+
// allowHalfOpen keeps the other direction alive when one side sends FIN —
|
|
159
|
+
// long-lived chat WS and SSE streams depend on it.
|
|
160
|
+
const server = net.createServer({ allowHalfOpen: true }, (client) => {
|
|
161
|
+
void handleConnection(client, connectWindowMs);
|
|
162
|
+
});
|
|
163
|
+
server.once("error", reject);
|
|
164
|
+
server.listen(opts.publicPort, host, () => {
|
|
165
|
+
server.removeListener("error", reject);
|
|
166
|
+
resolvePromise(server);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ─── Process entry ─────────────────────────────────────────────────────
|
|
172
|
+
// Mirrors the `__serve__` guard in src/server/index.ts: the supervisor
|
|
173
|
+
// re-invokes the binary as `<bin> __edge__ <publicPort> <host>`.
|
|
174
|
+
if (process.argv.includes("__edge__")) {
|
|
175
|
+
const idx = process.argv.indexOf("__edge__");
|
|
176
|
+
const publicPort = parseInt(process.argv[idx + 1] ?? "", 10);
|
|
177
|
+
const host = process.argv[idx + 2] ?? "0.0.0.0";
|
|
178
|
+
|
|
179
|
+
if (!Number.isInteger(publicPort) || publicPort <= 0 || publicPort > 65535) {
|
|
180
|
+
process.stderr.write(`[edge] Invalid public port: ${process.argv[idx + 1]}\n`);
|
|
181
|
+
process.exit(2);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await startEdgeForwarder({ publicPort, host });
|
|
186
|
+
process.stderr.write(`[edge] Listening on ${host}:${publicPort}\n`);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
process.stderr.write(`[edge] Failed to bind ${host}:${publicPort}: ${err}\n`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -2,9 +2,15 @@
|
|
|
2
2
|
* Discover ffmpeg/ffprobe on this machine and pick the fastest working H.264 encoder.
|
|
3
3
|
*
|
|
4
4
|
* ffmpeg is an *optional* dependency: without it PPM still plays browser-native
|
|
5
|
-
* media (mp4/webm) via Range requests, it just cannot transcode AVI/MKV.
|
|
6
|
-
* result is cached for the process lifetime —
|
|
7
|
-
*
|
|
5
|
+
* media (mp4/webm) via Range requests, it just cannot transcode AVI/MKV. A
|
|
6
|
+
* positive result is cached for the process lifetime — probing hardware encoders
|
|
7
|
+
* costs a real encode each. A *negative* result is not: the remote-desktop
|
|
8
|
+
* checklist tells the user to install ffmpeg and promises to notice, so "not
|
|
9
|
+
* found" is re-checked on every call (one `which` + a few `stat`s).
|
|
10
|
+
*
|
|
11
|
+
* `PATH` alone is not enough to find it. A daemon started by launchd inherits
|
|
12
|
+
* `/usr/bin:/bin:/usr/sbin:/sbin` — Homebrew's `/opt/homebrew/bin` is not on it —
|
|
13
|
+
* so the well-known install directories are probed after `Bun.which`.
|
|
8
14
|
*
|
|
9
15
|
* Hardware encoders are verified by encoding a few synthetic frames rather than by
|
|
10
16
|
* grepping `ffmpeg -encoders`: the list only says the build was compiled with
|
|
@@ -60,11 +66,42 @@ async function encoderWorks(ffmpeg: string, encoder: string): Promise<boolean> {
|
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
/** Where package managers put ffmpeg when it is not on the daemon's PATH. */
|
|
70
|
+
function wellKnownBinDirs(): string[] {
|
|
71
|
+
switch (process.platform) {
|
|
72
|
+
case "darwin": return ["/opt/homebrew/bin", "/usr/local/bin", "/opt/local/bin"];
|
|
73
|
+
case "win32": {
|
|
74
|
+
const local = process.env.LOCALAPPDATA;
|
|
75
|
+
return [
|
|
76
|
+
...(local ? [`${local}\\Microsoft\\WinGet\\Links`] : []),
|
|
77
|
+
"C:\\ProgramData\\chocolatey\\bin", "C:\\ffmpeg\\bin",
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
default: return ["/usr/local/bin", "/usr/bin", "/snap/bin", `${process.env.HOME ?? ""}/.local/bin`];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** `Bun.which` on `searchPath` (the process PATH by default) first, then the well-known
|
|
85
|
+
* directories. Exported for tests. */
|
|
86
|
+
export function findFfmpegBinary(
|
|
87
|
+
name: "ffmpeg" | "ffprobe",
|
|
88
|
+
dirs: string[] = wellKnownBinDirs(),
|
|
89
|
+
searchPath: string | undefined = process.env.PATH,
|
|
90
|
+
): string | null {
|
|
91
|
+
const onPath = Bun.which(name, searchPath ? { PATH: searchPath } : undefined);
|
|
92
|
+
if (onPath) return onPath;
|
|
93
|
+
for (const dir of dirs) {
|
|
94
|
+
const found = Bun.which(name, { PATH: dir });
|
|
95
|
+
if (found) return found;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
63
100
|
let cached: Promise<FfmpegCapabilities> | null = null;
|
|
64
101
|
|
|
65
102
|
async function detect(): Promise<FfmpegCapabilities> {
|
|
66
|
-
const ffmpeg =
|
|
67
|
-
const ffprobe =
|
|
103
|
+
const ffmpeg = findFfmpegBinary("ffmpeg");
|
|
104
|
+
const ffprobe = findFfmpegBinary("ffprobe");
|
|
68
105
|
if (!ffmpeg) return { ffmpeg: null, ffprobe, encoder: null };
|
|
69
106
|
for (const candidate of encoderCandidates()) {
|
|
70
107
|
if (await encoderWorks(ffmpeg, candidate)) return { ffmpeg, ffprobe, encoder: candidate };
|
|
@@ -72,10 +109,14 @@ async function detect(): Promise<FfmpegCapabilities> {
|
|
|
72
109
|
return { ffmpeg, ffprobe, encoder: null };
|
|
73
110
|
}
|
|
74
111
|
|
|
75
|
-
/** Cached capability lookup; the first call pays for the encoder probes.
|
|
112
|
+
/** Cached capability lookup; the first call that finds ffmpeg pays for the encoder probes.
|
|
113
|
+
* "Not installed" is never cached — the user may be running `brew install ffmpeg` right now. */
|
|
76
114
|
export function getFfmpegCapabilities(): Promise<FfmpegCapabilities> {
|
|
77
115
|
if (!cached) {
|
|
78
|
-
cached = detect().
|
|
116
|
+
cached = detect().then((caps) => {
|
|
117
|
+
if (!caps.ffmpeg) cached = null;
|
|
118
|
+
return caps;
|
|
119
|
+
}).catch((e) => {
|
|
79
120
|
cached = null; // let a later call retry after an unexpected failure
|
|
80
121
|
throw e;
|
|
81
122
|
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-platform ffmpeg *input* side of the capture pipeline. The shared low-latency flags and
|
|
3
|
+
* the encoder live in `remote-desktop-capture.ts` / `remote-desktop-encoder-args.ts`; this file
|
|
4
|
+
* only answers "which grabber, pointed at what, filtered how".
|
|
5
|
+
*
|
|
6
|
+
* macOS (`avfoundation`) lessons, measured on macOS 26 / ffmpeg 7.1.1:
|
|
7
|
+
* - Devices are addressed by NAME, never by index. avfoundation lists cameras before screens
|
|
8
|
+
* and the list changes at runtime (an iPhone Continuity Camera joining/leaving moved
|
|
9
|
+
* "Capture screen 0" from index 5 to 3 and back within a minute) — an index captured at
|
|
10
|
+
* detect time pointed at the phone camera by spawn time. Addressing by name also removes
|
|
11
|
+
* the `-list_devices` pre-flight, which cost 0.4–1 s per session start.
|
|
12
|
+
* - `-framerate 30` is ignored: the screen input delivers at display refresh (120–165 fps on
|
|
13
|
+
* ProMotion) with a pts that never advances. `-use_wallclock_as_timestamps 1` restores pts
|
|
14
|
+
* and an `fps=30` filter drops back to the target rate (measured 29 fps steady, ~1.3 s to
|
|
15
|
+
* first frame). Without it the encoder spends 6 Mbit/s and a 30-frame GOP on 160 fps.
|
|
16
|
+
* - `-pixel_format nv12` on the input side: the device has no yuv420p, and letting the output
|
|
17
|
+
* `-pix_fmt` request leak into the device negotiation logs a warning per open.
|
|
18
|
+
* - A missing Screen Recording grant does NOT fail the spawn — macOS hands the process black
|
|
19
|
+
* frames instead; that is surfaced client-side by the "real frame" check, not here.
|
|
20
|
+
*/
|
|
21
|
+
import { CAPTURE_FRAMERATE } from "./remote-desktop-encoder-args.ts";
|
|
22
|
+
|
|
23
|
+
export type CaptureInput =
|
|
24
|
+
| { kind: "gdigrab" }
|
|
25
|
+
| { kind: "avfoundation"; screen: string };
|
|
26
|
+
|
|
27
|
+
/** avfoundation's name for the N-th display in `CGGetActiveDisplayList` order (0 = main). */
|
|
28
|
+
export function avfoundationScreenName(captureIndex: number): string {
|
|
29
|
+
return `Capture screen ${captureIndex}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** ffmpeg args from `-f <grabber>` through `-i <source>` for the given input. */
|
|
33
|
+
export function captureInputArgs(input: CaptureInput): string[] {
|
|
34
|
+
switch (input.kind) {
|
|
35
|
+
case "gdigrab":
|
|
36
|
+
return ["-f", "gdigrab", "-framerate", String(CAPTURE_FRAMERATE), "-i", "desktop"];
|
|
37
|
+
case "avfoundation":
|
|
38
|
+
// `-capture_cursor 1` draws the pointer into the frame (gdigrab does so by default).
|
|
39
|
+
return ["-use_wallclock_as_timestamps", "1",
|
|
40
|
+
"-f", "avfoundation", "-capture_cursor", "1", "-pixel_format", "nv12",
|
|
41
|
+
"-framerate", String(CAPTURE_FRAMERATE), "-i", input.screen];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** `-vf` chain for the given input; the scale step is shared, avfoundation adds the rate cap. */
|
|
46
|
+
export function captureVideoFilter(input: CaptureInput): string {
|
|
47
|
+
const scale = "scale=-2:720";
|
|
48
|
+
return input.kind === "avfoundation" ? `fps=${CAPTURE_FRAMERATE},${scale}` : scale;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Pick the capture input for this host; null on platforms without a grabber. Whether the
|
|
52
|
+
* grabber actually works (ffmpeg built without it, no display) surfaces through ffmpeg's
|
|
53
|
+
* own exit + stderr tail in `startCapture`, the same way gdigrab failures do.
|
|
54
|
+
* `captureIndex` selects the display on backends that capture one at a time (avfoundation);
|
|
55
|
+
* gdigrab always grabs the whole virtual desktop. */
|
|
56
|
+
export function captureInputForPlatform(platform: NodeJS.Platform = process.platform, captureIndex = 0): CaptureInput | null {
|
|
57
|
+
switch (platform) {
|
|
58
|
+
case "win32": return { kind: "gdigrab" };
|
|
59
|
+
case "darwin": return { kind: "avfoundation", screen: avfoundationScreenName(captureIndex) };
|
|
60
|
+
default: return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Spawn ffmpeg (
|
|
2
|
+
* Spawn ffmpeg (platform grabber → H.264 Annex-B on `pipe:1`) and turn its stdout into access units.
|
|
3
3
|
*
|
|
4
4
|
* Teardown is `proc.kill()` only — never `reader.cancel()`, never `for await (chunk of
|
|
5
5
|
* proc.stdout) { ... break }`. Both of those cancel the underlying stream reader, which
|
|
@@ -11,27 +11,35 @@
|
|
|
11
11
|
* its own — it is never cancelled from outside.
|
|
12
12
|
*/
|
|
13
13
|
import { getFfmpegCapabilities } from "../media-transcode/ffmpeg-capabilities.ts";
|
|
14
|
-
import { captureEncoderArgs
|
|
14
|
+
import { captureEncoderArgs } from "./remote-desktop-encoder-args.ts";
|
|
15
|
+
import {
|
|
16
|
+
captureInputArgs, captureVideoFilter, captureInputForPlatform, type CaptureInput,
|
|
17
|
+
} from "./remote-desktop-capture-input.ts";
|
|
15
18
|
import { AccessUnitAssembler, type AccessUnit } from "./access-unit-assembler.ts";
|
|
19
|
+
import type { RemoteDisplay } from "./remote-desktop-displays.ts";
|
|
16
20
|
|
|
17
21
|
export class CaptureUnavailableError extends Error {
|
|
18
|
-
constructor(msg = "ffmpeg is not installed (
|
|
22
|
+
constructor(msg = "ffmpeg is not installed (screen capture requires it)") {
|
|
19
23
|
super(msg);
|
|
20
24
|
this.name = "CaptureUnavailableError";
|
|
21
25
|
}
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
/** Build the
|
|
28
|
+
/** Build the capture argv; pure so it can be asserted on without spawning a process.
|
|
25
29
|
* `-fflags nobuffer -flags low_delay` + `-flush_packets 1` stop ffmpeg from holding frames in
|
|
26
30
|
* its demux/mux buffers before emitting — on a fast/LAN transport that buffering is a big slice
|
|
27
|
-
* of the felt lag. `encoder` selects the H.264 encoder args (hardware NVENC/QSV/AMF
|
|
28
|
-
* capability probe found one, else libx264). */
|
|
29
|
-
export function buildCaptureArgs(
|
|
31
|
+
* of the felt lag. `encoder` selects the H.264 encoder args (hardware NVENC/QSV/AMF/VideoToolbox
|
|
32
|
+
* when the capability probe found one, else libx264); `input` selects the platform grabber. */
|
|
33
|
+
export function buildCaptureArgs(
|
|
34
|
+
ffmpeg: string,
|
|
35
|
+
encoder: string = "libx264",
|
|
36
|
+
input: CaptureInput = { kind: "gdigrab" },
|
|
37
|
+
): string[] {
|
|
30
38
|
return [
|
|
31
39
|
ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
|
|
32
40
|
"-fflags", "nobuffer", "-flags", "low_delay",
|
|
33
|
-
|
|
34
|
-
"-vf",
|
|
41
|
+
...captureInputArgs(input),
|
|
42
|
+
"-vf", captureVideoFilter(input),
|
|
35
43
|
...captureEncoderArgs(encoder),
|
|
36
44
|
"-flush_packets", "1",
|
|
37
45
|
"-f", "h264", "pipe:1",
|
|
@@ -49,6 +57,8 @@ export interface CaptureHandle {
|
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
export interface StartCaptureOptions {
|
|
60
|
+
/** Which display to grab; null/undefined = the platform default (primary / whole desktop). */
|
|
61
|
+
display?: RemoteDisplay | null;
|
|
52
62
|
onAccessUnit: (au: AccessUnit) => void;
|
|
53
63
|
/** Called once the process exits, whether via `stop()` or on its own (crash/killed
|
|
54
64
|
* externally) — lets the session registry clean up without polling. `reason` is a short
|
|
@@ -58,12 +68,15 @@ export interface StartCaptureOptions {
|
|
|
58
68
|
onExit?: (code: number | null, reason?: string) => void;
|
|
59
69
|
}
|
|
60
70
|
|
|
61
|
-
/** Start
|
|
71
|
+
/** Start screen capture. Rejects immediately if ffmpeg is not on PATH or the platform has
|
|
72
|
+
* no supported grabber. */
|
|
62
73
|
export async function startCapture(opts: StartCaptureOptions): Promise<CaptureHandle> {
|
|
63
74
|
const caps = await getFfmpegCapabilities();
|
|
64
75
|
if (!caps.ffmpeg) throw new CaptureUnavailableError();
|
|
76
|
+
const input = captureInputForPlatform(process.platform, opts.display?.captureIndex ?? 0);
|
|
77
|
+
if (!input) throw new CaptureUnavailableError(`no screen capture input on ${process.platform}`);
|
|
65
78
|
|
|
66
|
-
const proc = Bun.spawn(buildCaptureArgs(caps.ffmpeg, caps.encoder ?? "libx264"), {
|
|
79
|
+
const proc = Bun.spawn(buildCaptureArgs(caps.ffmpeg, caps.encoder ?? "libx264", input), {
|
|
67
80
|
stdout: "pipe",
|
|
68
81
|
stderr: "pipe",
|
|
69
82
|
stdin: "ignore",
|
|
@@ -73,7 +86,11 @@ export async function startCapture(opts: StartCaptureOptions): Promise<CaptureHa
|
|
|
73
86
|
const stop = () => {
|
|
74
87
|
if (stopped) return;
|
|
75
88
|
stopped = true;
|
|
76
|
-
|
|
89
|
+
// SIGKILL, not the default SIGTERM: ffmpeg's avfoundation input hangs on SIGTERM/SIGINT
|
|
90
|
+
// during capture-session teardown (verified on macOS 26 — the process stayed alive for
|
|
91
|
+
// minutes after both) and would leak one screen-capture ffmpeg per remote-desktop session.
|
|
92
|
+
// On Windows Bun's kill is TerminateProcess either way, so the semantics are unchanged.
|
|
93
|
+
if (!proc.killed) proc.kill("SIGKILL");
|
|
77
94
|
};
|
|
78
95
|
|
|
79
96
|
// Drain stderr so ffmpeg never blocks on a full pipe; keep a short tail for diagnostics.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `KeyboardEvent.code` (physical key, layout-independent) → macOS virtual keycode (CGKeyCode,
|
|
3
|
+
* the `kVK_*` constants from HIToolbox/Events.h, ANSI layout). Same contract as
|
|
4
|
+
* `remote-desktop-vk-map.ts`: only `code` is accepted, never `key`.
|
|
5
|
+
*
|
|
6
|
+
* Web `MetaLeft/MetaRight` = Command, `AltLeft/AltRight` = Option. `ContextMenu` has no Mac
|
|
7
|
+
* key and stays unmapped on purpose.
|
|
8
|
+
*/
|
|
9
|
+
const CODE_TO_CGKEY: Record<string, number> = {
|
|
10
|
+
KeyA: 0x00, KeyS: 0x01, KeyD: 0x02, KeyF: 0x03, KeyH: 0x04, KeyG: 0x05, KeyZ: 0x06, KeyX: 0x07,
|
|
11
|
+
KeyC: 0x08, KeyV: 0x09, KeyB: 0x0b, KeyQ: 0x0c, KeyW: 0x0d, KeyE: 0x0e, KeyR: 0x0f, KeyY: 0x10,
|
|
12
|
+
KeyT: 0x11, Digit1: 0x12, Digit2: 0x13, Digit3: 0x14, Digit4: 0x15, Digit6: 0x16, Digit5: 0x17,
|
|
13
|
+
Equal: 0x18, Digit9: 0x19, Digit7: 0x1a, Minus: 0x1b, Digit8: 0x1c, Digit0: 0x1d,
|
|
14
|
+
BracketRight: 0x1e, KeyO: 0x1f, KeyU: 0x20, BracketLeft: 0x21, KeyI: 0x22, KeyP: 0x23,
|
|
15
|
+
Enter: 0x24, KeyL: 0x25, KeyJ: 0x26, Quote: 0x27, KeyK: 0x28, Semicolon: 0x29, Backslash: 0x2a,
|
|
16
|
+
Comma: 0x2b, Slash: 0x2c, KeyN: 0x2d, KeyM: 0x2e, Period: 0x2f, Tab: 0x30, Space: 0x31,
|
|
17
|
+
Backquote: 0x32, Backspace: 0x33, Escape: 0x35,
|
|
18
|
+
MetaRight: 0x36, MetaLeft: 0x37, ShiftLeft: 0x38, CapsLock: 0x39, AltLeft: 0x3a, ControlLeft: 0x3b,
|
|
19
|
+
ShiftRight: 0x3c, AltRight: 0x3d, ControlRight: 0x3e,
|
|
20
|
+
NumpadDecimal: 0x41, NumpadMultiply: 0x43, NumpadAdd: 0x45, NumLock: 0x47, NumpadDivide: 0x4b,
|
|
21
|
+
NumpadEnter: 0x4c, NumpadSubtract: 0x4e, NumpadEqual: 0x51,
|
|
22
|
+
Numpad0: 0x52, Numpad1: 0x53, Numpad2: 0x54, Numpad3: 0x55, Numpad4: 0x56, Numpad5: 0x57,
|
|
23
|
+
Numpad6: 0x58, Numpad7: 0x59, Numpad8: 0x5b, Numpad9: 0x5c,
|
|
24
|
+
F5: 0x60, F6: 0x61, F7: 0x62, F3: 0x63, F8: 0x64, F9: 0x65, F11: 0x67, F13: 0x69, F14: 0x6b,
|
|
25
|
+
F10: 0x6d, F12: 0x6f, F15: 0x71, Insert: 0x72 /* Help/Insert */, Home: 0x73, PageUp: 0x74,
|
|
26
|
+
Delete: 0x75 /* forward delete */, F4: 0x76, End: 0x77, F2: 0x78, PageDown: 0x79, F1: 0x7a,
|
|
27
|
+
ArrowLeft: 0x7b, ArrowRight: 0x7c, ArrowDown: 0x7d, ArrowUp: 0x7e,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** CGEventFlags bit for each modifier keycode — macOS carries modifier *state* on every event
|
|
31
|
+
* rather than inferring it from prior key events, so the backend must OR these in itself. */
|
|
32
|
+
export const CGKEY_MODIFIER_FLAG: Record<number, number> = {
|
|
33
|
+
0x38: 0x00020000, 0x3c: 0x00020000, // Shift → kCGEventFlagMaskShift
|
|
34
|
+
0x3b: 0x00040000, 0x3e: 0x00040000, // Control → kCGEventFlagMaskControl
|
|
35
|
+
0x3a: 0x00080000, 0x3d: 0x00080000, // Option (Alt) → kCGEventFlagMaskAlternate
|
|
36
|
+
0x37: 0x00100000, 0x36: 0x00100000, // Command → kCGEventFlagMaskCommand
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** All modifier keycodes — force-released on teardown (mirror of `MODIFIER_VK_CODES`). */
|
|
40
|
+
export const MODIFIER_CGKEY_CODES = [0x38, 0x3c, 0x3b, 0x3e, 0x3a, 0x3d, 0x37, 0x36] as const;
|
|
41
|
+
|
|
42
|
+
/** Resolve a `KeyboardEvent.code` to its CGKeyCode; null for anything unmapped. */
|
|
43
|
+
export function codeToCgKey(code: string): number | null {
|
|
44
|
+
return CODE_TO_CGKEY[code] ?? null;
|
|
45
|
+
}
|