@hienlh/ppm 0.18.15 → 0.19.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.
- package/CHANGELOG.md +18 -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-ChBZWLLq.js → adaptive-context-menu-JfuUSqbe.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-C8aAY8Po.js → ai-resource-editor-MfdJKVM5.js} +1 -1
- package/dist/web/assets/ai-settings-section-CJE7jFsj.js +1 -0
- package/dist/web/assets/{angular-html-CXgHIQ2U.js → angular-html-VzxSzTCy.js} +1 -1
- package/dist/web/assets/{angular-ts-C8renMV9.js → angular-ts-CmJ4bmzM.js} +1 -1
- package/dist/web/assets/api-client-DLWwuUkJ.js +1 -0
- package/dist/web/assets/{api-client-BkGKTjsq.js → api-client-qRWu6_s5.js} +1 -1
- package/dist/web/assets/api-settings-BFuaZrNC.js +1 -0
- package/dist/web/assets/{api-settings-R59c66DE.js → api-settings-CPg4yGvS.js} +1 -1
- package/dist/web/assets/{api-themes-DKxrwJda.js → api-themes-DsAp8m7m.js} +1 -1
- package/dist/web/assets/{apl-DkGH8laI.js → apl-do17bnVx.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +1 -0
- package/dist/web/assets/{astro-tHUd8Zlg.js → astro-ClY0e0OA.js} +1 -1
- package/dist/web/assets/{audio-preview-B_C6Dpvz.js → audio-preview-Fw935Mok.js} +1 -1
- package/dist/web/assets/{blade-BbKpjIYK.js → blade-BXSdXDGq.js} +1 -1
- package/dist/web/assets/{bot-D6sx11y0.js → bot-CWZAuloY.js} +1 -1
- package/dist/web/assets/{bsl-DA0UYto0.js → bsl-MxsMKDUZ.js} +1 -1
- package/dist/web/assets/{button-DBSpDPS0.js → button-CrwgGd1y.js} +1 -1
- package/dist/web/assets/{cairo-79ayucbB.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-DSS2EOYo.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-DvSxq2iD.js → coffee-Cjuukfz3.js} +1 -1
- package/dist/web/assets/{conflict-editor-C4VCv51R.js → conflict-editor-Ni0PPsKu.js} +1 -1
- package/dist/web/assets/{context-menu-BpwUny-o.js → context-menu-BEAa1qhd.js} +1 -1
- package/dist/web/assets/{copy-BewHj66E.js → copy-CbPdNp7E.js} +1 -1
- package/dist/web/assets/{cpp-BhWZ-FSn.js → cpp-CT7tGBC3.js} +1 -1
- package/dist/web/assets/{createLucideIcon-D8pyde1A.js → createLucideIcon-ConhwJnb.js} +1 -1
- package/dist/web/assets/{crystal-yP2OeF-f.js → crystal-D8U1Qnqh.js} +1 -1
- package/dist/web/assets/{csv-preview-D46W_icb.js → csv-preview-Coc2oed1.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-DeGkPYtA.js → data-grid-overlay-editor-B8WQkZDB.js} +1 -1
- package/dist/web/assets/{data-grid-types-DkEQ-Na9.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-DFI4YtrH.js → database-viewer-Dta7lSSk.js} +1 -1
- package/dist/web/assets/{dialog-RIpeb87v.js → dialog-DrL3WEXd.js} +1 -1
- package/dist/web/assets/{diff-viewer-eaNOLcBL.js → diff-viewer-CT0lmDc9.js} +1 -1
- package/dist/web/assets/{dist-Do3fljss.js → dist-C2_dKAa_.js} +1 -1
- package/dist/web/assets/{docx-preview-Cbj-pOK3.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-D3K9cd-p.js → edge-Db4ze6bw.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DDqy3KLA.js → edit-diff-preview-DyC_-4My.js} +1 -1
- package/dist/web/assets/{elixir-D4PBmsLp.js → elixir-RZPZ0472.js} +1 -1
- package/dist/web/assets/{elm-DSpXjuaH.js → elm-DNZcQt_7.js} +1 -1
- package/dist/web/assets/{erb-CaYZMTJW.js → erb-DRONsZXs.js} +1 -1
- package/dist/web/assets/{erlang-DX5BFqJj.js → erlang-DeG0-QwJ.js} +1 -1
- package/dist/web/assets/{esm-Cv7HsFnk.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-B1EClUAr.js → extension-store-ClQpnxQr.js} +1 -1
- package/dist/web/assets/{extension-webview-BaGvDk8v.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-m9crh6uq.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-B0bAufUe.js → fortran-fixed-form-CK5VRJNd.js} +1 -1
- package/dist/web/assets/{fsharp-C2AL2b1M.js → fsharp-TVVgzHWp.js} +1 -1
- package/dist/web/assets/{gdresource-DRQsDMNI.js → gdresource-DiUcyewI.js} +1 -1
- package/dist/web/assets/{git-commit-GtBdnrzk.js → git-commit-CfOruFrg.js} +1 -1
- package/dist/web/assets/{git-log-panel-hwV1flSO.js → git-log-panel-D1-YXZ0O.js} +1 -1
- package/dist/web/assets/{git-rebase-h_0CGA6F.js → git-rebase-Bxyr24oc.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +1 -0
- package/dist/web/assets/{glide-data-grid-YsN-xm6x.js → glide-data-grid-BSsqxe2v.js} +6 -6
- package/dist/web/assets/{glimmer-js-Bxwu93bS.js → glimmer-js-CT5aJxz_.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CIUYdR4E.js → glimmer-ts-CRjP9Yey.js} +1 -1
- package/dist/web/assets/{glsl-CfxLI5jn.js → glsl-BX2--oI_.js} +1 -1
- package/dist/web/assets/{graphql-CstqDPde.js → graphql-BIQsvBxQ.js} +1 -1
- package/dist/web/assets/{group-chat-tab-B7mJO1gG.js → group-chat-tab-CMtzoaFc.js} +1 -1
- package/dist/web/assets/{hack-Bjqnssb5.js → hack-CTWk0k1m.js} +1 -1
- package/dist/web/assets/{haml-lVc-DfbM.js → haml-DGh27hXc.js} +1 -1
- package/dist/web/assets/{hand-x0ejq3fS.js → hand-DKSxDa5c.js} +1 -1
- package/dist/web/assets/{handlebars-Yy6fPQI_.js → handlebars-BJL1jNrG.js} +1 -1
- package/dist/web/assets/{html-lqtcy6-2.js → html-DALyDNqU.js} +1 -1
- package/dist/web/assets/{html-derivative-CchzCY1z.js → html-derivative-B4XcU57e.js} +1 -1
- package/dist/web/assets/{http-ChclRrko.js → http-DfMo7nNu.js} +1 -1
- package/dist/web/assets/{hurl-CoOK7sXa.js → hurl-CGc_n2Qf.js} +1 -1
- package/dist/web/assets/{hxml-fZSZ7Iyt.js → hxml-40zZ1t4x.js} +1 -1
- package/dist/web/assets/{image-preview-ClAcqDlL.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/info-3K5VOQVL-BAgmboBp.js +1 -0
- package/dist/web/assets/{input-DXPgUtxi.js → input-DTmI80l2.js} +1 -1
- package/dist/web/assets/{jinja-Y0w_KBdE.js → jinja-D5zuIn9u.js} +1 -1
- package/dist/web/assets/{jison-BILj3F2e.js → jison-Dxut9QwA.js} +1 -1
- package/dist/web/assets/{julia-DRfOPDts.js → julia-Cu85tTPo.js} +1 -1
- package/dist/web/assets/{just-n3U_oODO.js → just-CctM4uLa.js} +1 -1
- package/dist/web/assets/{keybindings-store-D-CHdq-G.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-D6gztEOL.js → latex-kpwS_CC7.js} +1 -1
- package/dist/web/assets/{lib-CXUwxw1M.js → lib-Ca7ai8xa.js} +1 -1
- package/dist/web/assets/{liquid-DPqCEVb9.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-I40KPUm-.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-DMjsThSW.js → marko-B28rvNL-.js} +1 -1
- package/dist/web/assets/{mdc-CN2alsbo.js → mdc-DQ6-STFO.js} +1 -1
- package/dist/web/assets/{media-url-COUvasjn.js → media-url-BjvbfTTl.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-Cj1YBq-c.js → mobile-bottom-sheet-DXn5g2LB.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BP6iU4ma.js +1 -0
- package/dist/web/assets/{monitor-C835ucWH.js → monitor-CXI_XpWJ.js} +1 -1
- package/dist/web/assets/monitor-smartphone-C1wUPOSg.js +1 -0
- package/dist/web/assets/{nextflow-CGa4JeZ6.js → nextflow-B1xq2O8L.js} +1 -1
- package/dist/web/assets/{nginx-CmqrIj84.js → nginx-xsLxRGxO.js} +1 -1
- package/dist/web/assets/{nim-CqhmTeMa.js → nim-wqcek3WO.js} +1 -1
- package/dist/web/assets/notification-store-II5Kw9rD.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BEzJ1YeD.js → number-overlay-editor-1c4sgZJt.js} +1 -1
- package/dist/web/assets/{overview-panel-Bf9Gn8qV.js → overview-panel-Cr2xsHc6.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +1 -0
- package/dist/web/assets/{panel-store-UWCH6B-_.js → panel-store-Dv28vz3L.js} +1 -1
- package/dist/web/assets/{pdf-preview-BMHM5cR2.js → pdf-preview-DKeIIJTv.js} +1 -1
- package/dist/web/assets/{pencil-BlIIg6RH.js → pencil-CHKLOgLs.js} +1 -1
- package/dist/web/assets/{perl-BOwpb6Tb.js → perl-BH54gN9q.js} +1 -1
- package/dist/web/assets/{php-wT9fVxxu.js → php-Br5ZrEZm.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +1 -0
- 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-D8rfEY5b.js → postgres-viewer-BJPr_FYL.js} +3 -3
- package/dist/web/assets/{process-table-CsQ-hDJr.js → process-table-BqTcAJ-h.js} +1 -1
- package/dist/web/assets/{project-store-CKAOr23O.js → project-store-KHjFTCxf.js} +1 -1
- package/dist/web/assets/{pug-DdlNnj-p.js → pug-8VCubZ3H.js} +1 -1
- package/dist/web/assets/{qml-C5zBdydf.js → qml-D8AF1RZ6.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +1 -0
- package/dist/web/assets/{razor-DqcJX4iq.js → razor-6v042IJW.js} +1 -1
- package/dist/web/assets/{react-Dfio3PfK.js → react-DHs6efqI.js} +1 -1
- package/dist/web/assets/{refresh-cw-84mz8aOE.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-CNTk2J7l.js → rotate-ccw-CuvNBM80.js} +1 -1
- package/dist/web/assets/{rst-DiMiMYUO.js → rst-mTGFo4AQ.js} +1 -1
- package/dist/web/assets/{ruby-DK62iflA.js → ruby-BBaOP636.js} +1 -1
- package/dist/web/assets/{run-in-terminal-DJSL6nwZ.js → run-in-terminal-Zl5iIx-B.js} +1 -1
- package/dist/web/assets/{sas-6QPGJRa5.js → sas-D5QEkIH2.js} +1 -1
- package/dist/web/assets/{save-CtW9ojKk.js → save-Dxq-Zepc.js} +1 -1
- package/dist/web/assets/{scroll-area-DXGXeiRz.js → scroll-area-B5yhPpZo.js} +1 -1
- package/dist/web/assets/{scss-DmUqh48_.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-BOony0sO.js → send-to-chat-Cv7ikFUC.js} +1 -1
- package/dist/web/assets/settings-store-Dp4i8zeA.js +2 -0
- package/dist/web/assets/settings-tab-BtMvvKAF.js +3 -0
- package/dist/web/assets/settings-tab-UPPycGRF.js +1 -0
- package/dist/web/assets/{shaderlab-DopFR1mi.js → shaderlab-mLnpMpN3.js} +1 -1
- package/dist/web/assets/{shallow-C1YoJQA-.js → shallow-BBJ_qoEF.js} +1 -1
- package/dist/web/assets/{shellsession-Bp_TGuXb.js → shellsession-qdcDfUuk.js} +1 -1
- package/dist/web/assets/{shield-check-Du6HKYn9.js → shield-check-CH7F1P7m.js} +1 -1
- package/dist/web/assets/{shield-off-CWKb1MQL.js → shield-off-C9HiF0-6.js} +1 -1
- package/dist/web/assets/{soy-DEB9KEFb.js → soy-CGGf1G43.js} +1 -1
- package/dist/web/assets/{sparkles-Dh-_J2Rd.js → sparkles-BnuLH6Ck.js} +1 -1
- package/dist/web/assets/{sparql-C8lEwO1p.js → sparql-tQHB50VD.js} +1 -1
- package/dist/web/assets/{sql-query-editor-MMfk0Kbz.js → sql-query-editor-DZqnYIB7.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-vglzrgnZ.js → sqlite-viewer-DXutos0e.js} +1 -1
- package/dist/web/assets/{stata-DVep4apN.js → stata-DVQNLMcR.js} +1 -1
- package/dist/web/assets/{surrealql-fSlJApOA.js → surrealql-lu576tBi.js} +1 -1
- package/dist/web/assets/{svelte-DezWBEDi.js → svelte-CSCrOBjT.js} +1 -1
- package/dist/web/assets/{switch-DqL_ZjB_.js → switch-BPol3BFC.js} +1 -1
- package/dist/web/assets/system-monitor-body-Ct8_GX_f.js +2 -0
- 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-DYlXR0cg.js → tab-host-window-content-C5kYP0-z.js} +1 -1
- package/dist/web/assets/{tab-store-D2thn4Vv.js → tab-store-DS3YlUwR.js} +1 -1
- package/dist/web/assets/{table-GAR4upb6.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-qrgFTC96.js → team-member-window-content-BJ_XFw8-.js} +1 -1
- package/dist/web/assets/{templ-D0zB42a9.js → templ-CPt8AS4Q.js} +1 -1
- package/dist/web/assets/{terminal-tab-D_-R5rGm.js → terminal-tab-8bKQ3CWR.js} +3 -3
- package/dist/web/assets/{tex-BJiPijcH.js → tex-BrTByT7i.js} +1 -1
- package/dist/web/assets/{text-wrap-D71Jqz5U.js → text-wrap-CzMjB6GD.js} +1 -1
- package/dist/web/assets/{tool-cards-BW7Xc20J.js → tool-cards-pIu_cmEL.js} +4 -4
- package/dist/web/assets/{trash-2-C08fhIg1.js → trash-2-BjShZ7if.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +1 -0
- package/dist/web/assets/{triangle-alert-85BQgs3T.js → triangle-alert-qQ6Plydp.js} +1 -1
- package/dist/web/assets/{ts-tags-Dcr4u6y5.js → ts-tags-DSqEZ7A8.js} +1 -1
- package/dist/web/assets/{twig-B-SQU0bg.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-BtPlqwLr.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-DUuki97q.js → use-is-mobile-ClE-UcPF.js} +1 -1
- package/dist/web/assets/use-monaco-theme-DaxAk3gB.js +1 -0
- 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-pVxLfE5B.js → use-visual-viewport-CCX21c9w.js} +1 -1
- package/dist/web/assets/{use-websocket-l39Odhlp.js → use-websocket-CWpG7PWN.js} +1 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +291 -0
- package/dist/web/assets/{vendor-mermaid-CtDsSxbM.js → vendor-mermaid-0Nt-ew76.js} +1 -1
- package/dist/web/assets/{vendor-ui-CaeeFO0B.js → vendor-ui-CXJtBULr.js} +1 -1
- package/dist/web/assets/{video-preview-C5AhkIDl.js → video-preview-C2_fdunE.js} +1 -1
- package/dist/web/assets/{vue-Cw10Sfbh.js → vue-CJNDL1g-.js} +1 -1
- package/dist/web/assets/{vue-html-Cc5C_Z_w.js → vue-html-CG5BKtq1.js} +1 -1
- package/dist/web/assets/{vue-vine-BuNMlnvu.js → vue-vine-Dd5HZ7T5.js} +1 -1
- package/dist/web/assets/{window-pip-registry-C7oz3Dfy.js → window-pip-registry-9H6Y-HqR.js} +1 -1
- package/dist/web/assets/{window-store-B5ZswdYg.js → window-store-CNzkeUyO.js} +1 -1
- package/dist/web/assets/x-Dzu9ecti.js +1 -0
- package/dist/web/assets/{xml-DqT1wkky.js → xml-DTOm5niz.js} +1 -1
- package/dist/web/assets/{xsl-DLkge3A2.js → xsl-BD_kBC04.js} +1 -1
- package/dist/web/index.html +65 -64
- 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/index.ts +1 -1
- package/src/server/routes/remote-desktop.ts +24 -7
- package/src/server/routes/settings.ts +1 -0
- package/src/server/ws/remote-desktop.ts +4 -2
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/named-tunnel/cloudflare-dns-api.ts +20 -4
- package/src/services/named-tunnel/named-tunnel-args.ts +17 -3
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +20 -2
- 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-flag.ts +8 -6
- 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 +12 -4
- 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-warning-gate.tsx +106 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +38 -4
- 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/src/web/stores/settings-store.ts +12 -0
- package/.rdtest2-cf.out +0 -0
- package/dist/web/assets/ai-settings-section-ERu1WiZc.js +0 -1
- package/dist/web/assets/api-client-WpZ2FDHg.js +0 -1
- package/dist/web/assets/api-settings-BgSEE6et.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-CCalSKAt.js +0 -1
- package/dist/web/assets/chat-tab-BraiJsYG.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-lhmLNhXu.js +0 -10
- package/dist/web/assets/explorer-body-BxbSttfl.js +0 -2
- package/dist/web/assets/explorer-window-content-DUqQCC7A.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DAUrJCqi.js +0 -1
- package/dist/web/assets/index-CtJbMNNI.css +0 -2
- package/dist/web/assets/index-H_gScEOk.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-Cx1ZfJbp.js +0 -1
- package/dist/web/assets/keybindings-store-BqctreaI.js +0 -1
- package/dist/web/assets/loader-circle-DouKgIXy.js +0 -1
- package/dist/web/assets/markdown-renderer-BwOkiZC1.js +0 -1
- package/dist/web/assets/markdown-renderer-oJ9jPdQ2.js +0 -3
- package/dist/web/assets/mobile-explorer-sheet-D1nM0Ik1.js +0 -1
- package/dist/web/assets/notification-store-n-XEQ5pH.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-Bqwiy7dz.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BXSJ2God.js +0 -1
- package/dist/web/assets/play-CJlWpVMw.js +0 -1
- package/dist/web/assets/portal-container-context-DSGdlXrJ.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-D6XrM7HH.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-CTiipn4k.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-Cf5B0dKo.js +0 -1
- package/dist/web/assets/remote-desktop-stats-overlay-vhJBIW7s.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-wCJUfI6G.js +0 -1
- package/dist/web/assets/settings-store-BpcY5aYE.js +0 -2
- package/dist/web/assets/settings-tab-BVqRO01j.js +0 -1
- package/dist/web/assets/settings-tab-FH581A_i.js +0 -3
- package/dist/web/assets/system-monitor-body-BYUfyF8f.js +0 -2
- package/dist/web/assets/system-monitor-tab-CKGXB8lH.js +0 -1
- package/dist/web/assets/system-monitor-window-content-CiJ2yIsr.js +0 -1
- package/dist/web/assets/team-member-sheet-Bnygu7Xw.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-BobWhcHN.js +0 -1
- package/dist/web/assets/use-monaco-theme-DQY1ZSaf.js +0 -1
- package/dist/web/assets/use-open-team-member-C5LQ_TCM.js +0 -1
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +0 -291
- package/dist/web/assets/x-CeN0Qybc.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-CKgBNk5K.js → abap-Cjj2olMr.js} +0 -0
- /package/dist/web/assets/{actionscript-3-oARhAY6c.js → actionscript-3-DCkW7kWC.js} +0 -0
- /package/dist/web/assets/{ada-2vi8ASy3.js → ada-AEWvZXuX.js} +0 -0
- /package/dist/web/assets/{andromeeda-B3vMjPKd.js → andromeeda-D7cSpME6.js} +0 -0
- /package/dist/web/assets/{apache-DnIEr338.js → apache-DjvX0RSe.js} +0 -0
- /package/dist/web/assets/{apex-B7Qs5AY5.js → apex-70xIZ0pH.js} +0 -0
- /package/dist/web/assets/{applescript-EbQNUCWb.js → applescript-C3qKqxmA.js} +0 -0
- /package/dist/web/assets/{apply-theme-Dd_cqGa-.js → apply-theme-DJ794zSQ.js} +0 -0
- /package/dist/web/assets/{ara-CtHdhFRw.js → ara-iGt2zixk.js} +0 -0
- /package/dist/web/assets/{asciidoc-BH6qin_O.js → asciidoc-Bs2t-06W.js} +0 -0
- /package/dist/web/assets/{asm-D1XU1ACk.js → asm-B-T0eqhE.js} +0 -0
- /package/dist/web/assets/{aurora-x-BkhcxkuS.js → aurora-x-D3h_9M3E.js} +0 -0
- /package/dist/web/assets/{awk-DijzB6o7.js → awk-CZwfwpPi.js} +0 -0
- /package/dist/web/assets/{ayu-dark-tysG77WC.js → ayu-dark-DulkglEA.js} +0 -0
- /package/dist/web/assets/{ayu-light-Dg0WMETC.js → ayu-light-DyNQiRpn.js} +0 -0
- /package/dist/web/assets/{ayu-mirage--RsJCjC4.js → ayu-mirage-CcEt4xkI.js} +0 -0
- /package/dist/web/assets/{ballerina-DT_5-l82.js → ballerina-OhQPjyHS.js} +0 -0
- /package/dist/web/assets/{bat-BRpDlkv0.js → bat-DqKcJTUn.js} +0 -0
- /package/dist/web/assets/{beancount--8Df-WGG.js → beancount-BdXlMi8T.js} +0 -0
- /package/dist/web/assets/{berry-Bc9fo35J.js → berry-DvgY6BkY.js} +0 -0
- /package/dist/web/assets/{bibtex-CwyBpp9X.js → bibtex-BSRJq0yM.js} +0 -0
- /package/dist/web/assets/{bicep-DiyH2Bs7.js → bicep-DpAdumKt.js} +0 -0
- /package/dist/web/assets/{bird2-CHgK4a2A.js → bird2-rlbtp8di.js} +0 -0
- /package/dist/web/assets/{c-BM1qL6-f.js → c-AjOLBXIs.js} +0 -0
- /package/dist/web/assets/{c3-D2oOjrP7.js → c3-DqIXz4Ia.js} +0 -0
- /package/dist/web/assets/{cadence-nS45wqF6.js → cadence-DAJtUpOE.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-DwP9KU8o.js → can-open-in-ppm-CdCK2c2a.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-BpnJHiCj.js → catppuccin-frappe-DUPoeZfb.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-Bb95Wj3h.js → catppuccin-latte-CK3U68Ql.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-Btx8c17W.js → catppuccin-macchiato-BzteLnIX.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-B4DZOUGI.js → catppuccin-mocha-ozLd0aBR.js} +0 -0
- /package/dist/web/assets/{clarity-1ps69jU6.js → clarity-6KqEoBfz.js} +0 -0
- /package/dist/web/assets/{clipboard-CrHX8AYw.js → clipboard-B-gwJ8DF.js} +0 -0
- /package/dist/web/assets/{clojure-BklC0hKV.js → clojure-B_uBL5C7.js} +0 -0
- /package/dist/web/assets/{cmake-B573LYAQ.js → cmake-B3nxBNmj.js} +0 -0
- /package/dist/web/assets/{codeowners-BoYeX89M.js → codeowners-DkS-51at.js} +0 -0
- /package/dist/web/assets/{codeql-7Dhf5Udk.js → codeql-rpr1z_mR.js} +0 -0
- /package/dist/web/assets/{common-lisp-m1nxlthv.js → common-lisp-BfNbv_W6.js} +0 -0
- /package/dist/web/assets/{coq-DNhGmd4a.js → coq-EAW-1nw0.js} +0 -0
- /package/dist/web/assets/{csharp-P-nAurtS.js → csharp-BE3Gn5bq.js} +0 -0
- /package/dist/web/assets/{css-LYbSeh3-.js → css-Cr27CfPY.js} +0 -0
- /package/dist/web/assets/{csv-DHZZZm7X.js → csv-B5VtaPtH.js} +0 -0
- /package/dist/web/assets/{csv-parser-CY9Ggtg5.js → csv-parser-B2v8MoUy.js} +0 -0
- /package/dist/web/assets/{cue-D8yLUOle.js → cue-jm6lYNrx.js} +0 -0
- /package/dist/web/assets/{cypher-9cbW8RYA.js → cypher-DXkvK-pn.js} +0 -0
- /package/dist/web/assets/{d-ZihzhxqI.js → d-CgG1_yzd.js} +0 -0
- /package/dist/web/assets/{dark-plus-C8xIA1u3.js → dark-plus-CALzcIVB.js} +0 -0
- /package/dist/web/assets/{dart-CBm6jKc7.js → dart-BEVoLlWg.js} +0 -0
- /package/dist/web/assets/{dax-B3cx6YeV.js → dax-H0BW3Q_P.js} +0 -0
- /package/dist/web/assets/{desktop-C03SpXth.js → desktop-BtYq1l_0.js} +0 -0
- /package/dist/web/assets/{diff-D8LnnvFY.js → diff-CnBYdfTg.js} +0 -0
- /package/dist/web/assets/{dist-C4XoS_zo.js → dist-CJJKpgR7.js} +0 -0
- /package/dist/web/assets/{docker-DwFiOQWW.js → docker-COBitpld.js} +0 -0
- /package/dist/web/assets/{dotenv-CVJ_vFSQ.js → dotenv-CG0x1m7c.js} +0 -0
- /package/dist/web/assets/{dracula-DMw8UACY.js → dracula-CLCi47X-.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BnB3nDrA.js → dracula-soft-BMv5cCAV.js} +0 -0
- /package/dist/web/assets/{dream-maker-2riXUQXo.js → dream-maker-1Bd4OSww.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-BQdYaiB5.js → emacs-lisp-B-2zKLn-.js} +0 -0
- /package/dist/web/assets/{everforest-dark-C2FPIWdj.js → everforest-dark-BGVynCbV.js} +0 -0
- /package/dist/web/assets/{everforest-light-DMOWf9sD.js → everforest-light--rn3R7Cm.js} +0 -0
- /package/dist/web/assets/{fennel-JtSHp0vW.js → fennel-SWg9Us_u.js} +0 -0
- /package/dist/web/assets/{fish-CflDcRfe.js → fish-B04oqgKk.js} +0 -0
- /package/dist/web/assets/{fluent-D4eu6Vac.js → fluent-BefzAGMN.js} +0 -0
- /package/dist/web/assets/{format-bytes-DR3228bu.js → format-bytes-gQw5wzqa.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-hO4RuC9Z.js → fortran-free-form-alw7jMxb.js} +0 -0
- /package/dist/web/assets/{gdscript-D7n3a8VM.js → gdscript-BAyhvjCx.js} +0 -0
- /package/dist/web/assets/{gdshader-BhUmrlzG.js → gdshader-Crl09X-B.js} +0 -0
- /package/dist/web/assets/{genie-VbyftqBp.js → genie-BSIGRHu7.js} +0 -0
- /package/dist/web/assets/{gherkin-BZzhvUUb.js → gherkin-DJhSnSWH.js} +0 -0
- /package/dist/web/assets/{github-dark-default-CMNi7dQX.js → github-dark-default-FVtXjcm8.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-BRf_-LlY.js → github-dark-dimmed-CzrUPgSb.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-xanKBsRI.js → github-dark-high-contrast-1s9rF5EB.js} +0 -0
- /package/dist/web/assets/{github-dark-C4q41woW.js → github-dark-r8C6qwug.js} +0 -0
- /package/dist/web/assets/{github-light-BEQeNQCs.js → github-light-BvIWCkl7.js} +0 -0
- /package/dist/web/assets/{github-light-default-ceQrtdXg.js → github-light-default-CVdhzrxV.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-doKbXzgW.js → github-light-high-contrast-CBAf6hjF.js} +0 -0
- /package/dist/web/assets/{gleam-Chzqf4fT.js → gleam-DvL1q7aa.js} +0 -0
- /package/dist/web/assets/{gn-CCN_-6fp.js → gn-BG5NvwAf.js} +0 -0
- /package/dist/web/assets/{gnuplot-CIplgwwP.js → gnuplot-BARnM-zG.js} +0 -0
- /package/dist/web/assets/{go-Cjj-9dRs.js → go-DXvjjq4q.js} +0 -0
- /package/dist/web/assets/{groovy-PqD4K7X1.js → groovy-CRylSKhr.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-BAbpUSF-.js → gruvbox-dark-hard-BVPjnqm7.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-BWcWXDBB.js → gruvbox-dark-medium-U_PDgq0a.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-Dkx1okkk.js → gruvbox-dark-soft-CkzNtpbr.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-WVsDK0MM.js → gruvbox-light-hard-DfMmR2G8.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-tiR9t7gr.js → gruvbox-light-medium-BGCak-dd.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-HzHohaX6.js → gruvbox-light-soft-DnpM3fkN.js} +0 -0
- /package/dist/web/assets/{haskell-B1eIZfr6.js → haskell-DXgGIDD_.js} +0 -0
- /package/dist/web/assets/{haxe-BcDdi5xr.js → haxe-B7qlTpRz.js} +0 -0
- /package/dist/web/assets/{hcl-Dq-HKVni.js → hcl-ADWfVe_Q.js} +0 -0
- /package/dist/web/assets/{hjson-Dr9D9WVU.js → hjson-BaM9gLbq.js} +0 -0
- /package/dist/web/assets/{hlsl-Bo8t2FuZ.js → hlsl-iqaRA4MG.js} +0 -0
- /package/dist/web/assets/{horizon-DWG5IXc-.js → horizon-BPpTnhB9.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DFr9wTTx.js → horizon-bright-yBLg3QbE.js} +0 -0
- /package/dist/web/assets/{houston-NAVNhkup.js → houston-eFOp0ySL.js} +0 -0
- /package/dist/web/assets/{hy-DnyvUk3B.js → hy-BALeMn3i.js} +0 -0
- /package/dist/web/assets/{image-gallery-DoFdNF9i.js → image-gallery-Bqb-wjNw.js} +0 -0
- /package/dist/web/assets/{imba-CPbR5W_A.js → imba-CdGUm_GD.js} +0 -0
- /package/dist/web/assets/{ini-lc8_Lt0y.js → ini-DG7imsqe.js} +0 -0
- /package/dist/web/assets/{java-B0KCo2vK.js → java-D5GTT474.js} +0 -0
- /package/dist/web/assets/{javascript-YqEbNRpd.js → javascript-V7WwJvRm.js} +0 -0
- /package/dist/web/assets/{json-Co0WtOlh.js → json-B9FhwAgR.js} +0 -0
- /package/dist/web/assets/{json5-2Jhb0QUN.js → json5--Abk7MUy.js} +0 -0
- /package/dist/web/assets/{jsonc-e72F11nU.js → jsonc-C8jb-Z6x.js} +0 -0
- /package/dist/web/assets/{jsonl-Bff8IPye.js → jsonl-CaJNxfkm.js} +0 -0
- /package/dist/web/assets/{jsonnet-DRCkb-_H.js → jsonnet-CPrKMSCU.js} +0 -0
- /package/dist/web/assets/{jssm-C6UiwVEm.js → jssm-DEu-9_uo.js} +0 -0
- /package/dist/web/assets/{jsx-u1aZlnfA.js → jsx-MfAsCWDW.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-De-RQFV3.js → kanagawa-dragon-DnbcAduF.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-K8elE4QU.js → kanagawa-lotus-yxsY3ew8.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BrStl_zx.js → kanagawa-wave-C2RyvZaM.js} +0 -0
- /package/dist/web/assets/{kdl-CLCfMeZy.js → kdl-CvssmvcU.js} +0 -0
- /package/dist/web/assets/{kotlin-DEEC3pNh.js → kotlin-jufO49mU.js} +0 -0
- /package/dist/web/assets/{kusto-DTqRhCup.js → kusto-BAFnylR7.js} +0 -0
- /package/dist/web/assets/{laserwave-DsgpUSbA.js → laserwave-BgxFC4Ge.js} +0 -0
- /package/dist/web/assets/{lean-Dl9MI0Y3.js → lean-Bgt24V1D.js} +0 -0
- /package/dist/web/assets/{less-Bg21rao6.js → less-Bk_UaqsM.js} +0 -0
- /package/dist/web/assets/{libesm-CVkncAQz.js → libesm-dcbw4_18.js} +0 -0
- /package/dist/web/assets/{light-plus-DGNbbK1V.js → light-plus-nMxFfInN.js} +0 -0
- /package/dist/web/assets/{llvm-91_ZX3p-.js → llvm-3_PASDOO.js} +0 -0
- /package/dist/web/assets/{log-DSXzbq8u.js → log-CKm0CxB1.js} +0 -0
- /package/dist/web/assets/{logo-BQRNd4tc.js → logo-DFP1_5U6.js} +0 -0
- /package/dist/web/assets/{luau-BE-TMpiZ.js → luau-BcZnoyh3.js} +0 -0
- /package/dist/web/assets/{make-ePYGKTpc.js → make-CpNSwgwT.js} +0 -0
- /package/dist/web/assets/{markdown-Cst3wgMW.js → markdown-DhU3Djpv.js} +0 -0
- /package/dist/web/assets/{material-theme-9XyvnNLb.js → material-theme-DD5L3OqF.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-H1VN4MTS.js → material-theme-darker-CG2_tD3R.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-DAreBbuF.js → material-theme-lighter-CzNkUc9Y.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-BpTR-3II.js → material-theme-ocean-BtLiAaF_.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DUNzX711.js → material-theme-palenight-Brvjz035.js} +0 -0
- /package/dist/web/assets/{matlab-BpYNufBU.js → matlab-CCY0KtMD.js} +0 -0
- /package/dist/web/assets/{mdx-BYjfe1DK.js → mdx-CH6YY0aS.js} +0 -0
- /package/dist/web/assets/{mermaid-DPCcIW9B.js → mermaid-Bqh3gFI0.js} +0 -0
- /package/dist/web/assets/{min-dark-DnGOAGCo.js → min-dark-BUwfTzI6.js} +0 -0
- /package/dist/web/assets/{min-light-CwV-o31y.js → min-light-oLanXCOk.js} +0 -0
- /package/dist/web/assets/{mipsasm-DdeCPC97.js → mipsasm-CTh3hPWK.js} +0 -0
- /package/dist/web/assets/{mojo-BmxxZ3iM.js → mojo-DXdszZKa.js} +0 -0
- /package/dist/web/assets/{monokai-Cx6cQ-MA.js → monokai-TNE_tRxc.js} +0 -0
- /package/dist/web/assets/{moonbit-D2-Gj5vj.js → moonbit-jrk-Gx1X.js} +0 -0
- /package/dist/web/assets/{move-DSWGoQa8.js → move-DItn61bH.js} +0 -0
- /package/dist/web/assets/{narrat-D9TnZSSu.js → narrat-3kB4dkh0.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BbL-8PwD.js → nextflow-groovy-CSKtwETr.js} +0 -0
- /package/dist/web/assets/{night-owl-CZitKm5d.js → night-owl-Dm91nSIq.js} +0 -0
- /package/dist/web/assets/{night-owl-light-DJVX1Pno.js → night-owl-light-riwrcPRg.js} +0 -0
- /package/dist/web/assets/{nix-R6hr3oln.js → nix-sLgl9zGF.js} +0 -0
- /package/dist/web/assets/{nord-ChGHya3l.js → nord-TWkJPI6M.js} +0 -0
- /package/dist/web/assets/{nushell-QQWB-GWx.js → nushell-CDZi4-Gf.js} +0 -0
- /package/dist/web/assets/{objective-c-C0pZ6y73.js → objective-c-DW9PboNp.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BdzVM31a.js → objective-cpp-2XCCX6q1.js} +0 -0
- /package/dist/web/assets/{ocaml-D4KktNlS.js → ocaml-CxPdnjrh.js} +0 -0
- /package/dist/web/assets/{odin-Y-fypmGT.js → odin-BJjlKpkM.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-BwecIxQL.js → one-dark-pro-K1KOmBZ7.js} +0 -0
- /package/dist/web/assets/{one-light-Cz7UVzLU.js → one-light-DQB-vUyZ.js} +0 -0
- /package/dist/web/assets/{openscad-DtRe3qYD.js → openscad-DCoLwn4j.js} +0 -0
- /package/dist/web/assets/{pascal-Ck6I1fGG.js → pascal-z2ktbbWp.js} +0 -0
- /package/dist/web/assets/{pkl-DdFK94VW.js → pkl-Dyq4-v6z.js} +0 -0
- /package/dist/web/assets/{plastic-BQSAzZwR.js → plastic-C4Rq5PnD.js} +0 -0
- /package/dist/web/assets/{plsql-DjXg_CP0.js → plsql-BCIjO63y.js} +0 -0
- /package/dist/web/assets/{po-Bw2SuM4r.js → po-_kXpd_0n.js} +0 -0
- /package/dist/web/assets/{poimandres-YMK6c5VN.js → poimandres-C9XggjB3.js} +0 -0
- /package/dist/web/assets/{polar-BMu17-O0.js → polar-DV_1L04z.js} +0 -0
- /package/dist/web/assets/{postcss-DWib10RZ.js → postcss-BuqS91mQ.js} +0 -0
- /package/dist/web/assets/{powerquery-Dht4CK0b.js → powerquery-DK13B2Dc.js} +0 -0
- /package/dist/web/assets/{powershell-DvrCg8ji.js → powershell-tkuhhtuP.js} +0 -0
- /package/dist/web/assets/{prisma-DbodYvCM.js → prisma-WNXfBtpr.js} +0 -0
- /package/dist/web/assets/{prolog-DZ-m6ysB.js → prolog-BzEdNKfO.js} +0 -0
- /package/dist/web/assets/{proto-HHpG3tGO.js → proto-B5meVsrA.js} +0 -0
- /package/dist/web/assets/{puppet-BljHe_75.js → puppet-C7h_p4r2.js} +0 -0
- /package/dist/web/assets/{purescript-DzCiFR0B.js → purescript-qH1KxAs6.js} +0 -0
- /package/dist/web/assets/{python-B5BQ27cC.js → python-DeTIYd5V.js} +0 -0
- /package/dist/web/assets/{qmldir-CYlcXrnn.js → qmldir-4Rm5AvD3.js} +0 -0
- /package/dist/web/assets/{qss-Ce7T_TEg.js → qss-YRbeNtr0.js} +0 -0
- /package/dist/web/assets/{r-Bku-2Jp_.js → r-DHwHK0Jh.js} +0 -0
- /package/dist/web/assets/{racket-DtfSL3WR.js → racket-gKn4SFLQ.js} +0 -0
- /package/dist/web/assets/{raku-CnDLsBJr.js → raku-DWTmAj3A.js} +0 -0
- /package/dist/web/assets/{red-vbqcE9b-.js → red-BFuHRZFS.js} +0 -0
- /package/dist/web/assets/{reg-DIDLT8-A.js → reg-DYoOF5tH.js} +0 -0
- /package/dist/web/assets/{regexp-CB4h-VPu.js → regexp-6rXQHD2-.js} +0 -0
- /package/dist/web/assets/{rel-CiQEnWXa.js → rel-Z_tTruCU.js} +0 -0
- /package/dist/web/assets/{resolve-theme-DPTQHAy1.js → resolve-theme-4fexHWl6.js} +0 -0
- /package/dist/web/assets/{riscv-R78iq5Rf.js → riscv-DfTfKuvU.js} +0 -0
- /package/dist/web/assets/{ron-DhYENxc4.js → ron-CsUtW8gQ.js} +0 -0
- /package/dist/web/assets/{rose-pine-5wBeyjfJ.js → rose-pine-Dng_vCo6.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-FpKtPCuH.js → rose-pine-dawn-Ds88Nezf.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-BwcIYc7C.js → rose-pine-moon-DWpzrqNz.js} +0 -0
- /package/dist/web/assets/{rosmsg-DWIGAYMl.js → rosmsg-h2sO84oD.js} +0 -0
- /package/dist/web/assets/{rust-DU_ofKgt.js → rust-Dx86yfCi.js} +0 -0
- /package/dist/web/assets/{sass-U9SUuYEy.js → sass-BwwVhdT9.js} +0 -0
- /package/dist/web/assets/{scala-op4gjA6j.js → scala-WJqNEN-w.js} +0 -0
- /package/dist/web/assets/{scheme-tEjkR8rL.js → scheme-uwj-t1Rp.js} +0 -0
- /package/dist/web/assets/{sdbl-C6SSRmZg.js → sdbl-BYy5hgEz.js} +0 -0
- /package/dist/web/assets/{shellscript-Bf2KiwQj.js → shellscript-DWTW_w2K.js} +0 -0
- /package/dist/web/assets/{slack-dark-oGgCey8r.js → slack-dark-BvhGq5vQ.js} +0 -0
- /package/dist/web/assets/{slack-ochin-CemlB8cE.js → slack-ochin-C9g0ZRFr.js} +0 -0
- /package/dist/web/assets/{smalltalk-CTE-6jrZ.js → smalltalk-ZEiZ6i7U.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DoEIGnmz.js → snazzy-light-29OA24q5.js} +0 -0
- /package/dist/web/assets/{solarized-dark-CStiJyBg.js → solarized-dark-Bnq9IOn_.js} +0 -0
- /package/dist/web/assets/{solarized-light-CfuNycS9.js → solarized-light-CYdI7T5g.js} +0 -0
- /package/dist/web/assets/{solidity-DL6sO5rd.js → solidity-DcuIDB-_.js} +0 -0
- /package/dist/web/assets/{splunk-yK4ZUFBB.js → splunk-D5ONE91g.js} +0 -0
- /package/dist/web/assets/{sql-Lw03eoN8.js → sql-DIwJJDU-.js} +0 -0
- /package/dist/web/assets/{ssh-config-DeRp25TR.js → ssh-config-D8717tLI.js} +0 -0
- /package/dist/web/assets/{stylus-D8OMsPh-.js → stylus-Cm1lO4Y6.js} +0 -0
- /package/dist/web/assets/{swift-nsl9stPV.js → swift-X8oWpSeU.js} +0 -0
- /package/dist/web/assets/{synthwave-84-exAgsgpf.js → synthwave-84-DF-t1UKY.js} +0 -0
- /package/dist/web/assets/{system-verilog-DBWf00T6.js → system-verilog-JYyHFpPf.js} +0 -0
- /package/dist/web/assets/{systemd-nhu27Dw5.js → systemd-Da-5Ma_c.js} +0 -0
- /package/dist/web/assets/{talonscript-CNOEcGDc.js → talonscript-CXraVPho.js} +0 -0
- /package/dist/web/assets/{tasl-CjYZenSH.js → tasl-BCmWezH9.js} +0 -0
- /package/dist/web/assets/{tcl-BVA09RDz.js → tcl-BTufzfbf.js} +0 -0
- /package/dist/web/assets/{terraform-C5EloH1q.js → terraform-CtUA9c8W.js} +0 -0
- /package/dist/web/assets/{tokyo-night-D764DdhO.js → tokyo-night-BCThYXmz.js} +0 -0
- /package/dist/web/assets/{toml-CCDYblSB.js → toml-D5YlDr6Y.js} +0 -0
- /package/dist/web/assets/{tsv-BZ4sA05e.js → tsv-CBB-P_Sl.js} +0 -0
- /package/dist/web/assets/{tsx-DPLBzAt5.js → tsx-D5B4juDp.js} +0 -0
- /package/dist/web/assets/{turtle-CwGHCq40.js → turtle-D8PCeJ-v.js} +0 -0
- /package/dist/web/assets/{typescript-CUH0vBAl.js → typescript-CMPnTawp.js} +0 -0
- /package/dist/web/assets/{typespec-Bq31Vq4u.js → typespec-CHxldxIW.js} +0 -0
- /package/dist/web/assets/{typst-MD9BR9W5.js → typst-Q8CqX5OR.js} +0 -0
- /package/dist/web/assets/{utils-u2fbYPrY.js → utils-QHe_Q6UF.js} +0 -0
- /package/dist/web/assets/{v-DZV7IxIT.js → v-Bf2hKAMu.js} +0 -0
- /package/dist/web/assets/{vala-urd1zi9S.js → vala-Cbete-UE.js} +0 -0
- /package/dist/web/assets/{vb-DIZU3zbJ.js → vb-CA-xWAcX.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-DCj0zgSC.js → vendor-xterm-BHKF_Nsf.js} +0 -0
- /package/dist/web/assets/{verilog-Ndz_ciTA.js → verilog-B3wb8CfT.js} +0 -0
- /package/dist/web/assets/{vesper-C86M7LJp.js → vesper-D6r3VJq_.js} +0 -0
- /package/dist/web/assets/{vhdl-_klwDUjd.js → vhdl-C42S_797.js} +0 -0
- /package/dist/web/assets/{viml-DzRXWoQh.js → viml-BePrIAs8.js} +0 -0
- /package/dist/web/assets/{vitesse-black-TWZ20_3U.js → vitesse-black-DRqn61Ck.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dl7R6fy9.js → vitesse-dark-39_8UE-w.js} +0 -0
- /package/dist/web/assets/{vitesse-light-Cs0Dt2_n.js → vitesse-light-B1jwrxDm.js} +0 -0
- /package/dist/web/assets/{vyper-qnCVj8ia.js → vyper-tDJv3imF.js} +0 -0
- /package/dist/web/assets/{wasm-Ckx9jBPe.js → wasm-ChtccNfv.js} +0 -0
- /package/dist/web/assets/{wasm-Dy-SJ3W9.js → wasm-DR7eskZH.js} +0 -0
- /package/dist/web/assets/{wenyan-DTEy2Lx-.js → wenyan-CezVfqN3.js} +0 -0
- /package/dist/web/assets/{wgsl-DC7n63Sy.js → wgsl-CwOcIiIG.js} +0 -0
- /package/dist/web/assets/{wikitext-1zFoZiW6.js → wikitext-C5coomYc.js} +0 -0
- /package/dist/web/assets/{wit-DtTfCRph.js → wit-C9yyX6-U.js} +0 -0
- /package/dist/web/assets/{wolfram-B2Bgu1KL.js → wolfram-BWi0rMAa.js} +0 -0
- /package/dist/web/assets/{yaml-B-Jxk0IQ.js → yaml-CGXhKUtT.js} +0 -0
- /package/dist/web/assets/{zenscript-BWRfvNER.js → zenscript-Do7WLoJh.js} +0 -0
- /package/dist/web/assets/{zig-CkwuvETm.js → zig-Cr80Qrru.js} +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which displays a remote-desktop session can capture, and where each sits in the host's
|
|
3
|
+
* global coordinate space (input for a non-primary display has to be offset by its origin).
|
|
4
|
+
*
|
|
5
|
+
* macOS: `CGGetActiveDisplayList` order is exactly avfoundation's `Capture screen N` numbering
|
|
6
|
+
* (ffmpeg builds its screen inputs from that same list) — verified: `[1]` 3440×1440 ⇔
|
|
7
|
+
* "Capture screen 1" 3440×1440. Sizes/main flag come from CoreGraphics via bun:ffi; names and
|
|
8
|
+
* bounds go through one JXA call because `CGDisplayBounds` returns a struct by value and
|
|
9
|
+
* `NSScreen.localizedName` is Objective-C — both out of bun:ffi's reach. Cached briefly: the
|
|
10
|
+
* capabilities route polls every 2 s while the checklist is up.
|
|
11
|
+
*
|
|
12
|
+
* Windows: gdigrab `desktop` grabs the whole virtual screen and SendInput maps 0..65535 onto
|
|
13
|
+
* that same rectangle, so there is exactly one "display" and no offsets to apply.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface RemoteDisplay {
|
|
17
|
+
/** Stable per host session (`CGDirectDisplayID` on macOS, `"desktop"` on Windows). */
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
primary: boolean;
|
|
21
|
+
/** Origin + size in the OS's global *logical* coordinate space (points on macOS). */
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
/** Position in the capture backend's own numbering (avfoundation `Capture screen N`). */
|
|
27
|
+
captureIndex: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const CACHE_MS = 5000;
|
|
31
|
+
let cache: { at: number; displays: RemoteDisplay[] } | null = null;
|
|
32
|
+
|
|
33
|
+
async function listDarwinDisplays(): Promise<RemoteDisplay[]> {
|
|
34
|
+
const { dlopen, FFIType: T, ptr } = await import("bun:ffi");
|
|
35
|
+
const cg = dlopen("/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics", {
|
|
36
|
+
CGGetActiveDisplayList: { args: [T.u32, T.ptr, T.ptr], returns: T.i32 },
|
|
37
|
+
CGDisplayPixelsWide: { args: [T.u32], returns: T.u64 },
|
|
38
|
+
CGDisplayPixelsHigh: { args: [T.u32], returns: T.u64 },
|
|
39
|
+
CGDisplayIsMain: { args: [T.u32], returns: T.bool },
|
|
40
|
+
}).symbols;
|
|
41
|
+
const ids = new Uint32Array(16), count = new Uint32Array(1);
|
|
42
|
+
if (cg.CGGetActiveDisplayList(16, ptr(ids), ptr(count)) !== 0) return [];
|
|
43
|
+
|
|
44
|
+
// Names + origins in one osascript round trip (~100 ms); tolerate its absence.
|
|
45
|
+
type Meta = { id: number; name: string; x: number; y: number };
|
|
46
|
+
let meta: Meta[] = [];
|
|
47
|
+
try {
|
|
48
|
+
const proc = Bun.spawnSync(["osascript", "-l", "JavaScript", "-e",
|
|
49
|
+
'ObjC.import("AppKit"); ObjC.import("CoreGraphics"); JSON.stringify($.NSScreen.screens.js.map(s => { ' +
|
|
50
|
+
'const id = s.deviceDescription.objectForKey("NSScreenNumber").unsignedIntValue; const b = $.CGDisplayBounds(id); ' +
|
|
51
|
+
'return { id, name: s.localizedName.js, x: b.origin.x, y: b.origin.y }; }))']);
|
|
52
|
+
meta = JSON.parse(proc.stdout.toString().trim() || "[]");
|
|
53
|
+
} catch { /* names fall back to "Display N", origins to 0 — capture still works */ }
|
|
54
|
+
|
|
55
|
+
const displays: RemoteDisplay[] = [];
|
|
56
|
+
const n = count[0] ?? 0;
|
|
57
|
+
for (let i = 0; i < n; i++) {
|
|
58
|
+
const id = ids[i] ?? 0;
|
|
59
|
+
const m = meta.find((x) => x.id === id);
|
|
60
|
+
displays.push({
|
|
61
|
+
id: String(id),
|
|
62
|
+
label: m?.name ?? `Display ${i + 1}`,
|
|
63
|
+
primary: cg.CGDisplayIsMain(id),
|
|
64
|
+
x: m?.x ?? 0,
|
|
65
|
+
y: m?.y ?? 0,
|
|
66
|
+
width: Number(cg.CGDisplayPixelsWide(id)),
|
|
67
|
+
height: Number(cg.CGDisplayPixelsHigh(id)),
|
|
68
|
+
captureIndex: i,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return displays;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function listDisplays(platform: NodeJS.Platform = process.platform): Promise<RemoteDisplay[]> {
|
|
75
|
+
if (platform === "win32") {
|
|
76
|
+
return [{ id: "desktop", label: "All displays", primary: true, x: 0, y: 0, width: 0, height: 0, captureIndex: 0 }];
|
|
77
|
+
}
|
|
78
|
+
if (platform !== "darwin") return [];
|
|
79
|
+
if (cache && Date.now() - cache.at < CACHE_MS) return cache.displays;
|
|
80
|
+
const displays = await listDarwinDisplays();
|
|
81
|
+
cache = { at: Date.now(), displays };
|
|
82
|
+
return displays;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The display a session should capture: the requested one when it still exists (a monitor
|
|
86
|
+
* can be unplugged between the client's pick and connect), else the primary. */
|
|
87
|
+
export async function resolveDisplay(id: string | undefined): Promise<RemoteDisplay | null> {
|
|
88
|
+
const displays = await listDisplays();
|
|
89
|
+
return displays.find((d) => d.id === id) ?? displays.find((d) => d.primary) ?? displays[0] ?? null;
|
|
90
|
+
}
|
|
@@ -32,6 +32,13 @@ export function captureEncoderArgs(encoder: string = "libx264"): string[] {
|
|
|
32
32
|
return ["-c:v", "h264_qsv", "-preset", "veryfast", "-low_power", "1", "-bufsize", "512k", ...common];
|
|
33
33
|
case "h264_amf":
|
|
34
34
|
return ["-c:v", "h264_amf", "-quality", "speed", "-rc", "cbr", "-bufsize", "512k", ...common];
|
|
35
|
+
case "h264_videotoolbox":
|
|
36
|
+
// macOS hardware encode. `-realtime 1` asks VT to hit the deadline over quality,
|
|
37
|
+
// `-prio_speed 1` likewise; `-allow_sw 1` keeps it working on hosts where the hardware
|
|
38
|
+
// encoder is busy/unavailable rather than failing the session. Verified on macOS 26:
|
|
39
|
+
// emits SPS/PPS/SEI before every IDR and one slice per frame, so the AU assembler's
|
|
40
|
+
// assumptions hold unchanged.
|
|
41
|
+
return ["-c:v", "h264_videotoolbox", "-realtime", "1", "-prio_speed", "1", "-allow_sw", "1", ...common];
|
|
35
42
|
default:
|
|
36
43
|
// libx264 low-latency. `-tune zerolatency` enables sliced-threads (multi-slice NALs);
|
|
37
44
|
// access-unit-assembler.ts assumes one slice/frame, so force it back with sliced-threads=0.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Feature
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Feature switch. Remote desktop is ON by default — the viewer itself shows a warning before
|
|
3
|
+
* the first frame is streamed (`remote-desktop-warning-gate.tsx`) and every entry point still
|
|
4
|
+
* sits behind PPM auth. `REMOTE_DESKTOP_ENABLED=0` (or `false`) turns the whole surface off for
|
|
5
|
+
* hosts that never want a live video+input channel reachable, e.g. one exposed on a public
|
|
6
|
+
* tunnel. Every entry point (REST routes, WS upgrade) checks this independently rather than
|
|
7
|
+
* trusting a single call site.
|
|
6
8
|
*/
|
|
7
9
|
export function isRemoteDesktopEnabled(): boolean {
|
|
8
|
-
const v = process.env.REMOTE_DESKTOP_ENABLED;
|
|
9
|
-
return v === "
|
|
10
|
+
const v = process.env.REMOTE_DESKTOP_ENABLED?.trim().toLowerCase();
|
|
11
|
+
return !(v === "0" || v === "false");
|
|
10
12
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract every platform input injector implements. `remote-desktop-input.ts` picks one per
|
|
3
|
+
* `process.platform`; adding an OS = one new module + one registry line there, nothing else
|
|
4
|
+
* (the session and the routes only ever talk to the facade).
|
|
5
|
+
*
|
|
6
|
+
* All coordinates are 0..1 fractions of the captured frame — the backend owns the mapping to
|
|
7
|
+
* whatever its OS wants (65535-normalised for SendInput, logical points for CoreGraphics).
|
|
8
|
+
* Keys are KeyboardEvent `code`s (physical, layout-independent); the backend owns the table.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Where the captured frame sits in the OS's global logical coordinates — what a 0..1 fraction
|
|
12
|
+
* is a fraction *of*. null = the backend's default surface (primary display / virtual desktop). */
|
|
13
|
+
export interface InputTargetRect { x: number; y: number; width: number; height: number }
|
|
14
|
+
|
|
15
|
+
export interface RemoteInputBackend {
|
|
16
|
+
/** Short id for logs/diagnostics (`"win32-sendinput"`, `"darwin-cgevent"`). Whether injection
|
|
17
|
+
* would actually work right now (OS permissions) is a *requirement* in
|
|
18
|
+
* `remote-desktop-requirements.ts`, not a backend concern. */
|
|
19
|
+
readonly id: string;
|
|
20
|
+
pointer(xFrac: number, yFrac: number, button: "left" | "right" | null, down: boolean | null, target: InputTargetRect | null): Promise<void>;
|
|
21
|
+
/** `deltaY` in wheel-notch units of 120 (positive = away from the user). */
|
|
22
|
+
wheel(deltaY: number): Promise<void>;
|
|
23
|
+
/** Returns false for a `code` the backend has no mapping for (caller drops it). */
|
|
24
|
+
key(code: string, down: boolean): Promise<boolean>;
|
|
25
|
+
/** Type a string as text, independent of layout. Optional: not every OS has a cheap path. */
|
|
26
|
+
text?(text: string): Promise<void>;
|
|
27
|
+
/** Force-release every modifier, regardless of tracked state — backstop for a lost keyup. */
|
|
28
|
+
releaseAllModifiers(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class RemoteInputUnavailableError extends Error {
|
|
32
|
+
constructor(msg = `Remote input injection is not available on ${process.platform}`) {
|
|
33
|
+
super(msg);
|
|
34
|
+
this.name = "RemoteInputUnavailableError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS input backend: Bun FFI straight into CoreGraphics (`CGEventPost` at the HID tap) — no
|
|
3
|
+
* helper binary, no signing question for the injector. Selected by `remote-desktop-input.ts`
|
|
4
|
+
* on `darwin` only; `bun:ffi` is `import()`-ed and `dlopen`-ed lazily inside `load()` so
|
|
5
|
+
* importing this module elsewhere never touches a framework.
|
|
6
|
+
*
|
|
7
|
+
* Spike facts this relies on (`plans/reports/spike-260908-remote-desktop-macos.md` §S2):
|
|
8
|
+
* - `CGPoint` is passed BY VALUE as two consecutive `double` args — matches the arm64 (d0,d1)
|
|
9
|
+
* and x86_64 (xmm0,xmm1) ABIs, integer args keep their own registers. Struct *returns* are
|
|
10
|
+
* not expressible, so nothing here reads the cursor back.
|
|
11
|
+
* - Injection needs the Accessibility grant on the `bun` executable (TCC); without it every
|
|
12
|
+
* post is a silent no-op — `remote-desktop-macos-permissions.ts` pre-flights that.
|
|
13
|
+
* - Coordinates are logical points of the main display (`CGDisplayPixelsWide/High` return
|
|
14
|
+
* points despite the name); the capture is that same display, so fractions map directly.
|
|
15
|
+
* - Modifier state is carried on every event as `CGEventFlags`; a modifier keyDown alone is
|
|
16
|
+
* not enough, so held modifiers are tracked and OR-ed onto each subsequent event.
|
|
17
|
+
*/
|
|
18
|
+
import { codeToCgKey, CGKEY_MODIFIER_FLAG, MODIFIER_CGKEY_CODES } from "./remote-desktop-cg-key-map.ts";
|
|
19
|
+
import { RemoteInputUnavailableError, type InputTargetRect, type RemoteInputBackend } from "./remote-desktop-input-backend.ts";
|
|
20
|
+
|
|
21
|
+
const CG = "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics";
|
|
22
|
+
const CORE_FOUNDATION = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
|
|
23
|
+
|
|
24
|
+
// CGEventType
|
|
25
|
+
const LEFT_DOWN = 1, LEFT_UP = 2, RIGHT_DOWN = 3, RIGHT_UP = 4, MOUSE_MOVED = 5;
|
|
26
|
+
const LEFT_DRAGGED = 6, RIGHT_DRAGGED = 7, FLAGS_CHANGED = 12;
|
|
27
|
+
const HID_EVENT_TAP = 0;
|
|
28
|
+
const BUTTON_LEFT = 0, BUTTON_RIGHT = 1;
|
|
29
|
+
const SCROLL_UNIT_LINE = 1;
|
|
30
|
+
/** kCGMouseEventClickState — must be set on synthesized clicks or apps never see a double-click. */
|
|
31
|
+
const FIELD_CLICK_STATE = 1;
|
|
32
|
+
const DOUBLE_CLICK_MS = 500;
|
|
33
|
+
/** One wheel notch (the client's 120-unit) scrolls this many lines, as it does on Windows. */
|
|
34
|
+
const LINES_PER_NOTCH = 3;
|
|
35
|
+
|
|
36
|
+
type Ptr = number | bigint;
|
|
37
|
+
type Symbols = {
|
|
38
|
+
CGEventCreateMouseEvent: (src: Ptr | null, type: number, x: number, y: number, button: number) => Ptr;
|
|
39
|
+
CGEventCreateKeyboardEvent: (src: Ptr | null, keycode: number, down: boolean) => Ptr;
|
|
40
|
+
CGEventCreateScrollWheelEvent2: (src: Ptr | null, units: number, count: number, w1: number, w2: number, w3: number) => Ptr;
|
|
41
|
+
CGEventSetFlags: (ev: Ptr, flags: bigint) => void;
|
|
42
|
+
CGEventSetType: (ev: Ptr, type: number) => void;
|
|
43
|
+
CGEventSetIntegerValueField: (ev: Ptr, field: number, value: bigint) => void;
|
|
44
|
+
CGEventKeyboardSetUnicodeString: (ev: Ptr, length: bigint, utf16: Ptr) => void;
|
|
45
|
+
CGEventPost: (tap: number, ev: Ptr) => void;
|
|
46
|
+
CGMainDisplayID: () => number;
|
|
47
|
+
CGDisplayPixelsWide: (display: number) => bigint;
|
|
48
|
+
CGDisplayPixelsHigh: (display: number) => bigint;
|
|
49
|
+
CFRelease: (obj: Ptr) => void;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let loaded: { ffi: typeof import("bun:ffi"); lib: Symbols } | null = null;
|
|
53
|
+
|
|
54
|
+
async function load() {
|
|
55
|
+
if (process.platform !== "darwin") throw new RemoteInputUnavailableError();
|
|
56
|
+
if (loaded) return loaded;
|
|
57
|
+
const ffi = await import("bun:ffi");
|
|
58
|
+
const { dlopen, FFIType: T } = ffi;
|
|
59
|
+
const cg = dlopen(CG, {
|
|
60
|
+
CGEventCreateMouseEvent: { args: [T.ptr, T.u32, T.double, T.double, T.u32], returns: T.ptr },
|
|
61
|
+
CGEventCreateKeyboardEvent: { args: [T.ptr, T.u16, T.bool], returns: T.ptr },
|
|
62
|
+
CGEventCreateScrollWheelEvent2: { args: [T.ptr, T.u32, T.u32, T.i32, T.i32, T.i32], returns: T.ptr },
|
|
63
|
+
CGEventSetFlags: { args: [T.ptr, T.u64], returns: T.void },
|
|
64
|
+
CGEventSetType: { args: [T.ptr, T.u32], returns: T.void },
|
|
65
|
+
CGEventSetIntegerValueField: { args: [T.ptr, T.u32, T.i64], returns: T.void },
|
|
66
|
+
CGEventKeyboardSetUnicodeString: { args: [T.ptr, T.u64, T.ptr], returns: T.void },
|
|
67
|
+
CGEventPost: { args: [T.u32, T.ptr], returns: T.void },
|
|
68
|
+
CGMainDisplayID: { args: [], returns: T.u32 },
|
|
69
|
+
CGDisplayPixelsWide: { args: [T.u32], returns: T.u64 },
|
|
70
|
+
CGDisplayPixelsHigh: { args: [T.u32], returns: T.u64 },
|
|
71
|
+
}).symbols;
|
|
72
|
+
const cf = dlopen(CORE_FOUNDATION, { CFRelease: { args: [T.ptr], returns: T.void } }).symbols;
|
|
73
|
+
loaded = { ffi, lib: { ...cg, ...cf } as unknown as Symbols };
|
|
74
|
+
return loaded;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Per-process injection state. One remote-desktop session at a time drives the real desktop,
|
|
78
|
+
// so module-level state is the right scope (same as the win32 backend's desktop handle).
|
|
79
|
+
let heldButton: "left" | "right" | null = null;
|
|
80
|
+
const heldModifiers = new Set<number>();
|
|
81
|
+
let lastDownAt = 0;
|
|
82
|
+
let lastDownX = -1, lastDownY = -1, clickState = 1;
|
|
83
|
+
|
|
84
|
+
function modifierFlags(): bigint {
|
|
85
|
+
let flags = 0;
|
|
86
|
+
for (const code of heldModifiers) flags |= CGKEY_MODIFIER_FLAG[code] ?? 0;
|
|
87
|
+
return BigInt(flags);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function post(lib: Symbols, ev: Ptr, type?: number): void {
|
|
91
|
+
if (type !== undefined) lib.CGEventSetType(ev, type);
|
|
92
|
+
lib.CGEventSetFlags(ev, modifierFlags());
|
|
93
|
+
lib.CGEventPost(HID_EVENT_TAP, ev);
|
|
94
|
+
lib.CFRelease(ev);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Fraction → global logical point on the target display (main display when none given),
|
|
98
|
+
* clamped so a wild client value stays on that screen. A secondary display's origin can be
|
|
99
|
+
* negative in CG's global space (one here sits at −929,−1440). */
|
|
100
|
+
function toPoint(lib: Symbols, xFrac: number, yFrac: number, target: InputTargetRect | null): { x: number; y: number } {
|
|
101
|
+
const rect = target ?? (() => {
|
|
102
|
+
const display = lib.CGMainDisplayID();
|
|
103
|
+
return { x: 0, y: 0, width: Number(lib.CGDisplayPixelsWide(display)), height: Number(lib.CGDisplayPixelsHigh(display)) };
|
|
104
|
+
})();
|
|
105
|
+
return {
|
|
106
|
+
x: rect.x + Math.min(Math.max(xFrac, 0), 1) * rect.width,
|
|
107
|
+
y: rect.y + Math.min(Math.max(yFrac, 0), 1) * rect.height,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function pointer(
|
|
112
|
+
xFrac: number, yFrac: number, button: "left" | "right" | null, down: boolean | null, target: InputTargetRect | null,
|
|
113
|
+
): Promise<void> {
|
|
114
|
+
const { lib } = await load();
|
|
115
|
+
const { x, y } = toPoint(lib, xFrac, yFrac, target);
|
|
116
|
+
if (button === null || down === null) {
|
|
117
|
+
// Pure move: a drag while a button is held, else a plain move.
|
|
118
|
+
const type = heldButton === "left" ? LEFT_DRAGGED : heldButton === "right" ? RIGHT_DRAGGED : MOUSE_MOVED;
|
|
119
|
+
post(lib, lib.CGEventCreateMouseEvent(null, type, x, y, heldButton === "right" ? BUTTON_RIGHT : BUTTON_LEFT));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const cgButton = button === "left" ? BUTTON_LEFT : BUTTON_RIGHT;
|
|
123
|
+
const type = button === "left" ? (down ? LEFT_DOWN : LEFT_UP) : (down ? RIGHT_DOWN : RIGHT_UP);
|
|
124
|
+
if (down) {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
const samePlace = Math.abs(x - lastDownX) < 4 && Math.abs(y - lastDownY) < 4;
|
|
127
|
+
clickState = now - lastDownAt < DOUBLE_CLICK_MS && samePlace ? clickState + 1 : 1;
|
|
128
|
+
lastDownAt = now; lastDownX = x; lastDownY = y;
|
|
129
|
+
heldButton = button;
|
|
130
|
+
} else {
|
|
131
|
+
heldButton = null;
|
|
132
|
+
}
|
|
133
|
+
const ev = lib.CGEventCreateMouseEvent(null, type, x, y, cgButton);
|
|
134
|
+
lib.CGEventSetIntegerValueField(ev, FIELD_CLICK_STATE, BigInt(clickState));
|
|
135
|
+
post(lib, ev);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function wheel(deltaY: number): Promise<void> {
|
|
139
|
+
const { lib } = await load();
|
|
140
|
+
const lines = Math.max(-32768, Math.min(32767, Math.round((deltaY / 120) * LINES_PER_NOTCH)));
|
|
141
|
+
if (lines === 0) return;
|
|
142
|
+
post(lib, lib.CGEventCreateScrollWheelEvent2(null, SCROLL_UNIT_LINE, 1, lines, 0, 0));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function key(code: string, down: boolean): Promise<boolean> {
|
|
146
|
+
const keycode = codeToCgKey(code);
|
|
147
|
+
if (keycode === null) return false;
|
|
148
|
+
const { lib } = await load();
|
|
149
|
+
const isModifier = keycode in CGKEY_MODIFIER_FLAG;
|
|
150
|
+
if (isModifier) { if (down) heldModifiers.add(keycode); else heldModifiers.delete(keycode); }
|
|
151
|
+
// Modifier keys travel as FlagsChanged (with the new flag state already applied above), never
|
|
152
|
+
// as KeyDown/KeyUp — apps ignore a KeyDown for Shift but honour the flags on it.
|
|
153
|
+
post(lib, lib.CGEventCreateKeyboardEvent(null, keycode, down), isModifier ? FLAGS_CHANGED : undefined);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Carrier keycode for Unicode text events. The attached string is what a Cocoa text view
|
|
158
|
+
* types, but the keycode still matters to anything hooking events by key:
|
|
159
|
+
* - `0` (kVK_ANSI_A, what most tools use) is re-read as a literal "a" by third-party IMEs
|
|
160
|
+
* (OpenKey/EVKey Telex turned "日本" into "â" — two keycode-0 events = "aa" → "â");
|
|
161
|
+
* - function keys (F13–F19) and unassigned codes (0x7F, 0xFF) produce NO text at all;
|
|
162
|
+
* - Space passes through IMEs as a word break and types every tested char, incl. `\t`, `\n`,
|
|
163
|
+
* CJK and surrogate-pair emoji.
|
|
164
|
+
* Text is still subject to the host's active IME (Telex rewrote "World" → "ửold"); that is
|
|
165
|
+
* inherent to injection on any OS, not something the carrier can avoid. */
|
|
166
|
+
const TEXT_CARRIER_KEYCODE = 0x31;
|
|
167
|
+
|
|
168
|
+
/** Type text as Unicode, one code point per keyDown/keyUp pair. */
|
|
169
|
+
async function text(str: string): Promise<void> {
|
|
170
|
+
const { ffi, lib } = await load();
|
|
171
|
+
for (const ch of str) {
|
|
172
|
+
const utf16 = new Uint16Array(ch.length);
|
|
173
|
+
for (let i = 0; i < ch.length; i++) utf16[i] = ch.charCodeAt(i);
|
|
174
|
+
for (const down of [true, false]) {
|
|
175
|
+
const ev = lib.CGEventCreateKeyboardEvent(null, TEXT_CARRIER_KEYCODE, down);
|
|
176
|
+
lib.CGEventKeyboardSetUnicodeString(ev, BigInt(utf16.length), ffi.ptr(utf16));
|
|
177
|
+
post(lib, ev);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function releaseAllModifiers(): Promise<void> {
|
|
183
|
+
if (process.platform !== "darwin") return;
|
|
184
|
+
try {
|
|
185
|
+
const { lib } = await load();
|
|
186
|
+
heldModifiers.clear();
|
|
187
|
+
for (const keycode of MODIFIER_CGKEY_CODES) post(lib, lib.CGEventCreateKeyboardEvent(null, keycode, false), FLAGS_CHANGED);
|
|
188
|
+
if (heldButton) {
|
|
189
|
+
// A button still down on disconnect would leave the host mid-drag; release it in place.
|
|
190
|
+
const type = heldButton === "left" ? LEFT_UP : RIGHT_UP;
|
|
191
|
+
post(lib, lib.CGEventCreateMouseEvent(null, type, lastDownX, lastDownY, heldButton === "left" ? BUTTON_LEFT : BUTTON_RIGHT));
|
|
192
|
+
heldButton = null;
|
|
193
|
+
}
|
|
194
|
+
} catch { /* best-effort */ }
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export const darwinInputBackend: RemoteInputBackend = {
|
|
198
|
+
id: "darwin-cgevent",
|
|
199
|
+
pointer, wheel, key, text, releaseAllModifiers,
|
|
200
|
+
};
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows input backend: Bun FFI straight into `user32.dll` `SendInput` — no separate helper
|
|
3
|
+
* process (drops the Rust-binary build step + unsigned-exe/SmartScreen risk the phase plan
|
|
4
|
+
* flagged for a bundled injector; phase 02's SYSTEM-session helper is a different
|
|
5
|
+
* process/token anyway, so nothing here is meant to be reused as-is).
|
|
6
|
+
*
|
|
7
|
+
* Normal desktop only, current interactive session, no SYSTEM/UAC/lock-screen support.
|
|
8
|
+
*
|
|
9
|
+
* `bun:ffi` is only ever `import()`-ed and `dlopen`-ed lazily from inside `loadUser32()`, so
|
|
10
|
+
* merely importing this module (routing, types) on Linux/macOS test runners never touches
|
|
11
|
+
* `user32.dll`. Selected by `remote-desktop-input.ts` on `win32` only.
|
|
12
|
+
*/
|
|
13
|
+
import { codeToVk, MODIFIER_VK_CODES } from "./remote-desktop-vk-map.ts";
|
|
14
|
+
import { RemoteInputUnavailableError, type RemoteInputBackend } from "./remote-desktop-input-backend.ts";
|
|
15
|
+
|
|
16
|
+
const INPUT_MOUSE = 0;
|
|
17
|
+
const INPUT_KEYBOARD = 1;
|
|
18
|
+
const MOUSEEVENTF_MOVE = 0x0001;
|
|
19
|
+
const MOUSEEVENTF_LEFTDOWN = 0x0002;
|
|
20
|
+
const MOUSEEVENTF_LEFTUP = 0x0004;
|
|
21
|
+
const MOUSEEVENTF_RIGHTDOWN = 0x0008;
|
|
22
|
+
const MOUSEEVENTF_RIGHTUP = 0x0010;
|
|
23
|
+
const MOUSEEVENTF_ABSOLUTE = 0x8000;
|
|
24
|
+
const MOUSEEVENTF_VIRTUALDESK = 0x4000;
|
|
25
|
+
const MOUSEEVENTF_WHEEL = 0x0800;
|
|
26
|
+
/** One notch of a real mouse wheel — `mouseData` for `MOUSEEVENTF_WHEEL` is a signed multiple
|
|
27
|
+
* of this constant, not a raw pixel delta. */
|
|
28
|
+
const WHEEL_DELTA = 120;
|
|
29
|
+
const KEYEVENTF_KEYUP = 0x0002;
|
|
30
|
+
/** `wScan` carries a UTF-16 unit instead of a scan code; `wVk` must be 0. */
|
|
31
|
+
const KEYEVENTF_UNICODE = 0x0004;
|
|
32
|
+
/** `((DPI_AWARENESS_CONTEXT)-4)` — PER_MONITOR_AWARE_V2. Not a real pointer; the x64 ABI
|
|
33
|
+
* passes it in the same register/slot as a pointer arg would, bit pattern only. */
|
|
34
|
+
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4n;
|
|
35
|
+
/** Each `INPUT` struct is 40 bytes on x64 (8-byte `type` slot + 32-byte union). */
|
|
36
|
+
const INPUT_STRUCT_SIZE = 40;
|
|
37
|
+
/** Desktop access rights for input injection. `SendInput` silently no-ops when the calling
|
|
38
|
+
* thread is not attached to the *input* desktop; attaching needs the READ/WRITE/SWITCH rights
|
|
39
|
+
* plus JOURNALPLAYBACK so the desktop accepts injected input. These MUST be the real DESKTOP_*
|
|
40
|
+
* bit values — an invalid mask (e.g. an out-of-range bit) makes `OpenInputDesktop` return NULL
|
|
41
|
+
* and the attach silently fails, leaving SendInput pointed at the wrong desktop. */
|
|
42
|
+
const DESKTOP_READOBJECTS = 0x0001;
|
|
43
|
+
const DESKTOP_JOURNALPLAYBACK = 0x0020;
|
|
44
|
+
const DESKTOP_WRITEOBJECTS = 0x0080;
|
|
45
|
+
const DESKTOP_SWITCHDESKTOP = 0x0100;
|
|
46
|
+
const INPUT_DESKTOP_ACCESS =
|
|
47
|
+
DESKTOP_READOBJECTS | DESKTOP_JOURNALPLAYBACK | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP; // 0x1A1
|
|
48
|
+
|
|
49
|
+
/** WINSTA_ALL_ACCESS — enough to open the interactive window station and make it this process's
|
|
50
|
+
* station so its input desktop becomes reachable. */
|
|
51
|
+
const WINSTA_ALL_ACCESS = 0x37f;
|
|
52
|
+
|
|
53
|
+
type FfiModule = typeof import("bun:ffi");
|
|
54
|
+
type User32Symbols = {
|
|
55
|
+
SendInput: (count: number, ptr: number | bigint, size: number) => number;
|
|
56
|
+
SetProcessDpiAwarenessContext: (ctx: bigint) => boolean;
|
|
57
|
+
OpenInputDesktop: (flags: number, inherit: boolean, access: number) => number | bigint;
|
|
58
|
+
SetThreadDesktop: (hdesk: number | bigint) => boolean;
|
|
59
|
+
CloseDesktop: (hdesk: number | bigint) => boolean;
|
|
60
|
+
OpenWindowStationW: (name: number | bigint, inherit: boolean, access: number) => number | bigint;
|
|
61
|
+
SetProcessWindowStation: (hwinsta: number | bigint) => boolean;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
let ffiModule: FfiModule | null = null;
|
|
65
|
+
let user32: User32Symbols | null = null;
|
|
66
|
+
let dpiAwarenessAttempted = false;
|
|
67
|
+
|
|
68
|
+
async function loadUser32(): Promise<{ ffi: FfiModule; lib: User32Symbols }> {
|
|
69
|
+
if (process.platform !== "win32") throw new RemoteInputUnavailableError();
|
|
70
|
+
if (!ffiModule) ffiModule = await import("bun:ffi");
|
|
71
|
+
const { dlopen, FFIType } = ffiModule;
|
|
72
|
+
if (!user32) {
|
|
73
|
+
user32 = dlopen("user32.dll", {
|
|
74
|
+
SendInput: { args: [FFIType.u32, FFIType.ptr, FFIType.i32], returns: FFIType.u32 },
|
|
75
|
+
SetProcessDpiAwarenessContext: { args: [FFIType.i64], returns: FFIType.bool },
|
|
76
|
+
OpenInputDesktop: { args: [FFIType.u32, FFIType.bool, FFIType.u32], returns: FFIType.ptr },
|
|
77
|
+
SetThreadDesktop: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
78
|
+
CloseDesktop: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
79
|
+
OpenWindowStationW: { args: [FFIType.ptr, FFIType.bool, FFIType.u32], returns: FFIType.ptr },
|
|
80
|
+
SetProcessWindowStation: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
81
|
+
}).symbols as unknown as User32Symbols;
|
|
82
|
+
}
|
|
83
|
+
if (!dpiAwarenessAttempted) {
|
|
84
|
+
dpiAwarenessAttempted = true;
|
|
85
|
+
// A second call to this in the same process returns E_ACCESSDENIED — swallow it, it just
|
|
86
|
+
// means something else (or a previous session) already set it.
|
|
87
|
+
try { user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); } catch { /* already set */ }
|
|
88
|
+
}
|
|
89
|
+
return { ffi: ffiModule, lib: user32 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeMouseInput(dv: DataView, offset: number, xAbs: number, yAbs: number, flags: number, mouseData = 0): void {
|
|
93
|
+
dv.setUint32(offset, INPUT_MOUSE, true);
|
|
94
|
+
dv.setInt32(offset + 8, xAbs, true);
|
|
95
|
+
dv.setInt32(offset + 12, yAbs, true);
|
|
96
|
+
dv.setInt32(offset + 16, mouseData, true); // wheel delta for MOUSEEVENTF_WHEEL, unused otherwise
|
|
97
|
+
dv.setUint32(offset + 20, flags >>> 0, true);
|
|
98
|
+
dv.setUint32(offset + 24, 0, true); // time: 0 lets the system supply it
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeKeyInput(dv: DataView, offset: number, vk: number, keyUp: boolean): void {
|
|
102
|
+
dv.setUint32(offset, INPUT_KEYBOARD, true);
|
|
103
|
+
dv.setUint16(offset + 8, vk, true);
|
|
104
|
+
dv.setUint16(offset + 10, 0, true); // wScan — unused, we inject by VK not scan code
|
|
105
|
+
dv.setUint32(offset + 12, keyUp ? KEYEVENTF_KEYUP : 0, true);
|
|
106
|
+
dv.setUint32(offset + 16, 0, true); // time
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeUnicodeKeyInput(dv: DataView, offset: number, unit: number, keyUp: boolean): void {
|
|
110
|
+
dv.setUint32(offset, INPUT_KEYBOARD, true);
|
|
111
|
+
dv.setUint16(offset + 8, 0, true); // wVk — must be 0 with KEYEVENTF_UNICODE
|
|
112
|
+
dv.setUint16(offset + 10, unit, true);
|
|
113
|
+
dv.setUint32(offset + 12, KEYEVENTF_UNICODE | (keyUp ? KEYEVENTF_KEYUP : 0), true);
|
|
114
|
+
dv.setUint32(offset + 16, 0, true); // time
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Handle of the desktop this thread is currently attached to, so we can close the previous one
|
|
118
|
+
* when the active input desktop changes (lock/unlock/UAC switches it). One handle in flight. */
|
|
119
|
+
let attachedDesktop: number | bigint | null = null;
|
|
120
|
+
|
|
121
|
+
/** Attach the calling thread to the *current* input desktop before injecting. Re-run every send
|
|
122
|
+
* because the input desktop changes on lock/unlock; `CreateProcessAsUser`/process start only
|
|
123
|
+
* fixes the initial desktop, not later switches. Best-effort: on failure we still try SendInput
|
|
124
|
+
* (it just no-ops), and log once rather than per-event spam. */
|
|
125
|
+
/** Attach the PROCESS to the interactive window station ("winsta0") once, so its input desktop
|
|
126
|
+
* is reachable. Without this, a PPM process launched from a non-interactive station (a service,
|
|
127
|
+
* or some spawners) can't OpenInputDesktop at all — SendInput then goes nowhere. No-op when the
|
|
128
|
+
* process is already on winsta0 (the normal case for PPM started inside the user's session). */
|
|
129
|
+
let stationEnsured = false;
|
|
130
|
+
function ensureInteractiveStation(ffi: FfiModule, lib: User32Symbols): void {
|
|
131
|
+
if (stationEnsured) return;
|
|
132
|
+
stationEnsured = true;
|
|
133
|
+
const name = "winsta0";
|
|
134
|
+
const wide = new Uint16Array(name.length + 1);
|
|
135
|
+
for (let i = 0; i < name.length; i++) wide[i] = name.charCodeAt(i);
|
|
136
|
+
const hwinsta = lib.OpenWindowStationW(ffi.ptr(wide), false, WINSTA_ALL_ACCESS);
|
|
137
|
+
if (!hwinsta) return; // already on it, or no access — OpenInputDesktop below will tell us
|
|
138
|
+
lib.SetProcessWindowStation(hwinsta);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function attachToInputDesktop(ffi: FfiModule, lib: User32Symbols): void {
|
|
142
|
+
ensureInteractiveStation(ffi, lib);
|
|
143
|
+
const hdesk = lib.OpenInputDesktop(0, false, INPUT_DESKTOP_ACCESS);
|
|
144
|
+
if (!hdesk) return; // couldn't open the input desktop (e.g. secure/lock desktop); leave thread as-is
|
|
145
|
+
const ok = lib.SetThreadDesktop(hdesk);
|
|
146
|
+
if (ok) {
|
|
147
|
+
if (attachedDesktop && attachedDesktop !== hdesk) {
|
|
148
|
+
try { lib.CloseDesktop(attachedDesktop); } catch { /* ignore */ }
|
|
149
|
+
}
|
|
150
|
+
attachedDesktop = hdesk;
|
|
151
|
+
} else {
|
|
152
|
+
// SetThreadDesktop fails if this thread owns windows/hooks — shouldn't for the server thread.
|
|
153
|
+
try { lib.CloseDesktop(hdesk); } catch { /* ignore */ }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function sendRaw(buf: Uint8Array, count: number): Promise<void> {
|
|
158
|
+
const { ffi, lib } = await loadUser32();
|
|
159
|
+
attachToInputDesktop(ffi, lib);
|
|
160
|
+
const sent = lib.SendInput(count, ffi.ptr(buf), INPUT_STRUCT_SIZE);
|
|
161
|
+
if (sent !== count) console.warn(`[remote-desktop] SendInput accepted ${sent}/${count} events`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Move the cursor and, if this is a click, press/release the button — one absolute move
|
|
165
|
+
* per call so the button acts at the position the client actually clicked. `xFrac`/`yFrac`
|
|
166
|
+
* are 0..1 fractions of the capture (client already dropped devicePixelRatio; only the
|
|
167
|
+
* canvas-relative fraction is meaningful for host coordinates). The target rect is ignored:
|
|
168
|
+
* gdigrab captures the whole virtual desktop and `MOUSEEVENTF_VIRTUALDESK` maps onto it. */
|
|
169
|
+
async function injectPointer(
|
|
170
|
+
xFrac: number,
|
|
171
|
+
yFrac: number,
|
|
172
|
+
button: "left" | "right" | null,
|
|
173
|
+
down: boolean | null,
|
|
174
|
+
): Promise<void> {
|
|
175
|
+
const xAbs = Math.round(Math.min(Math.max(xFrac, 0), 1) * 65535);
|
|
176
|
+
const yAbs = Math.round(Math.min(Math.max(yFrac, 0), 1) * 65535);
|
|
177
|
+
let flags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
|
178
|
+
if (button === "left") flags |= down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
|
|
179
|
+
else if (button === "right") flags |= down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
|
|
180
|
+
|
|
181
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE);
|
|
182
|
+
writeMouseInput(new DataView(buf.buffer), 0, xAbs, yAbs, flags);
|
|
183
|
+
await sendRaw(buf, 1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Scroll the wheel. `deltaY` is in `WHEEL_DELTA` (120) units — positive rotates the wheel
|
|
187
|
+
* forward (scrolls up/away from the user), negative rotates backward (scrolls down). The
|
|
188
|
+
* client accumulates raw two-finger-drag pixels into this unit before sending, so this layer
|
|
189
|
+
* stays a dumb relay: round + clamp to a safe i16 range so a runaway client value can't wrap
|
|
190
|
+
* into a huge or negative-looking mouseData field. */
|
|
191
|
+
async function injectWheel(deltaY: number): Promise<void> {
|
|
192
|
+
const clamped = Math.max(-32768, Math.min(32767, Math.round(deltaY)));
|
|
193
|
+
if (clamped === 0) return;
|
|
194
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE);
|
|
195
|
+
writeMouseInput(new DataView(buf.buffer), 0, 0, 0, MOUSEEVENTF_WHEEL, clamped);
|
|
196
|
+
await sendRaw(buf, 1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Send a keydown/keyup by physical key code. Returns false for an unmapped code (caller
|
|
200
|
+
* should just drop it rather than injecting a wrong key). */
|
|
201
|
+
async function injectKey(code: string, down: boolean): Promise<boolean> {
|
|
202
|
+
const vk = codeToVk(code);
|
|
203
|
+
if (vk === null) return false;
|
|
204
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE);
|
|
205
|
+
writeKeyInput(new DataView(buf.buffer), 0, vk, !down);
|
|
206
|
+
await sendRaw(buf, 1);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Force keyup for every modifier VK, regardless of tracked state — the backstop for a lost
|
|
211
|
+
* keyup (blur, WS drop) leaving Shift/Ctrl/Alt/Win logically held on the host. */
|
|
212
|
+
async function releaseAllModifiers(): Promise<void> {
|
|
213
|
+
if (process.platform !== "win32") return;
|
|
214
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE * MODIFIER_VK_CODES.length);
|
|
215
|
+
const dv = new DataView(buf.buffer);
|
|
216
|
+
MODIFIER_VK_CODES.forEach((vk, i) => writeKeyInput(dv, i * INPUT_STRUCT_SIZE, vk, true));
|
|
217
|
+
try { await sendRaw(buf, MODIFIER_VK_CODES.length); } catch { /* best-effort */ }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Type text as Unicode via `KEYEVENTF_UNICODE` — one down/up pair per UTF-16 unit (surrogate
|
|
221
|
+
* halves are sent as two units, which is what Windows expects), all in a single `SendInput`
|
|
222
|
+
* call so nothing interleaves. Written against the documented struct layout on the Mac side of
|
|
223
|
+
* the port; exercised by the e2e harness on Windows, not by unit tests. */
|
|
224
|
+
async function injectText(text: string): Promise<void> {
|
|
225
|
+
if (text.length === 0) return;
|
|
226
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE * text.length * 2);
|
|
227
|
+
const dv = new DataView(buf.buffer);
|
|
228
|
+
for (let i = 0; i < text.length; i++) {
|
|
229
|
+
const unit = text.charCodeAt(i);
|
|
230
|
+
writeUnicodeKeyInput(dv, (i * 2) * INPUT_STRUCT_SIZE, unit, false);
|
|
231
|
+
writeUnicodeKeyInput(dv, (i * 2 + 1) * INPUT_STRUCT_SIZE, unit, true);
|
|
232
|
+
}
|
|
233
|
+
await sendRaw(buf, text.length * 2);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const win32InputBackend: RemoteInputBackend = {
|
|
237
|
+
id: "win32-sendinput",
|
|
238
|
+
pointer: injectPointer,
|
|
239
|
+
wheel: injectWheel,
|
|
240
|
+
key: injectKey,
|
|
241
|
+
text: injectText,
|
|
242
|
+
releaseAllModifiers,
|
|
243
|
+
};
|