@hienlh/ppm 0.23.11 → 0.23.12
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 +13 -2
- 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 +37 -1
- package/bun.lock +34 -0
- package/dist/web/assets/accounts-settings-section-CGe5ikvs.js +1 -0
- package/dist/web/assets/accounts-settings-section-CGe5ikvs.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-CGe5ikvs.js.gz +0 -0
- package/dist/web/assets/{agent-session-sheet-CBMsnl_p.js → agent-session-sheet-vBhfHYrC.js} +2 -2
- package/dist/web/assets/agent-session-sheet-vBhfHYrC.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-vBhfHYrC.js.gz +0 -0
- package/dist/web/assets/{agent-session-window-content-D9C9FA7E.js → agent-session-window-content-BxIxDN5i.js} +1 -1
- package/dist/web/assets/agent-session-window-content-BxIxDN5i.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-BxIxDN5i.js.gz +0 -0
- package/dist/web/assets/{ai-resource-editor-DhUVgDEG.js → ai-resource-editor-Y8clBPRv.js} +1 -1
- package/dist/web/assets/ai-resource-editor-Y8clBPRv.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-Y8clBPRv.js.gz +0 -0
- package/dist/web/assets/{ai-settings-section-DOckWvp4.js → ai-settings-section-BB8vscte.js} +1 -1
- package/dist/web/assets/{ai-settings-section-CETpSpYd.js → ai-settings-section-Ck8uN_Bw.js} +1 -1
- package/dist/web/assets/ai-settings-section-Ck8uN_Bw.js.br +0 -0
- package/dist/web/assets/ai-settings-section-Ck8uN_Bw.js.gz +0 -0
- package/dist/web/assets/android-tab-BIOUVVoW.js +2 -0
- package/dist/web/assets/android-tab-BIOUVVoW.js.br +0 -0
- package/dist/web/assets/android-tab-BIOUVVoW.js.gz +0 -0
- package/dist/web/assets/{api-settings-JIgKfcCx.js → api-settings-D1axn9vm.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-BJUiLUb7.js → appearance-settings-section-Bv5_n9lH.js} +1 -1
- package/dist/web/assets/appearance-settings-section-Bv5_n9lH.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-Bv5_n9lH.js.gz +0 -0
- package/dist/web/assets/{apps-panel-1VElRgV3.js → apps-panel-DXHF0QEv.js} +1 -1
- package/dist/web/assets/apps-panel-DXHF0QEv.js.br +0 -0
- package/dist/web/assets/apps-panel-DXHF0QEv.js.gz +0 -0
- package/dist/web/assets/{audio-preview-DYkyHu5X.js → audio-preview-DpjOKEm4.js} +1 -1
- package/dist/web/assets/audio-preview-DpjOKEm4.js.br +0 -0
- package/dist/web/assets/audio-preview-DpjOKEm4.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-4dgMNadQ.js +1 -0
- package/dist/web/assets/branch-review-tab-4dgMNadQ.js.br +0 -0
- package/dist/web/assets/branch-review-tab-4dgMNadQ.js.gz +0 -0
- package/dist/web/assets/chat-tab-Bbe_77oJ.js +15 -0
- package/dist/web/assets/chat-tab-Bbe_77oJ.js.br +0 -0
- package/dist/web/assets/chat-tab-Bbe_77oJ.js.gz +0 -0
- package/dist/web/assets/chat-tab-D3i5O3dO.js +1 -0
- package/dist/web/assets/chat-tab-D3i5O3dO.js.br +0 -0
- package/dist/web/assets/chat-tab-D3i5O3dO.js.gz +0 -0
- package/dist/web/assets/code-editor-DXsIWoQ1.js +7 -0
- package/dist/web/assets/code-editor-DXsIWoQ1.js.br +0 -0
- package/dist/web/assets/code-editor-DXsIWoQ1.js.gz +0 -0
- package/dist/web/assets/{codex-reset-credit-button-Fr45FUxi.js → codex-reset-credit-button-DRhievFQ.js} +1 -1
- package/dist/web/assets/codex-reset-credit-button-DRhievFQ.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-DRhievFQ.js.gz +0 -0
- package/dist/web/assets/{conflict-editor-B-wdjSXI.js → conflict-editor-D_RImo_V.js} +3 -3
- package/dist/web/assets/conflict-editor-D_RImo_V.js.br +0 -0
- package/dist/web/assets/conflict-editor-D_RImo_V.js.gz +0 -0
- package/dist/web/assets/{csv-preview-B6RodObB.js → csv-preview-BuFdfLo3.js} +1 -1
- package/dist/web/assets/csv-preview-BuFdfLo3.js.br +0 -0
- package/dist/web/assets/csv-preview-BuFdfLo3.js.gz +0 -0
- package/dist/web/assets/{database-viewer-CdPBUQPY.js → database-viewer-ChcbYn9E.js} +1 -1
- package/dist/web/assets/database-viewer-ChcbYn9E.js.br +0 -0
- package/dist/web/assets/database-viewer-ChcbYn9E.js.gz +0 -0
- package/dist/web/assets/{design-settings-section-BpCpaBwL.js → design-settings-section-B_xOf51h.js} +1 -1
- package/dist/web/assets/design-settings-section-B_xOf51h.js.br +0 -0
- package/dist/web/assets/design-settings-section-B_xOf51h.js.gz +0 -0
- package/dist/web/assets/{design-skill-suggestion-DkT2E2IV.js → design-skill-suggestion-BbEDg15Z.js} +1 -1
- package/dist/web/assets/design-skill-suggestion-BbEDg15Z.js.br +0 -0
- package/dist/web/assets/design-skill-suggestion-BbEDg15Z.js.gz +0 -0
- package/dist/web/assets/{design-tab-BZb9eemI.js → design-tab-BQSYJ4cS.js} +6 -6
- package/dist/web/assets/design-tab-BQSYJ4cS.js.br +0 -0
- package/dist/web/assets/design-tab-BQSYJ4cS.js.gz +0 -0
- package/dist/web/assets/{designs-sidebar-panel-CF4tp6j-.js → designs-sidebar-panel-AiPaYfSU.js} +1 -1
- package/dist/web/assets/designs-sidebar-panel-AiPaYfSU.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-AiPaYfSU.js.gz +0 -0
- package/dist/web/assets/{diff-viewer-BqEqwSw6.js → diff-viewer-BZ6glGK-.js} +1 -1
- package/dist/web/assets/diff-viewer-BZ6glGK-.js.br +0 -0
- package/dist/web/assets/diff-viewer-BZ6glGK-.js.gz +0 -0
- package/dist/web/assets/{edit-diff-preview-DD4HayXY.js → edit-diff-preview-D-FNLOU2.js} +1 -1
- package/dist/web/assets/edit-diff-preview-D-FNLOU2.js.br +0 -0
- package/dist/web/assets/edit-diff-preview-D-FNLOU2.js.gz +0 -0
- package/dist/web/assets/{editor-language-service-CkA5sEdV.js → editor-language-service-4sbsdqBV.js} +1 -1
- package/dist/web/assets/editor-language-service-4sbsdqBV.js.br +0 -0
- package/dist/web/assets/editor-language-service-4sbsdqBV.js.gz +0 -0
- package/dist/web/assets/explorer-body-DbgrG1fe.js +2 -0
- package/dist/web/assets/explorer-body-DbgrG1fe.js.br +0 -0
- package/dist/web/assets/explorer-body-DbgrG1fe.js.gz +0 -0
- package/dist/web/assets/{explorer-window-content-CRJ2eMZ4.js → explorer-window-content-BCjWvHpp.js} +1 -1
- package/dist/web/assets/explorer-window-content-BCjWvHpp.js.br +0 -0
- package/dist/web/assets/explorer-window-content-BCjWvHpp.js.gz +0 -0
- package/dist/web/assets/{extension-manager-section-DwQTdCII.js → extension-manager-section-DiMF4RnT.js} +1 -1
- package/dist/web/assets/extension-manager-section-DiMF4RnT.js.br +0 -0
- package/dist/web/assets/extension-manager-section-DiMF4RnT.js.gz +0 -0
- package/dist/web/assets/{extension-webview-CMq-VTLu.js → extension-webview-Coti6Vfw.js} +1 -1
- package/dist/web/assets/extension-webview-Coti6Vfw.js.br +0 -0
- package/dist/web/assets/extension-webview-Coti6Vfw.js.gz +0 -0
- package/dist/web/assets/file-store-Cm7-pHux.js +1 -0
- package/dist/web/assets/file-store-Cm7-pHux.js.br +0 -0
- package/dist/web/assets/file-store-Cm7-pHux.js.gz +0 -0
- package/dist/web/assets/{files-settings-section-B2lcyh-6.js → files-settings-section-ZTVwOwx0.js} +1 -1
- package/dist/web/assets/files-settings-section-ZTVwOwx0.js.br +0 -0
- package/dist/web/assets/files-settings-section-ZTVwOwx0.js.gz +0 -0
- package/dist/web/assets/{general-settings-section-Doepm2sH.js → general-settings-section-B7Hjx_fb.js} +1 -1
- package/dist/web/assets/general-settings-section-B7Hjx_fb.js.br +0 -0
- package/dist/web/assets/general-settings-section-B7Hjx_fb.js.gz +0 -0
- package/dist/web/assets/{git-log-panel-CJakNMDc.js → git-log-panel-CbcJ_YEH.js} +1 -1
- package/dist/web/assets/git-log-panel-CbcJ_YEH.js.br +0 -0
- package/dist/web/assets/git-log-panel-CbcJ_YEH.js.gz +0 -0
- package/dist/web/assets/{glide-data-grid-C_SeOhSq.js → glide-data-grid-BfByGSf1.js} +3 -3
- package/dist/web/assets/glide-data-grid-BfByGSf1.js.br +0 -0
- package/dist/web/assets/glide-data-grid-BfByGSf1.js.gz +0 -0
- package/dist/web/assets/group-chat-tab-DCo9OP3D.js +1 -0
- package/dist/web/assets/group-chat-tab-DCo9OP3D.js.br +0 -0
- package/dist/web/assets/group-chat-tab-DCo9OP3D.js.gz +0 -0
- package/dist/web/assets/{image-preview-C1Frlx22.js → image-preview-Cjlps7Pt.js} +1 -1
- package/dist/web/assets/index-CyDLBddf.js +52 -0
- package/dist/web/assets/index-CyDLBddf.js.br +0 -0
- package/dist/web/assets/index-CyDLBddf.js.gz +0 -0
- package/dist/web/assets/index-Dh5apC3I.css +1 -0
- package/dist/web/assets/index-Dh5apC3I.css.br +0 -0
- package/dist/web/assets/index-Dh5apC3I.css.gz +0 -0
- package/dist/web/assets/{jira-watcher-section-BmkIGQFV.js → jira-watcher-section-Dz_-sUC2.js} +1 -1
- package/dist/web/assets/keybindings-store-BTq5Z7XP.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BgZe3QDr.js → keyboard-shortcuts-section-Ba5NYwlG.js} +1 -1
- package/dist/web/assets/keyboard-shortcuts-section-Ba5NYwlG.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-Ba5NYwlG.js.gz +0 -0
- package/dist/web/assets/{language-servers-section-CCMzgOaE.js → language-servers-section-DJJ7SFXo.js} +2 -2
- package/dist/web/assets/language-servers-section-DJJ7SFXo.js.br +0 -0
- package/dist/web/assets/language-servers-section-DJJ7SFXo.js.gz +0 -0
- package/dist/web/assets/{lsp-client-B10suuaZ.js → lsp-client-Dr3vQ1bb.js} +1 -1
- package/dist/web/assets/lsp-client-Dr3vQ1bb.js.br +0 -0
- package/dist/web/assets/lsp-client-Dr3vQ1bb.js.gz +0 -0
- package/dist/web/assets/{markdown-renderer-AHHFS3jc.js → markdown-renderer-CmnqFfTU.js} +3 -3
- package/dist/web/assets/markdown-renderer-CmnqFfTU.js.br +0 -0
- package/dist/web/assets/markdown-renderer-CmnqFfTU.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-LJdVMbGl.js +1 -0
- package/dist/web/assets/{mobile-explorer-sheet-CqCsLWSr.js → mobile-explorer-sheet-BiIhZtJ2.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BiIhZtJ2.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-BiIhZtJ2.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-DI_hxvFV.js +1 -0
- package/dist/web/assets/new-design-dialog-DI_hxvFV.js.br +0 -0
- package/dist/web/assets/new-design-dialog-DI_hxvFV.js.gz +0 -0
- package/dist/web/assets/notification-store-9lBSplOV.js +1 -0
- package/dist/web/assets/{notifications-settings-section-CXgzH_bK.js → notifications-settings-section-0s-2OKRv.js} +1 -1
- package/dist/web/assets/notifications-settings-section-0s-2OKRv.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-0s-2OKRv.js.gz +0 -0
- package/dist/web/assets/{overview-panel-BYB8mgRE.js → overview-panel-3htxfZS2.js} +1 -1
- package/dist/web/assets/overview-panel-3htxfZS2.js.br +0 -0
- package/dist/web/assets/overview-panel-3htxfZS2.js.gz +0 -0
- package/dist/web/assets/panel-store-B6PoCwBA.js +1 -0
- package/dist/web/assets/panel-store-B6PoCwBA.js.br +0 -0
- package/dist/web/assets/panel-store-B6PoCwBA.js.gz +0 -0
- package/dist/web/assets/{performance-panel-Dpuq3g0W.js → performance-panel-V8gpnnf7.js} +2 -2
- package/dist/web/assets/performance-panel-V8gpnnf7.js.br +0 -0
- package/dist/web/assets/performance-panel-V8gpnnf7.js.gz +0 -0
- package/dist/web/assets/{postgres-viewer-DW0Gkmow.js → postgres-viewer-sWS-ogTC.js} +1 -1
- package/dist/web/assets/postgres-viewer-sWS-ogTC.js.br +0 -0
- package/dist/web/assets/postgres-viewer-sWS-ogTC.js.gz +0 -0
- package/dist/web/assets/{ppmbot-settings-section-DFtlGoW6.js → ppmbot-settings-section-CT8vvlzk.js} +1 -1
- package/dist/web/assets/ppmbot-settings-section-CT8vvlzk.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-CT8vvlzk.js.gz +0 -0
- package/dist/web/assets/{pptx-export-CNyG32Xr.js → pptx-export-Ugjv0hso.js} +4 -4
- package/dist/web/assets/pptx-export-Ugjv0hso.js.br +0 -0
- package/dist/web/assets/pptx-export-Ugjv0hso.js.gz +0 -0
- package/dist/web/assets/{problems-panel-V5zqpuXq.js → problems-panel-Gcy79pSS.js} +1 -1
- package/dist/web/assets/problems-panel-Gcy79pSS.js.br +0 -0
- package/dist/web/assets/problems-panel-Gcy79pSS.js.gz +0 -0
- package/dist/web/assets/{process-row-format-CiJJSf_V.js → process-row-format-u6y7lOee.js} +1 -1
- package/dist/web/assets/process-table-BUTIp9Oy.js +1 -0
- package/dist/web/assets/process-table-BUTIp9Oy.js.br +0 -0
- package/dist/web/assets/process-table-BUTIp9Oy.js.gz +0 -0
- package/dist/web/assets/{proxy-settings-section-B7AVlTfY.js → proxy-settings-section-Buv_8y54.js} +1 -1
- package/dist/web/assets/proxy-settings-section-Buv_8y54.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-Buv_8y54.js.gz +0 -0
- package/dist/web/assets/{query-audit-section-gHmRfWKr.js → query-audit-section-N2DQtgJx.js} +1 -1
- package/dist/web/assets/query-audit-section-N2DQtgJx.js.br +0 -0
- package/dist/web/assets/query-audit-section-N2DQtgJx.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js +1 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-CRvJqmaz.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-BoYINLbJ.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DPdP0yYu.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js +1 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-BeQ_Jrp7.js.gz +0 -0
- package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js +1 -0
- package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js.br +0 -0
- package/dist/web/assets/remote-desktop-settings-section-Bs4sHS3l.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-DDr__JxQ.js.gz +0 -0
- package/dist/web/assets/{run-in-terminal-DKBcEGVu.js → run-in-terminal-DCBNtYHQ.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-sYDAsBMO.js → schedules-settings-section-_cHX2qOa.js} +1 -1
- package/dist/web/assets/schedules-settings-section-_cHX2qOa.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-_cHX2qOa.js.gz +0 -0
- package/dist/web/assets/{send-to-chat-CCdOrKc7.js → send-to-chat-dNqNV7Y4.js} +1 -1
- package/dist/web/assets/send-to-chat-dNqNV7Y4.js.br +0 -0
- package/dist/web/assets/send-to-chat-dNqNV7Y4.js.gz +0 -0
- package/dist/web/assets/{services-panel-CKa2aP1d.js → services-panel-Dm54cnW3.js} +1 -1
- package/dist/web/assets/services-panel-Dm54cnW3.js.br +0 -0
- package/dist/web/assets/services-panel-Dm54cnW3.js.gz +0 -0
- package/dist/web/assets/settings-body-DRRQNaDA.js +1 -0
- package/dist/web/assets/settings-body-DRRQNaDA.js.br +0 -0
- package/dist/web/assets/settings-body-DRRQNaDA.js.gz +0 -0
- package/dist/web/assets/settings-section-content-DRaHfvm8.js +1 -0
- package/dist/web/assets/settings-section-content-U3mIx6hF.js +2 -0
- package/dist/web/assets/settings-section-content-U3mIx6hF.js.br +0 -0
- package/dist/web/assets/settings-section-content-U3mIx6hF.js.gz +0 -0
- package/dist/web/assets/{settings-store-DfuiLq_9.js → settings-store-Cyuo8ceh.js} +2 -2
- package/dist/web/assets/settings-store-Cyuo8ceh.js.br +0 -0
- package/dist/web/assets/settings-store-Cyuo8ceh.js.gz +0 -0
- package/dist/web/assets/settings-tab-CB_BhHTK.js +1 -0
- package/dist/web/assets/settings-window-content-CgZIMNX8.js +1 -0
- package/dist/web/assets/{slash-command-picker-CLevA_Gf.js → slash-command-picker-CQlIqMW0.js} +1 -1
- package/dist/web/assets/slash-command-picker-CQlIqMW0.js.br +0 -0
- package/dist/web/assets/slash-command-picker-CQlIqMW0.js.gz +0 -0
- package/dist/web/assets/{sql-query-editor-Db8b0jEr.js → sql-query-editor--Z4bcqd8.js} +1 -1
- package/dist/web/assets/sql-query-editor--Z4bcqd8.js.br +0 -0
- package/dist/web/assets/sql-query-editor--Z4bcqd8.js.gz +0 -0
- package/dist/web/assets/{sqlite-viewer-VzYLMqkD.js → sqlite-viewer-CSmX3qaj.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CSmX3qaj.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-CSmX3qaj.js.gz +0 -0
- package/dist/web/assets/{system-monitor-body-BR60W_mG.js → system-monitor-body-LannpU6H.js} +2 -2
- package/dist/web/assets/system-monitor-body-LannpU6H.js.br +0 -0
- package/dist/web/assets/system-monitor-body-LannpU6H.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-BfVucwEF.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BLw1QBXo.js → system-monitor-window-content-kLNW9Ln3.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BL61SB5m.js → tab-host-window-content-II_FtOYM.js} +1 -1
- package/dist/web/assets/tab-host-window-content-II_FtOYM.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-II_FtOYM.js.gz +0 -0
- package/dist/web/assets/{tab-store-CbPh8CAX.js → tab-store-DCpc0g0Q.js} +1 -1
- package/dist/web/assets/{terminal-tab-B4y6at4h.js → terminal-tab-CTe2polt.js} +1 -1
- package/dist/web/assets/terminal-tab-CTe2polt.js.br +0 -0
- package/dist/web/assets/terminal-tab-CTe2polt.js.gz +0 -0
- package/dist/web/assets/{tool-cards-K0r_yxI0.js → tool-cards-rxLXbBSm.js} +5 -5
- package/dist/web/assets/tool-cards-rxLXbBSm.js.br +0 -0
- package/dist/web/assets/tool-cards-rxLXbBSm.js.gz +0 -0
- package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js +1 -0
- package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js.br +0 -0
- package/dist/web/assets/use-h264-canvas-decoder-BfO9QrlH.js.gz +0 -0
- package/dist/web/assets/{use-inline-blame-BK23icsE.js → use-inline-blame-CxZ_QWzS.js} +3 -3
- package/dist/web/assets/use-inline-blame-CxZ_QWzS.js.br +0 -0
- package/dist/web/assets/use-inline-blame-CxZ_QWzS.js.gz +0 -0
- package/dist/web/assets/{use-lsp-CRdCPCKW.js → use-lsp-gCNE5A9K.js} +1 -1
- package/dist/web/assets/use-lsp-gCNE5A9K.js.br +0 -0
- package/dist/web/assets/use-lsp-gCNE5A9K.js.gz +0 -0
- package/dist/web/assets/{use-monaco-theme-CpXT2uh3.js → use-monaco-theme-DvNGcCvz.js} +1 -1
- package/dist/web/assets/{use-process-kill-BWYX03KF.js → use-process-kill-Cbiu1mbY.js} +1 -1
- package/dist/web/assets/use-process-kill-Cbiu1mbY.js.br +0 -0
- package/dist/web/assets/use-process-kill-Cbiu1mbY.js.gz +0 -0
- package/dist/web/assets/{use-websocket-CiWQKhHr.js → use-websocket-AC7EG1LK.js} +1 -1
- package/dist/web/assets/video-preview-C2Y61WUE.js +1 -0
- package/dist/web/assets/video-preview-C2Y61WUE.js.br +0 -0
- package/dist/web/assets/video-preview-C2Y61WUE.js.gz +0 -0
- package/dist/web/assets/{voice-settings-section-Ceihggqf.js → voice-settings-section-DCXZeLYI.js} +1 -1
- package/dist/web/assets/voice-settings-section-DCXZeLYI.js.br +0 -0
- package/dist/web/assets/voice-settings-section-DCXZeLYI.js.gz +0 -0
- package/dist/web/assets/{window-pip-registry-mDgwpAwP.js → window-pip-registry-9Y9LQQYS.js} +1 -1
- package/dist/web/index.html +50 -7
- package/dist/web/index.html.br +0 -0
- package/dist/web/index.html.gz +0 -0
- package/dist/web/sw.js +1 -1
- package/dist/web/sw.js.br +0 -0
- package/dist/web/sw.js.gz +0 -0
- package/docs/architecture/android-emulator-phase0-report-260921.md +310 -0
- package/docs/architecture/android-emulator-phase2-report-260921.md +163 -0
- package/docs/architecture/android-emulator-phase3-report-260921.md +203 -0
- package/docs/architecture/android-emulator-phase4-report-260921.md +191 -0
- package/docs/architecture/android-emulator-plan-260920.md +376 -0
- package/docs/architecture/plugins-and-tracing.md +48 -7
- package/package.json +4 -2
- package/scripts/gen-android-proto.ts +37 -0
- package/scripts/release.sh +3 -1
- package/src/cli/commands/chat-cmd.ts +2 -2
- package/src/providers/claude-agent-sdk-query-options.ts +28 -0
- package/src/providers/claude-agent-sdk.ts +153 -71
- package/src/providers/claude-warm-spare.ts +176 -0
- package/src/providers/provider.interface.ts +1 -0
- package/src/server/index.ts +53 -0
- package/src/server/middleware/gzip-json.ts +13 -1
- package/src/server/routes/android.ts +616 -0
- package/src/server/routes/chat.ts +31 -0
- package/src/server/routes/files.ts +52 -5
- package/src/server/routes/remote-desktop.ts +95 -2
- package/src/server/routes/trace.ts +66 -0
- package/src/server/ws/android.ts +101 -0
- package/src/server/ws/chat.ts +12 -17
- package/src/server/ws/global.ts +13 -1
- package/src/server/ws/remote-desktop.ts +6 -1
- package/src/services/android/android-apk.ts +387 -0
- package/src/services/android/android-capabilities.ts +154 -0
- package/src/services/android/android-flag.ts +12 -0
- package/src/services/android/android-grpc.ts +124 -0
- package/src/services/android/android-input.ts +129 -0
- package/src/services/android/android-logcat.ts +169 -0
- package/src/services/android/android-nonce.ts +50 -0
- package/src/services/android/android-operations.ts +124 -0
- package/src/services/android/android-screenshot.ts +53 -0
- package/src/services/android/android-session.ts +510 -0
- package/src/services/android/android-video.ts +495 -0
- package/src/services/android/avd-list.ts +131 -0
- package/src/services/android/avd-manager.ts +397 -0
- package/src/services/android/device-registry.ts +223 -0
- package/src/services/android/emulator-discovery.ts +160 -0
- package/src/services/android/emulator-launcher.ts +290 -0
- package/src/services/android/proto/emulator-controller-descriptor.ts +10 -0
- package/src/services/android/proto/emulator_controller.proto +1791 -0
- package/src/services/android/sdk-discovery.ts +178 -0
- package/src/services/android/system-images.ts +124 -0
- package/src/services/chat.service.ts +81 -4
- package/src/services/config.service.ts +1 -1
- package/src/services/file-filter.service.ts +2 -61
- package/src/services/file-glob.ts +67 -0
- package/src/services/file-index/file-index-runner.ts +169 -0
- package/src/services/file-index/file-index-worker.ts +73 -0
- package/src/services/file-index/index-search.ts +152 -0
- package/src/services/file-index/index-walk.ts +109 -0
- package/src/services/file-list-index.service.ts +189 -70
- package/src/services/file-watcher/linux-inotify.ts +238 -0
- package/src/services/file-watcher/watch-tree.ts +37 -6
- package/src/services/file-watcher.service.ts +40 -8
- package/src/services/file.service.ts +17 -6
- package/src/services/group-chat/group-chat.service.ts +1 -1
- package/src/services/jira-debug-session.service.ts +1 -1
- package/src/services/ppmbot/ppmbot-delegation.ts +1 -1
- package/src/services/ppmbot/ppmbot-service.ts +1 -1
- package/src/services/project-content-search.service.ts +45 -11
- package/src/services/proxy-agent-turn.ts +7 -3
- package/src/services/remote-desktop/mediamtx-catalog.ts +89 -0
- package/src/services/remote-desktop/mediamtx-config.ts +91 -0
- package/src/services/remote-desktop/mediamtx-install.service.ts +191 -0
- package/src/services/remote-desktop/mediamtx-paths.ts +51 -0
- package/src/services/remote-desktop/mediamtx-process.ts +152 -0
- package/src/services/remote-desktop/remote-desktop-capture-wayland.ts +312 -0
- package/src/services/remote-desktop/remote-desktop-capture.ts +60 -4
- package/src/services/remote-desktop/remote-desktop-portal.ts +529 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +56 -20
- package/src/services/remote-desktop/remote-desktop-session.ts +77 -3
- package/src/services/remote-desktop/remote-desktop-whep-registry.ts +36 -0
- package/src/services/scheduler-runner.ts +2 -3
- package/src/services/session-trace/browser-trace-ingest.ts +131 -0
- package/src/services/session-trace/session-trace-cleanup.ts +65 -0
- package/src/services/session-trace/session-trace-db.ts +89 -0
- package/src/services/session-trace/session-trace-store.ts +132 -0
- package/src/services/session-trace/trace-coalescer.ts +86 -0
- package/src/services/session-trace/trace-recorder.ts +304 -0
- package/src/services/session-trace/trace-writer.ts +83 -0
- package/src/shared/android-avd.ts +53 -0
- package/src/shared/android-protocol.ts +184 -0
- package/src/shared/file-index-limits.ts +17 -0
- package/src/shared/session-trace.ts +61 -0
- package/src/types/chat.ts +17 -0
- package/src/types/config.ts +27 -0
- package/src/types/project.ts +9 -0
- package/src/web/app.tsx +5 -0
- package/src/web/components/android/android-apk-upload.ts +75 -0
- package/src/web/components/android/android-avd-create-dialog.tsx +305 -0
- package/src/web/components/android/android-avd-destroy-dialog.tsx +151 -0
- package/src/web/components/android/android-clipboard-panel.tsx +94 -0
- package/src/web/components/android/android-controls.tsx +128 -0
- package/src/web/components/android/android-coords.ts +99 -0
- package/src/web/components/android/android-device-list.tsx +329 -0
- package/src/web/components/android/android-install-panel.tsx +280 -0
- package/src/web/components/android/android-log-filter.ts +75 -0
- package/src/web/components/android/android-logcat-panel.tsx +214 -0
- package/src/web/components/android/android-screenshot-download.ts +56 -0
- package/src/web/components/android/android-tab.tsx +66 -0
- package/src/web/components/android/android-tools-drawer.tsx +76 -0
- package/src/web/components/android/android-viewer.tsx +200 -0
- package/src/web/components/android/android-ws-url.ts +21 -0
- package/src/web/components/android/open-android.ts +21 -0
- package/src/web/components/android/use-android-available.ts +29 -0
- package/src/web/components/android/use-android-keyboard.ts +111 -0
- package/src/web/components/android/use-android-session.ts +277 -0
- package/src/web/components/android/use-android-touch.ts +123 -0
- package/src/web/components/chat/chat-tab.tsx +28 -2
- package/src/web/components/chat/message-input.tsx +3 -5
- package/src/web/components/editor/compare-picker.tsx +12 -2
- package/src/web/components/explorer/file-tree.tsx +6 -7
- package/src/web/components/explorer/use-tree-row-dnd.ts +0 -2
- package/src/web/components/floating-window/window-content-registry.ts +8 -8
- package/src/web/components/layout/command-palette.tsx +31 -9
- package/src/web/components/layout/mobile-drawer.tsx +8 -1
- package/src/web/components/layout/nav-section-rail.tsx +8 -1
- package/src/web/components/layout/reparenting-tab.tsx +2 -2
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/tab-pool.tsx +31 -20
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +23 -1
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +64 -6
- package/src/web/components/remote-desktop/use-webrtc-canvas-video.ts +255 -0
- package/src/web/components/root-error-boundary.tsx +5 -0
- package/src/web/components/settings/remote-desktop-settings-section.tsx +176 -0
- package/src/web/components/settings/settings-categories.ts +3 -0
- package/src/web/components/settings/settings-section-content.tsx +25 -18
- package/src/web/hooks/use-chat-prewarm.ts +53 -0
- package/src/web/hooks/use-chat.ts +18 -4
- package/src/web/hooks/use-file-index-invalidation.ts +7 -5
- package/src/web/hooks/use-global-events.ts +12 -1
- package/src/web/hooks/use-remote-file-search.ts +39 -0
- package/src/web/hooks/use-ui-preload.ts +38 -0
- package/src/web/index.html +43 -0
- package/src/web/lib/chunk-recovery.ts +5 -1
- package/src/web/lib/device-id.ts +39 -0
- package/src/web/lib/feature-badges.ts +2 -0
- package/src/web/lib/lazy-with-preload.tsx +43 -0
- package/src/web/lib/preload-ui.ts +60 -0
- package/src/web/lib/score-file-search.ts +27 -14
- package/src/web/lib/tab-type-icons.ts +2 -1
- package/src/web/lib/trace-boot.ts +8 -0
- package/src/web/lib/trace-client.ts +516 -0
- package/src/web/main.tsx +12 -0
- package/src/web/stores/file-store.ts +124 -18
- package/src/web/stores/panel-utils.ts +3 -0
- package/src/web/stores/tab-store.ts +2 -1
- package/src/web/stores/window-panel-persistence.ts +3 -0
- package/vite.config.ts +3 -0
- package/dist/web/assets/accounts-settings-section-DEIm-W-D.js +0 -1
- package/dist/web/assets/accounts-settings-section-DEIm-W-D.js.br +0 -0
- package/dist/web/assets/accounts-settings-section-DEIm-W-D.js.gz +0 -0
- package/dist/web/assets/agent-session-sheet-CBMsnl_p.js.br +0 -0
- package/dist/web/assets/agent-session-sheet-CBMsnl_p.js.gz +0 -0
- package/dist/web/assets/agent-session-window-content-D9C9FA7E.js.br +0 -0
- package/dist/web/assets/agent-session-window-content-D9C9FA7E.js.gz +0 -0
- package/dist/web/assets/ai-resource-editor-DhUVgDEG.js.br +0 -0
- package/dist/web/assets/ai-resource-editor-DhUVgDEG.js.gz +0 -0
- package/dist/web/assets/ai-settings-section-CETpSpYd.js.br +0 -0
- package/dist/web/assets/ai-settings-section-CETpSpYd.js.gz +0 -0
- package/dist/web/assets/appearance-settings-section-BJUiLUb7.js.br +0 -0
- package/dist/web/assets/appearance-settings-section-BJUiLUb7.js.gz +0 -0
- package/dist/web/assets/apps-panel-1VElRgV3.js.br +0 -0
- package/dist/web/assets/apps-panel-1VElRgV3.js.gz +0 -0
- package/dist/web/assets/audio-preview-DYkyHu5X.js.br +0 -0
- package/dist/web/assets/audio-preview-DYkyHu5X.js.gz +0 -0
- package/dist/web/assets/branch-review-tab-DhTMSpSU.js +0 -1
- package/dist/web/assets/branch-review-tab-DhTMSpSU.js.br +0 -0
- package/dist/web/assets/branch-review-tab-DhTMSpSU.js.gz +0 -0
- package/dist/web/assets/chat-tab-Cg3BKugo.js +0 -15
- package/dist/web/assets/chat-tab-Cg3BKugo.js.br +0 -0
- package/dist/web/assets/chat-tab-Cg3BKugo.js.gz +0 -0
- package/dist/web/assets/chat-tab-tbUDK__Y.js +0 -1
- package/dist/web/assets/chat-tab-tbUDK__Y.js.br +0 -0
- package/dist/web/assets/chat-tab-tbUDK__Y.js.gz +0 -0
- package/dist/web/assets/code-editor-6ZiQehbr.js +0 -7
- package/dist/web/assets/code-editor-6ZiQehbr.js.br +0 -0
- package/dist/web/assets/code-editor-6ZiQehbr.js.gz +0 -0
- package/dist/web/assets/codex-reset-credit-button-Fr45FUxi.js.br +0 -0
- package/dist/web/assets/codex-reset-credit-button-Fr45FUxi.js.gz +0 -0
- package/dist/web/assets/conflict-editor-B-wdjSXI.js.br +0 -0
- package/dist/web/assets/conflict-editor-B-wdjSXI.js.gz +0 -0
- package/dist/web/assets/csv-preview-B6RodObB.js.br +0 -0
- package/dist/web/assets/csv-preview-B6RodObB.js.gz +0 -0
- package/dist/web/assets/database-viewer-CdPBUQPY.js.br +0 -0
- package/dist/web/assets/database-viewer-CdPBUQPY.js.gz +0 -0
- package/dist/web/assets/design-settings-section-BpCpaBwL.js.br +0 -0
- package/dist/web/assets/design-settings-section-BpCpaBwL.js.gz +0 -0
- package/dist/web/assets/design-skill-suggestion-DkT2E2IV.js.br +0 -0
- package/dist/web/assets/design-skill-suggestion-DkT2E2IV.js.gz +0 -0
- package/dist/web/assets/design-tab-BZb9eemI.js.br +0 -0
- package/dist/web/assets/design-tab-BZb9eemI.js.gz +0 -0
- package/dist/web/assets/designs-sidebar-panel-CF4tp6j-.js.br +0 -0
- package/dist/web/assets/designs-sidebar-panel-CF4tp6j-.js.gz +0 -0
- package/dist/web/assets/diff-viewer-BqEqwSw6.js.br +0 -0
- package/dist/web/assets/diff-viewer-BqEqwSw6.js.gz +0 -0
- package/dist/web/assets/edit-diff-preview-DD4HayXY.js.br +0 -0
- package/dist/web/assets/edit-diff-preview-DD4HayXY.js.gz +0 -0
- package/dist/web/assets/editor-language-service-CkA5sEdV.js.br +0 -0
- package/dist/web/assets/editor-language-service-CkA5sEdV.js.gz +0 -0
- package/dist/web/assets/explorer-body-Ds7oKuz7.js +0 -2
- package/dist/web/assets/explorer-body-Ds7oKuz7.js.br +0 -0
- package/dist/web/assets/explorer-body-Ds7oKuz7.js.gz +0 -0
- package/dist/web/assets/explorer-window-content-CRJ2eMZ4.js.br +0 -0
- package/dist/web/assets/explorer-window-content-CRJ2eMZ4.js.gz +0 -0
- package/dist/web/assets/extension-manager-section-DwQTdCII.js.br +0 -0
- package/dist/web/assets/extension-manager-section-DwQTdCII.js.gz +0 -0
- package/dist/web/assets/extension-webview-CMq-VTLu.js.br +0 -0
- package/dist/web/assets/extension-webview-CMq-VTLu.js.gz +0 -0
- package/dist/web/assets/file-store-DY1_0nzY.js +0 -1
- package/dist/web/assets/file-store-DY1_0nzY.js.br +0 -0
- package/dist/web/assets/file-store-DY1_0nzY.js.gz +0 -0
- package/dist/web/assets/files-settings-section-B2lcyh-6.js.br +0 -0
- package/dist/web/assets/files-settings-section-B2lcyh-6.js.gz +0 -0
- package/dist/web/assets/general-settings-section-Doepm2sH.js.br +0 -0
- package/dist/web/assets/general-settings-section-Doepm2sH.js.gz +0 -0
- package/dist/web/assets/git-log-panel-CJakNMDc.js.br +0 -0
- package/dist/web/assets/git-log-panel-CJakNMDc.js.gz +0 -0
- package/dist/web/assets/glide-data-grid-C_SeOhSq.js.br +0 -0
- package/dist/web/assets/glide-data-grid-C_SeOhSq.js.gz +0 -0
- package/dist/web/assets/group-chat-tab--by1xCQW.js +0 -1
- package/dist/web/assets/group-chat-tab--by1xCQW.js.br +0 -0
- package/dist/web/assets/group-chat-tab--by1xCQW.js.gz +0 -0
- package/dist/web/assets/index-DhWjx5iR.js +0 -52
- package/dist/web/assets/index-DhWjx5iR.js.br +0 -0
- package/dist/web/assets/index-DhWjx5iR.js.gz +0 -0
- package/dist/web/assets/index-oIvYSKTT.css +0 -1
- package/dist/web/assets/index-oIvYSKTT.css.br +0 -0
- package/dist/web/assets/index-oIvYSKTT.css.gz +0 -0
- package/dist/web/assets/keybindings-store-30LYZU0l.js +0 -1
- package/dist/web/assets/keyboard-shortcuts-section-BgZe3QDr.js.br +0 -0
- package/dist/web/assets/keyboard-shortcuts-section-BgZe3QDr.js.gz +0 -0
- package/dist/web/assets/language-servers-section-CCMzgOaE.js.br +0 -0
- package/dist/web/assets/language-servers-section-CCMzgOaE.js.gz +0 -0
- package/dist/web/assets/lsp-client-B10suuaZ.js.br +0 -0
- package/dist/web/assets/lsp-client-B10suuaZ.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-AHHFS3jc.js.br +0 -0
- package/dist/web/assets/markdown-renderer-AHHFS3jc.js.gz +0 -0
- package/dist/web/assets/markdown-renderer-DxnXtBoj.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CqCsLWSr.js.br +0 -0
- package/dist/web/assets/mobile-explorer-sheet-CqCsLWSr.js.gz +0 -0
- package/dist/web/assets/new-design-dialog-B9tgnQXq.js +0 -1
- package/dist/web/assets/new-design-dialog-B9tgnQXq.js.br +0 -0
- package/dist/web/assets/new-design-dialog-B9tgnQXq.js.gz +0 -0
- package/dist/web/assets/notification-store-HUVzO_bp.js +0 -1
- package/dist/web/assets/notifications-settings-section-CXgzH_bK.js.br +0 -0
- package/dist/web/assets/notifications-settings-section-CXgzH_bK.js.gz +0 -0
- package/dist/web/assets/overview-panel-BYB8mgRE.js.br +0 -0
- package/dist/web/assets/overview-panel-BYB8mgRE.js.gz +0 -0
- package/dist/web/assets/panel-store-CWHPmHwj.js +0 -1
- package/dist/web/assets/panel-store-CWHPmHwj.js.br +0 -0
- package/dist/web/assets/panel-store-CWHPmHwj.js.gz +0 -0
- package/dist/web/assets/performance-panel-Dpuq3g0W.js.br +0 -0
- package/dist/web/assets/performance-panel-Dpuq3g0W.js.gz +0 -0
- package/dist/web/assets/postgres-viewer-DW0Gkmow.js.br +0 -0
- package/dist/web/assets/postgres-viewer-DW0Gkmow.js.gz +0 -0
- package/dist/web/assets/ppmbot-settings-section-DFtlGoW6.js.br +0 -0
- package/dist/web/assets/ppmbot-settings-section-DFtlGoW6.js.gz +0 -0
- package/dist/web/assets/pptx-export-CNyG32Xr.js.br +0 -0
- package/dist/web/assets/pptx-export-CNyG32Xr.js.gz +0 -0
- package/dist/web/assets/problems-panel-V5zqpuXq.js.br +0 -0
- package/dist/web/assets/problems-panel-V5zqpuXq.js.gz +0 -0
- package/dist/web/assets/process-table-DAj46HIl.js +0 -1
- package/dist/web/assets/process-table-DAj46HIl.js.br +0 -0
- package/dist/web/assets/process-table-DAj46HIl.js.gz +0 -0
- package/dist/web/assets/proxy-settings-section-B7AVlTfY.js.br +0 -0
- package/dist/web/assets/proxy-settings-section-B7AVlTfY.js.gz +0 -0
- package/dist/web/assets/query-audit-section-gHmRfWKr.js.br +0 -0
- package/dist/web/assets/query-audit-section-gHmRfWKr.js.gz +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js +0 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js.br +0 -0
- package/dist/web/assets/remote-desktop-clipboard-notice-Cgoznh8L.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-X12QbPhO.js.gz +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js.br +0 -0
- package/dist/web/assets/remote-desktop-mobile-view-DsSHawSK.js.gz +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js +0 -1
- package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js.br +0 -0
- package/dist/web/assets/remote-desktop-readiness-gate-CHiQEd9Q.js.gz +0 -0
- package/dist/web/assets/remote-desktop-window-content-BnznIby3.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-BnznIby3.js.br +0 -0
- package/dist/web/assets/remote-desktop-window-content-BnznIby3.js.gz +0 -0
- package/dist/web/assets/schedules-settings-section-sYDAsBMO.js.br +0 -0
- package/dist/web/assets/schedules-settings-section-sYDAsBMO.js.gz +0 -0
- package/dist/web/assets/send-to-chat-CCdOrKc7.js.br +0 -0
- package/dist/web/assets/send-to-chat-CCdOrKc7.js.gz +0 -0
- package/dist/web/assets/services-panel-CKa2aP1d.js.br +0 -0
- package/dist/web/assets/services-panel-CKa2aP1d.js.gz +0 -0
- package/dist/web/assets/settings-body-CEIF8lkl.js +0 -2
- package/dist/web/assets/settings-body-CEIF8lkl.js.br +0 -0
- package/dist/web/assets/settings-body-CEIF8lkl.js.gz +0 -0
- package/dist/web/assets/settings-store-DfuiLq_9.js.br +0 -0
- package/dist/web/assets/settings-store-DfuiLq_9.js.gz +0 -0
- package/dist/web/assets/settings-tab-Du5AUPni.js +0 -1
- package/dist/web/assets/settings-window-content-BTflfLez.js +0 -1
- package/dist/web/assets/slash-command-picker-CLevA_Gf.js.br +0 -0
- package/dist/web/assets/slash-command-picker-CLevA_Gf.js.gz +0 -0
- package/dist/web/assets/sql-query-editor-Db8b0jEr.js.br +0 -0
- package/dist/web/assets/sql-query-editor-Db8b0jEr.js.gz +0 -0
- package/dist/web/assets/sqlite-viewer-VzYLMqkD.js.br +0 -0
- package/dist/web/assets/sqlite-viewer-VzYLMqkD.js.gz +0 -0
- package/dist/web/assets/system-monitor-body-BR60W_mG.js.br +0 -0
- package/dist/web/assets/system-monitor-body-BR60W_mG.js.gz +0 -0
- package/dist/web/assets/system-monitor-tab-BYpantE2.js +0 -1
- package/dist/web/assets/tab-host-window-content-BL61SB5m.js.br +0 -0
- package/dist/web/assets/tab-host-window-content-BL61SB5m.js.gz +0 -0
- package/dist/web/assets/terminal-tab-B4y6at4h.js.br +0 -0
- package/dist/web/assets/terminal-tab-B4y6at4h.js.gz +0 -0
- package/dist/web/assets/tool-cards-K0r_yxI0.js.br +0 -0
- package/dist/web/assets/tool-cards-K0r_yxI0.js.gz +0 -0
- package/dist/web/assets/use-inline-blame-BK23icsE.js.br +0 -0
- package/dist/web/assets/use-inline-blame-BK23icsE.js.gz +0 -0
- package/dist/web/assets/use-lsp-CRdCPCKW.js.br +0 -0
- package/dist/web/assets/use-lsp-CRdCPCKW.js.gz +0 -0
- package/dist/web/assets/use-process-kill-BWYX03KF.js.br +0 -0
- package/dist/web/assets/use-process-kill-BWYX03KF.js.gz +0 -0
- package/dist/web/assets/video-preview-DugVkMB7.js +0 -1
- package/dist/web/assets/video-preview-DugVkMB7.js.br +0 -0
- package/dist/web/assets/video-preview-DugVkMB7.js.gz +0 -0
- package/dist/web/assets/voice-settings-section-Ceihggqf.js.br +0 -0
- package/dist/web/assets/voice-settings-section-Ceihggqf.js.gz +0 -0
- /package/dist/web/assets/{__vite-browser-external-B_XMEguz.js → __vite-browser-external-C5mXParC.js} +0 -0
- /package/dist/web/assets/{agent-session-sheet-store-DO682WSg.js → agent-session-sheet-store-_OeezCXB.js} +0 -0
- /package/dist/web/assets/{api-settings-CpO8SPk9.js → api-settings-Cc_CpfmC.js} +0 -0
- /package/dist/web/assets/{api-settings-CpO8SPk9.js.br → api-settings-Cc_CpfmC.js.br} +0 -0
- /package/dist/web/assets/{api-settings-CpO8SPk9.js.gz → api-settings-Cc_CpfmC.js.gz} +0 -0
- /package/dist/web/assets/{api-themes-CJipDH_l.js → api-themes-BtMBSjuM.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js → can-open-in-ppm-DeE2-kd5.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js.br → can-open-in-ppm-DeE2-kd5.js.br} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Bw_xiOXX.js.gz → can-open-in-ppm-DeE2-kd5.js.gz} +0 -0
- /package/dist/web/assets/{csv-parser-DcRWxcNV.js → csv-parser-CoxPdyLw.js} +0 -0
- /package/dist/web/assets/{csv-parser-DcRWxcNV.js.br → csv-parser-CoxPdyLw.js.br} +0 -0
- /package/dist/web/assets/{csv-parser-DcRWxcNV.js.gz → csv-parser-CoxPdyLw.js.gz} +0 -0
- /package/dist/web/assets/{design-export-client-CHffN3DM.js → design-export-client-BfdZB7xK.js} +0 -0
- /package/dist/web/assets/{design-export-client-CHffN3DM.js.br → design-export-client-BfdZB7xK.js.br} +0 -0
- /package/dist/web/assets/{design-export-client-CHffN3DM.js.gz → design-export-client-BfdZB7xK.js.gz} +0 -0
- /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js → design-responsive-dialog-BiYy3ibS.js} +0 -0
- /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js.br → design-responsive-dialog-BiYy3ibS.js.br} +0 -0
- /package/dist/web/assets/{design-responsive-dialog-DSCpv6O1.js.gz → design-responsive-dialog-BiYy3ibS.js.gz} +0 -0
- /package/dist/web/assets/{design-system-status-label-B_0I_3a8.js → design-system-status-label-ekiLyjKn.js} +0 -0
- /package/dist/web/assets/{docx-preview-B3eJyY2y.js → docx-preview-Dt6RQpfA.js} +0 -0
- /package/dist/web/assets/{docx-preview-B3eJyY2y.js.br → docx-preview-Dt6RQpfA.js.br} +0 -0
- /package/dist/web/assets/{docx-preview-B3eJyY2y.js.gz → docx-preview-Dt6RQpfA.js.gz} +0 -0
- /package/dist/web/assets/{editor-font-BNwo7zo5.js → editor-font-CBszn6vt.js} +0 -0
- /package/dist/web/assets/{format-bytes-CRZfxO2J.js → format-bytes-FQmpZ25A.js} +0 -0
- /package/dist/web/assets/{image-gallery-DC3gRAqk.js → image-gallery-B16hO6Ab.js} +0 -0
- /package/dist/web/assets/{image-preview-geometry-DN3DZQp0.js → image-preview-geometry-D4Ihz4UV.js} +0 -0
- /package/dist/web/assets/{lib-B9k5tn9s.js → lib-6APiJkU3.js} +0 -0
- /package/dist/web/assets/{lib-B9k5tn9s.js.br → lib-6APiJkU3.js.br} +0 -0
- /package/dist/web/assets/{lib-B9k5tn9s.js.gz → lib-6APiJkU3.js.gz} +0 -0
- /package/dist/web/assets/{libesm-5f7T4AHA.js → libesm-5wREk7JN.js} +0 -0
- /package/dist/web/assets/{libesm-5f7T4AHA.js.br → libesm-5wREk7JN.js.br} +0 -0
- /package/dist/web/assets/{libesm-5f7T4AHA.js.gz → libesm-5wREk7JN.js.gz} +0 -0
- /package/dist/web/assets/{media-url-CCGOIxzS.js → media-url-CrT-qulZ.js} +0 -0
- /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js → metric-chart-canvas-D7f-LQ8x.js} +0 -0
- /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js.br → metric-chart-canvas-D7f-LQ8x.js.br} +0 -0
- /package/dist/web/assets/{metric-chart-canvas-CjFbZuhk.js.gz → metric-chart-canvas-D7f-LQ8x.js.gz} +0 -0
- /package/dist/web/assets/{monaco-dotenv-language-Bwq3gQsi.js → monaco-dotenv-language-DbSss7YR.js} +0 -0
- /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js → onboarding-entry-CGFdGLLP.js} +0 -0
- /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js.br → onboarding-entry-CGFdGLLP.js.br} +0 -0
- /package/dist/web/assets/{onboarding-entry-CuH1Kqwu.js.gz → onboarding-entry-CGFdGLLP.js.gz} +0 -0
- /package/dist/web/assets/{pdf-preview-X8bdpfE9.js → pdf-preview-B8A4T-ES.js} +0 -0
- /package/dist/web/assets/{pdf-preview-X8bdpfE9.js.br → pdf-preview-B8A4T-ES.js.br} +0 -0
- /package/dist/web/assets/{pdf-preview-X8bdpfE9.js.gz → pdf-preview-B8A4T-ES.js.gz} +0 -0
- /package/dist/web/assets/{separator-DMu7VFQa.js → separator-dHj3Km--.js} +0 -0
- /package/dist/web/assets/{sortable-header-CipIP7aH.js → sortable-header-BOa8o7NJ.js} +0 -0
- /package/dist/web/assets/{sortable-header-CipIP7aH.js.br → sortable-header-BOa8o7NJ.js.br} +0 -0
- /package/dist/web/assets/{sortable-header-CipIP7aH.js.gz → sortable-header-BOa8o7NJ.js.gz} +0 -0
- /package/dist/web/assets/{use-blob-url-DjuRQWnR.js → use-blob-url-BC69n15x.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js → vendor-xterm-CfasyE0m.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js.br → vendor-xterm-CfasyE0m.js.br} +0 -0
- /package/dist/web/assets/{vendor-xterm-Be4L1NHd.js.gz → vendor-xterm-CfasyE0m.js.gz} +0 -0
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An `org.freedesktop.portal.ScreenCast` session, spoken over libdbus through `bun:ffi`.
|
|
3
|
+
*
|
|
4
|
+
* This is the only way to capture a Wayland screen: there is no `x11grab` equivalent, and a
|
|
5
|
+
* compositor will not hand pixels to anything that has not been through the portal. The portal
|
|
6
|
+
* is D-Bus, and Bun has no D-Bus — hence FFI against `libdbus-1.so.3`, which is present on any
|
|
7
|
+
* host running a desktop session (it is what the session bus itself is built on).
|
|
8
|
+
*
|
|
9
|
+
* Three things here are load-bearing and each was measured rather than reasoned about:
|
|
10
|
+
*
|
|
11
|
+
* 1. **`OpenPipeWireRemote` is deliberately never called.** The documented flow ends by asking
|
|
12
|
+
* the portal for a PipeWire file descriptor, and an fd cannot be passed to a *separate*
|
|
13
|
+
* process by any shell-out (`busctl`, `gdbus`) — which is what made this look impossible.
|
|
14
|
+
* It turns out not to be needed: the node the portal creates lives in the user's own
|
|
15
|
+
* PipeWire daemon, so `pipewiresrc path=<node>` reaches it through the ordinary
|
|
16
|
+
* `$XDG_RUNTIME_DIR/pipewire-0` socket. Measured: a plain `pipewiresrc path=N` negotiated
|
|
17
|
+
* `BGRA 1920x1080` with no fd anywhere in the pipeline.
|
|
18
|
+
*
|
|
19
|
+
* 2. **The connection must stay open for the node's whole life.** A portal session is owned by
|
|
20
|
+
* the D-Bus connection that created it; when that connection drops, the compositor tears the
|
|
21
|
+
* session down and the node disappears. So this returns a handle that owns the connection,
|
|
22
|
+
* and it must outlive the capture process — not be created per call and closed.
|
|
23
|
+
*
|
|
24
|
+
* 3. **`persist_mode: 2` + `restore_token`** is what stops the consent dialog appearing on the
|
|
25
|
+
* host's screen for every session. That matters more here than it would for a screen
|
|
26
|
+
* recorder: this is a *remote* desktop, so a prompt that only the person sitting at the
|
|
27
|
+
* machine can answer is a prompt nobody can answer. The first session still asks — there is
|
|
28
|
+
* no way around that, and there should not be — and every session after it restores
|
|
29
|
+
* silently. Verified: a second session went straight to a live node with no prompt.
|
|
30
|
+
*/
|
|
31
|
+
import { CString, dlopen, FFIType, ptr, suffix, type Pointer } from "bun:ffi";
|
|
32
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
33
|
+
import { dirname, resolve } from "node:path";
|
|
34
|
+
import { getPpmDir } from "../ppm-dir.ts";
|
|
35
|
+
|
|
36
|
+
const BUS = "org.freedesktop.portal.Desktop";
|
|
37
|
+
const OBJ = "/org/freedesktop/portal/desktop";
|
|
38
|
+
const SCREENCAST = "org.freedesktop.portal.ScreenCast";
|
|
39
|
+
const REQUEST = "org.freedesktop.portal.Request";
|
|
40
|
+
|
|
41
|
+
// D-Bus type codes, as the ASCII of their signature characters.
|
|
42
|
+
const T_STRING = 115; // 's'
|
|
43
|
+
const T_ARRAY = 97; // 'a'
|
|
44
|
+
const T_VARIANT = 118; // 'v'
|
|
45
|
+
const T_DICT_ENTRY = 101; // 'e'
|
|
46
|
+
const T_UINT32 = 117; // 'u'
|
|
47
|
+
const T_BOOLEAN = 98; // 'b'
|
|
48
|
+
const T_OBJECT_PATH = 111; // 'o'
|
|
49
|
+
const T_INVALID = 0;
|
|
50
|
+
const T_STRUCT = 114; // 'r'
|
|
51
|
+
|
|
52
|
+
const DBUS_BUS_SESSION = 0;
|
|
53
|
+
/** `DBusMessageIter` is 72 bytes on x86-64; `DBusError` 32. Both are over-allocated rather than
|
|
54
|
+
* computed, because getting either too small corrupts the stack of the C library silently. */
|
|
55
|
+
const ITER_BYTES = 128;
|
|
56
|
+
const ERR_BYTES = 64;
|
|
57
|
+
|
|
58
|
+
/** Source types: 1 = MONITOR, 2 = WINDOW, 4 = VIRTUAL. Only whole monitors are asked for. */
|
|
59
|
+
const SOURCE_MONITOR = 1;
|
|
60
|
+
/** Cursor modes: 1 = HIDDEN, 2 = EMBEDDED (drawn into the frames), 4 = METADATA (sent apart).
|
|
61
|
+
* EMBEDDED is what `-draw_mouse` gives on the other platforms, so the two agree. */
|
|
62
|
+
const CURSOR_EMBEDDED = 2;
|
|
63
|
+
const CURSOR_HIDDEN = 1;
|
|
64
|
+
/** 2 = the permission persists until the user revokes it, which is what yields a restore token. */
|
|
65
|
+
const PERSIST_UNTIL_REVOKED = 2;
|
|
66
|
+
|
|
67
|
+
type Ptr = Pointer;
|
|
68
|
+
|
|
69
|
+
let lib: ReturnType<typeof openLibdbus> | null = null;
|
|
70
|
+
/** Anything whose address has been handed to C stays referenced here for the call's duration —
|
|
71
|
+
* a `Buffer` collected while libdbus still holds its pointer is a use-after-free, and Bun's GC
|
|
72
|
+
* has no idea the pointer escaped. */
|
|
73
|
+
let pinned: unknown[] = [];
|
|
74
|
+
|
|
75
|
+
function openLibdbus() {
|
|
76
|
+
return dlopen(`libdbus-1.${suffix}.3`, {
|
|
77
|
+
dbus_error_init: { args: [FFIType.ptr], returns: FFIType.void },
|
|
78
|
+
dbus_error_is_set: { args: [FFIType.ptr], returns: FFIType.i32 },
|
|
79
|
+
dbus_error_free: { args: [FFIType.ptr], returns: FFIType.void },
|
|
80
|
+
dbus_bus_get_private: { args: [FFIType.i32, FFIType.ptr], returns: FFIType.ptr },
|
|
81
|
+
dbus_bus_get_unique_name: { args: [FFIType.ptr], returns: FFIType.cstring },
|
|
82
|
+
dbus_bus_add_match: { args: [FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.void },
|
|
83
|
+
dbus_connection_close: { args: [FFIType.ptr], returns: FFIType.void },
|
|
84
|
+
dbus_connection_unref: { args: [FFIType.ptr], returns: FFIType.void },
|
|
85
|
+
dbus_connection_read_write: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
|
|
86
|
+
dbus_connection_pop_message: { args: [FFIType.ptr], returns: FFIType.ptr },
|
|
87
|
+
dbus_connection_send_with_reply_and_block: {
|
|
88
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.i32, FFIType.ptr], returns: FFIType.ptr,
|
|
89
|
+
},
|
|
90
|
+
dbus_message_new_method_call: {
|
|
91
|
+
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.ptr,
|
|
92
|
+
},
|
|
93
|
+
dbus_message_unref: { args: [FFIType.ptr], returns: FFIType.void },
|
|
94
|
+
dbus_message_is_signal: { args: [FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
|
|
95
|
+
dbus_message_get_path: { args: [FFIType.ptr], returns: FFIType.cstring },
|
|
96
|
+
dbus_message_iter_init: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
|
|
97
|
+
dbus_message_iter_init_append: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
|
|
98
|
+
dbus_message_iter_append_basic: { args: [FFIType.ptr, FFIType.i32, FFIType.ptr], returns: FFIType.i32 },
|
|
99
|
+
dbus_message_iter_open_container: {
|
|
100
|
+
args: [FFIType.ptr, FFIType.i32, FFIType.ptr, FFIType.ptr], returns: FFIType.i32,
|
|
101
|
+
},
|
|
102
|
+
dbus_message_iter_close_container: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.i32 },
|
|
103
|
+
dbus_message_iter_get_arg_type: { args: [FFIType.ptr], returns: FFIType.i32 },
|
|
104
|
+
dbus_message_iter_recurse: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
|
|
105
|
+
dbus_message_iter_get_basic: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
|
|
106
|
+
dbus_message_iter_next: { args: [FFIType.ptr], returns: FFIType.i32 },
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** libdbus, or null on a host without it (no desktop session — there is nothing to capture
|
|
111
|
+
* there anyway, so this is reported as "unavailable", never thrown past the caller). */
|
|
112
|
+
function dbus() {
|
|
113
|
+
if (lib) return lib.symbols;
|
|
114
|
+
try {
|
|
115
|
+
lib = openLibdbus();
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return lib.symbols;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const cstr = (s: string) => {
|
|
123
|
+
const b = Buffer.from(s + "\0", "utf8");
|
|
124
|
+
pinned.push(b);
|
|
125
|
+
return ptr(b);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** A pointer-sized cell holding the address of `value`, for the `void*` out/in params libdbus
|
|
129
|
+
* takes for every basic type. */
|
|
130
|
+
const cell = (value: Ptr) => {
|
|
131
|
+
const b = new BigUint64Array([BigInt(value)]);
|
|
132
|
+
pinned.push(b);
|
|
133
|
+
return ptr(b);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const scratch = (bytes: number) => {
|
|
137
|
+
const b = new Uint8Array(bytes);
|
|
138
|
+
pinned.push(b);
|
|
139
|
+
return b;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/** The variant types this client ever sends. */
|
|
143
|
+
type VariantValue =
|
|
144
|
+
| { t: "s"; v: string }
|
|
145
|
+
| { t: "u"; v: number }
|
|
146
|
+
| { t: "b"; v: boolean };
|
|
147
|
+
|
|
148
|
+
/** Append `entries` to `iter` as an `a{sv}` — the options dictionary every portal method takes. */
|
|
149
|
+
function appendOptions(d: NonNullable<ReturnType<typeof dbus>>, iter: Uint8Array, entries: Record<string, VariantValue>) {
|
|
150
|
+
const arr = scratch(ITER_BYTES);
|
|
151
|
+
d.dbus_message_iter_open_container(ptr(iter), T_ARRAY, cstr("{sv}"), ptr(arr));
|
|
152
|
+
for (const [key, val] of Object.entries(entries)) {
|
|
153
|
+
const ent = scratch(ITER_BYTES);
|
|
154
|
+
d.dbus_message_iter_open_container(ptr(arr), T_DICT_ENTRY, null as unknown as number, ptr(ent));
|
|
155
|
+
d.dbus_message_iter_append_basic(ptr(ent), T_STRING, cell(cstr(key)));
|
|
156
|
+
const varIter = scratch(ITER_BYTES);
|
|
157
|
+
d.dbus_message_iter_open_container(ptr(ent), T_VARIANT, cstr(val.t), ptr(varIter));
|
|
158
|
+
if (val.t === "s") {
|
|
159
|
+
d.dbus_message_iter_append_basic(ptr(varIter), T_STRING, cell(cstr(val.v)));
|
|
160
|
+
} else if (val.t === "u") {
|
|
161
|
+
const n = new Uint32Array([val.v]);
|
|
162
|
+
pinned.push(n);
|
|
163
|
+
d.dbus_message_iter_append_basic(ptr(varIter), T_UINT32, ptr(n));
|
|
164
|
+
} else {
|
|
165
|
+
// D-Bus booleans marshal as uint32 0/1, not as a byte.
|
|
166
|
+
const n = new Uint32Array([val.v ? 1 : 0]);
|
|
167
|
+
pinned.push(n);
|
|
168
|
+
d.dbus_message_iter_append_basic(ptr(varIter), T_BOOLEAN, ptr(n));
|
|
169
|
+
}
|
|
170
|
+
d.dbus_message_iter_close_container(ptr(ent), ptr(varIter));
|
|
171
|
+
d.dbus_message_iter_close_container(ptr(arr), ptr(ent));
|
|
172
|
+
}
|
|
173
|
+
d.dbus_message_iter_close_container(ptr(iter), ptr(arr));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Read a basic value out of `iter` according to its own arg type. Returns null for anything
|
|
177
|
+
* this client does not need (nested containers other than the ones handled by the callers). */
|
|
178
|
+
function readBasic(d: NonNullable<ReturnType<typeof dbus>>, iter: Uint8Array): string | number | boolean | null {
|
|
179
|
+
const t = d.dbus_message_iter_get_arg_type(ptr(iter));
|
|
180
|
+
if (t === T_STRING || t === T_OBJECT_PATH) {
|
|
181
|
+
const out = new BigUint64Array(1);
|
|
182
|
+
d.dbus_message_iter_get_basic(ptr(iter), ptr(out));
|
|
183
|
+
const addr = out[0];
|
|
184
|
+
if (!addr) return null;
|
|
185
|
+
return new CString(Number(addr) as Pointer).toString();
|
|
186
|
+
}
|
|
187
|
+
if (t === T_UINT32) {
|
|
188
|
+
const out = new Uint32Array(1);
|
|
189
|
+
d.dbus_message_iter_get_basic(ptr(iter), ptr(out));
|
|
190
|
+
return out[0] ?? null;
|
|
191
|
+
}
|
|
192
|
+
if (t === T_BOOLEAN) {
|
|
193
|
+
const out = new Uint32Array(1);
|
|
194
|
+
d.dbus_message_iter_get_basic(ptr(iter), ptr(out));
|
|
195
|
+
return out[0] === 1;
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Walk an `a{sv}` at `iter` and return the keys `wanted` asks for, as raw iterators resolved
|
|
201
|
+
* by `read`. Keys not in `wanted` are skipped without being decoded. */
|
|
202
|
+
function readOptions<T>(
|
|
203
|
+
d: NonNullable<ReturnType<typeof dbus>>,
|
|
204
|
+
iter: Uint8Array,
|
|
205
|
+
read: (key: string, value: Uint8Array) => void,
|
|
206
|
+
): void {
|
|
207
|
+
if (d.dbus_message_iter_get_arg_type(ptr(iter)) !== T_ARRAY) return;
|
|
208
|
+
const arr = scratch(ITER_BYTES);
|
|
209
|
+
d.dbus_message_iter_recurse(ptr(iter), ptr(arr));
|
|
210
|
+
while (d.dbus_message_iter_get_arg_type(ptr(arr)) === T_DICT_ENTRY) {
|
|
211
|
+
const ent = scratch(ITER_BYTES);
|
|
212
|
+
d.dbus_message_iter_recurse(ptr(arr), ptr(ent));
|
|
213
|
+
const key = readBasic(d, ent);
|
|
214
|
+
d.dbus_message_iter_next(ptr(ent));
|
|
215
|
+
const varIter = scratch(ITER_BYTES);
|
|
216
|
+
if (d.dbus_message_iter_get_arg_type(ptr(ent)) === T_VARIANT) {
|
|
217
|
+
d.dbus_message_iter_recurse(ptr(ent), ptr(varIter));
|
|
218
|
+
if (typeof key === "string") read(key, varIter);
|
|
219
|
+
}
|
|
220
|
+
d.dbus_message_iter_next(ptr(arr));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Where the restore token is kept. `getPpmDir()` rather than `homedir()`: under a test
|
|
225
|
+
* `PPM_HOME` this must not read or write the user's real grant. */
|
|
226
|
+
function restoreTokenPath(): string {
|
|
227
|
+
return resolve(getPpmDir(), "remote-desktop", "portal-restore-token");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function readRestoreToken(): string | null {
|
|
231
|
+
try {
|
|
232
|
+
const t = readFileSync(restoreTokenPath(), "utf8").trim();
|
|
233
|
+
return t || null;
|
|
234
|
+
} catch {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function saveRestoreToken(token: string): void {
|
|
240
|
+
try {
|
|
241
|
+
const p = restoreTokenPath();
|
|
242
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
243
|
+
// 0600: the token is a standing grant to capture this user's screen.
|
|
244
|
+
writeFileSync(p, token, { mode: 0o600 });
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.warn(`[remote-desktop] could not save the portal restore token: ${(e as Error).message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Forget the stored grant, so the next session asks the user again. */
|
|
251
|
+
export function clearPortalRestoreToken(): void {
|
|
252
|
+
try {
|
|
253
|
+
writeFileSync(restoreTokenPath(), "", { mode: 0o600 });
|
|
254
|
+
} catch {
|
|
255
|
+
/* nothing stored yet */
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface PortalScreenCast {
|
|
260
|
+
/** The PipeWire node to hand to `pipewiresrc path=`. */
|
|
261
|
+
nodeId: number;
|
|
262
|
+
width: number | null;
|
|
263
|
+
height: number | null;
|
|
264
|
+
/** Close the session and drop the connection. The node dies with it — call only once the
|
|
265
|
+
* capture process has exited, or GStreamer loses its source mid-stream. */
|
|
266
|
+
stop(): void;
|
|
267
|
+
isStopped(): boolean;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export class PortalUnavailableError extends Error {
|
|
271
|
+
constructor(msg: string) {
|
|
272
|
+
super(msg);
|
|
273
|
+
this.name = "PortalUnavailableError";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export interface PortalOptions {
|
|
278
|
+
/** Draw the host pointer into the frames, matching `-draw_mouse` elsewhere. */
|
|
279
|
+
drawMouse?: boolean;
|
|
280
|
+
/** How long to wait for the user to answer the consent dialog, first time round. */
|
|
281
|
+
timeoutMs?: number;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Open a ScreenCast session and return its PipeWire node.
|
|
286
|
+
*
|
|
287
|
+
* Rejects with `PortalUnavailableError` when there is no libdbus, no portal on the bus, or the
|
|
288
|
+
* user dismissed the dialog — all three are ordinary states on a host that simply has not been
|
|
289
|
+
* set up, so the caller reports them as an unmet requirement rather than a crash.
|
|
290
|
+
*/
|
|
291
|
+
export async function startPortalScreenCast(opts: PortalOptions = {}): Promise<PortalScreenCast> {
|
|
292
|
+
const d = dbus();
|
|
293
|
+
if (!d) throw new PortalUnavailableError("libdbus-1 is not installed on this host");
|
|
294
|
+
|
|
295
|
+
const err = scratch(ERR_BYTES);
|
|
296
|
+
d.dbus_error_init(ptr(err));
|
|
297
|
+
// A *private* connection: a shared one is reference-counted process-wide, so closing it
|
|
298
|
+
// could tear down a session belonging to another caller, and not closing it would leak the
|
|
299
|
+
// portal session for the life of PPM.
|
|
300
|
+
const conn = d.dbus_bus_get_private(DBUS_BUS_SESSION, ptr(err));
|
|
301
|
+
if (!conn) throw new PortalUnavailableError("no D-Bus session bus (is a desktop session running?)");
|
|
302
|
+
|
|
303
|
+
let closed = false;
|
|
304
|
+
const close = () => {
|
|
305
|
+
if (closed) return;
|
|
306
|
+
closed = true;
|
|
307
|
+
try {
|
|
308
|
+
d.dbus_connection_close(conn);
|
|
309
|
+
d.dbus_connection_unref(conn);
|
|
310
|
+
} catch { /* already gone */ }
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const unique = String(d.dbus_bus_get_unique_name(conn) ?? "");
|
|
315
|
+
if (!unique) throw new PortalUnavailableError("the D-Bus connection has no unique name");
|
|
316
|
+
// ":1.234" -> "1_234", which is how the portal builds the Request object path.
|
|
317
|
+
const sender = unique.replace(/^:/, "").replace(/\./g, "_");
|
|
318
|
+
|
|
319
|
+
d.dbus_bus_add_match(conn, cstr(`type='signal',interface='${REQUEST}',member='Response'`), ptr(err));
|
|
320
|
+
|
|
321
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 120_000);
|
|
322
|
+
const call = (method: string, build: (iter: Uint8Array, handleToken: string) => void) =>
|
|
323
|
+
portalCall(d, conn, sender, method, build, deadline);
|
|
324
|
+
|
|
325
|
+
const created = call("CreateSession", (iter, handleToken) => {
|
|
326
|
+
appendOptions(d, iter, {
|
|
327
|
+
handle_token: { t: "s", v: handleToken },
|
|
328
|
+
session_handle_token: { t: "s", v: `ppm${randomToken()}` },
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
const session = stringField(await created, "session_handle");
|
|
332
|
+
if (!session) throw new PortalUnavailableError("the portal returned no session handle");
|
|
333
|
+
|
|
334
|
+
const restore = readRestoreToken();
|
|
335
|
+
await call("SelectSources", (iter, handleToken) => {
|
|
336
|
+
appendPath(d, iter, session);
|
|
337
|
+
appendOptions(d, iter, {
|
|
338
|
+
handle_token: { t: "s", v: handleToken },
|
|
339
|
+
types: { t: "u", v: SOURCE_MONITOR },
|
|
340
|
+
multiple: { t: "b", v: false },
|
|
341
|
+
cursor_mode: { t: "u", v: opts.drawMouse === false ? CURSOR_HIDDEN : CURSOR_EMBEDDED },
|
|
342
|
+
persist_mode: { t: "u", v: PERSIST_UNTIL_REVOKED },
|
|
343
|
+
...(restore ? { restore_token: { t: "s" as const, v: restore } } : {}),
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
const started = await call("Start", (iter, handleToken) => {
|
|
348
|
+
appendPath(d, iter, session);
|
|
349
|
+
// parent_window: an X11/Wayland handle the portal would parent its dialog to. PPM has no
|
|
350
|
+
// window on the host, so this is empty — the dialog appears unparented, which is correct.
|
|
351
|
+
d.dbus_message_iter_append_basic(ptr(iter), T_STRING, cell(cstr("")));
|
|
352
|
+
appendOptions(d, iter, { handle_token: { t: "s", v: handleToken } });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const token = stringField(started, "restore_token");
|
|
356
|
+
if (token) saveRestoreToken(token);
|
|
357
|
+
|
|
358
|
+
const stream = firstStream(started);
|
|
359
|
+
if (!stream) {
|
|
360
|
+
throw new PortalUnavailableError(
|
|
361
|
+
"the portal granted no stream — the screen-share dialog was dismissed",
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
nodeId: stream.nodeId,
|
|
367
|
+
width: stream.width,
|
|
368
|
+
height: stream.height,
|
|
369
|
+
stop: close,
|
|
370
|
+
isStopped: () => closed,
|
|
371
|
+
};
|
|
372
|
+
} catch (e) {
|
|
373
|
+
close();
|
|
374
|
+
throw e;
|
|
375
|
+
} finally {
|
|
376
|
+
pinned = [];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** A handle token unique to this process and call. Only has to be unique within our own
|
|
381
|
+
* sender name, which the portal already scopes the Request path by. */
|
|
382
|
+
function randomToken(): string {
|
|
383
|
+
return crypto.randomUUID().replace(/-/g, "").slice(0, 16);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function appendPath(d: NonNullable<ReturnType<typeof dbus>>, iter: Uint8Array, path: string): void {
|
|
387
|
+
d.dbus_message_iter_append_basic(ptr(iter), T_OBJECT_PATH, cell(cstr(path)));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Invoke one portal method and wait for its `Response` signal.
|
|
392
|
+
*
|
|
393
|
+
* Every ScreenCast method answers immediately with a Request object path and delivers the real
|
|
394
|
+
* answer later as a signal on it — so the match rule has to be in place *before* the call, and
|
|
395
|
+
* the path is derived from the `handle_token` we choose rather than read from the reply (the
|
|
396
|
+
* portal spec documents that derivation precisely so a client can subscribe first; racing the
|
|
397
|
+
* reply is how a fast portal's signal gets missed).
|
|
398
|
+
*/
|
|
399
|
+
async function portalCall(
|
|
400
|
+
d: NonNullable<ReturnType<typeof dbus>>,
|
|
401
|
+
conn: Ptr,
|
|
402
|
+
sender: string,
|
|
403
|
+
method: string,
|
|
404
|
+
build: (iter: Uint8Array, handleToken: string) => void,
|
|
405
|
+
deadline: number,
|
|
406
|
+
): Promise<Record<string, unknown>> {
|
|
407
|
+
const token = `ppm${randomToken()}`;
|
|
408
|
+
const wantPath = `/org/freedesktop/portal/desktop/request/${sender}/${token}`;
|
|
409
|
+
|
|
410
|
+
const msg = d.dbus_message_new_method_call(cstr(BUS), cstr(OBJ), cstr(SCREENCAST), cstr(method));
|
|
411
|
+
if (!msg) throw new PortalUnavailableError(`could not build the ${method} call`);
|
|
412
|
+
const iter = scratch(ITER_BYTES);
|
|
413
|
+
d.dbus_message_iter_init_append(msg, ptr(iter));
|
|
414
|
+
build(iter, token);
|
|
415
|
+
// `build` receives the token and must put it in the options dict as `handle_token`: it has
|
|
416
|
+
// to match the path subscribed above, or the Response is delivered somewhere nothing is
|
|
417
|
+
// listening and the call hangs until the deadline. `session_handle_token` on CreateSession
|
|
418
|
+
// is a *different* token naming the session, not the request.
|
|
419
|
+
const err = scratch(ERR_BYTES);
|
|
420
|
+
d.dbus_error_init(ptr(err));
|
|
421
|
+
const reply = d.dbus_connection_send_with_reply_and_block(conn, msg, 25_000, ptr(err));
|
|
422
|
+
d.dbus_message_unref(msg);
|
|
423
|
+
if (!reply) throw new PortalUnavailableError(`${method} was refused by the portal`);
|
|
424
|
+
d.dbus_message_unref(reply);
|
|
425
|
+
|
|
426
|
+
for (;;) {
|
|
427
|
+
if (Date.now() > deadline) {
|
|
428
|
+
throw new PortalUnavailableError(
|
|
429
|
+
`${method} timed out — the screen-share dialog on the host was not answered`,
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
// 200 ms so a dismissed dialog still unblocks the loop promptly; this runs on the event
|
|
433
|
+
// loop, and a long block here would stall the whole server.
|
|
434
|
+
d.dbus_connection_read_write(conn, 200);
|
|
435
|
+
for (;;) {
|
|
436
|
+
const sig = d.dbus_connection_pop_message(conn);
|
|
437
|
+
if (!sig) break;
|
|
438
|
+
const isResponse = d.dbus_message_is_signal(sig, cstr(REQUEST), cstr("Response")) === 1;
|
|
439
|
+
const path = isResponse ? String(d.dbus_message_get_path(sig) ?? "") : "";
|
|
440
|
+
if (isResponse && path === wantPath) {
|
|
441
|
+
const out = decodeResponse(d, sig);
|
|
442
|
+
d.dbus_message_unref(sig);
|
|
443
|
+
if (out.code !== 0) {
|
|
444
|
+
throw new PortalUnavailableError(
|
|
445
|
+
out.code === 1
|
|
446
|
+
? `${method} was cancelled on the host (the dialog was dismissed)`
|
|
447
|
+
: `${method} failed with portal code ${out.code}`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
return out.results;
|
|
451
|
+
}
|
|
452
|
+
d.dbus_message_unref(sig);
|
|
453
|
+
}
|
|
454
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/** A Response signal is `(u a{sv})`: a result code then the method's own fields. */
|
|
459
|
+
function decodeResponse(
|
|
460
|
+
d: NonNullable<ReturnType<typeof dbus>>,
|
|
461
|
+
msg: Ptr,
|
|
462
|
+
): { code: number; results: Record<string, unknown> } {
|
|
463
|
+
const iter = scratch(ITER_BYTES);
|
|
464
|
+
if (d.dbus_message_iter_init(msg, ptr(iter)) !== 1) return { code: -1, results: {} };
|
|
465
|
+
const code = readBasic(d, iter);
|
|
466
|
+
d.dbus_message_iter_next(ptr(iter));
|
|
467
|
+
const results: Record<string, unknown> = {};
|
|
468
|
+
readOptions(d, iter, (key, value) => {
|
|
469
|
+
results[key] = key === "streams" ? decodeStreams(d, value) : readBasic(d, value);
|
|
470
|
+
});
|
|
471
|
+
return { code: typeof code === "number" ? code : -1, results };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/** `streams` is `a(ua{sv})` — one entry per shared monitor, each a node id plus properties.
|
|
475
|
+
* Only `size` is read out of the properties; `position` matters for a multi-monitor crop,
|
|
476
|
+
* which the portal does not offer (it shares whole outputs, already cropped). */
|
|
477
|
+
function decodeStreams(
|
|
478
|
+
d: NonNullable<ReturnType<typeof dbus>>,
|
|
479
|
+
iter: Uint8Array,
|
|
480
|
+
): Array<{ nodeId: number; width: number | null; height: number | null }> {
|
|
481
|
+
const out: Array<{ nodeId: number; width: number | null; height: number | null }> = [];
|
|
482
|
+
if (d.dbus_message_iter_get_arg_type(ptr(iter)) !== T_ARRAY) return out;
|
|
483
|
+
const arr = scratch(ITER_BYTES);
|
|
484
|
+
d.dbus_message_iter_recurse(ptr(iter), ptr(arr));
|
|
485
|
+
while (d.dbus_message_iter_get_arg_type(ptr(arr)) === T_STRUCT) {
|
|
486
|
+
const st = scratch(ITER_BYTES);
|
|
487
|
+
d.dbus_message_iter_recurse(ptr(arr), ptr(st));
|
|
488
|
+
const nodeId = readBasic(d, st);
|
|
489
|
+
d.dbus_message_iter_next(ptr(st));
|
|
490
|
+
let width: number | null = null;
|
|
491
|
+
let height: number | null = null;
|
|
492
|
+
readOptions(d, st, (key, value) => {
|
|
493
|
+
if (key !== "size") return;
|
|
494
|
+
// `size` is `(ii)`, a struct of two int32 — read through the same recurse.
|
|
495
|
+
if (d.dbus_message_iter_get_arg_type(ptr(value)) !== T_STRUCT) return;
|
|
496
|
+
const sz = scratch(ITER_BYTES);
|
|
497
|
+
d.dbus_message_iter_recurse(ptr(value), ptr(sz));
|
|
498
|
+
const w = new Int32Array(1);
|
|
499
|
+
d.dbus_message_iter_get_basic(ptr(sz), ptr(w));
|
|
500
|
+
d.dbus_message_iter_next(ptr(sz));
|
|
501
|
+
const h = new Int32Array(1);
|
|
502
|
+
d.dbus_message_iter_get_basic(ptr(sz), ptr(h));
|
|
503
|
+
width = w[0] || null;
|
|
504
|
+
height = h[0] || null;
|
|
505
|
+
});
|
|
506
|
+
if (typeof nodeId === "number") out.push({ nodeId, width, height });
|
|
507
|
+
d.dbus_message_iter_next(ptr(arr));
|
|
508
|
+
}
|
|
509
|
+
return out;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function stringField(results: Record<string, unknown>, key: string): string | null {
|
|
513
|
+
const v = results[key];
|
|
514
|
+
return typeof v === "string" && v ? v : null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function firstStream(
|
|
518
|
+
results: Record<string, unknown>,
|
|
519
|
+
): { nodeId: number; width: number | null; height: number | null } | null {
|
|
520
|
+
const streams = results.streams;
|
|
521
|
+
if (!Array.isArray(streams) || streams.length === 0) return null;
|
|
522
|
+
return streams[0] as { nodeId: number; width: number | null; height: number | null };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Is there a portal on this host at all? Used by the requirements checklist, which must be
|
|
526
|
+
* able to say "install xdg-desktop-portal" rather than failing at capture time. */
|
|
527
|
+
export function portalLibraryPresent(): boolean {
|
|
528
|
+
return dbus() !== null;
|
|
529
|
+
}
|
|
@@ -15,6 +15,7 @@ import { audioSupport, type AudioSupport } from "./remote-desktop-audio.ts";
|
|
|
15
15
|
import { privacySupport, type PrivacySupport } from "./remote-desktop-privacy.ts";
|
|
16
16
|
import { listHostResolutions, type HostResolutions } from "./remote-desktop-resolution.ts";
|
|
17
17
|
import { detectLinuxSession, type LinuxSession } from "./remote-desktop-linux-session.ts";
|
|
18
|
+
import { gstElements, type GstElements } from "./remote-desktop-capture-wayland.ts";
|
|
18
19
|
import { getX11 } from "./remote-desktop-x11.ts";
|
|
19
20
|
import {
|
|
20
21
|
MAC_PERMISSION_SETTINGS_URL,
|
|
@@ -131,22 +132,60 @@ function macPermissionRequirement(id: MacPermissionId, granted: boolean): Remote
|
|
|
131
132
|
};
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
/**
|
|
136
|
+
* What a Wayland host still needs for capture.
|
|
137
|
+
*
|
|
138
|
+
* Wayland used to be reported here as a hard blocker telling the user to log out and pick X11.
|
|
139
|
+
* It is not one any more: the desktop portal hands out a PipeWire node and GStreamer reads it
|
|
140
|
+
* (`remote-desktop-capture-wayland.ts`). What *can* be missing is the GStreamer side, and it is
|
|
141
|
+
* split into two rows because they are two different packages with two different install
|
|
142
|
+
* commands — a single "GStreamer" row would name a package that is already installed on a host
|
|
143
|
+
* missing only the other one.
|
|
144
|
+
*
|
|
145
|
+
* `pipewiresrc` is the one that decides whether capture is possible at all; the encoder row is
|
|
146
|
+
* separate because a host can have the source and no H.264 encoder, which fails later and for a
|
|
147
|
+
* reason the first row's install command cannot fix.
|
|
148
|
+
*/
|
|
149
|
+
function waylandCaptureRequirements(els: GstElements): RemoteDesktopRequirement[] {
|
|
150
|
+
const rows: RemoteDesktopRequirement[] = [];
|
|
151
|
+
const haveSource = !!els.launch && els.pipewiresrc;
|
|
152
|
+
rows.push({
|
|
153
|
+
id: "gst-pipewire",
|
|
154
|
+
ok: haveSource,
|
|
143
155
|
gates: "video",
|
|
144
|
-
title: "
|
|
145
|
-
detail: "
|
|
146
|
-
+ "
|
|
147
|
-
+ "
|
|
148
|
-
actions: [],
|
|
149
|
-
};
|
|
156
|
+
title: "GStreamer PipeWire capture",
|
|
157
|
+
detail: "A Wayland compositor only shares the screen through the desktop portal, which hands "
|
|
158
|
+
+ "back a PipeWire stream. PPM reads it with GStreamer — the same way RustDesk does. "
|
|
159
|
+
+ "Install the tools and the PipeWire plugin, then come back; PPM re-checks automatically.",
|
|
160
|
+
actions: [linuxInstallAction(gstPipewirePackages())].filter((a): a is RequirementAction => a !== undefined),
|
|
161
|
+
});
|
|
162
|
+
if (haveSource && !els.vah264enc && !els.x264enc) {
|
|
163
|
+
rows.push({
|
|
164
|
+
id: "gst-h264",
|
|
165
|
+
ok: false,
|
|
166
|
+
gates: "video",
|
|
167
|
+
title: "A GStreamer H.264 encoder",
|
|
168
|
+
detail: "The screen is captured but nothing can encode it. `vah264enc` uses the GPU and is "
|
|
169
|
+
+ "much cheaper; `x264enc` is the software fallback. Installing either is enough.",
|
|
170
|
+
actions: [linuxInstallAction(gstEncoderPackages())].filter((a): a is RequirementAction => a !== undefined),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return rows;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** `gst-launch-1.0` plus the PipeWire plugin. Arch splits the tools out of the core package;
|
|
177
|
+
* Debian/Ubuntu call the plugin `gstreamer1.0-pipewire`; Fedora ships it as part of pipewire. */
|
|
178
|
+
function gstPipewirePackages(): string {
|
|
179
|
+
if (existsSync("/usr/bin/pacman")) return "gstreamer gst-plugin-pipewire";
|
|
180
|
+
if (existsSync("/usr/bin/apt")) return "gstreamer1.0-tools gstreamer1.0-pipewire";
|
|
181
|
+
return "gstreamer1 pipewire-gstreamer";
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** `vah264enc` lives in the bad set, `x264enc` in the ugly one; either satisfies the row. */
|
|
185
|
+
function gstEncoderPackages(): string {
|
|
186
|
+
if (existsSync("/usr/bin/pacman")) return "gst-plugins-bad gst-plugins-ugly";
|
|
187
|
+
if (existsSync("/usr/bin/apt")) return "gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly";
|
|
188
|
+
return "gstreamer1-plugins-bad-free gstreamer1-plugins-ugly-free";
|
|
150
189
|
}
|
|
151
190
|
|
|
152
191
|
/** uinput is how a Wayland session receives input, and the device is root-owned by default. */
|
|
@@ -220,14 +259,11 @@ function clipboardSupport(platform: NodeJS.Platform, session: LinuxSession | nul
|
|
|
220
259
|
}
|
|
221
260
|
|
|
222
261
|
async function linuxRequirements(session: LinuxSession): Promise<RemoteDesktopRequirement[]> {
|
|
223
|
-
const rows: RemoteDesktopRequirement[] = [linuxSessionRequirement(session)];
|
|
224
262
|
if (session.kind === "x11") {
|
|
225
263
|
const conn = await getX11(session);
|
|
226
|
-
|
|
227
|
-
} else {
|
|
228
|
-
rows.push(uinputRequirement());
|
|
264
|
+
return [conn ? xtestRequirement(conn.hasXTest) : xlibRequirement(session.display)];
|
|
229
265
|
}
|
|
230
|
-
return
|
|
266
|
+
return [...waylandCaptureRequirements(await gstElements()), uinputRequirement()];
|
|
231
267
|
}
|
|
232
268
|
|
|
233
269
|
export async function remoteDesktopReadiness(
|