flea-qml 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +511 -2
- package/THIRD-PARTY-NOTICES.md +29 -0
- package/demos/App.java +5 -0
- package/demos/Dockerfile +4 -0
- package/demos/Main.kt +3 -0
- package/demos/Program.cs +1 -0
- package/demos/README.md +12 -0
- package/demos/app.dart +3 -0
- package/demos/app.deb +0 -0
- package/demos/app.js +1 -0
- package/demos/backup.tar +0 -0
- package/demos/budget.xlsx +0 -0
- package/demos/build.sh +2 -0
- package/demos/bundle.zip +0 -0
- package/demos/clip.mp4 +0 -0
- package/demos/data.sqlite +0 -0
- package/demos/disk.iso +0 -0
- package/demos/engine.cpp +5 -0
- package/demos/firmware.bin +0 -0
- package/demos/flake.nix +4 -0
- package/demos/folder/inside.txt +1 -0
- package/demos/font.ttf +0 -0
- package/demos/index.html +5 -0
- package/demos/index.php +2 -0
- package/demos/init.lua +1 -0
- package/demos/letter.docx +0 -0
- package/demos/lib.rs +3 -0
- package/demos/main.c +6 -0
- package/demos/main.h +3 -0
- package/demos/math-and-diagrams.md +200 -0
- package/demos/notes.tar.gz +0 -0
- package/demos/notes.txt +3 -0
- package/demos/photo.png +0 -0
- package/demos/program +2 -0
- package/demos/report.pdf +32 -0
- package/demos/script.py +1 -0
- package/demos/server.go +7 -0
- package/demos/settings.json +4 -0
- package/demos/slides.pptx +0 -0
- package/demos/song.mp3 +0 -0
- package/demos/style.css +4 -0
- package/demos/tool.rb +1 -0
- package/demos/types.ts +2 -0
- package/docs/pele.md +23 -0
- package/docs/protocol.md +3420 -0
- package/docs/temml.md +63 -0
- package/index.js +107 -0
- package/package.json +34 -4
- package/src/archive.js +407 -0
- package/src/archiveops.js +728 -0
- package/src/backend.js +369 -0
- package/src/bulkrename.js +95 -0
- package/src/clip-host.js +76 -0
- package/src/clip.js +462 -0
- package/src/code.js +168 -0
- package/src/compare.js +74 -0
- package/src/create.js +128 -0
- package/src/dirsize.js +147 -0
- package/src/figure-svg.js +530 -0
- package/src/figure-thread.js +23 -0
- package/src/figure.js +132 -0
- package/src/find.js +55 -0
- package/src/flea.js +39 -0
- package/src/fsinfo.js +39 -0
- package/src/highlight.js +141 -0
- package/src/html.js +52 -0
- package/src/kinds.js +89 -0
- package/src/listing.js +130 -0
- package/src/markdown-html.js +317 -0
- package/src/mathml.js +186 -0
- package/src/menu.js +180 -0
- package/src/meta.js +135 -0
- package/src/office.js +810 -0
- package/src/order.js +100 -0
- package/src/peek.js +49 -0
- package/src/pele-wrapper.js +695 -0
- package/src/permissions.js +180 -0
- package/src/places.js +313 -0
- package/src/preview-language.js +10 -0
- package/src/terminal-server.js +95 -0
- package/src/terminal-shell.js +26 -0
- package/src/transfer.js +669 -0
- package/src/trash-record.js +41 -0
- package/src/trash.js +210 -0
- package/src/trashbrowse.js +327 -0
- package/src/uistate.js +393 -0
- package/src/uistore.js +168 -0
- package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
- package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
- package/src/vendor/LICENSES/MIT-katex.txt +21 -0
- package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
- package/src/vendor/LICENSES/MIT-pele.txt +40 -0
- package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
- package/src/vendor/LICENSES/MIT-temml.txt +21 -0
- package/src/vendor/pele.js +145 -0
- package/src/vendor/temml.mjs +14708 -0
- package/src/watch.js +98 -0
- package/src/wayland/client.js +109 -0
- package/src/wayland/protocol.js +55 -0
- package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
- package/src/wayland/protocols/wayland.xml +3248 -0
- package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
- package/src/wayland/resource-io.js +6 -0
- package/src/wayland/socket-discovery.js +26 -0
- package/src/wayland/transport-posix.js +120 -0
- package/src/wayland/transport-win32.js +11 -0
- package/src/wayland/transport.js +3 -0
- package/src/wayland/win32-resources.js +80 -0
- package/src/wayland/win32-wire.js +62 -0
- package/test/archive.test.js +80 -0
- package/test/bench-markdown-scroll.mjs +190 -0
- package/test/clip-host.test.js +113 -0
- package/test/code-preview.test.js +35 -0
- package/test/compare-ui-paths.test.js +24 -0
- package/test/compare.test.js +54 -0
- package/test/delete-trash.test.js +67 -0
- package/test/dirsize.test.js +138 -0
- package/test/file-ops.test.js +307 -0
- package/test/file-url.test.js +24 -0
- package/test/find-contents.test.js +161 -0
- package/test/find-input.test.js +66 -0
- package/test/fixtures/pele-parity.json +1006 -0
- package/test/highlight-launcher.test.js +35 -0
- package/test/html-preview.test.js +22 -0
- package/test/input.test.js +15 -0
- package/test/keymap.test.js +26 -0
- package/test/language-icons.test.js +36 -0
- package/test/launcher.test.js +51 -0
- package/test/manual-archive-here-ui.mjs +47 -0
- package/test/manual-code-scroll.mjs +28 -0
- package/test/manual-column-scroll.mjs +24 -0
- package/test/manual-compare-ui.mjs +20 -0
- package/test/manual-find-status.mjs +37 -0
- package/test/manual-folder-focus.mjs +63 -0
- package/test/manual-html-security.mjs +23 -0
- package/test/manual-html-ui.mjs +30 -0
- package/test/manual-syntax-preview.mjs +26 -0
- package/test/manual-terminal-lifecycle.mjs +42 -0
- package/test/manual-terminal-navigation.mjs +48 -0
- package/test/manual-terminal-ui.mjs +47 -0
- package/test/manual-trash-ui.mjs +86 -0
- package/test/markdown-diagrams.test.js +93 -0
- package/test/markdown-html.test.js +105 -0
- package/test/mathml.test.js +92 -0
- package/test/menu-transfer.test.js +57 -0
- package/test/office-preview.test.js +154 -0
- package/test/pele-parity.test.js +28 -0
- package/test/places.test.js +134 -0
- package/test/preview-keys.test.js +60 -0
- package/test/preview-language.test.js +22 -0
- package/test/status-filename.test.js +20 -0
- package/test/transfer-replace-merge.test.js +187 -0
- package/test/trash-address.test.js +23 -0
- package/test/trash-view.test.js +37 -0
- package/test/trashbrowse.test.js +135 -0
- package/ui/AboutFacts.qml +78 -0
- package/ui/ArchiveCards.qml +95 -0
- package/ui/ArchiveCompressDialog.qml +378 -0
- package/ui/ArchiveHereConfirm.qml +127 -0
- package/ui/ArchivePasswordDialog.qml +257 -0
- package/ui/Backend.qml +775 -0
- package/ui/BulkOldNames.qml +47 -0
- package/ui/BulkRenameDialog.qml +380 -0
- package/ui/CardFrame.qml +67 -0
- package/ui/CardScroll.qml +79 -0
- package/ui/CheckBox.qml +73 -0
- package/ui/ChromeBar.qml +360 -0
- package/ui/ChromeButton.qml +131 -0
- package/ui/ChromeModes.qml +43 -0
- package/ui/CodeLines.qml +256 -0
- package/ui/CodeRefusal.qml +52 -0
- package/ui/CollideConfirm.qml +147 -0
- package/ui/CollideHost.qml +167 -0
- package/ui/ColumnPane.qml +525 -0
- package/ui/ColumnRow.qml +380 -0
- package/ui/ColumnsArea.qml +594 -0
- package/ui/ColumnsLanding.qml +147 -0
- package/ui/CompareView.qml +321 -0
- package/ui/ContextMenu.qml +743 -0
- package/ui/ConvertDialog.qml +456 -0
- package/ui/CountedWorker.qml +7 -0
- package/ui/Crumb.qml +64 -0
- package/ui/CrumbDrop.qml +25 -0
- package/ui/CsvTable.qml +394 -0
- package/ui/CursorMark.qml +41 -0
- package/ui/DefaultClaim.qml +130 -0
- package/ui/DeviceLock.qml +47 -0
- package/ui/DeviceMounts.qml +540 -0
- package/ui/DialogButton.qml +150 -0
- package/ui/DialogField.qml +147 -0
- package/ui/DialogHeader.qml +31 -0
- package/ui/DialogScrim.qml +20 -0
- package/ui/DialogTitle.qml +37 -0
- package/ui/Divider.qml +9 -0
- package/ui/DropInto.qml +126 -0
- package/ui/DropTarget.qml +35 -0
- package/ui/DropboxMark.qml +60 -0
- package/ui/EmptyState.qml +150 -0
- package/ui/EscapeHint.qml +11 -0
- package/ui/FactsTable.qml +54 -0
- package/ui/FastScrollHandler.qml +476 -0
- package/ui/FatNamesConfirm.qml +157 -0
- package/ui/Favourites.qml +143 -0
- package/ui/FigureService.qml +120 -0
- package/ui/FileDrag.qml +267 -0
- package/ui/FilterStrip.qml +133 -0
- package/ui/FitMetrics.qml +22 -0
- package/ui/FlashDriveRows.qml +137 -0
- package/ui/FlashHost.qml +72 -0
- package/ui/FlashImageRow.qml +104 -0
- package/ui/FlashRunner.qml +119 -0
- package/ui/FlashStepRow.qml +172 -0
- package/ui/FlashStrip.qml +38 -0
- package/ui/FlashView.qml +397 -0
- package/ui/FleaMark.qml +88 -0
- package/ui/FlyoutFade.qml +23 -0
- package/ui/FontPane.qml +170 -0
- package/ui/Glass.qml +114 -0
- package/ui/GlassTagger.qml +59 -0
- package/ui/Glyph.qml +64 -0
- package/ui/GlyphSolid.qml +22 -0
- package/ui/GridArea.qml +664 -0
- package/ui/GridTile.qml +325 -0
- package/ui/GvfsBridge.qml +164 -0
- package/ui/Header.qml +433 -0
- package/ui/HoverTimers.qml +39 -0
- package/ui/HtmlClip.qml +30 -0
- package/ui/HtmlPane.qml +63 -0
- package/ui/HyprlandMark.qml +45 -0
- package/ui/Ipc.qml +1005 -0
- package/ui/IpcControls.qml +7 -0
- package/ui/IpcMenuState.qml +48 -0
- package/ui/IpcPreviewState.qml +185 -0
- package/ui/JumpPath.qml +51 -0
- package/ui/KeymapSheet.qml +561 -0
- package/ui/KeymapSheetResult.qml +89 -0
- package/ui/KindStack.qml +40 -0
- package/ui/LangMark.qml +51 -0
- package/ui/LangMarkLoader.qml +26 -0
- package/ui/List.qml +480 -0
- package/ui/LoadingState.qml +54 -0
- package/ui/LocalSend.qml +56 -0
- package/ui/LocalSendMark.qml +73 -0
- package/ui/MarkdownBlockView.qml +311 -0
- package/ui/MarkdownBlocks.qml +35 -0
- package/ui/MarkdownFigure.qml +254 -0
- package/ui/MarkdownImages.qml +72 -0
- package/ui/MarkdownList.qml +81 -0
- package/ui/MarkdownMathsText.qml +97 -0
- package/ui/MarkdownPane.qml +83 -0
- package/ui/MarkdownPictures.qml +41 -0
- package/ui/MarkdownQuote.qml +52 -0
- package/ui/MarkdownRemote.qml +82 -0
- package/ui/MarkdownSource.qml +155 -0
- package/ui/MarkdownTable.qml +222 -0
- package/ui/MarkdownTableScroll.qml +64 -0
- package/ui/MarkdownTableWheel.qml +126 -0
- package/ui/MarkdownText.qml +87 -0
- package/ui/MarkdownWorker.js +132 -0
- package/ui/MatchText.qml +99 -0
- package/ui/MediaSession.qml +38 -0
- package/ui/MediaSound.qml +26 -0
- package/ui/MediaStrip.qml +191 -0
- package/ui/MediaTrack.qml +36 -0
- package/ui/MediaVolume.qml +54 -0
- package/ui/MenuActionDialog.qml +396 -0
- package/ui/MenuEdgeFade.qml +24 -0
- package/ui/MenuHost.qml +227 -0
- package/ui/MenuRow.qml +351 -0
- package/ui/MountListing.qml +100 -0
- package/ui/NetworkDialog.qml +435 -0
- package/ui/NetworkForm.qml +426 -0
- package/ui/NetworkMounts.qml +913 -0
- package/ui/NetworkPlaces.qml +127 -0
- package/ui/OpenWithDialog.qml +613 -0
- package/ui/Opener.qml +95 -0
- package/ui/Pane.qml +1320 -0
- package/ui/PaneMenuActions.qml +527 -0
- package/ui/PanePath.qml +90 -0
- package/ui/PaneRail.qml +268 -0
- package/ui/PaneStates.qml +88 -0
- package/ui/PaneSwap.qml +339 -0
- package/ui/PaneWire.qml +601 -0
- package/ui/ParkedWait.qml +9 -0
- package/ui/PathEntry.qml +186 -0
- package/ui/PathJump.qml +400 -0
- package/ui/PdfViewer.qml +297 -0
- package/ui/PermissionsDialog.qml +658 -0
- package/ui/PermissionsHeadings.qml +31 -0
- package/ui/PhoneMounts.qml +67 -0
- package/ui/PickerAnswers.qml +75 -0
- package/ui/PickerBox.qml +26 -0
- package/ui/PickerChrome.qml +295 -0
- package/ui/PickerFilter.qml +97 -0
- package/ui/PickerGrid.qml +308 -0
- package/ui/PickerHeader.qml +28 -0
- package/ui/PickerLifecycle.qml +27 -0
- package/ui/PickerList.qml +218 -0
- package/ui/PickerListing.qml +149 -0
- package/ui/PickerPathBar.qml +127 -0
- package/ui/PickerPlaces.qml +189 -0
- package/ui/PickerRecent.qml +92 -0
- package/ui/PickerSave.qml +101 -0
- package/ui/PickerSelection.qml +155 -0
- package/ui/PickerWindow.qml +846 -0
- package/ui/PowerSectorsReader.qml +10 -0
- package/ui/PrebuildDriver.qml +146 -0
- package/ui/Preview.qml +1005 -0
- package/ui/PreviewAnimation.qml +24 -0
- package/ui/PreviewArchive.qml +91 -0
- package/ui/PreviewColumn.qml +553 -0
- package/ui/PreviewEdge.qml +63 -0
- package/ui/PreviewFacts.qml +30 -0
- package/ui/PreviewFactsLine.qml +34 -0
- package/ui/PreviewHtml.qml +233 -0
- package/ui/PreviewImage.qml +253 -0
- package/ui/PreviewImagePan.qml +13 -0
- package/ui/PreviewImageSharp.qml +32 -0
- package/ui/PreviewLines.qml +106 -0
- package/ui/PreviewMarkdown.qml +706 -0
- package/ui/PreviewMedia.qml +250 -0
- package/ui/PreviewPdf.qml +482 -0
- package/ui/PreviewScroll.qml +201 -0
- package/ui/PreviewSwap.qml +335 -0
- package/ui/PreviewText.qml +149 -0
- package/ui/ProtocolChip.qml +18 -0
- package/ui/QuickLookArchive.qml +41 -0
- package/ui/QuickLookBar.qml +103 -0
- package/ui/QuickLookPrepare.qml +355 -0
- package/ui/QuickLookSwap.qml +10 -0
- package/ui/QuickLookWarm.qml +65 -0
- package/ui/RailDrop.qml +155 -0
- package/ui/RailHeading.qml +19 -0
- package/ui/RailPlaces.qml +38 -0
- package/ui/RailTrashRow.qml +67 -0
- package/ui/RenameField.qml +353 -0
- package/ui/RendererRetry.qml +11 -0
- package/ui/ResizeHandle.qml +52 -0
- package/ui/Reveal.qml +54 -0
- package/ui/Row.qml +476 -0
- package/ui/RowDrag.qml +213 -0
- package/ui/RowDropTarget.qml +27 -0
- package/ui/RunCard.qml +286 -0
- package/ui/Scripts.qml +65 -0
- package/ui/SearchStrip.qml +232 -0
- package/ui/SelectionBand.qml +140 -0
- package/ui/SelectionPreview.qml +370 -0
- package/ui/SettingsFavourite.qml +122 -0
- package/ui/SettingsGroup.qml +96 -0
- package/ui/SettingsHint.qml +12 -0
- package/ui/SettingsMetrics.qml +29 -0
- package/ui/SettingsPane.qml +178 -0
- package/ui/SettingsPanel.qml +547 -0
- package/ui/SettingsRail.qml +128 -0
- package/ui/SettingsRow.qml +409 -0
- package/ui/SettingsRuler.qml +85 -0
- package/ui/SettingsSegment.qml +85 -0
- package/ui/ShareBrowser.qml +113 -0
- package/ui/ShareLink.qml +67 -0
- package/ui/ShelfPins.qml +101 -0
- package/ui/Sidebar.qml +728 -0
- package/ui/SidebarRow.qml +358 -0
- package/ui/Spinner.qml +60 -0
- package/ui/StableModel.qml +89 -0
- package/ui/StateMessage.qml +84 -0
- package/ui/StatusBar.qml +512 -0
- package/ui/StatusFileName.qml +27 -0
- package/ui/TabBar.qml +673 -0
- package/ui/TabDragGeometry.qml +60 -0
- package/ui/TagMenuMark.qml +52 -0
- package/ui/TagRailSection.qml +53 -0
- package/ui/TagSlot.qml +40 -0
- package/ui/TagState.qml +150 -0
- package/ui/TagSwatch.qml +28 -0
- package/ui/Taildrop.qml +86 -0
- package/ui/TailscaleMark.qml +50 -0
- package/ui/TerminalService.qml +69 -0
- package/ui/TerminalTab.qml +48 -0
- package/ui/TerminalTabs.qml +27 -0
- package/ui/Theme.qml +460 -0
- package/ui/ThemeColors.qml +43 -0
- package/ui/TransferCard.qml +239 -0
- package/ui/TrashConfirm.qml +129 -0
- package/ui/TrashDropHandler.qml +11 -0
- package/ui/TrashHost.qml +202 -0
- package/ui/TrashMonitor.qml +30 -0
- package/ui/TrashToolbar.qml +19 -0
- package/ui/TrashView.qml +492 -0
- package/ui/UnlockDialog.qml +450 -0
- package/ui/UnlockHost.qml +206 -0
- package/ui/UpdateCheck.qml +97 -0
- package/ui/UserDirs.qml +25 -0
- package/ui/ViewState.qml +565 -0
- package/ui/ViewportScrollBar.qml +195 -0
- package/ui/ViewportScrollBars.qml +25 -0
- package/ui/WebPreview.qml +33 -0
- package/ui/WebPreviewView.qml +26 -0
- package/ui/WindowBody.qml +829 -0
- package/ui/WindowDrag.qml +35 -0
- package/ui/boot/Commons/AudioNodes.qml +50 -0
- package/ui/boot/Commons/AudioNodesModel.js +96 -0
- package/ui/boot/Commons/Border.qml +243 -0
- package/ui/boot/Commons/BorderGeometry.js +373 -0
- package/ui/boot/Commons/Color.qml +258 -0
- package/ui/boot/Commons/I18n.qml +118 -0
- package/ui/boot/Commons/InputDevices.qml +112 -0
- package/ui/boot/Commons/InputMethodState.qml +38 -0
- package/ui/boot/Commons/IpcRegistry.qml +77 -0
- package/ui/boot/Commons/Numbers.js +19 -0
- package/ui/boot/Commons/Plural.js +91 -0
- package/ui/boot/Commons/ShellIpc.qml +12 -0
- package/ui/boot/Commons/Style.qml +561 -0
- package/ui/boot/Commons/Translations.qml +100 -0
- package/ui/boot/Commons/UntypedInput.qml +152 -0
- package/ui/boot/Commons/Util.qml +188 -0
- package/ui/boot/Commons/qmldir +13 -0
- package/ui/boot/LICENSE-omarchy +20 -0
- package/ui/boot/Ui/BackgroundMedia.qml +49 -0
- package/ui/boot/Ui/BarIconButton.qml +106 -0
- package/ui/boot/Ui/BarIconContent.qml +38 -0
- package/ui/boot/Ui/BarIndicator.qml +53 -0
- package/ui/boot/Ui/BarWidget.qml +50 -0
- package/ui/boot/Ui/BorderOverlay.qml +54 -0
- package/ui/boot/Ui/BorderSurface.qml +40 -0
- package/ui/boot/Ui/Button.qml +224 -0
- package/ui/boot/Ui/ButtonGroup.qml +131 -0
- package/ui/boot/Ui/ConfirmDialog.qml +134 -0
- package/ui/boot/Ui/CursorSurface.qml +42 -0
- package/ui/boot/Ui/Dropdown.qml +245 -0
- package/ui/boot/Ui/FilterInput.qml +26 -0
- package/ui/boot/Ui/IconContentBounds.qml +141 -0
- package/ui/boot/Ui/KeyboardPanel.qml +453 -0
- package/ui/boot/Ui/MultiSelect.qml +624 -0
- package/ui/boot/Ui/NumberField.qml +86 -0
- package/ui/boot/Ui/OpticalGlyph.qml +68 -0
- package/ui/boot/Ui/OverlayWindow.qml +102 -0
- package/ui/boot/Ui/Panel.qml +60 -0
- package/ui/boot/Ui/PanelActionButton.qml +105 -0
- package/ui/boot/Ui/PanelController.qml +16 -0
- package/ui/boot/Ui/PanelHero.qml +112 -0
- package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
- package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
- package/ui/boot/Ui/PanelSeparator.qml +19 -0
- package/ui/boot/Ui/PanelSlider.qml +156 -0
- package/ui/boot/Ui/PanelToolTip.qml +50 -0
- package/ui/boot/Ui/PluginBarApi.qml +89 -0
- package/ui/boot/Ui/PointerMoveGate.qml +54 -0
- package/ui/boot/Ui/PopupCard.qml +181 -0
- package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
- package/ui/boot/Ui/SearchableDropdown.qml +354 -0
- package/ui/boot/Ui/SizedImage.qml +125 -0
- package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
- package/ui/boot/Ui/TextField.qml +58 -0
- package/ui/boot/Ui/Toggle.qml +118 -0
- package/ui/boot/Ui/ToggleSwitch.qml +115 -0
- package/ui/boot/Ui/WidgetButton.qml +172 -0
- package/ui/boot/Ui/qmldir +41 -0
- package/ui/boot/fleatab.qml +50 -0
- package/ui/boot/picker.qml +39 -0
- package/ui/boot/shell.qml +131 -0
- package/ui/boot/tabtearoff.qml +51 -0
- package/ui/js/Anchor.js +379 -0
- package/ui/js/AnchorHold.js +162 -0
- package/ui/js/Archive.js +157 -0
- package/ui/js/ArchiveCard.js +44 -0
- package/ui/js/ArchiveOps.js +44 -0
- package/ui/js/BackReturn.js +118 -0
- package/ui/js/BulkRename.js +100 -0
- package/ui/js/Buttons.js +54 -0
- package/ui/js/ChromeKeys.js +116 -0
- package/ui/js/ClipMarks.js +73 -0
- package/ui/js/ClipSelection.js +67 -0
- package/ui/js/Clipboard.js +179 -0
- package/ui/js/Cloud.js +97 -0
- package/ui/js/CodeRuns.js +72 -0
- package/ui/js/Collide.js +212 -0
- package/ui/js/ColumnFit.js +28 -0
- package/ui/js/ColumnMenu.js +111 -0
- package/ui/js/Columns.js +298 -0
- package/ui/js/ColumnsCarry.js +103 -0
- package/ui/js/Compare.js +74 -0
- package/ui/js/Contrast.js +157 -0
- package/ui/js/ControlReader.js +42 -0
- package/ui/js/Convert.js +52 -0
- package/ui/js/CopyAs.js +52 -0
- package/ui/js/Crumbs.js +178 -0
- package/ui/js/Csv.js +231 -0
- package/ui/js/Density.js +44 -0
- package/ui/js/Devices.js +440 -0
- package/ui/js/DirSizes.js +180 -0
- package/ui/js/Drag.js +360 -0
- package/ui/js/DragOut.js +166 -0
- package/ui/js/DropDev.js +52 -0
- package/ui/js/Dropbox.js +41 -0
- package/ui/js/Eject.js +217 -0
- package/ui/js/Errors.js +188 -0
- package/ui/js/ExtThumbs.js +115 -0
- package/ui/js/Facts.js +402 -0
- package/ui/js/FigureDate.js +14 -0
- package/ui/js/Filter.js +285 -0
- package/ui/js/Find.js +46 -0
- package/ui/js/Flash.js +184 -0
- package/ui/js/FlashCard.js +79 -0
- package/ui/js/FlashEndings.js +125 -0
- package/ui/js/FlashPane.js +116 -0
- package/ui/js/FleaBin.js +12 -0
- package/ui/js/Focus.js +504 -0
- package/ui/js/FolderSorts.js +108 -0
- package/ui/js/FolderViews.js +44 -0
- package/ui/js/Format.js +314 -0
- package/ui/js/Fuzzy.js +141 -0
- package/ui/js/GlassTag.js +72 -0
- package/ui/js/Grid.js +33 -0
- package/ui/js/GridGeometry.js +90 -0
- package/ui/js/GridNames.js +153 -0
- package/ui/js/GvfsBridge.js +200 -0
- package/ui/js/HostClipboard.js +29 -0
- package/ui/js/Icons.js +218 -0
- package/ui/js/Input.js +50 -0
- package/ui/js/Jump.js +200 -0
- package/ui/js/KeyName.js +64 -0
- package/ui/js/Keymap.js +401 -0
- package/ui/js/Kinds.js +164 -0
- package/ui/js/LICENSES/ISC-lucide.txt +43 -0
- package/ui/js/LangMarkPaths.js +47 -0
- package/ui/js/LangMarks.js +120 -0
- package/ui/js/Lists.js +11 -0
- package/ui/js/LocalSend.js +39 -0
- package/ui/js/LockedMenu.js +63 -0
- package/ui/js/MakeDefault.js +186 -0
- package/ui/js/MarkBox.js +7 -0
- package/ui/js/Markdown.js +119 -0
- package/ui/js/MarkdownLists.js +86 -0
- package/ui/js/MarkdownMaths.js +58 -0
- package/ui/js/MarkdownPictures.js +32 -0
- package/ui/js/MarkdownPrepared.js +139 -0
- package/ui/js/MarkdownTableFit.js +34 -0
- package/ui/js/Marks.js +148 -0
- package/ui/js/Marquee.js +96 -0
- package/ui/js/Match.js +93 -0
- package/ui/js/Material.js +109 -0
- package/ui/js/MdBlocks.js +437 -0
- package/ui/js/MdBreak.js +71 -0
- package/ui/js/MdChunks.js +70 -0
- package/ui/js/MdContainer.js +137 -0
- package/ui/js/MdDocument.js +337 -0
- package/ui/js/MdEmph.js +97 -0
- package/ui/js/MdEntity.js +108 -0
- package/ui/js/MdEntityTable.js +223 -0
- package/ui/js/MdEscape.js +59 -0
- package/ui/js/MdFront.js +37 -0
- package/ui/js/MdHold.js +32 -0
- package/ui/js/MdHtml.js +307 -0
- package/ui/js/MdHtmlBlock.js +290 -0
- package/ui/js/MdHtmlHeading.js +53 -0
- package/ui/js/MdHtmlImage.js +179 -0
- package/ui/js/MdInline.js +154 -0
- package/ui/js/MdItems.js +212 -0
- package/ui/js/MdLeaf.js +309 -0
- package/ui/js/MdLink.js +196 -0
- package/ui/js/MdMath.js +67 -0
- package/ui/js/MdParagraphs.js +138 -0
- package/ui/js/MdRefs.js +232 -0
- package/ui/js/MdResolve.js +226 -0
- package/ui/js/MdRun.js +300 -0
- package/ui/js/MdUrl.js +226 -0
- package/ui/js/Menu.js +514 -0
- package/ui/js/MenuContext.js +85 -0
- package/ui/js/MenuFit.js +13 -0
- package/ui/js/MenuRefresh.js +74 -0
- package/ui/js/MenuWheel.js +35 -0
- package/ui/js/Messages.js +186 -0
- package/ui/js/Motion.js +16 -0
- package/ui/js/Mounts.js +320 -0
- package/ui/js/MouseNav.js +13 -0
- package/ui/js/Nav.js +329 -0
- package/ui/js/NetFs.js +26 -0
- package/ui/js/OpenWith.js +83 -0
- package/ui/js/Ops.js +585 -0
- package/ui/js/Palette.js +33 -0
- package/ui/js/PathBar.js +260 -0
- package/ui/js/Permissions.js +179 -0
- package/ui/js/Phones.js +112 -0
- package/ui/js/Picker.js +353 -0
- package/ui/js/PlaceMenu.js +96 -0
- package/ui/js/Places.js +265 -0
- package/ui/js/PreviewAsk.js +21 -0
- package/ui/js/PreviewKeys.js +155 -0
- package/ui/js/PreviewSettle.js +29 -0
- package/ui/js/PreviewSwap.js +101 -0
- package/ui/js/PropertyFacts.js +144 -0
- package/ui/js/Protocols.js +296 -0
- package/ui/js/QuickLookKinds.js +27 -0
- package/ui/js/QuickLookRect.js +24 -0
- package/ui/js/RailDrop.js +51 -0
- package/ui/js/RailKeys.js +87 -0
- package/ui/js/RailMenu.js +133 -0
- package/ui/js/Recent.js +135 -0
- package/ui/js/RecentMode.js +156 -0
- package/ui/js/Reload.js +60 -0
- package/ui/js/Renderer.js +9 -0
- package/ui/js/RunFile.js +143 -0
- package/ui/js/Scripts.js +28 -0
- package/ui/js/Scroll.js +304 -0
- package/ui/js/ScrollOff.js +69 -0
- package/ui/js/Search.js +478 -0
- package/ui/js/Selection.js +97 -0
- package/ui/js/Settings.js +476 -0
- package/ui/js/SettingsAbout.js +97 -0
- package/ui/js/SettingsShelf.js +52 -0
- package/ui/js/SheetKeys.js +42 -0
- package/ui/js/SheetQuery.js +281 -0
- package/ui/js/ShelfPile.js +27 -0
- package/ui/js/SlowClick.js +53 -0
- package/ui/js/SlowOp.js +6 -0
- package/ui/js/Sort.js +134 -0
- package/ui/js/Startup.js +63 -0
- package/ui/js/StateWords.js +17 -0
- package/ui/js/Status.js +238 -0
- package/ui/js/Swap.js +166 -0
- package/ui/js/TabMove.js +39 -0
- package/ui/js/Tabs.js +751 -0
- package/ui/js/Tags.js +134 -0
- package/ui/js/Taildrop.js +128 -0
- package/ui/js/Tap.js +180 -0
- package/ui/js/TextSize.js +107 -0
- package/ui/js/Thumbs.js +158 -0
- package/ui/js/Transfer.js +172 -0
- package/ui/js/Trash.js +84 -0
- package/ui/js/TrashDates.js +47 -0
- package/ui/js/TrashDrop.js +64 -0
- package/ui/js/UiState.js +438 -0
- package/ui/js/Unlock.js +152 -0
- package/ui/js/Update.js +130 -0
- package/ui/js/UserDirs.js +56 -0
- package/ui/qmldir +230 -0
|
@@ -0,0 +1,913 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import Quickshell.Io
|
|
4
|
+
import "js/Errors.js" as Errors
|
|
5
|
+
import "js/Cloud.js" as Cloud
|
|
6
|
+
import "js/Crumbs.js" as Crumbs
|
|
7
|
+
import "js/Format.js" as Format
|
|
8
|
+
import "js/Mounts.js" as Mounts
|
|
9
|
+
import "js/Protocols.js" as Protocols
|
|
10
|
+
import "js/Dropbox.js" as Dropbox
|
|
11
|
+
|
|
12
|
+
// OEM-shaped Network service: nothing but this file, its two children and ui/PhoneMounts.qml, which
|
|
13
|
+
// only unmounts off this listing, touches gio or the saved places file; Sidebar renders their rows.
|
|
14
|
+
Item {
|
|
15
|
+
id: root
|
|
16
|
+
|
|
17
|
+
property string bookmarksText: ""
|
|
18
|
+
property var savedFavourites: []
|
|
19
|
+
onSavedFavouritesChanged: root.rebuild()
|
|
20
|
+
property var backend: null
|
|
21
|
+
property var entries: []
|
|
22
|
+
// Retained across unmounts: the same GVFS path denotes the same share; navigation still uses the real path.
|
|
23
|
+
property var pathAliases: []
|
|
24
|
+
property var _aliasLabels: ({})
|
|
25
|
+
// A bridge wait outlives _pending*, so the identity it opened under waits here by path until the check lands.
|
|
26
|
+
property var _bridgeIdentities: ({})
|
|
27
|
+
function pathAlias(path) { return Crumbs.aliasAt(path, root.pathAliases) }
|
|
28
|
+
function rememberPathAlias(path, label, uri) {
|
|
29
|
+
var key = Mounts.normalize(uri)
|
|
30
|
+
root.pathAliases = Crumbs.rememberAlias(root.pathAliases, path, root._aliasLabels[key] || label, key)
|
|
31
|
+
}
|
|
32
|
+
// Secrets live only here for this QML process lifetime; the map is never serialized or exposed.
|
|
33
|
+
property var _passwords: ({})
|
|
34
|
+
property string result: "idle"
|
|
35
|
+
property Item origin: null
|
|
36
|
+
property Item _pendingOrigin: null
|
|
37
|
+
|
|
38
|
+
signal opened(string path, var origin)
|
|
39
|
+
// A FUSE path that is a file, not a folder: the opener takes it, so a typed network URL
|
|
40
|
+
// naming a file is never listed as a folder.
|
|
41
|
+
signal openFileRequested(string path, var origin)
|
|
42
|
+
signal message(string text, bool isError)
|
|
43
|
+
// The bridge wait's own sticky line, cleared with "" when the folder lands or fails.
|
|
44
|
+
signal sticky(string text, var origin)
|
|
45
|
+
// The GVFS bridge's Starting line, which the status bar marks busy; a clear still goes through sticky.
|
|
46
|
+
signal startingBridge(string text, var origin)
|
|
47
|
+
// Client-side only, see "listShares" below: ui/ShareBrowser.qml renders these as pane rows.
|
|
48
|
+
signal sharesListed(string baseUri, string baseLabel, var names, var origin)
|
|
49
|
+
// Fired once ui/NetworkPlaces.qml's write has actually landed, so a caller's reload reads it.
|
|
50
|
+
signal renamed()
|
|
51
|
+
signal retryRequested(string uri, string label, string password, string reason, bool failedConnect, var origin)
|
|
52
|
+
// connectFailed says the connection itself failed (refused, unanswered, no folder), so the dialog marks the location unacknowledged without reading the sentence.
|
|
53
|
+
signal completed(string requestId, string uri, bool success, string reason, bool connectFailed)
|
|
54
|
+
|
|
55
|
+
// Hyprland has no auth portal here, so "gio mount" on a share that wants a credential prompt
|
|
56
|
+
// hangs forever with no stdin to answer it, and "gio info" on a location gvfs cannot reach does
|
|
57
|
+
// the same. Every gio leg of an open gets this bound; the helper leg has its own below.
|
|
58
|
+
readonly property int mountTimeoutMs: 15000
|
|
59
|
+
// The frozen helper has its own 30 s deadline; this outer bound also contains a broken test override.
|
|
60
|
+
readonly property int authTimeoutSeconds: 35
|
|
61
|
+
// Issue #36: every gio call this Service starts is pinned to C, so the wording of the ones it
|
|
62
|
+
// reads output from (info, list, and the listing in ui/MountListing.qml) cannot be translated.
|
|
63
|
+
// gvfsd composes a mount's own label and its refusals, and no client locale reaches those, which
|
|
64
|
+
// is why nothing below decides anything on one.
|
|
65
|
+
readonly property var gioEnvironment: ({ "LC_ALL": "C" })
|
|
66
|
+
property string _mountListing: ""
|
|
67
|
+
// What the rail waits for before it draws NETWORK: the shared gio listing's own answer, timed out or not.
|
|
68
|
+
readonly property bool listingAnswered: listing.answered
|
|
69
|
+
// ui/PhoneMounts.qml builds its rows off the same five second poll rather than walking the gvfs volume monitors a second time.
|
|
70
|
+
readonly property alias mountListing: root._mountListing
|
|
71
|
+
property string _pendingUri: ""
|
|
72
|
+
// OEM collectors cache finished output because onExited can race their text property.
|
|
73
|
+
property string _infoOutput: ""
|
|
74
|
+
property string _listSharesOutput: ""
|
|
75
|
+
// Set right before mountTimeout terminates that leg's process, so its own onExited does not
|
|
76
|
+
// also report a second, redundant failure for the exact same open attempt.
|
|
77
|
+
property bool _mountTimedOut: false
|
|
78
|
+
property bool _infoTimedOut: false
|
|
79
|
+
property bool _listSharesTimedOut: false
|
|
80
|
+
// Set from "gio mount"'s own exit code and consumed by the "gio info" that follows it, which is
|
|
81
|
+
// what actually decides whether the location is mounted; this only colours the failure message.
|
|
82
|
+
property bool _mountFailed: false
|
|
83
|
+
property string _pendingUnmountLabel: ""
|
|
84
|
+
// The entry's own label at activation time, carried through to the sharesListed signal.
|
|
85
|
+
property string _pendingLabel: ""
|
|
86
|
+
property string _pendingPassword: ""
|
|
87
|
+
property bool _authAwaitingStart: false
|
|
88
|
+
property bool _authCancelled: false
|
|
89
|
+
property string _requestId: ""
|
|
90
|
+
property string _requestPassword: ""
|
|
91
|
+
// Issue 194's repair: gio info answered with a folder that is not the requested share.
|
|
92
|
+
// Its own path is peeked first, then the FUSE root it names, all through backend.peek,
|
|
93
|
+
// so no new poll is added and a second open waits on the same single-flight guard.
|
|
94
|
+
// Only a root that peeks empty or unreadable starts the bridge for the gvfs root, then
|
|
95
|
+
// peeks once under the same deadline; the #194 folder itself does not exist, so waiting
|
|
96
|
+
// on it first costs 15 s before failing. _repairWaiting covers that bridge leg,
|
|
97
|
+
// _repairActive the peek legs, _repairBridged the one bridge start a repair may spend.
|
|
98
|
+
property bool _repairWaiting: false
|
|
99
|
+
property string _repairUri: ""
|
|
100
|
+
property string _repairPath: ""
|
|
101
|
+
property string _repairRoot: ""
|
|
102
|
+
property bool _repairFailed: false
|
|
103
|
+
property bool _repairActive: false
|
|
104
|
+
property bool _repairBridged: false
|
|
105
|
+
|
|
106
|
+
onBookmarksTextChanged: root.rebuild()
|
|
107
|
+
|
|
108
|
+
property string dropboxPath: ""
|
|
109
|
+
onDropboxPathChanged: root.rebuild()
|
|
110
|
+
property string dropboxReason: "Checking Dropbox"
|
|
111
|
+
// What state the reason is, for logic: ui/js/Dropbox.js names the codes.
|
|
112
|
+
property string dropboxCode: ""
|
|
113
|
+
property bool dropboxChecking: false
|
|
114
|
+
property bool _dropboxAwaitingStart: false
|
|
115
|
+
property string _dropboxOutput: ""
|
|
116
|
+
property string _dropboxError: ""
|
|
117
|
+
// OEM dropbox/status.py uses a four-second daemon status deadline.
|
|
118
|
+
readonly property int dropboxStatusTimeoutSeconds: 4
|
|
119
|
+
signal dropboxRefreshed()
|
|
120
|
+
// Any finished status check answers, whatever it said; the providers' determination is only the startup answer.
|
|
121
|
+
onDropboxRefreshed: root.dropboxAnswered = true
|
|
122
|
+
// What the rail waits for from Dropbox: the providers' determination, or a finished status check.
|
|
123
|
+
property bool dropboxAnswered: false
|
|
124
|
+
readonly property bool dropboxReady: dropboxPath.length > 0 && dropboxReason.length === 0 && !dropboxChecking
|
|
125
|
+
property int _dropboxMetadataRequest: 0
|
|
126
|
+
property bool _dropboxMetadataAgain: false
|
|
127
|
+
|
|
128
|
+
function readDropboxAccount(facts) {
|
|
129
|
+
if (!facts || facts.dropboxInfo === undefined) return
|
|
130
|
+
root.dropboxAnswered = true
|
|
131
|
+
var account = Dropbox.account(facts.dropboxInfo, facts.dropboxError)
|
|
132
|
+
if (dropboxPath !== account.path || account.reason) {
|
|
133
|
+
dropboxReason = account.reason || "Checking Dropbox"
|
|
134
|
+
dropboxCode = account.code
|
|
135
|
+
}
|
|
136
|
+
dropboxPath = account.path
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function refreshDropboxAccount() {
|
|
140
|
+
if (!backend) return
|
|
141
|
+
if (_dropboxMetadataRequest) { _dropboxMetadataAgain = true; return }
|
|
142
|
+
_dropboxMetadataRequest = backend.askFormats()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function rearmDropboxAccount() {
|
|
146
|
+
dropboxAccountFile.path = Quickshell.env("HOME") + "/.dropbox/info.json"
|
|
147
|
+
root.refreshDropboxAccount()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
onBackendChanged: if (backend) root.readDropboxAccount(backend.providers)
|
|
151
|
+
Connections {
|
|
152
|
+
target: root.backend
|
|
153
|
+
function onProvidersChanged() { root.readDropboxAccount(root.backend.providers) }
|
|
154
|
+
function onFormatsResult(message) {
|
|
155
|
+
if (message.id !== root._dropboxMetadataRequest) return
|
|
156
|
+
root._dropboxMetadataRequest = 0
|
|
157
|
+
if (root._dropboxMetadataAgain) {
|
|
158
|
+
root._dropboxMetadataAgain = false
|
|
159
|
+
root.refreshDropboxAccount()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// Issue 194's repair answers here. The chrome answers only the Tab request its own key
|
|
163
|
+
// names and the columns view only ever reads the ancestors it asked for, so sharing the
|
|
164
|
+
// peek wire costs those readers nothing they would otherwise notice.
|
|
165
|
+
function onPeeked(path, hidden, total, rows, readFailed, mode) { root.repairPeeked(path, rows, readFailed) }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function refreshDropbox(facts) {
|
|
169
|
+
if (dropboxChecking) return false
|
|
170
|
+
var provider = facts.dropbox || {}
|
|
171
|
+
root.readDropboxAccount(facts)
|
|
172
|
+
if (provider.reason || dropboxPath) dropboxCode = ""
|
|
173
|
+
dropboxReason = provider.reason || (dropboxPath ? "Checking Dropbox" : dropboxReason)
|
|
174
|
+
if (!provider.command || !dropboxPath) { root.dropboxAnswered = true; return true }
|
|
175
|
+
dropboxChecking = true
|
|
176
|
+
_dropboxAwaitingStart = true
|
|
177
|
+
_dropboxOutput = ""
|
|
178
|
+
_dropboxError = ""
|
|
179
|
+
dropboxStatus.command = ["timeout", "--signal=KILL", String(dropboxStatusTimeoutSeconds), provider.command, "status"]
|
|
180
|
+
dropboxStatus.running = true
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Process {
|
|
185
|
+
id: dropboxStatus
|
|
186
|
+
environment: root.gioEnvironment
|
|
187
|
+
stdout: StdioCollector { id: dropboxOut; waitForEnd: true; onStreamFinished: root._dropboxOutput = text }
|
|
188
|
+
stderr: StdioCollector { id: dropboxErr; waitForEnd: true; onStreamFinished: root._dropboxError = text }
|
|
189
|
+
onStarted: root._dropboxAwaitingStart = false
|
|
190
|
+
onRunningChanged: {
|
|
191
|
+
if (root._dropboxAwaitingStart && !running) {
|
|
192
|
+
root._dropboxAwaitingStart = false
|
|
193
|
+
root.dropboxChecking = false
|
|
194
|
+
root.dropboxReason = "Dropbox could not be checked"
|
|
195
|
+
root.dropboxCode = ""
|
|
196
|
+
root.dropboxRefreshed()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
onExited: function(exitCode) {
|
|
200
|
+
root._dropboxAwaitingStart = false
|
|
201
|
+
var answer = exitCode === 137 || exitCode === 9 ? { reason: "Dropbox did not answer in time", code: "statusFailed" }
|
|
202
|
+
: Dropbox.status(dropboxOut.text || root._dropboxOutput, exitCode, dropboxErr.text || root._dropboxError)
|
|
203
|
+
root.dropboxReason = answer.reason
|
|
204
|
+
root.dropboxCode = answer.code
|
|
205
|
+
root.dropboxChecking = false
|
|
206
|
+
root.dropboxRefreshed()
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
FileView {
|
|
211
|
+
id: dropboxAccountFile
|
|
212
|
+
path: Quickshell.env("HOME") + "/.dropbox/info.json"
|
|
213
|
+
// Watch only; providers.rs owns the regular-file check and bounded metadata read.
|
|
214
|
+
preload: false
|
|
215
|
+
watchChanges: true
|
|
216
|
+
printErrors: false
|
|
217
|
+
onFileChanged: Qt.callLater(root.refreshDropboxAccount)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
FileView {
|
|
221
|
+
// A FileView directory watch does not report its own removal; its parent does.
|
|
222
|
+
path: Quickshell.env("HOME")
|
|
223
|
+
preload: false
|
|
224
|
+
watchChanges: true
|
|
225
|
+
printErrors: false
|
|
226
|
+
onFileChanged: {
|
|
227
|
+
// FileView must observe the empty path for an event turn before it can re-arm a new parent.
|
|
228
|
+
dropboxAccountFile.path = ""
|
|
229
|
+
Qt.callLater(root.rearmDropboxAccount)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// The five second "gio mount -li" poll is ui/MountListing.qml's: this Service reads its listing and asks for a re-read through pollMounts() below.
|
|
234
|
+
// How many rails are loaded now; an open or a bridge wait also holds the poll, so a
|
|
235
|
+
// hidden rail costs no gio while a mount in flight still refreshes behind it.
|
|
236
|
+
property int _rails: 0
|
|
237
|
+
// The listing's timer polls on start when this arrival activates it, so only an already active one is asked again.
|
|
238
|
+
function railArrived() { var wasActive = listing.active; root._rails++; if (wasActive) root.pollMounts() }
|
|
239
|
+
function railLeft() { root._rails = Math.max(0, root._rails - 1) }
|
|
240
|
+
MountListing {
|
|
241
|
+
id: listing
|
|
242
|
+
environment: root.gioEnvironment
|
|
243
|
+
active: root._rails > 0 || mountProcess.running || authProcess.running || infoProcess.running
|
|
244
|
+
|| listSharesProcess.running || root._repairActive || root._repairWaiting || root.bridgeWaiting
|
|
245
|
+
// Cloud rows ride the same five second rhythm: the mount table is a file read, so
|
|
246
|
+
// nothing new runs. The read is asynchronous: /proc has no write of ours to wait for,
|
|
247
|
+
// and a blocking read every tick stalled the GUI thread mid-scroll.
|
|
248
|
+
onListed: { root._polledListing = listing.text; root._listedOnce = true; cloudFile.reload() }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// The last texts a rebuild parsed; see ui/js/Mounts.js pollDecision for the change gate.
|
|
252
|
+
property string _polledListing: ""
|
|
253
|
+
property bool _listedOnce: false
|
|
254
|
+
property string _lastMountListing: ""
|
|
255
|
+
property string _lastMountinfo: ""
|
|
256
|
+
function pollAnswered() {
|
|
257
|
+
var infoText = cloudFile.text()
|
|
258
|
+
if (Mounts.pollDecision(root._listedOnce, root._polledListing, root._lastMountListing, infoText, root._lastMountinfo) !== "rebuild")
|
|
259
|
+
return
|
|
260
|
+
root._lastMountListing = root._polledListing
|
|
261
|
+
root._lastMountinfo = infoText
|
|
262
|
+
root._mountListing = root._polledListing
|
|
263
|
+
root.rebuild()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// /proc/self/mountinfo, read on the listing's own poll for ui/js/Cloud.js's FUSE rows.
|
|
267
|
+
// A FileView watch is not set up: proc files report no change events, so the poll drives it.
|
|
268
|
+
FileView {
|
|
269
|
+
id: cloudFile
|
|
270
|
+
path: "/proc/self/mountinfo"
|
|
271
|
+
printErrors: false
|
|
272
|
+
onLoaded: root.pollAnswered()
|
|
273
|
+
onLoadFailed: root.pollAnswered()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// The saved places file is ui/NetworkPlaces.qml's, the only writer of it in this Service.
|
|
277
|
+
NetworkPlaces {
|
|
278
|
+
id: places
|
|
279
|
+
entries: root.entries
|
|
280
|
+
onMessage: function (text, isError) { root.message(text, isError) }
|
|
281
|
+
onWrote: root.renamed()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Phones and shares open through the GVFS FUSE bridge, hosted window-long rather than
|
|
285
|
+
// in the rail: hiding the rail mid-wait kills no wait, and the ready, the failure and the
|
|
286
|
+
// Starting line it showed all still land. Files land on openFileRequested, so a typed
|
|
287
|
+
// network URL naming a file is never listed as a folder.
|
|
288
|
+
Loader {
|
|
289
|
+
id: bridgeLoader
|
|
290
|
+
active: false // built on first ensure, so launch pays no Timers, Process or compile
|
|
291
|
+
source: "GvfsBridge.qml"
|
|
292
|
+
}
|
|
293
|
+
// Null until the first ensure; every reader below guards it.
|
|
294
|
+
readonly property var bridge: bridgeLoader.item
|
|
295
|
+
readonly property bool bridgeBuilt: bridgeLoader.active
|
|
296
|
+
// Whether a bridge wait is in flight, so the listing poll holds while one stands.
|
|
297
|
+
readonly property bool bridgeWaiting: root.bridge !== null && root.bridge.flow.waiter !== null
|
|
298
|
+
// Synchronous: a local source: URL answers item on the same call that sets active.
|
|
299
|
+
function ensureBridge() {
|
|
300
|
+
if (!bridgeLoader.active)
|
|
301
|
+
bridgeLoader.active = true
|
|
302
|
+
return bridgeLoader.item
|
|
303
|
+
}
|
|
304
|
+
// A null bridge is a missing component, not a refused mount, so the reason says Flea did not load.
|
|
305
|
+
function bridgeMissingReason() {
|
|
306
|
+
return Format.fill(Mounts.WORDS.bridgeMissing, root._pendingLabel || root._pendingUri)
|
|
307
|
+
}
|
|
308
|
+
// The log warning names the file and the Loader status, and the wait ends with no retry.
|
|
309
|
+
function failBridgeMissing() {
|
|
310
|
+
console.warn("GvfsBridge.qml did not load: Loader status " + bridgeLoader.status)
|
|
311
|
+
if (root._requestPassword.length > 0) root.remember(root._pendingUri, root._requestPassword)
|
|
312
|
+
mountTimeout.stop()
|
|
313
|
+
root._repairActive = false
|
|
314
|
+
root._repairWaiting = false
|
|
315
|
+
root._repairBridged = false
|
|
316
|
+
root._repairRoot = ""
|
|
317
|
+
root._repairPath = ""
|
|
318
|
+
root.result = "failed"
|
|
319
|
+
var reason = root.bridgeMissingReason()
|
|
320
|
+
root.message(reason, true)
|
|
321
|
+
root.finishRequest(false, reason)
|
|
322
|
+
}
|
|
323
|
+
Connections {
|
|
324
|
+
target: root.bridge
|
|
325
|
+
function onReady(path, origin, isDir) {
|
|
326
|
+
if (root._repairWaiting) {
|
|
327
|
+
root._repairWaiting = false
|
|
328
|
+
root.sticky("", origin)
|
|
329
|
+
root._repairActive = true
|
|
330
|
+
mountTimeout.restart()
|
|
331
|
+
root.backend.peek(root._repairRoot, 512, false)
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
root.sticky("", origin)
|
|
335
|
+
var held = root._bridgeIdentities[path]
|
|
336
|
+
delete root._bridgeIdentities[path]
|
|
337
|
+
if (!isDir) {
|
|
338
|
+
root.openFileRequested(path, origin)
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
if (held) root.rememberPathAlias(path, held.label, held.uri)
|
|
342
|
+
root.opened(path, origin)
|
|
343
|
+
}
|
|
344
|
+
function onStarting(text, origin) { root.startingBridge(text, origin) }
|
|
345
|
+
function onFailed(text, origin) {
|
|
346
|
+
if (root._repairWaiting) {
|
|
347
|
+
root._repairWaiting = false
|
|
348
|
+
root.sticky("", origin)
|
|
349
|
+
root.repairFailed()
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
root.result = "failed"
|
|
353
|
+
root.sticky("", origin)
|
|
354
|
+
root.message(text, true)
|
|
355
|
+
}
|
|
356
|
+
// The bridge's busy refusal reaches a repair the same way: a second bridge is never
|
|
357
|
+
// started, so the repair ends instead of waiting on a wait it did not start.
|
|
358
|
+
function onNotice(text, origin) {
|
|
359
|
+
if (root._repairWaiting) {
|
|
360
|
+
root._repairWaiting = false
|
|
361
|
+
root.sticky("", origin)
|
|
362
|
+
root.repairFailed()
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
root.message(text, false)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Three sources, deduped on the normalized uri (see ui/js/Mounts.js "normalize"): a live gio mount wins over a bookmark for the same share even when the trailing slash differs.
|
|
370
|
+
// The bookmark's own label wins on that merged row, or a rename of a mounted share would be written to the file and never drawn again; see ui/js/Mounts.js "railLabel".
|
|
371
|
+
function rebuild(mountinfoText) {
|
|
372
|
+
var out = []
|
|
373
|
+
var seen = {}
|
|
374
|
+
var mounts = Mounts.parseMounts(root._mountListing)
|
|
375
|
+
var marks = Mounts.nonFileBookmarks(root.bookmarksText)
|
|
376
|
+
for (var i = 0; i < mounts.length; i++) {
|
|
377
|
+
var covered = false
|
|
378
|
+
for (var j = 0; j < marks.length; j++) {
|
|
379
|
+
if (!Mounts.addressMountCovers(mounts[i].uri, marks[j].uri)) continue
|
|
380
|
+
covered = true
|
|
381
|
+
var coveredKey = Mounts.normalize(marks[j].uri)
|
|
382
|
+
if (seen[coveredKey]) continue
|
|
383
|
+
seen[coveredKey] = true
|
|
384
|
+
out.push({ path: "", label: marks[j].label, group: "network", kind: "share", uri: marks[j].uri, mountUri: mounts[i].uri, mounted: true, glyph: "server" })
|
|
385
|
+
}
|
|
386
|
+
if (covered) continue
|
|
387
|
+
var mkey = Mounts.normalize(mounts[i].uri)
|
|
388
|
+
if (seen[mkey]) continue
|
|
389
|
+
seen[mkey] = true
|
|
390
|
+
out.push({ path: "", label: Mounts.railLabel(mounts[i], marks), group: "network", kind: "share", uri: mounts[i].uri, mounted: true, glyph: "server" })
|
|
391
|
+
}
|
|
392
|
+
for (var k = 0; k < marks.length; k++) {
|
|
393
|
+
var bkey = Mounts.normalize(marks[k].uri)
|
|
394
|
+
if (seen[bkey]) continue
|
|
395
|
+
seen[bkey] = true
|
|
396
|
+
out.push({ path: "", label: marks[k].label, group: "network", kind: "share", uri: marks[k].uri, mounted: false, glyph: "server" })
|
|
397
|
+
}
|
|
398
|
+
if (root.dropboxPath.length > 0) {
|
|
399
|
+
out.push({ path: root.dropboxPath, label: "Dropbox", group: "network", kind: "dropbox", uri: "", mounted: true, glyph: "" })
|
|
400
|
+
}
|
|
401
|
+
// Cloud rows show a FUSE mount under its folder's name with no menu; mountinfoText is the tests' seam, live callers pass nothing.
|
|
402
|
+
var infoText = mountinfoText === undefined ? cloudFile.text() : mountinfoText
|
|
403
|
+
var clouds = Cloud.parseCloudMounts(infoText, Quickshell.env("HOME"))
|
|
404
|
+
var seenPath = {}
|
|
405
|
+
for (var c = 0; c < clouds.length; c++) {
|
|
406
|
+
seenPath[clouds[c].path] = true
|
|
407
|
+
out.push({ path: clouds[c].path, label: Mounts.leaf(clouds[c].path), group: "network",
|
|
408
|
+
kind: "cloud", uri: "", mounted: true, glyph: "server" })
|
|
409
|
+
}
|
|
410
|
+
// Every mountinfo kernel row stays; a gio line at its mountpoint lends name and release spelling, the rest keep the leaf and unmount by path.
|
|
411
|
+
var kernels = Cloud.parseNetworkMounts(infoText)
|
|
412
|
+
var gioRows = Mounts.gioKernelMounts(root._mountListing)
|
|
413
|
+
var gioByPath = {}
|
|
414
|
+
for (var g = 0; g < gioRows.length; g++) {
|
|
415
|
+
if (!gioByPath[gioRows[g].path])
|
|
416
|
+
gioByPath[gioRows[g].path] = gioRows[g]
|
|
417
|
+
}
|
|
418
|
+
for (var v = 0; v < kernels.length; v++) {
|
|
419
|
+
if (seenPath[kernels[v].path])
|
|
420
|
+
continue
|
|
421
|
+
seenPath[kernels[v].path] = true
|
|
422
|
+
var lent = gioByPath[kernels[v].path]
|
|
423
|
+
out.push({ path: kernels[v].path, label: lent ? lent.label : Mounts.leaf(kernels[v].path),
|
|
424
|
+
group: "network", kind: "share", uri: lent ? lent.uri : kernels[v].path,
|
|
425
|
+
fstype: kernels[v].fstype, mounted: true, glyph: "server", editable: false })
|
|
426
|
+
}
|
|
427
|
+
// Only persisted names may relabel an alias; live gio labels are deliberately excluded.
|
|
428
|
+
var labelSources = []
|
|
429
|
+
for (var s = 0; s < marks.length; s++)
|
|
430
|
+
labelSources.push({ uri: marks[s].uri, label: marks[s].label, saved: true })
|
|
431
|
+
for (var f = 0; root.savedFavourites && f < root.savedFavourites.length; f++) {
|
|
432
|
+
var favourite = root.savedFavourites[f]
|
|
433
|
+
if (favourite && favourite.path && favourite.label)
|
|
434
|
+
labelSources.push({ path: favourite.path, label: favourite.label, saved: true })
|
|
435
|
+
}
|
|
436
|
+
root._aliasLabels = Crumbs.labelsByKey(labelSources, Mounts.normalize)
|
|
437
|
+
root.pathAliases = Crumbs.relabelAliases(root.pathAliases, root._aliasLabels)
|
|
438
|
+
// Every five seconds forever, so an unchanged poll must not assign: see Mounts.sameEntries.
|
|
439
|
+
if (!Mounts.sameEntries(root.entries, out))
|
|
440
|
+
root.entries = out
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// A favourite's path is already real; a share needs mounting (if not live) then resolving.
|
|
444
|
+
// A cloud row's path is already real too: its mount is the tool that made it, not Flea's.
|
|
445
|
+
// origin rides along explicitly, because this service outlives the rail that renders it and
|
|
446
|
+
// the rail's own origin is gone by the time a hidden-rail answer lands.
|
|
447
|
+
function activate(index, origin) {
|
|
448
|
+
var e = root.entries[index]
|
|
449
|
+
if (!e) return
|
|
450
|
+
if (e.kind === "cloud") {
|
|
451
|
+
root.opened(e.path, origin === undefined ? root.origin : origin)
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
if (e.kind === "share") {
|
|
455
|
+
// A kernel mount's path is already real, so it opens with no gio round trip; a gvfs share carries no path and rides openShare below.
|
|
456
|
+
if (e.path && e.path.length > 0) {
|
|
457
|
+
root.opened(e.path, origin === undefined ? root.origin : origin)
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
root.openShare(e.uri, e.mounted, e.label, false, { origin: origin === undefined ? root.origin : origin })
|
|
461
|
+
return
|
|
462
|
+
}
|
|
463
|
+
root.opened(e.path, origin === undefined ? root.origin : origin)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function passwordFor(uri) {
|
|
467
|
+
return root._passwords[Mounts.normalize(uri)] || ""
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function remember(uri, password) {
|
|
471
|
+
if (password.length === 0) return
|
|
472
|
+
var next = Object.assign({}, root._passwords)
|
|
473
|
+
next[Mounts.normalize(uri)] = password
|
|
474
|
+
root._passwords = next
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// forgetPassword, not forget: forget(uri) below is the bookmark writer, and one name for both
|
|
478
|
+
// would have made a refused connect delete the saved place.
|
|
479
|
+
function forgetPassword(uri) {
|
|
480
|
+
var key = Mounts.normalize(uri)
|
|
481
|
+
if (root._passwords[key] === undefined) return
|
|
482
|
+
var next = Object.assign({}, root._passwords)
|
|
483
|
+
delete next[key]
|
|
484
|
+
root._passwords = next
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function saveLocation(uri, label, password, requestId, origin) {
|
|
488
|
+
return root.openShare(uri, false, label, password.length > 0, { id: requestId, password: password, origin: origin })
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function openChildShare(uri, label, origin) {
|
|
492
|
+
var password = root.passwordFor(root._pendingUri)
|
|
493
|
+
root.remember(uri, password)
|
|
494
|
+
root.openShare(uri, false, label, password.length > 0, { origin: origin })
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function openShare(uri, alreadyMounted, label, authenticated, request) {
|
|
498
|
+
request = request || ({})
|
|
499
|
+
// An open is single flight over four children, the bridge wait and the repair peek,
|
|
500
|
+
// the share listing included, so a new one must not start over the running leg and hand
|
|
501
|
+
// that leg's deadline to itself; see "listShares". A refusal names the moment and
|
|
502
|
+
// touches no flight state.
|
|
503
|
+
if (mountProcess.running || authProcess.running || infoProcess.running || listSharesProcess.running || root._repairActive || root._repairWaiting) {
|
|
504
|
+
// A guard that returns in silence names nothing at all, and a leg can hold it 15 s.
|
|
505
|
+
var reason = Mounts.WORDS.anotherOpening
|
|
506
|
+
if (request.id) root.completed(request.id, Mounts.normalize(uri), false, reason, false)
|
|
507
|
+
else root.message(reason, false)
|
|
508
|
+
return
|
|
509
|
+
}
|
|
510
|
+
if (root.result === "failed") root.message("", false)
|
|
511
|
+
// One canonical spelling from here: tests/network-open-share.sh pins the info leg to it.
|
|
512
|
+
root._pendingUri = Mounts.normalize(uri)
|
|
513
|
+
root._pendingLabel = label || ""
|
|
514
|
+
root._requestId = request.id || ""
|
|
515
|
+
root._requestPassword = request.password || ""
|
|
516
|
+
root._pendingOrigin = request.origin === undefined ? root.origin : request.origin
|
|
517
|
+
root._mountFailed = false
|
|
518
|
+
if (alreadyMounted) {
|
|
519
|
+
root.result = "resolving"
|
|
520
|
+
root.runInfo(uri)
|
|
521
|
+
return
|
|
522
|
+
}
|
|
523
|
+
var password = request.password || root.passwordFor(uri)
|
|
524
|
+
// A remembered password is a fallback for sftp, never a first resort: Mounts.keyless.
|
|
525
|
+
if (authenticated === true || (!Mounts.keyless(uri)
|
|
526
|
+
&& (password.length > 0 || Mounts.credentialed(uri)))) {
|
|
527
|
+
if (password.length === 0) {
|
|
528
|
+
root.result = "missing-credential"
|
|
529
|
+
if (!root.finishRequest(false, "Enter the password to mount this location."))
|
|
530
|
+
root.retryRequested(uri, root._pendingLabel, "", "Enter the password to mount this location.", false, root._pendingOrigin)
|
|
531
|
+
return
|
|
532
|
+
}
|
|
533
|
+
root._pendingPassword = password
|
|
534
|
+
password = ""
|
|
535
|
+
root._authAwaitingStart = true
|
|
536
|
+
root.result = "mounting"
|
|
537
|
+
authProcess.command = ["timeout", String(root.authTimeoutSeconds),
|
|
538
|
+
Quickshell.env("FLEA_GIO_AUTH") || "/usr/lib/flea/flea-gio-auth", root._pendingUri]
|
|
539
|
+
authProcess.running = true
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
root.result = "mounting"
|
|
543
|
+
mountProcess.command = /^smb:\/\//i.test(uri)
|
|
544
|
+
? ["gio", "mount", "--anonymous", uri] : ["gio", "mount", uri]
|
|
545
|
+
mountProcess.running = true
|
|
546
|
+
mountTimeout.restart()
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function runInfo(uri) {
|
|
550
|
+
infoProcess.command = ["gio", "info", uri]
|
|
551
|
+
root._infoOutput = ""
|
|
552
|
+
infoProcess.running = true
|
|
553
|
+
mountTimeout.restart()
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// refused says the server itself turned the credential down. Only that invalidates it: a helper
|
|
557
|
+
// that could not start and a host that never answered say nothing about the password, and the
|
|
558
|
+
// reopened dialog has to be populated from it exactly as 0.1.6 populated it.
|
|
559
|
+
function failMount(reason, password, refused, missing) {
|
|
560
|
+
root._pendingPassword = ""
|
|
561
|
+
root.result = missing === true ? "missing-credential" : "failed"
|
|
562
|
+
root.message(reason, true)
|
|
563
|
+
var attempted = password || root._requestPassword
|
|
564
|
+
// Keeping a refused secret meant every later click on that location replayed it with no
|
|
565
|
+
// prompt at all, which is how a domain account locks itself out.
|
|
566
|
+
if (refused === true) root.forgetPassword(root._pendingUri)
|
|
567
|
+
else if (attempted.length > 0) root.remember(root._pendingUri, attempted)
|
|
568
|
+
if (!root.finishRequest(false, reason, missing !== true))
|
|
569
|
+
root.retryRequested(root._pendingUri, root._pendingLabel, attempted, reason,
|
|
570
|
+
missing !== true, root._pendingOrigin)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Only when nothing in the FUSE root answers for the request: the mount fails with the
|
|
574
|
+
// reason gio gave, never as an unreadable directory.
|
|
575
|
+
function repairFailed() {
|
|
576
|
+
var uri = root._repairUri
|
|
577
|
+
var refused = root._repairFailed
|
|
578
|
+
root._repairActive = false
|
|
579
|
+
root._repairWaiting = false
|
|
580
|
+
root._repairBridged = false
|
|
581
|
+
root._repairRoot = ""
|
|
582
|
+
root._repairPath = ""
|
|
583
|
+
if (refused) root.failMount(Errors.WORDS.connectRefused, root.passwordFor(uri))
|
|
584
|
+
else root.failMount(Errors.WORDS.connectNoFolder, root.passwordFor(uri))
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Issue 194's repair, answered through backend.peek: gio's own folder first, then every
|
|
588
|
+
// entry of the FUSE root it names, matched by ui/js/Protocols.js smbResolve. A real
|
|
589
|
+
// directory always opens; several users prefer the desktop login and otherwise name
|
|
590
|
+
// themselves; anything else is the mount failing with the reason gio gave. Only a root
|
|
591
|
+
// that peeks empty or unreadable starts the bridge for the gvfs root, then peeks that
|
|
592
|
+
// root once under the same deadline.
|
|
593
|
+
function repairPeeked(path, rows, readFailed) {
|
|
594
|
+
if (!root._repairActive) return
|
|
595
|
+
if (path === root._repairPath) {
|
|
596
|
+
if (!readFailed) {
|
|
597
|
+
var openPath = root._repairPath
|
|
598
|
+
var openOrigin = root._pendingOrigin
|
|
599
|
+
mountTimeout.stop()
|
|
600
|
+
root._repairActive = false
|
|
601
|
+
root._repairBridged = false
|
|
602
|
+
root._repairPath = ""
|
|
603
|
+
root.result = "mounted"
|
|
604
|
+
root.rememberPathAlias(openPath, root._pendingLabel, root._pendingUri)
|
|
605
|
+
root.finishRequest(true, "")
|
|
606
|
+
root.opened(openPath, openOrigin)
|
|
607
|
+
return
|
|
608
|
+
}
|
|
609
|
+
var cut = root._repairPath.indexOf("/gvfs/")
|
|
610
|
+
if (cut < 0 || !root.backend) {
|
|
611
|
+
mountTimeout.stop()
|
|
612
|
+
root.repairFailed()
|
|
613
|
+
return
|
|
614
|
+
}
|
|
615
|
+
root._repairRoot = root._repairPath.substring(0, cut + 5)
|
|
616
|
+
mountTimeout.restart()
|
|
617
|
+
root.backend.peek(root._repairRoot, 512, false)
|
|
618
|
+
return
|
|
619
|
+
}
|
|
620
|
+
if (root._repairRoot.length === 0 || path !== root._repairRoot) return
|
|
621
|
+
// The bridge down reads as an empty root, so only then does the repair spend its one
|
|
622
|
+
// bridge start; a root that answered with entries but no match fails without one.
|
|
623
|
+
if ((readFailed || rows.length === 0) && !root._repairBridged && root.backend) {
|
|
624
|
+
root._repairBridged = true
|
|
625
|
+
root._repairWaiting = true
|
|
626
|
+
mountTimeout.restart()
|
|
627
|
+
var repairBridge = root.ensureBridge()
|
|
628
|
+
if (repairBridge)
|
|
629
|
+
repairBridge.ensure(root._repairRoot, root._pendingLabel, root._pendingOrigin)
|
|
630
|
+
else
|
|
631
|
+
root.failBridgeMissing()
|
|
632
|
+
return
|
|
633
|
+
}
|
|
634
|
+
mountTimeout.stop()
|
|
635
|
+
var candidates = []
|
|
636
|
+
for (var i = 0; i < rows.length; i++) candidates.push(root._repairRoot + "/" + rows[i].n)
|
|
637
|
+
var login = Quickshell.env("USER") || Quickshell.env("LOGNAME") || ""
|
|
638
|
+
var picked = Protocols.smbResolve(root._repairUri, candidates, login)
|
|
639
|
+
root._repairActive = false
|
|
640
|
+
root._repairBridged = false
|
|
641
|
+
root._repairRoot = ""
|
|
642
|
+
root._repairPath = ""
|
|
643
|
+
if (picked.path.length > 0) {
|
|
644
|
+
var pickedOrigin = root._pendingOrigin
|
|
645
|
+
root.result = "mounted"
|
|
646
|
+
root.rememberPathAlias(picked.path, root._pendingLabel, root._pendingUri)
|
|
647
|
+
root.finishRequest(true, "")
|
|
648
|
+
root.opened(picked.path, pickedOrigin)
|
|
649
|
+
return
|
|
650
|
+
}
|
|
651
|
+
if (picked.users.length > 0) {
|
|
652
|
+
root.failMount(Format.fill(Errors.WORDS.connectMountedAs, picked.users.join(", ")),
|
|
653
|
+
root.passwordFor(root._repairUri))
|
|
654
|
+
return
|
|
655
|
+
}
|
|
656
|
+
root.repairFailed()
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function finishRequest(success, reason, connectFailed) {
|
|
660
|
+
var requestId = root._requestId
|
|
661
|
+
if (!requestId) return false
|
|
662
|
+
root._requestId = ""
|
|
663
|
+
if (success) root.remember(root._pendingUri, root._requestPassword)
|
|
664
|
+
root._requestPassword = ""
|
|
665
|
+
root.completed(requestId, root._pendingUri, success, reason || "", connectFailed === true)
|
|
666
|
+
return true
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function cancelLocation(requestId) {
|
|
670
|
+
if (!requestId || requestId !== root._requestId) return
|
|
671
|
+
var origin = root._pendingOrigin
|
|
672
|
+
root._requestId = ""
|
|
673
|
+
root._requestPassword = ""
|
|
674
|
+
root._pendingPassword = ""
|
|
675
|
+
root._authAwaitingStart = false
|
|
676
|
+
root._repairActive = false
|
|
677
|
+
root._repairWaiting = false
|
|
678
|
+
root._repairBridged = false
|
|
679
|
+
// Only this flight's own bridge waiter ends; another flight's wait is never cancelled,
|
|
680
|
+
// and with no bridge built no wait can stand.
|
|
681
|
+
if (root.bridge)
|
|
682
|
+
root.bridge.cancelFor(origin)
|
|
683
|
+
root.sticky("", origin)
|
|
684
|
+
mountTimeout.stop()
|
|
685
|
+
if (mountProcess.running) { root._mountTimedOut = true; mountProcess.running = false }
|
|
686
|
+
if (infoProcess.running) { root._infoTimedOut = true; infoProcess.running = false }
|
|
687
|
+
if (listSharesProcess.running) { root._listSharesTimedOut = true; listSharesProcess.running = false }
|
|
688
|
+
if (authProcess.running) { root._authCancelled = true; authProcess.running = false }
|
|
689
|
+
root.result = "cancelled"
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// A server root with no share segment mounts but has no FUSE path of its own.
|
|
693
|
+
function isBareRoot(uri) {
|
|
694
|
+
return /^[a-z][a-z0-9+.-]*:\/\/[^\/]+\/?$/i.test(uri)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Client-side only, never writes bookmarks; see AGENTS.md "The share browser overlay". This is
|
|
698
|
+
// the third leg of an open and takes the same bound: "gio list" on a server gvfs cannot reach
|
|
699
|
+
// hangs exactly the way "gio info" does, with nothing else in the chain left to end it.
|
|
700
|
+
function listShares(uri) {
|
|
701
|
+
listSharesProcess.command = ["gio", "list", uri]
|
|
702
|
+
root._listSharesOutput = ""
|
|
703
|
+
listSharesProcess.running = true
|
|
704
|
+
mountTimeout.restart()
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// One shared ContextMenu preserves keyboard focus; this service performs its Unmount action.
|
|
708
|
+
function unmount(index) {
|
|
709
|
+
var e = root.entries[index]
|
|
710
|
+
if (!e || e.kind !== "share" || !e.mounted || unmountProcess.running) return
|
|
711
|
+
root._pendingUnmountLabel = e.label
|
|
712
|
+
// A user FUSE mount answers to its own unmounter; the exit status below decides.
|
|
713
|
+
if (e.fstype && String(e.fstype).indexOf("fuse.") === 0)
|
|
714
|
+
unmountProcess.command = ["fusermount3", "-u", e.path]
|
|
715
|
+
else
|
|
716
|
+
unmountProcess.command = ["gio", "mount", "-u", e.mountUri || e.uri]
|
|
717
|
+
unmountProcess.running = true
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// What the rail and the Processes below still call by name; the work is in the two children above.
|
|
721
|
+
function rename(uri, name) { places.rename(uri, name) }
|
|
722
|
+
function forget(uri) { places.forget(uri) }
|
|
723
|
+
function replacePlace(oldUri) { places.replace(oldUri, root._pendingUri, root._pendingLabel) }
|
|
724
|
+
function pollMounts() { listing.poll() }
|
|
725
|
+
|
|
726
|
+
Timer {
|
|
727
|
+
id: mountTimeout
|
|
728
|
+
interval: root.mountTimeoutMs
|
|
729
|
+
repeat: false
|
|
730
|
+
onTriggered: {
|
|
731
|
+
// Whichever leg of the open is still running is the one that missed the deadline.
|
|
732
|
+
if (mountProcess.running) {
|
|
733
|
+
root._mountTimedOut = true
|
|
734
|
+
mountProcess.running = false
|
|
735
|
+
} else if (infoProcess.running) {
|
|
736
|
+
root._infoTimedOut = true
|
|
737
|
+
infoProcess.running = false
|
|
738
|
+
} else if (listSharesProcess.running) {
|
|
739
|
+
root._listSharesTimedOut = true
|
|
740
|
+
listSharesProcess.running = false
|
|
741
|
+
} else if (root._repairActive) {
|
|
742
|
+
root._repairActive = false
|
|
743
|
+
root._repairWaiting = false
|
|
744
|
+
root._repairBridged = false
|
|
745
|
+
root._repairRoot = ""
|
|
746
|
+
root._repairPath = ""
|
|
747
|
+
} else {
|
|
748
|
+
return
|
|
749
|
+
}
|
|
750
|
+
// Not failMount: that offers a Retry over the rail, and the deadline's own arm in
|
|
751
|
+
// tests/ui.sh presses l on the rail the instant this fires. An address that never
|
|
752
|
+
// answered has no credential to correct anyway.
|
|
753
|
+
root.result = "failed"
|
|
754
|
+
root.message(Errors.WORDS.connectNoAnswer, true)
|
|
755
|
+
root.finishRequest(false, Errors.WORDS.connectNoAnswer, true)
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// The credentialed leg: "timeout" bounds it rather than mountTimeout, so a hung helper answers
|
|
760
|
+
// 124 and Errors.connectFailure names it, and the C locale above reaches the gio the helper runs.
|
|
761
|
+
Process {
|
|
762
|
+
id: authProcess
|
|
763
|
+
environment: root.gioEnvironment
|
|
764
|
+
stdinEnabled: true
|
|
765
|
+
stderr: StdioCollector { waitForEnd: true }
|
|
766
|
+
onStarted: {
|
|
767
|
+
root._authAwaitingStart = false
|
|
768
|
+
var password = root._pendingPassword
|
|
769
|
+
root._pendingPassword = ""
|
|
770
|
+
authProcess.write(password + "\n")
|
|
771
|
+
password = ""
|
|
772
|
+
}
|
|
773
|
+
onRunningChanged: {
|
|
774
|
+
if (root._authAwaitingStart && !authProcess.running) {
|
|
775
|
+
root._authAwaitingStart = false
|
|
776
|
+
root.failMount(Errors.WORDS.connectNoPrompt,
|
|
777
|
+
root.passwordFor(root._pendingUri))
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
onExited: function (exitCode) {
|
|
781
|
+
if (root._authCancelled) { root._authCancelled = false; return }
|
|
782
|
+
root._pendingPassword = ""
|
|
783
|
+
if (exitCode === 0) {
|
|
784
|
+
root.runInfo(root._pendingUri)
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
// 124 is a host that never answered and 126/127 a helper that could not start; neither
|
|
788
|
+
// is the server turning the credential down, so neither invalidates it.
|
|
789
|
+
var refused = exitCode !== 124 && exitCode !== 126 && exitCode !== 127
|
|
790
|
+
root.failMount(Errors.connectFailure(exitCode),
|
|
791
|
+
root.passwordFor(root._pendingUri), refused)
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
Process {
|
|
796
|
+
id: mountProcess
|
|
797
|
+
environment: root.gioEnvironment
|
|
798
|
+
onExited: function (exitCode) {
|
|
799
|
+
mountTimeout.stop()
|
|
800
|
+
var timedOut = root._mountTimedOut
|
|
801
|
+
root._mountTimedOut = false
|
|
802
|
+
if (timedOut) return
|
|
803
|
+
// A refusal for a location that is already mounted and a refusal for one that does not
|
|
804
|
+
// exist differ only in a translated sentence, so neither is read: the info call below
|
|
805
|
+
// answers with a FUSE path when the location really is mounted, whatever this code was.
|
|
806
|
+
root._mountFailed = exitCode !== 0
|
|
807
|
+
root.runInfo(root._pendingUri)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// The FUSE path is ui/js/Mounts.js "localPath"'s to find, one resolver for the product and for
|
|
812
|
+
// tests/js/network.js, and the C locale above is what keeps gio's own wording stable for it.
|
|
813
|
+
Process {
|
|
814
|
+
id: infoProcess
|
|
815
|
+
environment: root.gioEnvironment
|
|
816
|
+
stdout: StdioCollector { id: infoOut; waitForEnd: true; onStreamFinished: root._infoOutput = text }
|
|
817
|
+
onExited: function (exitCode) {
|
|
818
|
+
mountTimeout.stop()
|
|
819
|
+
root.pollMounts()
|
|
820
|
+
var timedOut = root._infoTimedOut
|
|
821
|
+
root._infoTimedOut = false
|
|
822
|
+
var failed = root._mountFailed
|
|
823
|
+
root._mountFailed = false
|
|
824
|
+
if (timedOut) return
|
|
825
|
+
var path = Mounts.localPath(String(infoOut.text || root._infoOutput || ""))
|
|
826
|
+
if (exitCode === 0 && path.length > 0 && root.backend && Protocols.schemeOf(root._pendingUri) === "smb"
|
|
827
|
+
&& !Protocols.smbFuseMatches(root._pendingUri, path)) {
|
|
828
|
+
// Issue 194: gio answered with a folder that is not this share (a user its line
|
|
829
|
+
// drops above all). Whether it exists is the backend's own answer to say: the
|
|
830
|
+
// folder peeks first, then the FUSE root, and only an empty or unreadable root
|
|
831
|
+
// starts the bridge for the gvfs root under the same deadline.
|
|
832
|
+
root._repairUri = root._pendingUri
|
|
833
|
+
root._repairPath = path
|
|
834
|
+
root._repairFailed = failed
|
|
835
|
+
root._repairRoot = ""
|
|
836
|
+
root._repairWaiting = false
|
|
837
|
+
root._repairBridged = false
|
|
838
|
+
root._repairActive = true
|
|
839
|
+
mountTimeout.restart()
|
|
840
|
+
root.backend.peek(path, 1, false)
|
|
841
|
+
return
|
|
842
|
+
}
|
|
843
|
+
if (exitCode === 0 && path.length > 0) {
|
|
844
|
+
var openBridge = root.ensureBridge()
|
|
845
|
+
if (!openBridge) {
|
|
846
|
+
root.failBridgeMissing()
|
|
847
|
+
return
|
|
848
|
+
}
|
|
849
|
+
// Capture before emitting completion or waiting: a later open may replace _pending* before onReady.
|
|
850
|
+
var bridgeLabel = root._pendingLabel
|
|
851
|
+
var bridgeUri = root._pendingUri
|
|
852
|
+
var bridgeOrigin = root._pendingOrigin
|
|
853
|
+
root._bridgeIdentities[path] = { label: bridgeLabel, uri: bridgeUri }
|
|
854
|
+
root.result = "mounted"
|
|
855
|
+
root.finishRequest(true, "")
|
|
856
|
+
openBridge.ensure(path, bridgeLabel, bridgeOrigin)
|
|
857
|
+
return
|
|
858
|
+
}
|
|
859
|
+
// A refused keyless sftp attempt is a missing credential and not a refused location, sftp only.
|
|
860
|
+
if (failed && Mounts.keyless(root._pendingUri) && Mounts.credentialed(root._pendingUri)) {
|
|
861
|
+
root.failMount("Enter the password to mount this location.",
|
|
862
|
+
root.passwordFor(root._pendingUri), false, true)
|
|
863
|
+
return
|
|
864
|
+
}
|
|
865
|
+
// A server root lists its shares whatever the exit code said, so this stays ahead of the refusal.
|
|
866
|
+
if (root.isBareRoot(root._pendingUri)) {
|
|
867
|
+
root.result = "resolving"
|
|
868
|
+
root.listShares(root._pendingUri)
|
|
869
|
+
return
|
|
870
|
+
}
|
|
871
|
+
if (failed) {
|
|
872
|
+
root.failMount(Errors.WORDS.connectRefused,
|
|
873
|
+
root.passwordFor(root._pendingUri))
|
|
874
|
+
return
|
|
875
|
+
}
|
|
876
|
+
root.failMount(Errors.WORDS.connectNoFolder, root.passwordFor(root._pendingUri))
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
Process {
|
|
881
|
+
id: listSharesProcess
|
|
882
|
+
environment: root.gioEnvironment
|
|
883
|
+
stdout: StdioCollector { id: listSharesOut; waitForEnd: true; onStreamFinished: root._listSharesOutput = text }
|
|
884
|
+
onExited: function (exitCode) {
|
|
885
|
+
mountTimeout.stop()
|
|
886
|
+
var timedOut = root._listSharesTimedOut
|
|
887
|
+
root._listSharesTimedOut = false
|
|
888
|
+
if (timedOut) return
|
|
889
|
+
var body = String(listSharesOut.text || root._listSharesOutput || "")
|
|
890
|
+
var names = body.split("\n").map(function (s) { return s.trim() }).filter(function (s) { return s.length > 0 })
|
|
891
|
+
if (exitCode !== 0 || names.length === 0) {
|
|
892
|
+
root.failMount(Errors.WORDS.connectNoFolder, root.passwordFor(root._pendingUri))
|
|
893
|
+
return
|
|
894
|
+
}
|
|
895
|
+
root.result = "mounted"
|
|
896
|
+
root.finishRequest(true, "")
|
|
897
|
+
root.sharesListed(root._pendingUri, root._pendingLabel, names, root._pendingOrigin)
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
Process {
|
|
902
|
+
id: unmountProcess
|
|
903
|
+
environment: root.gioEnvironment
|
|
904
|
+
onExited: function (exitCode) {
|
|
905
|
+
root.pollMounts()
|
|
906
|
+
root.result = exitCode === 0 ? "unmounted" : "failed"
|
|
907
|
+
// Replace the arm prompt immediately after successful unmount.
|
|
908
|
+
root.message(exitCode === 0
|
|
909
|
+
? Format.fill(Mounts.WORDS.unmountedLine, root._pendingUnmountLabel)
|
|
910
|
+
: "That share could not be unmounted; it may still be in use.", exitCode !== 0)
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|