@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
|
@@ -16,12 +16,16 @@
|
|
|
16
16
|
* reads `visualViewport` itself and floats just its toolbar/key-bar row above the keyboard.
|
|
17
17
|
*
|
|
18
18
|
* The heavy viewer (canvas, WebCodecs decoder, gesture engine) is a nested lazy import, same as
|
|
19
|
-
* `TeamMemberSheet` does for its content — nothing here loads until the sheet actually opens
|
|
19
|
+
* `TeamMemberSheet` does for its content — nothing here loads until the sheet actually opens,
|
|
20
|
+
* and the lazy chunk itself sits behind `RemoteDesktopWarningGate`, so it isn't even fetched
|
|
21
|
+
* until the user continues past the warning.
|
|
20
22
|
*/
|
|
21
23
|
import { Suspense, lazy } from "react";
|
|
22
24
|
import { createPortal } from "react-dom";
|
|
23
25
|
import { usePortalContainer } from "@/components/ui/portal-container-context";
|
|
24
26
|
import { useRemoteDesktopMobileOpenState } from "./use-remote-desktop-mobile-open-state";
|
|
27
|
+
import { RemoteDesktopWarningGate } from "./remote-desktop-warning-gate";
|
|
28
|
+
import { RemoteDesktopReadinessGate } from "./remote-desktop-readiness-gate";
|
|
25
29
|
|
|
26
30
|
const RemoteDesktopMobileView = lazy(() => import("./remote-desktop-mobile-view"));
|
|
27
31
|
|
|
@@ -34,9 +38,13 @@ export function RemoteDesktopMobileSheet() {
|
|
|
34
38
|
|
|
35
39
|
return createPortal(
|
|
36
40
|
<div className="fixed inset-0 z-[45] bg-black" data-testid="remote-desktop-mobile-sheet">
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
41
|
+
<RemoteDesktopWarningGate onCancel={close}>
|
|
42
|
+
<RemoteDesktopReadinessGate onCancel={close}>
|
|
43
|
+
<Suspense fallback={<div className="flex h-full items-center justify-center text-sm text-text-2">Loading…</div>}>
|
|
44
|
+
<RemoteDesktopMobileView onClose={close} />
|
|
45
|
+
</Suspense>
|
|
46
|
+
</RemoteDesktopReadinessGate>
|
|
47
|
+
</RemoteDesktopWarningGate>
|
|
40
48
|
</div>,
|
|
41
49
|
portalContainer ?? document.getElementById("root") ?? document.body,
|
|
42
50
|
);
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
* virtual keyboard, zoom reset, close. Every button is a 44px+ touch target per
|
|
4
4
|
* `docs/design-guidelines.md`'s Mobile-First UI Rules.
|
|
5
5
|
*/
|
|
6
|
-
import { Hand, MousePointer2, Keyboard, ZoomOut, Gauge, X } from "lucide-react";
|
|
6
|
+
import { Hand, MousePointer2, Keyboard, ZoomOut, Gauge, Monitor, X } from "lucide-react";
|
|
7
7
|
import { cn } from "@/lib/utils";
|
|
8
8
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
9
9
|
import type { RemoteDesktopInputMode } from "./use-remote-desktop-touch";
|
|
10
10
|
|
|
11
11
|
export interface RemoteDesktopMobileToolbarProps {
|
|
12
|
+
/** Current display's name on a multi-monitor host; null hides the button (single display). */
|
|
13
|
+
displayLabel: string | null;
|
|
14
|
+
/** Cycle to the next display — one tap per hop beats a dropdown in the thumb zone. */
|
|
15
|
+
onNextDisplay: () => void;
|
|
12
16
|
mode: RemoteDesktopInputMode;
|
|
13
17
|
onToggleMode: () => void;
|
|
14
18
|
onOpenKeyboard: () => void;
|
|
@@ -45,6 +49,8 @@ function ToolbarButton({
|
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
export function RemoteDesktopMobileToolbar({
|
|
52
|
+
displayLabel,
|
|
53
|
+
onNextDisplay,
|
|
48
54
|
mode,
|
|
49
55
|
onToggleMode,
|
|
50
56
|
onOpenKeyboard,
|
|
@@ -68,6 +74,11 @@ export function RemoteDesktopMobileToolbar({
|
|
|
68
74
|
<ToolbarButton onClick={onResetZoom} label="Reset zoom">
|
|
69
75
|
<ZoomOut className="size-5" />
|
|
70
76
|
</ToolbarButton>
|
|
77
|
+
{displayLabel !== null && (
|
|
78
|
+
<ToolbarButton onClick={onNextDisplay} label={displayLabel.length > 10 ? `${displayLabel.slice(0, 9)}…` : displayLabel}>
|
|
79
|
+
<Monitor className="size-5" />
|
|
80
|
+
</ToolbarButton>
|
|
81
|
+
)}
|
|
71
82
|
<ToolbarButton onClick={toggleStats} label="Stats" active={statsVisible}>
|
|
72
83
|
<Gauge className="size-5" />
|
|
73
84
|
</ToolbarButton>
|
|
@@ -16,6 +16,7 @@ import { RemoteDesktopMobileToolbar } from "./remote-desktop-mobile-toolbar";
|
|
|
16
16
|
import { RemoteDesktopMobileKeyBar } from "./remote-desktop-mobile-key-bar";
|
|
17
17
|
import { RemoteDesktopStatsOverlay } from "./remote-desktop-stats-overlay";
|
|
18
18
|
import { letterboxedContentRect } from "./remote-desktop-coords";
|
|
19
|
+
import { useRemoteDesktopDisplayChoice } from "./use-remote-desktop-display-choice";
|
|
19
20
|
|
|
20
21
|
export interface RemoteDesktopMobileViewProps {
|
|
21
22
|
onClose: () => void;
|
|
@@ -26,11 +27,12 @@ export default function RemoteDesktopMobileView({ onClose }: RemoteDesktopMobile
|
|
|
26
27
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
27
28
|
const [mode, setMode] = useState<RemoteDesktopInputMode>("mouse");
|
|
28
29
|
const [keyBarOpen, setKeyBarOpen] = useState(false);
|
|
30
|
+
const display = useRemoteDesktopDisplayChoice();
|
|
29
31
|
|
|
30
32
|
const {
|
|
31
33
|
connState, errorMessage, decoderStatus, decoderErrorMessage, sendMessage, reconnect,
|
|
32
34
|
getTotalBytes, getFrameCount,
|
|
33
|
-
} = useRemoteDesktopConnection(canvasRef);
|
|
35
|
+
} = useRemoteDesktopConnection(canvasRef, { displayId: display.displayId });
|
|
34
36
|
const streaming = connState === "streaming";
|
|
35
37
|
|
|
36
38
|
const { transform, virtualCursor, resetZoom } = useRemoteDesktopTouch({
|
|
@@ -169,6 +171,8 @@ export default function RemoteDesktopMobileView({ onClose }: RemoteDesktopMobile
|
|
|
169
171
|
>
|
|
170
172
|
{keyBarOpen && <RemoteDesktopMobileKeyBar sendMessage={sendMessage} />}
|
|
171
173
|
<RemoteDesktopMobileToolbar
|
|
174
|
+
displayLabel={display.displays.length > 1 ? display.current?.label ?? null : null}
|
|
175
|
+
onNextDisplay={display.next}
|
|
172
176
|
mode={mode}
|
|
173
177
|
onToggleMode={toggleMode}
|
|
174
178
|
onOpenKeyboard={openKeyboard}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Second gate after the warning: the host's requirements checklist. Renders `children` (the
|
|
3
|
+
* viewer) straight away when every requirement is met; otherwise shows one row per item with
|
|
4
|
+
* its actions and re-polls until they clear. Video requirements block; input requirements only
|
|
5
|
+
* downgrade the button to "view only" — a screen you can see but not drive is still useful.
|
|
6
|
+
*
|
|
7
|
+
* Actions are generic (`terminal` types into a PPM dock terminal — the host's shell, so it works
|
|
8
|
+
* from a phone; `host` asks the server to prompt/open Settings on the host; `link` opens on the
|
|
9
|
+
* client). Same dark chrome and ≥44px targets as the warning gate, both surfaces.
|
|
10
|
+
*/
|
|
11
|
+
import { useState, type ReactNode } from "react";
|
|
12
|
+
import { CheckCircle2, Circle, ExternalLink, Loader2, MonitorSmartphone, TerminalSquare } from "lucide-react";
|
|
13
|
+
import { runInTerminal } from "@/lib/run-in-terminal";
|
|
14
|
+
import { useRemoteDesktopReadiness, type RemoteDesktopRequirement, type RequirementAction } from "./use-remote-desktop-readiness";
|
|
15
|
+
|
|
16
|
+
export interface RemoteDesktopReadinessGateProps {
|
|
17
|
+
onCancel: () => void;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function RemoteDesktopReadinessGate({ onCancel, children }: RemoteDesktopReadinessGateProps) {
|
|
22
|
+
const [accepted, setAccepted] = useState(false);
|
|
23
|
+
const { caps, failed, runHostAction } = useRemoteDesktopReadiness(!accepted);
|
|
24
|
+
|
|
25
|
+
if (accepted || (caps?.videoReady && caps.inputReady)) return <>{children}</>;
|
|
26
|
+
|
|
27
|
+
const pending = caps?.requirements.filter((r) => !r.ok) ?? [];
|
|
28
|
+
const viewOnly = !!caps?.videoReady && !caps.inputReady;
|
|
29
|
+
// The dock terminal opens *under* this surface (the mobile sheet covers the whole viewport),
|
|
30
|
+
// so hand the command over and get out of the way; the entry stays in the nav to come back to.
|
|
31
|
+
const onTerminal = (command: string) => { runInTerminal(command); onCancel(); };
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className="flex h-full w-full items-center justify-center overflow-y-auto bg-black p-4 text-white"
|
|
36
|
+
data-testid="remote-desktop-readiness-gate"
|
|
37
|
+
>
|
|
38
|
+
<div className="flex w-full max-w-sm flex-col gap-4 rounded-xl border border-white/10 bg-white/5 p-5">
|
|
39
|
+
<div className="flex items-center gap-2">
|
|
40
|
+
<MonitorSmartphone className="size-5 shrink-0 text-primary" />
|
|
41
|
+
<h2 className="text-base font-semibold">This host needs a few things</h2>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
{!caps && !failed && (
|
|
45
|
+
<div className="flex items-center gap-2 text-sm text-white/70"><Loader2 className="size-4 animate-spin" /> Checking the host…</div>
|
|
46
|
+
)}
|
|
47
|
+
{failed && <p className="text-sm text-red-300">Could not reach the host's remote-desktop service.</p>}
|
|
48
|
+
|
|
49
|
+
<ul className="flex flex-col gap-3">
|
|
50
|
+
{caps?.requirements.map((r) => (
|
|
51
|
+
<RequirementRow key={r.id} req={r} onHostAction={runHostAction} onTerminal={onTerminal} />
|
|
52
|
+
))}
|
|
53
|
+
</ul>
|
|
54
|
+
|
|
55
|
+
{pending.length > 0 && (
|
|
56
|
+
<p className="text-xs text-white/50">Re-checking every few seconds — this updates by itself once fixed.</p>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
<div className="flex gap-2">
|
|
60
|
+
<button type="button" onClick={onCancel} className="min-h-11 flex-1 rounded-md bg-white/10 px-3 text-sm hover:bg-white/20">
|
|
61
|
+
Cancel
|
|
62
|
+
</button>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
disabled={!caps?.videoReady}
|
|
66
|
+
onClick={() => setAccepted(true)}
|
|
67
|
+
className="min-h-11 flex-1 rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-40"
|
|
68
|
+
data-testid="remote-desktop-readiness-continue"
|
|
69
|
+
>
|
|
70
|
+
{viewOnly ? "Continue (view only)" : "Continue"}
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ActionHandlers {
|
|
79
|
+
onHostAction: (id: string, a: "request" | "open-settings") => Promise<void>;
|
|
80
|
+
onTerminal: (command: string) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function RequirementRow({ req, ...handlers }: { req: RemoteDesktopRequirement } & ActionHandlers) {
|
|
84
|
+
return (
|
|
85
|
+
<li className="flex gap-2" data-testid={`remote-desktop-requirement-${req.id}`} data-ok={req.ok}>
|
|
86
|
+
{req.ok
|
|
87
|
+
? <CheckCircle2 className="mt-0.5 size-4 shrink-0 text-emerald-400" />
|
|
88
|
+
: <Circle className="mt-0.5 size-4 shrink-0 text-amber-300" />}
|
|
89
|
+
<div className="flex min-w-0 flex-1 flex-col gap-2">
|
|
90
|
+
<div className="text-sm font-medium">
|
|
91
|
+
{req.title}
|
|
92
|
+
{!req.ok && <span className="ml-2 text-xs font-normal text-white/50">{req.gates === "video" ? "required" : "for mouse & keyboard"}</span>}
|
|
93
|
+
</div>
|
|
94
|
+
{!req.ok && (
|
|
95
|
+
<>
|
|
96
|
+
<p className="text-sm text-white/70">{req.detail}</p>
|
|
97
|
+
<div className="flex flex-wrap gap-2">
|
|
98
|
+
{req.actions.map((a) => <ActionButton key={a.label} action={a} reqId={req.id} {...handlers} />)}
|
|
99
|
+
</div>
|
|
100
|
+
</>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
</li>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function ActionButton({ action, reqId, onHostAction, onTerminal }: { action: RequirementAction; reqId: string } & ActionHandlers) {
|
|
108
|
+
const cls = "inline-flex min-h-11 items-center gap-1.5 rounded-md bg-white/10 px-3 text-sm hover:bg-white/20";
|
|
109
|
+
if (action.kind === "terminal") {
|
|
110
|
+
return (
|
|
111
|
+
<button type="button" className={cls} onClick={() => onTerminal(action.command)} title={action.command}>
|
|
112
|
+
<TerminalSquare className="size-4" /> {action.label}
|
|
113
|
+
</button>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (action.kind === "link") {
|
|
117
|
+
return (
|
|
118
|
+
<a className={cls} href={action.url} target="_blank" rel="noreferrer">
|
|
119
|
+
<ExternalLink className="size-4" /> {action.label}
|
|
120
|
+
</a>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return (
|
|
124
|
+
<button type="button" className={cls} onClick={() => void onHostAction(reqId, action.action)}>
|
|
125
|
+
{action.label}
|
|
126
|
+
</button>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Warning shown before the Remote Desktop viewer connects — wraps both the desktop window body
|
|
3
|
+
* (`remote-desktop-window-content.tsx`) and the mobile full-screen view
|
|
4
|
+
* (`remote-desktop-mobile-sheet.tsx`). The viewer is only mounted after the user continues, so
|
|
5
|
+
* no session nonce is minted and no frame is captured while the warning is up.
|
|
6
|
+
*
|
|
7
|
+
* Shown on every open until the user ticks "don't show again", which persists via the shared
|
|
8
|
+
* `remoteDesktopWarningDismissed` UI pref (server-side, like the stats-overlay toggle) so the
|
|
9
|
+
* choice follows them across devices. Dark chrome on purpose: it sits where the black canvas
|
|
10
|
+
* will be, on both surfaces.
|
|
11
|
+
*/
|
|
12
|
+
import { useState, type ReactNode } from "react";
|
|
13
|
+
import { MonitorSmartphone, ShieldAlert } from "lucide-react";
|
|
14
|
+
import { useSettingsStore } from "@/stores/settings-store";
|
|
15
|
+
|
|
16
|
+
export interface RemoteDesktopWarningGateProps {
|
|
17
|
+
/** Called when the user backs out instead of continuing — close the window/sheet. */
|
|
18
|
+
onCancel: () => void;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function RemoteDesktopWarningGate({ onCancel, children }: RemoteDesktopWarningGateProps) {
|
|
23
|
+
const dismissed = useSettingsStore((s) => s.remoteDesktopWarningDismissed);
|
|
24
|
+
const setDismissed = useSettingsStore((s) => s.setRemoteDesktopWarningDismissed);
|
|
25
|
+
const [accepted, setAccepted] = useState(false);
|
|
26
|
+
const [dontShowAgain, setDontShowAgain] = useState(false);
|
|
27
|
+
|
|
28
|
+
if (dismissed || accepted) return <>{children}</>;
|
|
29
|
+
|
|
30
|
+
const onContinue = () => {
|
|
31
|
+
if (dontShowAgain) setDismissed(true);
|
|
32
|
+
setAccepted(true);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className="flex h-full w-full items-center justify-center overflow-y-auto bg-black p-4 text-white"
|
|
38
|
+
data-testid="remote-desktop-warning-gate"
|
|
39
|
+
>
|
|
40
|
+
<div className="flex w-full max-w-sm flex-col gap-4 rounded-xl border border-white/10 bg-white/5 p-5">
|
|
41
|
+
<div className="flex items-center gap-2">
|
|
42
|
+
<MonitorSmartphone className="size-5 shrink-0 text-primary" />
|
|
43
|
+
<h2 className="text-base font-semibold">Remote Desktop</h2>
|
|
44
|
+
<span className="ml-auto rounded-full bg-amber-500/20 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-300">
|
|
45
|
+
Beta
|
|
46
|
+
</span>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<ul className="flex flex-col gap-2 text-sm text-white/80">
|
|
50
|
+
<li className="flex gap-2">
|
|
51
|
+
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-300" />
|
|
52
|
+
<span>
|
|
53
|
+
This streams the host's <strong>whole screen</strong> and lets you <strong>move its mouse and type</strong> as if
|
|
54
|
+
you were sitting at it. Anything you do here happens on the real desktop.
|
|
55
|
+
</span>
|
|
56
|
+
</li>
|
|
57
|
+
<li className="flex gap-2">
|
|
58
|
+
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-300" />
|
|
59
|
+
<span>
|
|
60
|
+
Anyone signed in to this PPM can do the same. Avoid opening it over a public share link you don't fully
|
|
61
|
+
trust; a private network (Tailscale, LAN) is safer.
|
|
62
|
+
</span>
|
|
63
|
+
</li>
|
|
64
|
+
<li className="flex gap-2">
|
|
65
|
+
<ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-300" />
|
|
66
|
+
<span>
|
|
67
|
+
Still beta: the host must be unlocked with an active session, UAC prompts stay hidden, and a person at the
|
|
68
|
+
host will fight you for the cursor.
|
|
69
|
+
</span>
|
|
70
|
+
</li>
|
|
71
|
+
</ul>
|
|
72
|
+
|
|
73
|
+
<label className="flex min-h-11 cursor-pointer items-center gap-2 text-sm text-white/70">
|
|
74
|
+
<input
|
|
75
|
+
type="checkbox"
|
|
76
|
+
checked={dontShowAgain}
|
|
77
|
+
onChange={(e) => setDontShowAgain(e.target.checked)}
|
|
78
|
+
className="size-4 accent-primary"
|
|
79
|
+
data-testid="remote-desktop-warning-dont-show"
|
|
80
|
+
/>
|
|
81
|
+
Don't show this again
|
|
82
|
+
</label>
|
|
83
|
+
|
|
84
|
+
{/* Primary action last (thumb zone on mobile); both ≥44px tall. */}
|
|
85
|
+
<div className="flex gap-2">
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onClick={onCancel}
|
|
89
|
+
className="min-h-11 flex-1 rounded-md bg-white/10 px-3 text-sm hover:bg-white/20"
|
|
90
|
+
data-testid="remote-desktop-warning-cancel"
|
|
91
|
+
>
|
|
92
|
+
Cancel
|
|
93
|
+
</button>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={onContinue}
|
|
97
|
+
className="min-h-11 flex-1 rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground hover:bg-primary/90"
|
|
98
|
+
data-testid="remote-desktop-warning-continue"
|
|
99
|
+
>
|
|
100
|
+
Continue
|
|
101
|
+
</button>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Remote Desktop window body: thin wrapper around `useRemoteDesktopConnection` (WS/nonce/ping/
|
|
3
3
|
* decode, shared with the mobile viewer) that renders the canvas, overlay states, and wires
|
|
4
|
-
* pointer/keyboard capture over the connection's `sendMessage`.
|
|
4
|
+
* pointer/keyboard capture over the connection's `sendMessage`. Sits behind
|
|
5
|
+
* `RemoteDesktopWarningGate`, so the connection hook (and its session nonce) only runs once the
|
|
6
|
+
* user has read the warning.
|
|
5
7
|
*/
|
|
6
|
-
import { useRef } from "react";
|
|
8
|
+
import { useCallback, useRef } from "react";
|
|
7
9
|
import { RotateCw, MonitorX, Gauge } from "lucide-react";
|
|
8
10
|
import type { WindowContentProps } from "@/components/floating-window/window-content-registry";
|
|
11
|
+
import { useWindowStore } from "@/components/floating-window/window-store";
|
|
9
12
|
import { useSettingsStore } from "@/stores/settings-store";
|
|
10
13
|
import { cn } from "@/lib/utils";
|
|
11
14
|
import { useRemoteDesktopConnection } from "./use-remote-desktop-connection";
|
|
12
15
|
import { useRemoteInputCapture } from "./use-remote-input-capture";
|
|
13
16
|
import { RemoteDesktopStatsOverlay } from "./remote-desktop-stats-overlay";
|
|
17
|
+
import { RemoteDesktopWarningGate } from "./remote-desktop-warning-gate";
|
|
18
|
+
import { RemoteDesktopReadinessGate } from "./remote-desktop-readiness-gate";
|
|
19
|
+
import { useRemoteDesktopDisplayChoice } from "./use-remote-desktop-display-choice";
|
|
14
20
|
|
|
15
|
-
export default function RemoteDesktopWindowContent(
|
|
21
|
+
export default function RemoteDesktopWindowContent({ id }: WindowContentProps) {
|
|
22
|
+
const closeWindow = useWindowStore((s) => s.close);
|
|
23
|
+
const onCancel = useCallback(() => closeWindow(id), [closeWindow, id]);
|
|
24
|
+
return (
|
|
25
|
+
<RemoteDesktopWarningGate onCancel={onCancel}>
|
|
26
|
+
<RemoteDesktopReadinessGate onCancel={onCancel}>
|
|
27
|
+
<RemoteDesktopViewer />
|
|
28
|
+
</RemoteDesktopReadinessGate>
|
|
29
|
+
</RemoteDesktopWarningGate>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function RemoteDesktopViewer() {
|
|
16
34
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
35
|
+
const display = useRemoteDesktopDisplayChoice();
|
|
17
36
|
const { connState, errorMessage, decoderStatus, decoderErrorMessage, sendMessage, reconnect, getFrameCount, getTotalBytes } =
|
|
18
|
-
useRemoteDesktopConnection(canvasRef);
|
|
37
|
+
useRemoteDesktopConnection(canvasRef, { displayId: display.displayId });
|
|
19
38
|
const statsVisible = useSettingsStore((s) => s.remoteDesktopStatsVisible);
|
|
20
39
|
const toggleStats = useSettingsStore((s) => s.toggleRemoteDesktopStatsVisible);
|
|
21
40
|
|
|
@@ -39,6 +58,21 @@ export default function RemoteDesktopWindowContent(_props: WindowContentProps) {
|
|
|
39
58
|
>
|
|
40
59
|
<canvas ref={canvasRef} data-testid="remote-desktop-canvas" className="max-h-full max-w-full outline-none" />
|
|
41
60
|
<RemoteDesktopStatsOverlay canvasRef={canvasRef} getFrameCount={getFrameCount} getTotalBytes={getTotalBytes} />
|
|
61
|
+
{/* Multi-monitor hosts: pick which display to stream. Left of the stats toggle, same
|
|
62
|
+
corner cluster; a single-display host renders nothing here. */}
|
|
63
|
+
{display.displays.length > 1 && (
|
|
64
|
+
<select
|
|
65
|
+
value={display.current?.id ?? ""}
|
|
66
|
+
onChange={(e) => display.select(e.target.value)}
|
|
67
|
+
aria-label="Display"
|
|
68
|
+
className="absolute right-8 top-1 z-40 h-6 max-w-[45%] rounded bg-black/40 px-1.5 text-xs text-white/80 hover:bg-black/60"
|
|
69
|
+
data-testid="remote-desktop-display-select"
|
|
70
|
+
>
|
|
71
|
+
{display.displays.map((d) => (
|
|
72
|
+
<option key={d.id} value={d.id}>{d.label}{d.primary ? " (main)" : ""} · {d.width}×{d.height}</option>
|
|
73
|
+
))}
|
|
74
|
+
</select>
|
|
75
|
+
)}
|
|
42
76
|
{/* Small corner toggle for the stats overlay above — top-right so it never collides with
|
|
43
77
|
the overlay itself (top-left) or the window's own title bar/controls above this body. */}
|
|
44
78
|
<button
|
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Whether the Remote Desktop entry should surface in the UI. Hidden only when the host opted
|
|
3
|
+
* out (`REMOTE_DESKTOP_ENABLED=0` — the endpoint 404s) or the OS has no capture path at all.
|
|
4
|
+
* Everything the user can fix in place — ffmpeg missing, a macOS permission not granted — keeps
|
|
5
|
+
* the entry visible; `RemoteDesktopReadinessGate` walks them through it after the warning.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
8
|
-
import { api } from "@/lib/api-client";
|
|
9
|
-
|
|
10
|
-
interface Capabilities {
|
|
11
|
-
ffmpegAvailable: boolean;
|
|
12
|
-
videoAvailable: boolean;
|
|
13
|
-
inputAvailable: boolean;
|
|
14
|
-
authRequired: boolean;
|
|
15
|
-
}
|
|
7
|
+
import { useRemoteDesktopReadiness } from "./use-remote-desktop-readiness";
|
|
16
8
|
|
|
17
9
|
export function useRemoteDesktopAvailable(): { available: boolean; authRequired: boolean } {
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
let cancelled = false;
|
|
22
|
-
api.get<Capabilities>("/api/remote-desktop/capabilities")
|
|
23
|
-
.then((c) => { if (!cancelled) setCaps(c); })
|
|
24
|
-
.catch(() => { if (!cancelled) setCaps(null); }); // 404 (flag off) or network error — stay hidden
|
|
25
|
-
return () => { cancelled = true; };
|
|
26
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
return { available: !!caps?.videoAvailable, authRequired: !!caps?.authRequired };
|
|
10
|
+
const { caps } = useRemoteDesktopReadiness(false);
|
|
11
|
+
return { available: !!caps?.platformSupported, authRequired: !!caps?.authRequired };
|
|
29
12
|
}
|
|
@@ -39,8 +39,15 @@ export interface UseRemoteDesktopConnectionResult {
|
|
|
39
39
|
getFrameCount: () => number;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export interface UseRemoteDesktopConnectionOptions {
|
|
43
|
+
/** One of `/capabilities`' `displays[].id`; undefined = the host's primary display. Changing it
|
|
44
|
+
* tears the session down and reconnects on the new display — one ffmpeg per display. */
|
|
45
|
+
displayId?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
export function useRemoteDesktopConnection(
|
|
43
49
|
canvasRef: React.RefObject<HTMLCanvasElement | null>,
|
|
50
|
+
{ displayId }: UseRemoteDesktopConnectionOptions = {},
|
|
44
51
|
): UseRemoteDesktopConnectionResult {
|
|
45
52
|
const wsRef = useRef<WebSocket | null>(null);
|
|
46
53
|
const totalBytesRef = useRef(0);
|
|
@@ -82,7 +89,7 @@ export function useRemoteDesktopConnection(
|
|
|
82
89
|
wsRef.current = ws;
|
|
83
90
|
|
|
84
91
|
ws.onopen = () => {
|
|
85
|
-
ws.send(JSON.stringify({ type: "auth", nonce }));
|
|
92
|
+
ws.send(JSON.stringify({ type: "auth", nonce, ...(displayId ? { displayId } : {}) }));
|
|
86
93
|
pingTimer = setInterval(() => sendMessage({ type: "ping" }), PING_INTERVAL_MS);
|
|
87
94
|
};
|
|
88
95
|
ws.onmessage = (event) => {
|
|
@@ -128,8 +135,8 @@ export function useRemoteDesktopConnection(
|
|
|
128
135
|
wsRef.current = null;
|
|
129
136
|
try { decoder.reset(); } catch { /* tearing down regardless */ }
|
|
130
137
|
};
|
|
131
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- reconnect is driven by `generation`
|
|
132
|
-
}, [generation]);
|
|
138
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- reconnect is driven by `generation` + the display pick
|
|
139
|
+
}, [generation, displayId]);
|
|
133
140
|
|
|
134
141
|
const reconnect = useCallback(() => setGeneration((g) => g + 1), []);
|
|
135
142
|
const getTotalBytes = useCallback(() => totalBytesRef.current, []);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which host display the viewer is on. Reads the list from `/capabilities`, defaults to the
|
|
3
|
+
* primary, and exposes `next()` for a one-button cycle (mobile toolbar) plus `select()` for a
|
|
4
|
+
* dropdown (desktop). Hosts with a single display never surface a control — `displays.length`
|
|
5
|
+
* tells the caller whether to render one.
|
|
6
|
+
*/
|
|
7
|
+
import { useCallback, useState } from "react";
|
|
8
|
+
import { useRemoteDesktopReadiness, type RemoteDisplay } from "./use-remote-desktop-readiness";
|
|
9
|
+
|
|
10
|
+
export interface RemoteDesktopDisplayChoice {
|
|
11
|
+
displays: RemoteDisplay[];
|
|
12
|
+
/** undefined until the list arrives or when the pick is the primary — the server treats both as "primary". */
|
|
13
|
+
displayId: string | undefined;
|
|
14
|
+
current: RemoteDisplay | undefined;
|
|
15
|
+
select: (id: string) => void;
|
|
16
|
+
next: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useRemoteDesktopDisplayChoice(): RemoteDesktopDisplayChoice {
|
|
20
|
+
const { caps } = useRemoteDesktopReadiness(false);
|
|
21
|
+
const displays = caps?.displays ?? [];
|
|
22
|
+
const [picked, setPicked] = useState<string | undefined>(undefined);
|
|
23
|
+
const current = displays.find((d) => d.id === picked) ?? displays.find((d) => d.primary) ?? displays[0];
|
|
24
|
+
const next = useCallback(() => {
|
|
25
|
+
if (displays.length < 2) return;
|
|
26
|
+
const i = displays.findIndex((d) => d.id === (current?.id));
|
|
27
|
+
setPicked(displays[(i + 1) % displays.length]?.id);
|
|
28
|
+
}, [displays, current]);
|
|
29
|
+
return { displays, displayId: picked, current, select: setPicked, next };
|
|
30
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client view of `GET /api/remote-desktop/capabilities`: the host's requirements checklist
|
|
3
|
+
* (ffmpeg, macOS permissions, …) plus the derived `videoReady` / `inputReady` flags.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the server types in `src/services/remote-desktop/remote-desktop-requirements.ts` —
|
|
6
|
+
* the UI renders whatever items arrive and never branches on the host OS. Polls while `poll`
|
|
7
|
+
* is on so a permission granted in System Settings (or `brew install ffmpeg` finishing in the
|
|
8
|
+
* dock terminal) flips the panel without a reload.
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useEffect, useState } from "react";
|
|
11
|
+
import { api } from "@/lib/api-client";
|
|
12
|
+
|
|
13
|
+
export type RequirementAction =
|
|
14
|
+
| { kind: "terminal"; label: string; command: string }
|
|
15
|
+
| { kind: "link"; label: string; url: string }
|
|
16
|
+
| { kind: "host"; label: string; action: "request" | "open-settings" };
|
|
17
|
+
|
|
18
|
+
export interface RemoteDesktopRequirement {
|
|
19
|
+
id: string;
|
|
20
|
+
ok: boolean;
|
|
21
|
+
gates: "video" | "input";
|
|
22
|
+
title: string;
|
|
23
|
+
detail: string;
|
|
24
|
+
actions: RequirementAction[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RemoteDisplay {
|
|
28
|
+
id: string;
|
|
29
|
+
label: string;
|
|
30
|
+
primary: boolean;
|
|
31
|
+
width: number;
|
|
32
|
+
height: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RemoteDesktopCapabilities {
|
|
36
|
+
displays: RemoteDisplay[];
|
|
37
|
+
ffmpegAvailable: boolean;
|
|
38
|
+
videoAvailable: boolean;
|
|
39
|
+
inputAvailable: boolean;
|
|
40
|
+
authRequired: boolean;
|
|
41
|
+
platform: string;
|
|
42
|
+
platformSupported: boolean;
|
|
43
|
+
requirements: RemoteDesktopRequirement[];
|
|
44
|
+
videoReady: boolean;
|
|
45
|
+
inputReady: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const READINESS_POLL_MS = 2000;
|
|
49
|
+
|
|
50
|
+
export function useRemoteDesktopReadiness(poll: boolean): {
|
|
51
|
+
caps: RemoteDesktopCapabilities | null;
|
|
52
|
+
/** 404 (feature flag off) or network failure — treat as "not available". */
|
|
53
|
+
failed: boolean;
|
|
54
|
+
refresh: () => void;
|
|
55
|
+
runHostAction: (id: string, action: "request" | "open-settings") => Promise<void>;
|
|
56
|
+
} {
|
|
57
|
+
const [caps, setCaps] = useState<RemoteDesktopCapabilities | null>(null);
|
|
58
|
+
const [failed, setFailed] = useState(false);
|
|
59
|
+
const [tick, setTick] = useState(0);
|
|
60
|
+
const refresh = useCallback(() => setTick((t) => t + 1), []);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
let cancelled = false;
|
|
64
|
+
api.get<RemoteDesktopCapabilities>("/api/remote-desktop/capabilities")
|
|
65
|
+
.then((c) => { if (!cancelled) { setCaps(c); setFailed(false); } })
|
|
66
|
+
.catch(() => { if (!cancelled) setFailed(true); });
|
|
67
|
+
return () => { cancelled = true; };
|
|
68
|
+
}, [tick]);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!poll) return;
|
|
72
|
+
const timer = setInterval(refresh, READINESS_POLL_MS);
|
|
73
|
+
return () => clearInterval(timer);
|
|
74
|
+
}, [poll, refresh]);
|
|
75
|
+
|
|
76
|
+
const runHostAction = useCallback(async (id: string, action: "request" | "open-settings") => {
|
|
77
|
+
try { await api.post(`/api/remote-desktop/requirements/${encodeURIComponent(id)}/${action}`); } catch { /* panel shows state on next poll */ }
|
|
78
|
+
refresh();
|
|
79
|
+
}, [refresh]);
|
|
80
|
+
|
|
81
|
+
return { caps, failed, refresh, runHostAction };
|
|
82
|
+
}
|