@hienlh/ppm 0.21.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/CLAUDE.md +29 -4
- package/THIRD-PARTY-NOTICES.md +1 -1
- 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 +1 -1
- package/bun.lock +16 -1
- package/dist/web/assets/accounts-settings-section-D3BylchP.js +1 -0
- package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-BLPcnhTM.js} +1 -1
- package/dist/web/assets/ai-settings-section-BtR7rF-T.js +1 -0
- package/dist/web/assets/ai-settings-section-r9JctHNM.js +1 -0
- package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
- package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
- package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
- package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
- package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
- package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
- package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-CiMdk3sb.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
- package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
- package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
- package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
- package/dist/web/assets/branch-review-tab-B1pFjlKM.js +1 -0
- package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
- package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
- package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
- package/dist/web/assets/chat-tab-DZagX06m.js +15 -0
- package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
- package/dist/web/assets/code-editor-BJhOEsnZ.js +7 -0
- package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-CaCQJ4r_.js} +1 -1
- package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
- package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-B7u_p49h.js} +3 -3
- package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
- package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
- package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
- package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-CttjJU1e.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
- package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-Ca4HxIfx.js} +1 -1
- package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
- package/dist/web/assets/diff-viewer-9-XjLIwa.js +4 -0
- package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
- package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
- package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-D0DuEOs_.js} +1 -1
- package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
- package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
- package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
- package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
- package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
- package/dist/web/assets/explorer-body-VdCBqcZu.js +2 -0
- package/dist/web/assets/explorer-window-content-BIsbl7Th.js +1 -0
- package/dist/web/assets/extension-manager-section-HkY7geS5.js +1 -0
- package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-DZG8YHX9.js} +1 -1
- package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
- package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
- package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
- package/dist/web/assets/files-settings-section-qnRtkX05.js +1 -0
- package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
- package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
- package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
- package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-BAFSZmCE.js} +1 -1
- package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
- package/dist/web/assets/git-log-panel-2sGpfEla.js +1 -0
- package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
- package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
- package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-f0hyITiF.js} +7 -7
- package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
- package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
- package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
- package/dist/web/assets/group-chat-tab-Ck6sk7yY.js +1 -0
- package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
- package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
- package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
- package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
- package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
- package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
- package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
- package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
- package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
- package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
- package/dist/web/assets/index-BH99TIE0.css +1 -0
- package/dist/web/assets/index-CEZVxM4G.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
- package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
- package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-CeLmhvu1.js} +1 -1
- package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
- package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
- package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
- package/dist/web/assets/keybindings-store-C_3ChOEf.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-PVz6kuh2.js} +1 -1
- package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
- package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
- package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CvL1pWeK.js → markdown-renderer-C140Ijk0.js} +3 -3
- package/dist/web/assets/markdown-renderer-Dz7-DYUg.js +1 -0
- package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
- package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
- package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CUG_owdf.js +1 -0
- package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
- package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
- package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
- package/dist/web/assets/notification-store-6vib1ISy.js +1 -0
- package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
- package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
- package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
- package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
- package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
- package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
- package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
- package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-Cji-Mil-.js} +1 -1
- package/dist/web/assets/problems-panel-a6_hrWvl.js +1 -0
- package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-CiIqlOIK.js} +1 -1
- package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
- package/dist/web/assets/project-store-B87hLRSk.js +1 -0
- package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-BjI4IFgp.js} +1 -1
- package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
- package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
- package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
- package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-eXYvwgx7.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-Doh-fci-.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-Dt8Akc3p.js +1 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-t83fqbID.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-CguhZMmx.js +1 -0
- package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
- package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
- package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-BVkpW5ms.js} +1 -1
- package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
- package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
- package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
- package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
- package/dist/web/assets/settings-body-CoIxYAh6.js +2 -0
- package/dist/web/assets/settings-store-krC6bc79.js +2 -0
- package/dist/web/assets/settings-tab-7DKVUTbM.js +1 -0
- package/dist/web/assets/settings-window-content-3_39TeX-.js +1 -0
- package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
- package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
- package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
- package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
- package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-CQB7Yp7U.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-Bd7rjr1D.js} +1 -1
- package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
- package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
- package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
- package/dist/web/assets/system-monitor-body-DsRXeMyU.js +2 -0
- package/dist/web/assets/system-monitor-tab-DdSZgux3.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-ClPo4-El.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-d2TAD4PU.js} +1 -1
- package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
- package/dist/web/assets/team-member-sheet-7d2Qp-ie.js +2 -0
- package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-Bctg2gmg.js} +1 -1
- package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
- package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-0HQic25V.js} +3 -3
- package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
- package/dist/web/assets/tool-cards-CmLLRACd.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
- package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
- package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
- package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
- package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-sDMjcz1-.js} +3 -3
- package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CBgID-EU.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-gvhJ_jZh.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
- package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-C9wlRliB.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
- package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
- package/dist/web/assets/video-preview-CSDK5j6H.js +1 -0
- package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
- package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
- package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
- package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
- package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
- package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
- package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
- package/dist/web/index.html +27 -26
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +9 -0
- package/docs/architecture/plugins-and-tracing.md +285 -0
- package/docs/lessons-learned.md +31 -0
- package/docs/system-architecture.md +1 -0
- package/package.json +2 -1
- package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
- package/packages/ext-git-graph/src/compare-view.ts +22 -3
- package/packages/ext-git-graph/src/extension.ts +6 -0
- package/packages/ext-git-graph/src/types.ts +19 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +159 -1
- package/packages/ext-git-graph/src/webview-html.ts +344 -26
- package/scripts/gen-file-icons.ts +493 -242
- package/src/providers/claude-agent-sdk.ts +42 -1
- package/src/providers/codex-app-server/codex-history.ts +77 -23
- package/src/server/index.ts +42 -7
- package/src/server/routes/chat.ts +15 -11
- package/src/server/routes/git.ts +83 -3
- package/src/server/routes/remote-desktop.ts +7 -2
- package/src/server/ws/global.ts +1 -1
- package/src/server/ws/remote-desktop.ts +12 -5
- package/src/services/binary-content.ts +25 -0
- package/src/services/chat-search.service.ts +1 -1
- package/src/services/chat.service.ts +9 -0
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/event-loop-lag.ts +208 -0
- package/src/services/file-lines.ts +114 -0
- package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
- package/src/services/file-watcher/watch-tree.ts +131 -10
- package/src/services/file-watcher.service.ts +73 -23
- package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
- package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
- package/src/services/git-hunks/git-hunks.service.ts +26 -11
- package/src/services/git-hunks/unified-diff.ts +15 -1
- package/src/services/git.service.ts +74 -24
- package/src/services/jsonl-transcript-parser.ts +138 -6
- package/src/services/log-rotate.ts +142 -0
- package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
- package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
- package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
- package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
- package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
- package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
- package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
- package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
- package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
- package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
- package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
- package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
- package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
- package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
- package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
- package/src/services/supervisor.ts +47 -4
- package/src/services/windows-process-tree.ts +351 -351
- package/src/shared/codex-daily-guard.ts +12 -5
- package/src/shared/remote-desktop-custom-quality.ts +117 -0
- package/src/shared/remote-desktop-quality.ts +127 -0
- package/src/types/chat.ts +4 -0
- package/src/types/git.ts +52 -0
- package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
- package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
- package/src/web/components/chat/attachment-chips.tsx +30 -6
- package/src/web/components/chat/chat-tab.tsx +12 -1
- package/src/web/components/chat/message-events.tsx +273 -0
- package/src/web/components/chat/message-list.tsx +107 -861
- package/src/web/components/chat/message-markdown.tsx +34 -0
- package/src/web/components/chat/message-media.tsx +174 -0
- package/src/web/components/chat/message-user-bubble.tsx +399 -0
- package/src/web/components/chat/provider-selector.tsx +30 -22
- package/src/web/components/chat/session-history-list.tsx +2 -2
- package/src/web/components/chat/session-list-panel.tsx +3 -2
- package/src/web/components/editor/binary-diff-view.tsx +212 -0
- package/src/web/components/editor/diff-viewer.tsx +76 -3
- package/src/web/components/explorer/file-tree.tsx +235 -134
- package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
- package/src/web/components/explorer/tree-node.tsx +69 -69
- package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
- package/src/web/components/extensions/extension-tree-view.tsx +4 -11
- package/src/web/components/git/git-status-panel.tsx +25 -0
- package/src/web/components/layout/command-palette.tsx +2 -18
- package/src/web/components/layout/draggable-tab.tsx +4 -2
- package/src/web/components/layout/nav-section-rail.tsx +12 -3
- package/src/web/components/layout/status-bar.tsx +10 -6
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/tab-pool.tsx +1 -0
- package/src/web/components/layout/upgrade-button.tsx +5 -1
- package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
- package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
- package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
- package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
- package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
- package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
- package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
- package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
- package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
- package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
- package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
- package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
- package/src/web/components/settings/accounts/account-card.tsx +2 -2
- package/src/web/components/settings/accounts/codex-accounts-section.tsx +4 -4
- package/src/web/components/settings/extension-manager-section.tsx +8 -1
- package/src/web/components/settings/theme-picker.tsx +2 -1
- package/src/web/components/ui/tooltip.tsx +15 -2
- package/src/web/lib/branch-review-state.ts +175 -0
- package/src/web/lib/extension-icons.ts +70 -0
- package/src/web/lib/file-icon-name.ts +62 -0
- package/src/web/lib/file-icons.generated.ts +3287 -25
- package/src/web/lib/file-icons.tsx +71 -73
- package/src/web/lib/flatten-expansions.ts +18 -5
- package/src/web/lib/message-ordinals.ts +23 -0
- package/src/web/lib/provider-logos.tsx +143 -0
- package/src/web/lib/tab-type-icons.ts +25 -0
- package/src/web/stores/file-store.ts +73 -35
- package/src/web/stores/panel-utils.ts +5 -0
- package/src/web/stores/project-framework-store.ts +82 -0
- package/src/web/stores/project-store.ts +7 -1
- package/src/web/stores/settings-store.ts +128 -0
- package/src/web/stores/tab-store.ts +1 -0
- package/src/web/stores/window-panel-persistence.ts +1 -0
- package/src/web/styles/file-icons.generated.css +3282 -234
- package/src/web/styles/globals.css +26 -0
- package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
- package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
- package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
- package/dist/web/assets/api-client-BUCo1lms.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
- package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
- package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
- package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
- package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
- package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
- package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
- package/dist/web/assets/extension-manager-section-BhwB6nqr.js +0 -1
- package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
- package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
- package/dist/web/assets/file-store-DMABxOkV.js +0 -1
- package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
- package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
- package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
- package/dist/web/assets/index-D7DbXD3G.css +0 -1
- package/dist/web/assets/index-ZtfxZOyb.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
- package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
- package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
- package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
- package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
- package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
- package/dist/web/assets/project-store-BhMbVln3.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
- package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
- package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
- package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
- package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
- package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
- package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
- package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
- package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
- package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
- package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
- package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
- package/dist/web/assets/ws-auth-DEAJiGCR.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-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
- /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
- /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
- /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
- /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
- /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
- /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
- /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
- /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
- /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
- /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
- /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
- /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
- /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
- /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
- /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
- /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
- /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
- /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
- /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
- /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
- /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
- /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
- /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
- /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
- /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
- /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
- /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
- /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
- /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
- /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
- /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
- /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
- /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
- /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
- /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
- /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
- /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
- /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
- /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
- /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
- /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
- /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
- /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
- /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
- /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
- /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
- /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
- /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
- /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
- /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
- /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
- /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
- /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
- /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
- /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
- /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
- /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
- /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
- /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
- /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
- /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
- /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
- /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
- /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
- /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
- /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
- /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
- /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
- /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
- /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
- /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
- /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
- /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
- /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
- /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
- /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
- /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
- /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
- /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
- /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
- /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
- /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
- /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
- /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
- /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
- /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
- /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
- /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
- /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
- /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
- /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
- /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
- /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
- /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
- /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
- /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
- /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
- /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
- /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
- /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
- /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
- /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
- /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
- /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
- /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
- /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
- /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
- /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
- /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
- /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
- /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
- /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
- /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
- /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
- /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
- /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
- /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
- /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
- /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
- /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
- /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
- /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
- /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
- /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
- /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
- /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
- /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
- /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
- /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
- /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
- /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
- /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
- /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
- /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
- /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
- /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
- /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
- /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
- /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
- /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
- /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
- /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
- /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
- /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
- /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
- /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
- /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
- /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
- /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
- /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
- /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
- /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
- /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
- /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
- /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
- /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
- /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
- /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
- /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
- /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
- /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
- /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
- /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
- /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
- /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
- /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
- /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
- /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
- /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
- /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
- /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
- /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
- /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
- /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
- /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
- /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
- /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
- /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
- /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
- /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
- /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
- /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
- /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
- /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
- /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
- /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
- /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
- /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
- /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
- /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
- /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
- /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
- /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
- /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
- /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
- /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
- /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
- /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
- /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
- /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
- /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
- /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
- /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
- /package/dist/web/assets/{zig-B2htezJ_.js → zig-CbaUfBQe.js} +0 -0
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
* Adding a requirement = push one more item here (and, for `host` actions, handle its id in
|
|
7
7
|
* `runHostAction`). The UI needs no change.
|
|
8
8
|
*/
|
|
9
|
+
import { accessSync, constants, existsSync } from "node:fs";
|
|
9
10
|
import { getFfmpegCapabilities } from "../media-transcode/ffmpeg-capabilities.ts";
|
|
10
11
|
import { captureInputForPlatform } from "./remote-desktop-capture-input.ts";
|
|
11
12
|
import { getInputBackend } from "./remote-desktop-input.ts";
|
|
13
|
+
import { clipboardAvailable, clipboardTool } from "./remote-desktop-clipboard.ts";
|
|
14
|
+
import { audioSupport, type AudioSupport } from "./remote-desktop-audio.ts";
|
|
15
|
+
import { privacySupport, type PrivacySupport } from "./remote-desktop-privacy.ts";
|
|
16
|
+
import { listHostResolutions, type HostResolutions } from "./remote-desktop-resolution.ts";
|
|
17
|
+
import { detectLinuxSession, type LinuxSession } from "./remote-desktop-linux-session.ts";
|
|
18
|
+
import { getX11 } from "./remote-desktop-x11.ts";
|
|
12
19
|
import {
|
|
13
20
|
MAC_PERMISSION_SETTINGS_URL,
|
|
14
21
|
macPermissionStatus,
|
|
@@ -39,6 +46,15 @@ export interface RemoteDesktopRequirement {
|
|
|
39
46
|
actions: RequirementAction[];
|
|
40
47
|
}
|
|
41
48
|
|
|
49
|
+
/** Clipboard sync is an *extra*, not a gate: it blocks neither video nor input, so it cannot
|
|
50
|
+
* be a `requirements` row — that checklist is only rendered while video or input is unmet, so a
|
|
51
|
+
* row there would be invisible on exactly the working hosts that are missing the tool. */
|
|
52
|
+
export interface ClipboardSupport {
|
|
53
|
+
available: boolean;
|
|
54
|
+
/** How to get the tool, when one is missing and this OS needs a package for it. */
|
|
55
|
+
action: RequirementAction | null;
|
|
56
|
+
}
|
|
57
|
+
|
|
42
58
|
export interface RemoteDesktopReadiness {
|
|
43
59
|
platform: NodeJS.Platform;
|
|
44
60
|
/** There is a capture path for this OS at all. Gates the UI entry; everything else is a
|
|
@@ -49,16 +65,42 @@ export interface RemoteDesktopReadiness {
|
|
|
49
65
|
videoReady: boolean;
|
|
50
66
|
/** Every `input` requirement is met (and the platform has an injector). */
|
|
51
67
|
inputReady: boolean;
|
|
68
|
+
clipboard: ClipboardSupport;
|
|
69
|
+
/** Whether the host can stream its own audio. Not a `requirements` row either, and for the
|
|
70
|
+
* same reason as clipboard: that checklist only renders while video or input is unmet, so a
|
|
71
|
+
* row here would be invisible on exactly the working hosts that cannot do audio. */
|
|
72
|
+
audio: AudioSupport;
|
|
73
|
+
/** Whether local input can be blocked and the host monitor blanked. Informational, like the
|
|
74
|
+
* two above — never a gate. */
|
|
75
|
+
privacy: PrivacySupport;
|
|
76
|
+
/** The host's own display modes, for the resolution picker. Same non-gating treatment. */
|
|
77
|
+
resolutions: HostResolutions;
|
|
52
78
|
}
|
|
53
79
|
|
|
54
80
|
const FFMPEG_INSTALL: Partial<Record<NodeJS.Platform, RequirementAction>> = {
|
|
55
81
|
darwin: { kind: "terminal", label: "Install with Homebrew", command: "brew install ffmpeg" },
|
|
56
82
|
win32: { kind: "terminal", label: "Install with winget", command: "winget install --id Gyan.FFmpeg -e" },
|
|
57
|
-
linux: { kind: "terminal", label: "Install with apt", command: "sudo apt install ffmpeg" },
|
|
58
83
|
};
|
|
59
84
|
|
|
85
|
+
/** Distro package managers, in the order a host is checked. `apt` last as the fallback label
|
|
86
|
+
* because it is the one most users recognise — but a command that names the wrong package
|
|
87
|
+
* manager is worse than none, since the checklist offers to *type it into a real terminal*. */
|
|
88
|
+
const LINUX_PACKAGE_MANAGERS: Array<{ bin: string; label: string; command: string }> = [
|
|
89
|
+
{ bin: "/usr/bin/pacman", label: "Install with pacman", command: "sudo pacman -S --needed ffmpeg" },
|
|
90
|
+
{ bin: "/usr/bin/dnf", label: "Install with dnf", command: "sudo dnf install ffmpeg" },
|
|
91
|
+
{ bin: "/usr/bin/zypper", label: "Install with zypper", command: "sudo zypper install ffmpeg" },
|
|
92
|
+
{ bin: "/usr/bin/apt", label: "Install with apt", command: "sudo apt install ffmpeg" },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
/** A `terminal` action installing `pkg` with whichever package manager this host has. */
|
|
96
|
+
function linuxInstallAction(pkg: string): RequirementAction | undefined {
|
|
97
|
+
const found = LINUX_PACKAGE_MANAGERS.find((p) => existsSync(p.bin));
|
|
98
|
+
if (!found) return undefined;
|
|
99
|
+
return { kind: "terminal", label: found.label, command: found.command.replace("ffmpeg", pkg) };
|
|
100
|
+
}
|
|
101
|
+
|
|
60
102
|
function ffmpegRequirement(platform: NodeJS.Platform, present: boolean): RemoteDesktopRequirement {
|
|
61
|
-
const install = FFMPEG_INSTALL[platform];
|
|
103
|
+
const install = platform === "linux" ? linuxInstallAction("ffmpeg") : FFMPEG_INSTALL[platform];
|
|
62
104
|
return {
|
|
63
105
|
id: "ffmpeg",
|
|
64
106
|
ok: present,
|
|
@@ -89,8 +131,114 @@ function macPermissionRequirement(id: MacPermissionId, granted: boolean): Remote
|
|
|
89
131
|
};
|
|
90
132
|
}
|
|
91
133
|
|
|
92
|
-
|
|
93
|
-
|
|
134
|
+
/** The session-type row. A Wayland host is *not* reported as an unsupported platform: nothing
|
|
135
|
+
* about Linux is missing, one specific capture path is — and the user can satisfy it in place
|
|
136
|
+
* by choosing X11 at the login screen, which is exactly what a requirement is for. Hiding the
|
|
137
|
+
* entry instead would leave them with no explanation at all. */
|
|
138
|
+
function linuxSessionRequirement(session: LinuxSession): RemoteDesktopRequirement {
|
|
139
|
+
const x11 = session.kind === "x11";
|
|
140
|
+
return {
|
|
141
|
+
id: "linux-session",
|
|
142
|
+
ok: x11,
|
|
143
|
+
gates: "video",
|
|
144
|
+
title: "X11 session",
|
|
145
|
+
detail: "Screen capture currently needs an X11 session. This host is on Wayland, which only "
|
|
146
|
+
+ "shares the screen through the desktop portal — not supported yet. Log out and pick "
|
|
147
|
+
+ "\"Plasma (X11)\" / \"GNOME on Xorg\" at the login screen; mouse and keyboard already work here.",
|
|
148
|
+
actions: [],
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** uinput is how a Wayland session receives input, and the device is root-owned by default. */
|
|
153
|
+
function uinputRequirement(): RemoteDesktopRequirement {
|
|
154
|
+
let writable = false;
|
|
155
|
+
try { accessSync("/dev/uinput", constants.W_OK); writable = true; } catch { writable = false; }
|
|
156
|
+
return {
|
|
157
|
+
id: "uinput",
|
|
158
|
+
ok: writable,
|
|
159
|
+
gates: "input",
|
|
160
|
+
title: "Write access to /dev/uinput",
|
|
161
|
+
detail: "Wayland has no input-injection protocol, so PPM types through a virtual input "
|
|
162
|
+
+ "device. Add yourself to the `input` group, then log out and back in; until then the "
|
|
163
|
+
+ "view is read-only.",
|
|
164
|
+
actions: [{ kind: "terminal", label: "Add me to the input group", command: "sudo usermod -aG input $USER" }],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** XTEST is a separate package from Xlib on several distros, and without it there is video but
|
|
169
|
+
* no input at all. */
|
|
170
|
+
function xtestRequirement(present: boolean): RemoteDesktopRequirement {
|
|
171
|
+
return {
|
|
172
|
+
id: "xtest",
|
|
173
|
+
ok: present,
|
|
174
|
+
gates: "input",
|
|
175
|
+
title: "XTEST extension (libXtst)",
|
|
176
|
+
detail: "Needed to move the mouse and type on an X11 host. Without it the view is read-only.",
|
|
177
|
+
actions: [linuxInstallAction(xtstPackage())].filter((a): a is RequirementAction => a !== undefined),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** libXtst's package name is not the same everywhere: Debian/Ubuntu ship `libxtst6`, Arch
|
|
182
|
+
* `libxtst`, Fedora/openSUSE `libXtst`. */
|
|
183
|
+
function xtstPackage(): string {
|
|
184
|
+
if (existsSync("/usr/bin/pacman")) return "libxtst";
|
|
185
|
+
if (existsSync("/usr/bin/apt")) return "libxtst6";
|
|
186
|
+
return "libXtst";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** No Xlib connection at all — which is a different fault from a missing XTEST extension, and
|
|
190
|
+
* used to be reported as one: `getX11()` answering null made the row below say "XTEST
|
|
191
|
+
* extension (libXtst)" and offer `apt install libxtst6`, an instruction that cannot help on a
|
|
192
|
+
* host whose libX11 is missing or whose server refused the connection. Capture is unaffected
|
|
193
|
+
* either way — ffmpeg opens the display itself, in its own process. */
|
|
194
|
+
function xlibRequirement(display: string): RemoteDesktopRequirement {
|
|
195
|
+
return {
|
|
196
|
+
id: "xlib",
|
|
197
|
+
ok: false,
|
|
198
|
+
gates: "input",
|
|
199
|
+
title: "A reachable X server (libX11)",
|
|
200
|
+
detail: `PPM could not open ${display} through Xlib: either libX11 is missing, or the server `
|
|
201
|
+
+ "refused the connection because PPM cannot read its auth cookie. The picture can still "
|
|
202
|
+
+ "arrive while this is unmet — it is mouse, keyboard and privacy mode that need it.",
|
|
203
|
+
actions: [linuxInstallAction(x11Package())].filter((a): a is RequirementAction => a !== undefined),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Same split as `xtstPackage`: Debian/Ubuntu `libx11-6`, Arch `libx11`, Fedora/openSUSE
|
|
208
|
+
* `libX11`. */
|
|
209
|
+
function x11Package(): string {
|
|
210
|
+
if (existsSync("/usr/bin/pacman")) return "libx11";
|
|
211
|
+
if (existsSync("/usr/bin/apt")) return "libx11-6";
|
|
212
|
+
return "libX11";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function clipboardSupport(platform: NodeJS.Platform, session: LinuxSession | null): ClipboardSupport {
|
|
216
|
+
const tool = clipboardTool(platform, session);
|
|
217
|
+
if (clipboardAvailable(tool)) return { available: true, action: null };
|
|
218
|
+
const pkg = tool?.install;
|
|
219
|
+
return { available: false, action: (pkg && linuxInstallAction(pkg)) || null };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function linuxRequirements(session: LinuxSession): Promise<RemoteDesktopRequirement[]> {
|
|
223
|
+
const rows: RemoteDesktopRequirement[] = [linuxSessionRequirement(session)];
|
|
224
|
+
if (session.kind === "x11") {
|
|
225
|
+
const conn = await getX11(session);
|
|
226
|
+
rows.push(conn ? xtestRequirement(conn.hasXTest) : xlibRequirement(session.display));
|
|
227
|
+
} else {
|
|
228
|
+
rows.push(uinputRequirement());
|
|
229
|
+
}
|
|
230
|
+
return rows;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export async function remoteDesktopReadiness(
|
|
234
|
+
platform: NodeJS.Platform = process.platform,
|
|
235
|
+
linuxSession = platform === "linux" ? detectLinuxSession() : null,
|
|
236
|
+
): Promise<RemoteDesktopReadiness> {
|
|
237
|
+
// On Linux "is this platform supported" means "is there a graphical session to capture" —
|
|
238
|
+
// the grabber then depends on its type, which is a requirement rather than a hard no.
|
|
239
|
+
const platformSupported = platform === "linux"
|
|
240
|
+
? linuxSession !== null
|
|
241
|
+
: captureInputForPlatform(platform) !== null;
|
|
94
242
|
const requirements: RemoteDesktopRequirement[] = [];
|
|
95
243
|
if (platformSupported) {
|
|
96
244
|
const caps = await getFfmpegCapabilities();
|
|
@@ -100,14 +248,19 @@ export async function remoteDesktopReadiness(platform: NodeJS.Platform = process
|
|
|
100
248
|
requirements.push(macPermissionRequirement("screen-recording", status["screen-recording"]));
|
|
101
249
|
requirements.push(macPermissionRequirement("accessibility", status.accessibility));
|
|
102
250
|
}
|
|
251
|
+
if (platform === "linux" && linuxSession) requirements.push(...await linuxRequirements(linuxSession));
|
|
103
252
|
}
|
|
104
|
-
const inputSupported = getInputBackend(platform) !== null;
|
|
253
|
+
const inputSupported = getInputBackend(platform, linuxSession) !== null;
|
|
105
254
|
return {
|
|
106
255
|
platform,
|
|
107
256
|
platformSupported,
|
|
108
257
|
requirements,
|
|
109
258
|
videoReady: platformSupported && requirements.filter((r) => r.gates === "video").every((r) => r.ok),
|
|
110
259
|
inputReady: inputSupported && requirements.filter((r) => r.gates === "input").every((r) => r.ok),
|
|
260
|
+
clipboard: clipboardSupport(platform, linuxSession),
|
|
261
|
+
audio: await audioSupport(platform),
|
|
262
|
+
privacy: await privacySupport(platform),
|
|
263
|
+
resolutions: await listHostResolutions(platform),
|
|
111
264
|
};
|
|
112
265
|
}
|
|
113
266
|
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lists and switches the *host's* real display mode — RustDesk's `_ResolutionsMenu`.
|
|
3
|
+
*
|
|
4
|
+
* Over FFI rather than by shelling out to `xrandr`, for the reason already documented for the
|
|
5
|
+
* monitor list: `xrandr` the binary and `libXrandr` the library are different packages, and
|
|
6
|
+
* this dev host has the library and not the binary. A version that shells out reports "no
|
|
7
|
+
* resolutions" on a perfectly good desktop.
|
|
8
|
+
*
|
|
9
|
+
* Struct offsets are x86-64 and are written out rather than derived, because a wrong one does
|
|
10
|
+
* not fail — it reads a neighbouring field, so a mode list comes back with plausible-looking
|
|
11
|
+
* garbage in it. They are checked against an independent oracle in the test: the kernel's own
|
|
12
|
+
* the `modes` file under `/sys/class/drm/<card>-<connector>`, which lists what the attached display advertises.
|
|
13
|
+
*
|
|
14
|
+
* XRRScreenResources (64): timestamp 0, configTimestamp 8, ncrtc 16, crtcs 24,
|
|
15
|
+
* noutput 32, outputs 40, nmode 48, modes 56
|
|
16
|
+
* XRRModeInfo (80): id 0, width 8, height 12, dotClock 16, hTotal 32, vTotal 48,
|
|
17
|
+
* name 56, nameLength 64, modeFlags 72
|
|
18
|
+
* XRROutputInfo (96): crtc 8, name 16, nameLen 24, mm_width 32, mm_height 40,
|
|
19
|
+
* connection 48, ncrtc 52, crtcs 56, nmode 80, npreferred 84,
|
|
20
|
+
* modes 88
|
|
21
|
+
* XRRCrtcInfo (64): x 8, y 12, width 16, height 20, mode 24, rotation 32,
|
|
22
|
+
* noutput 36, outputs 40
|
|
23
|
+
*
|
|
24
|
+
* Switching is not one call but a *sequence*, because the root window is a container for the
|
|
25
|
+
* CRTCs and neither may be outside the other. A CRTC cannot be configured past the screen
|
|
26
|
+
* bounds, so any dimension that grows needs `XRRSetScreenSize` **first**; and a screen smaller
|
|
27
|
+
* than a still-active CRTC is equally invalid, so trimming it down has to come **last**. A
|
|
28
|
+
* switch where one dimension grows and the other shrinks (1920x1200 → 2560x1080) needs both,
|
|
29
|
+
* which is why `planResolutionSwitch` is a pure function with its own test rather than a pair
|
|
30
|
+
* of branches — getting it wrong leaves the mode applied and the root window the wrong size,
|
|
31
|
+
* i.e. a desktop with a dead strip down one side, which no error reports.
|
|
32
|
+
*/
|
|
33
|
+
import { detectLinuxSession } from "./remote-desktop-linux-session.ts";
|
|
34
|
+
import { asPointer, getX11, type X11Connection } from "./remote-desktop-x11.ts";
|
|
35
|
+
|
|
36
|
+
/** `RR_Connected` from `randr.h`. */
|
|
37
|
+
const RR_CONNECTED = 0;
|
|
38
|
+
const RR_ROTATE_0 = 1;
|
|
39
|
+
const CURRENT_TIME = 0n;
|
|
40
|
+
/** `RRSetConfigSuccess`. */
|
|
41
|
+
const SET_CONFIG_SUCCESS = 0;
|
|
42
|
+
/** `RR_Interlace` / `RR_DoubleScan` in `modeFlags`, which change what the timings mean. */
|
|
43
|
+
const RR_INTERLACE = 0x10;
|
|
44
|
+
const RR_DOUBLE_SCAN = 0x20;
|
|
45
|
+
|
|
46
|
+
const SIZEOF_MODE_INFO = 80;
|
|
47
|
+
|
|
48
|
+
export interface HostMode {
|
|
49
|
+
/** `RRMode` XID, the value a switch is requested by. Stringified because it is a 64-bit id
|
|
50
|
+
* and it travels over JSON. */
|
|
51
|
+
id: string;
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
/** Vertical refresh, one decimal place. 0 when the timings do not allow computing one. */
|
|
55
|
+
refresh: number;
|
|
56
|
+
/** True for the mode the output is on right now. */
|
|
57
|
+
current: boolean;
|
|
58
|
+
/** True for the display's preferred (native) mode. */
|
|
59
|
+
preferred: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface HostResolutions {
|
|
63
|
+
/** Connector name (`HDMI-2`, `DP-1`), or null when nothing usable was found. */
|
|
64
|
+
output: string | null;
|
|
65
|
+
modes: HostMode[];
|
|
66
|
+
/** Why the list is empty, or null. */
|
|
67
|
+
reason: string | null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** dotClock / (hTotal * vTotal), corrected for interlace and doublescan the way `xrandr` does. */
|
|
71
|
+
export function modeRefresh(dotClock: number, hTotal: number, vTotal: number, flags: number): number {
|
|
72
|
+
if (hTotal <= 0 || vTotal <= 0 || dotClock <= 0) return 0;
|
|
73
|
+
let vTotalAdjusted = vTotal;
|
|
74
|
+
if (flags & RR_DOUBLE_SCAN) vTotalAdjusted *= 2;
|
|
75
|
+
if (flags & RR_INTERLACE) vTotalAdjusted /= 2;
|
|
76
|
+
return Math.round((dotClock / (hTotal * vTotalAdjusted)) * 10) / 10;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface Resources {
|
|
80
|
+
res: number;
|
|
81
|
+
view: DataView;
|
|
82
|
+
modes: Map<string, { width: number; height: number; refresh: number }>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readResources(x: X11Connection): Resources | null {
|
|
86
|
+
const res = x.xrandr!.XRRGetScreenResourcesCurrent(x.dpy, x.root);
|
|
87
|
+
if (!res) return null;
|
|
88
|
+
const header = new DataView(x.ffi.toArrayBuffer(asPointer(Number(res)), 0, 64));
|
|
89
|
+
const nmode = header.getInt32(48, true);
|
|
90
|
+
const modesPtr = Number(header.getBigUint64(56, true));
|
|
91
|
+
const modes = new Map<string, { width: number; height: number; refresh: number }>();
|
|
92
|
+
if (modesPtr && nmode > 0) {
|
|
93
|
+
const table = new DataView(x.ffi.toArrayBuffer(asPointer(modesPtr), 0, nmode * SIZEOF_MODE_INFO));
|
|
94
|
+
for (let i = 0; i < nmode; i++) {
|
|
95
|
+
const at = i * SIZEOF_MODE_INFO;
|
|
96
|
+
modes.set(String(table.getBigUint64(at, true)), {
|
|
97
|
+
width: table.getUint32(at + 8, true),
|
|
98
|
+
height: table.getUint32(at + 12, true),
|
|
99
|
+
refresh: modeRefresh(
|
|
100
|
+
Number(table.getBigUint64(at + 16, true)),
|
|
101
|
+
table.getUint32(at + 32, true),
|
|
102
|
+
table.getUint32(at + 48, true),
|
|
103
|
+
Number(table.getBigUint64(at + 72, true)),
|
|
104
|
+
),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return { res: Number(res), view: header, modes };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The first connected output that has a CRTC, plus its info pointer. Multi-head hosts get the
|
|
112
|
+
* one the session is actually driving; PPM streams one display at a time anyway. */
|
|
113
|
+
function firstActiveOutput(x: X11Connection, r: Resources): { output: number; info: number } | null {
|
|
114
|
+
const noutput = r.view.getInt32(32, true);
|
|
115
|
+
const outputsPtr = Number(r.view.getBigUint64(40, true));
|
|
116
|
+
if (!outputsPtr || noutput <= 0) return null;
|
|
117
|
+
const ids = new DataView(x.ffi.toArrayBuffer(asPointer(outputsPtr), 0, noutput * 8));
|
|
118
|
+
for (let i = 0; i < noutput; i++) {
|
|
119
|
+
const id = ids.getBigUint64(i * 8, true);
|
|
120
|
+
const info = Number(x.xrandr!.XRRGetOutputInfo(x.dpy, asPointer(r.res), id));
|
|
121
|
+
if (!info) continue;
|
|
122
|
+
const iv = new DataView(x.ffi.toArrayBuffer(asPointer(info), 0, 96));
|
|
123
|
+
const connected = iv.getUint16(48, true) === RR_CONNECTED;
|
|
124
|
+
const crtc = iv.getBigUint64(8, true);
|
|
125
|
+
if (connected && crtc !== 0n) return { output: Number(id), info };
|
|
126
|
+
x.xrandr!.XRRFreeOutputInfo(asPointer(info));
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function outputName(x: X11Connection, info: number): string {
|
|
132
|
+
const iv = new DataView(x.ffi.toArrayBuffer(asPointer(info), 0, 96));
|
|
133
|
+
const ptr = Number(iv.getBigUint64(16, true));
|
|
134
|
+
const len = iv.getInt32(24, true);
|
|
135
|
+
if (!ptr || len <= 0) return "";
|
|
136
|
+
return new TextDecoder().decode(new Uint8Array(x.ffi.toArrayBuffer(asPointer(ptr), 0, len)));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Every mode the host's active output supports. */
|
|
140
|
+
export async function listHostResolutions(
|
|
141
|
+
platform: NodeJS.Platform = process.platform,
|
|
142
|
+
): Promise<HostResolutions> {
|
|
143
|
+
if (platform !== "linux") {
|
|
144
|
+
return { output: null, modes: [], reason: "Only implemented on Linux (X11) so far." };
|
|
145
|
+
}
|
|
146
|
+
const session = detectLinuxSession();
|
|
147
|
+
if (session?.kind !== "x11") {
|
|
148
|
+
return { output: null, modes: [], reason: "Changing the host resolution needs an X11 session." };
|
|
149
|
+
}
|
|
150
|
+
const x = await getX11(session);
|
|
151
|
+
if (!x?.xrandr) {
|
|
152
|
+
return { output: null, modes: [], reason: "The host has no libXrandr." };
|
|
153
|
+
}
|
|
154
|
+
const r = readResources(x);
|
|
155
|
+
if (!r) return { output: null, modes: [], reason: "The X server returned no screen resources." };
|
|
156
|
+
try {
|
|
157
|
+
const active = firstActiveOutput(x, r);
|
|
158
|
+
if (!active) return { output: null, modes: [], reason: "No connected output with a CRTC." };
|
|
159
|
+
try {
|
|
160
|
+
const iv = new DataView(x.ffi.toArrayBuffer(asPointer(active.info), 0, 96));
|
|
161
|
+
const crtc = iv.getBigUint64(8, true);
|
|
162
|
+
const nmode = iv.getInt32(80, true);
|
|
163
|
+
const npreferred = iv.getInt32(84, true);
|
|
164
|
+
const modesPtr = Number(iv.getBigUint64(88, true));
|
|
165
|
+
|
|
166
|
+
// Which mode is live right now comes from the CRTC, not the output.
|
|
167
|
+
let currentMode = "0";
|
|
168
|
+
const crtcInfo = Number(x.xrandr.XRRGetCrtcInfo(x.dpy, asPointer(r.res), crtc));
|
|
169
|
+
if (crtcInfo) {
|
|
170
|
+
const cv = new DataView(x.ffi.toArrayBuffer(asPointer(crtcInfo), 0, 64));
|
|
171
|
+
currentMode = String(cv.getBigUint64(24, true));
|
|
172
|
+
x.xrandr.XRRFreeCrtcInfo(asPointer(crtcInfo));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const modes: HostMode[] = [];
|
|
176
|
+
if (modesPtr && nmode > 0) {
|
|
177
|
+
const ids = new DataView(x.ffi.toArrayBuffer(asPointer(modesPtr), 0, nmode * 8));
|
|
178
|
+
for (let i = 0; i < nmode; i++) {
|
|
179
|
+
const id = String(ids.getBigUint64(i * 8, true));
|
|
180
|
+
const info = r.modes.get(id);
|
|
181
|
+
if (!info || info.width === 0 || info.height === 0) continue;
|
|
182
|
+
modes.push({
|
|
183
|
+
id,
|
|
184
|
+
width: info.width,
|
|
185
|
+
height: info.height,
|
|
186
|
+
refresh: info.refresh,
|
|
187
|
+
current: id === currentMode,
|
|
188
|
+
// `npreferred` is a *count* of preferred modes at the head of the list, not an
|
|
189
|
+
// index — reading it as an index marks the wrong mode native.
|
|
190
|
+
preferred: i < npreferred,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { output: outputName(x, active.info) || null, modes, reason: null };
|
|
195
|
+
} finally {
|
|
196
|
+
x.xrandr.XRRFreeOutputInfo(asPointer(active.info));
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
x.xrandr.XRRFreeScreenResources(asPointer(r.res));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface ResolutionSwitchPlan {
|
|
204
|
+
/** The mode is already live; make no X calls at all. */
|
|
205
|
+
noop: boolean;
|
|
206
|
+
/** Screen size to set *before* reconfiguring the CRTC, or null when nothing grows. */
|
|
207
|
+
grow: { width: number; height: number } | null;
|
|
208
|
+
/** Screen size to set *after*. A no-op when `grow` already hit these numbers. */
|
|
209
|
+
final: { width: number; height: number };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** The order of `XRRSetScreenSize`/`XRRSetCrtcConfig` calls for one switch. See the header. */
|
|
213
|
+
export function planResolutionSwitch(
|
|
214
|
+
oldWidth: number, oldHeight: number, newWidth: number, newHeight: number,
|
|
215
|
+
): ResolutionSwitchPlan {
|
|
216
|
+
const final = { width: newWidth, height: newHeight };
|
|
217
|
+
if (oldWidth === newWidth && oldHeight === newHeight) return { noop: true, grow: null, final };
|
|
218
|
+
const grows = newWidth > oldWidth || newHeight > oldHeight;
|
|
219
|
+
return {
|
|
220
|
+
noop: false,
|
|
221
|
+
// Per dimension, not per switch: the shrinking half of a mixed switch still may not clip
|
|
222
|
+
// the CRTC while it is live, so both dimensions hold their maximum across the reconfigure
|
|
223
|
+
// and `final` trims whichever one overshot.
|
|
224
|
+
grow: grows ? { width: Math.max(oldWidth, newWidth), height: Math.max(oldHeight, newHeight) } : null,
|
|
225
|
+
final,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface SetResolutionResult {
|
|
230
|
+
ok: boolean;
|
|
231
|
+
/** The mode that is live afterwards, whether or not the switch was the one asked for. */
|
|
232
|
+
width: number;
|
|
233
|
+
height: number;
|
|
234
|
+
error: string | null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Put the host's active output on `modeId`. Returns what is actually live afterwards.
|
|
239
|
+
*/
|
|
240
|
+
export async function setHostResolution(
|
|
241
|
+
modeId: string, platform: NodeJS.Platform = process.platform,
|
|
242
|
+
): Promise<SetResolutionResult> {
|
|
243
|
+
const fail = (error: string): SetResolutionResult => ({ ok: false, width: 0, height: 0, error });
|
|
244
|
+
if (platform !== "linux") return fail("Only implemented on Linux (X11) so far.");
|
|
245
|
+
const session = detectLinuxSession();
|
|
246
|
+
if (session?.kind !== "x11") return fail("Needs an X11 session.");
|
|
247
|
+
const x = await getX11(session);
|
|
248
|
+
if (!x?.xrandr) return fail("The host has no libXrandr.");
|
|
249
|
+
const r = readResources(x);
|
|
250
|
+
if (!r) return fail("The X server returned no screen resources.");
|
|
251
|
+
try {
|
|
252
|
+
const target = r.modes.get(modeId);
|
|
253
|
+
if (!target) return fail("That mode is not one this host advertises.");
|
|
254
|
+
const active = firstActiveOutput(x, r);
|
|
255
|
+
if (!active) return fail("No connected output with a CRTC.");
|
|
256
|
+
try {
|
|
257
|
+
const iv = new DataView(x.ffi.toArrayBuffer(asPointer(active.info), 0, 96));
|
|
258
|
+
const crtc = iv.getBigUint64(8, true);
|
|
259
|
+
const mmWidth = Number(iv.getBigUint64(32, true));
|
|
260
|
+
const mmHeight = Number(iv.getBigUint64(40, true));
|
|
261
|
+
|
|
262
|
+
const crtcInfo = Number(x.xrandr.XRRGetCrtcInfo(x.dpy, asPointer(r.res), crtc));
|
|
263
|
+
if (!crtcInfo) return fail("Could not read the CRTC.");
|
|
264
|
+
const cv = new DataView(x.ffi.toArrayBuffer(asPointer(crtcInfo), 0, 64));
|
|
265
|
+
const cx = cv.getInt32(8, true);
|
|
266
|
+
const cy = cv.getInt32(12, true);
|
|
267
|
+
const oldWidth = cv.getUint32(16, true);
|
|
268
|
+
const oldHeight = cv.getUint32(20, true);
|
|
269
|
+
x.xrandr.XRRFreeCrtcInfo(asPointer(crtcInfo));
|
|
270
|
+
|
|
271
|
+
const plan = planResolutionSwitch(oldWidth, oldHeight, target.width, target.height);
|
|
272
|
+
if (plan.noop) return { ok: true, width: oldWidth, height: oldHeight, error: null };
|
|
273
|
+
|
|
274
|
+
const setScreen = (width: number, height: number) => {
|
|
275
|
+
// The millimetres are the output's real ones, so the desktop's DPI does not move with
|
|
276
|
+
// the mode. Passing the conventional 96dpi figure instead makes every font on the host
|
|
277
|
+
// change size when a remote user picks a resolution.
|
|
278
|
+
x.xrandr!.XRRSetScreenSize(x.dpy, x.root, width, height, mmWidth, mmHeight);
|
|
279
|
+
x.x11.XSync(x.dpy, 0);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (plan.grow) setScreen(plan.grow.width, plan.grow.height);
|
|
283
|
+
const outputs = new BigUint64Array([BigInt(active.output)]);
|
|
284
|
+
const status = x.xrandr.XRRSetCrtcConfig(
|
|
285
|
+
x.dpy, asPointer(r.res), crtc, CURRENT_TIME, cx, cy,
|
|
286
|
+
BigInt(modeId), RR_ROTATE_0, x.ffi.ptr(outputs), 1,
|
|
287
|
+
);
|
|
288
|
+
x.x11.XSync(x.dpy, 0);
|
|
289
|
+
if (status !== SET_CONFIG_SUCCESS) {
|
|
290
|
+
// Put the container back, or the desktop keeps a dead strip the CRTC never filled.
|
|
291
|
+
if (plan.grow) setScreen(oldWidth, oldHeight);
|
|
292
|
+
return fail(`The X server refused the mode (status ${status}).`);
|
|
293
|
+
}
|
|
294
|
+
setScreen(plan.final.width, plan.final.height);
|
|
295
|
+
return { ok: true, width: target.width, height: target.height, error: null };
|
|
296
|
+
} finally {
|
|
297
|
+
x.xrandr.XRRFreeOutputInfo(asPointer(active.info));
|
|
298
|
+
}
|
|
299
|
+
} finally {
|
|
300
|
+
x.xrandr.XRRFreeScreenResources(asPointer(r.res));
|
|
301
|
+
}
|
|
302
|
+
}
|