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,540 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell.Io
|
|
3
|
+
import "js/Devices.js" as Devices
|
|
4
|
+
import "js/Format.js" as Format
|
|
5
|
+
import "js/Mounts.js" as Mounts
|
|
6
|
+
import "js/Eject.js" as Eject
|
|
7
|
+
|
|
8
|
+
// The DEVICES group's own Service, the shape ui/NetworkMounts.qml has for NETWORK: the only thing
|
|
9
|
+
// here that runs a process, while ui/Sidebar.qml reads "entries" and renders it. Enumeration is
|
|
10
|
+
// lsblk, which any user may run; acting on a volume is gio, the tool the shares already drive.
|
|
11
|
+
Item {
|
|
12
|
+
id: root
|
|
13
|
+
|
|
14
|
+
property var entries: []
|
|
15
|
+
// RailAdditions rule 1's switch, on by default from 0.3.3; off answers 0.2.1's rows, and the state document is read elsewhere.
|
|
16
|
+
property bool showUnmounted: true
|
|
17
|
+
|
|
18
|
+
signal opened(string path)
|
|
19
|
+
signal message(string text, bool isError)
|
|
20
|
+
// A failed mount, unmount or eject: the headline plus gio's own reason for the status bar's detail line.
|
|
21
|
+
signal operationResult(string headline, string detail, bool isError)
|
|
22
|
+
// An eject is about to release this mountpoint: readers on it stop first (#232).
|
|
23
|
+
signal quiesce(string path)
|
|
24
|
+
signal unlockRequested(string device, string label, double size, string fstype)
|
|
25
|
+
// The verdict this surface last posted, so a newer one replaces it and nothing else.
|
|
26
|
+
signal forgetMessage(string text)
|
|
27
|
+
property string _lastVerdict: ""
|
|
28
|
+
|
|
29
|
+
// lsblk costs 5 ms on this box where gio mount -l costs 513 ms, so the rail's own five second
|
|
30
|
+
// rhythm carries this too rather than earning a slower clock of its own.
|
|
31
|
+
readonly property int pollMs: 5000
|
|
32
|
+
// A listing is bounded the same way a mount is below, and for the same reason ui/NetworkMounts.qml
|
|
33
|
+
// bounds its own: an lsblk that never answers left poll() refusing forever and froze the rail.
|
|
34
|
+
readonly property int listTimeoutMs: 10000
|
|
35
|
+
// A mount that never grows a mountpoint has to stop waiting, or the row waits on the poll forever.
|
|
36
|
+
readonly property int mountTimeoutMs: 15000
|
|
37
|
+
// How long an eject waits for a listing it can judge after gio exits, before the user is told
|
|
38
|
+
// that nothing was confirmed: three polls, the same bound the mount above gets.
|
|
39
|
+
readonly property int ejectVerdictMs: 15000
|
|
40
|
+
// How long a power-off chain may hold the eject guard: a hung unmount leg ends here instead.
|
|
41
|
+
readonly property int powerOffWaitMs: 15000
|
|
42
|
+
|
|
43
|
+
property string _listing: ""
|
|
44
|
+
// A Process's own onExited can race its StdioCollector's text property, the same guard every
|
|
45
|
+
// stdout-reading process in ui/NetworkMounts.qml already carries.
|
|
46
|
+
property string _listOutput: ""
|
|
47
|
+
// The device a mount was asked for; rebuild() opens it the moment lsblk reports its mountpoint.
|
|
48
|
+
property string _pendingOpenDevice: ""
|
|
49
|
+
property string _pendingOpenLabel: ""
|
|
50
|
+
// The volume an unmount was asked for, so its refusal can name it.
|
|
51
|
+
property string _unmountLabel: ""
|
|
52
|
+
// The last written-sector count the chain saw, so a flushing leg restarts the deadline.
|
|
53
|
+
property string _powerSectors: ""
|
|
54
|
+
// A power-off in flight: the disk to stop and the nodes still to unmount.
|
|
55
|
+
property string _powerOffDisk: ""
|
|
56
|
+
property var _powerOffQueue: []
|
|
57
|
+
// gio's own stderr per action, so a refusal names its cause instead of a guess.
|
|
58
|
+
property string _mountErr: ""
|
|
59
|
+
property string _unmountErr: ""
|
|
60
|
+
property string _ejectErr: ""
|
|
61
|
+
property string _unmountDevice: ""
|
|
62
|
+
// The device whose eject awaits its verdict, "" when none. The listing taken after gio exits is
|
|
63
|
+
// the only witness: gio's own exit code has been 0 over a volume that was still mounted.
|
|
64
|
+
property string _ejectDevice: ""
|
|
65
|
+
property string _ejectLabel: ""
|
|
66
|
+
property bool _ejectLoop: false
|
|
67
|
+
// A loop detach failed; the next listing judges whether the unmount succeeded.
|
|
68
|
+
property bool _ejectDetachFailed: false
|
|
69
|
+
// Listings are counted as they start. _verdictFromListing is 0 while gio runs, so nothing is
|
|
70
|
+
// judged before it exits; at exit it becomes one past the count, so the listing in flight at
|
|
71
|
+
// that moment, taken before the eject finished, is never the witness.
|
|
72
|
+
property int _listingsStarted: 0
|
|
73
|
+
property int _verdictFromListing: 0
|
|
74
|
+
// poll() refuses to start a listing until the previous one's stream has finished, so the body a
|
|
75
|
+
// verdict is read from and the count it is gated on always belong to the same run.
|
|
76
|
+
property bool _streamPending: false
|
|
77
|
+
// Cleared only when the next listing starts, which onExited's own release of _streamPending
|
|
78
|
+
// guarantees cannot happen until the ended listing is fully done with.
|
|
79
|
+
property bool listTimedOut: false
|
|
80
|
+
// Set when the chain deadline ends that leg, so its own onExited is swallowed once.
|
|
81
|
+
property bool _unmountEndedLate: false
|
|
82
|
+
// The -t leg's own ended-late flag, never set by an unmount the deadline ends.
|
|
83
|
+
property bool _ejectEndedLate: false
|
|
84
|
+
// The rail's DEVICES gate: true once the first listing answered, timed out or not, without replacing anything.
|
|
85
|
+
property bool firstAnswered: false
|
|
86
|
+
// Set on the unmount's first turn, since a binding on Process.running would still say idle when PaneRail decides whether to unload this host.
|
|
87
|
+
property bool _unmounting: false
|
|
88
|
+
// True while a release is in flight: an eject awaiting its verdict, which a power-off chain holds too, or a user unmount.
|
|
89
|
+
readonly property bool releasing: root._ejectDevice.length > 0 || root._unmounting
|
|
90
|
+
|
|
91
|
+
// The internal disk row reads the hostname alone (decided 2026-09-08; the canvas drew "<host> · <kernel name>"), and /etc/hostname is the
|
|
92
|
+
// one source for that host name that costs no process.
|
|
93
|
+
FileView {
|
|
94
|
+
id: hostnameFile
|
|
95
|
+
path: "/etc/hostname"
|
|
96
|
+
blockLoading: true
|
|
97
|
+
printErrors: false
|
|
98
|
+
onLoaded: root.rebuild()
|
|
99
|
+
onLoadFailed: root.rebuild()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The write counter is built by the first power-off and retained for later chains.
|
|
103
|
+
Loader {
|
|
104
|
+
id: powerSectorsFile
|
|
105
|
+
active: false
|
|
106
|
+
source: "PowerSectorsReader.qml"
|
|
107
|
+
onLoaded: item.owner = root
|
|
108
|
+
}
|
|
109
|
+
on_PowerOffDiskChanged: if (_powerOffDisk.length > 0) powerSectorsFile.active = true
|
|
110
|
+
|
|
111
|
+
// False parks the poll once the first listing answered; a mount or a release in flight reads the next listing, so it keeps polling.
|
|
112
|
+
property bool polling: true
|
|
113
|
+
Timer {
|
|
114
|
+
interval: root.pollMs
|
|
115
|
+
running: root.polling || !root.firstAnswered || root.releasing || root._pendingOpenDevice.length > 0 || root._powerOffDisk.length > 0
|
|
116
|
+
repeat: true
|
|
117
|
+
triggeredOnStart: true
|
|
118
|
+
onTriggered: root.poll()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function poll() {
|
|
122
|
+
if (listProcess.running || root._streamPending)
|
|
123
|
+
return
|
|
124
|
+
root.listTimedOut = false
|
|
125
|
+
root._streamPending = true
|
|
126
|
+
root._listingsStarted += 1
|
|
127
|
+
listProcess.running = true
|
|
128
|
+
listTimeout.restart()
|
|
129
|
+
if (root._powerOffDisk.length > 0)
|
|
130
|
+
powerSectorsFile.item.reload()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Timer {
|
|
134
|
+
id: listTimeout
|
|
135
|
+
interval: root.listTimeoutMs
|
|
136
|
+
repeat: false
|
|
137
|
+
onTriggered: {
|
|
138
|
+
if (!listProcess.running)
|
|
139
|
+
return
|
|
140
|
+
// Ending it is what lets the next poll run at all; a listing nobody can end froze the rail.
|
|
141
|
+
root.listTimedOut = true
|
|
142
|
+
listProcess.running = false
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Decided 2026-09-08: the machine's row is its hostname alone; the kernel name stays the fallback for a box with no hostname, and r.device still names the disk for the actions.
|
|
147
|
+
function hostLabel(fallback) {
|
|
148
|
+
var host = String(hostnameFile.text() || "").trim()
|
|
149
|
+
return host.length > 0 ? host : fallback
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function rebuild() {
|
|
153
|
+
var rows = Devices.parseDevices(root._listing, root.showUnmounted)
|
|
154
|
+
var out = []
|
|
155
|
+
for (var i = 0; i < rows.length; i++) {
|
|
156
|
+
var r = rows[i]
|
|
157
|
+
var label = r.kind === "disk" ? root.hostLabel(r.label) : r.label
|
|
158
|
+
out.push({ path: r.path, label: label, group: "device", kind: r.kind,
|
|
159
|
+
device: r.device, mounted: r.mounted, removable: r.removable,
|
|
160
|
+
mediaRemovable: r.mediaRemovable === true, readOnly: r.readOnly === true, size: r.size,
|
|
161
|
+
glyph: r.locked === true ? "lock" : "drive", locked: r.locked === true,
|
|
162
|
+
fstype: r.fstype || "", fsver: r.fsver || "", loop: !!r.loop })
|
|
163
|
+
}
|
|
164
|
+
// Same rule as ui/NetworkMounts.qml's: an unchanged poll assigns nothing, see Mounts.sameEntries.
|
|
165
|
+
if (!Mounts.sameEntries(root.entries, out))
|
|
166
|
+
root.entries = out
|
|
167
|
+
root.openWhenMounted()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// gio returns before udisks has published the mountpoint, so the poll after it is what
|
|
171
|
+
// resolves the path; this is the one place a just-mounted volume is opened.
|
|
172
|
+
function openWhenMounted() {
|
|
173
|
+
if (root._pendingOpenDevice.length === 0)
|
|
174
|
+
return
|
|
175
|
+
for (var i = 0; i < root.entries.length; i++) {
|
|
176
|
+
var e = root.entries[i]
|
|
177
|
+
if (e.device === root._pendingOpenDevice && e.mounted) {
|
|
178
|
+
root._pendingOpenDevice = ""
|
|
179
|
+
mountTimeout.stop()
|
|
180
|
+
root.opened(e.path)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The disk row and a mounted volume open directly; an unmounted volume mounts first, then
|
|
187
|
+
// opens, because a user who picks a disk means to look inside it.
|
|
188
|
+
function activate(index) {
|
|
189
|
+
var e = root.entries[index]
|
|
190
|
+
if (!e)
|
|
191
|
+
return
|
|
192
|
+
if (e.locked === true) {
|
|
193
|
+
root.unlockRequested(e.device, e.label, Number(e.size) || 0, String(e.fstype || ""))
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
if (e.mounted) {
|
|
197
|
+
root.opened(e.path)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
root.mountVolume(e)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function reportFailure(headline, stderr) { root.operationResult(headline, Devices.mountDetail(stderr), true) }
|
|
204
|
+
|
|
205
|
+
function mountVolume(e) {
|
|
206
|
+
if (mountProcess.running) {
|
|
207
|
+
root.message(Mounts.WORDS.anotherMounting, false)
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
root._pendingOpenDevice = e.device
|
|
211
|
+
root._pendingOpenLabel = e.label
|
|
212
|
+
root._mountErr = ""
|
|
213
|
+
mountProcess.command = ["gio", "mount", "-d", e.device]
|
|
214
|
+
mountProcess.running = true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// RailAdditions rule 2's Unmount, which is not Eject: a fixed disk stays where it is and only
|
|
218
|
+
// its filesystem goes away. gio is handed the mount point, the way eject below is, and the poll
|
|
219
|
+
// that follows is what redraws the row as unmounted.
|
|
220
|
+
function unmount(index) {
|
|
221
|
+
// A chain owns unmountProcess for its legs, so a user unmount waits for the eject.
|
|
222
|
+
if (root._powerOffDisk.length > 0) {
|
|
223
|
+
root.message(Format.fill(Mounts.WORDS.stillEjecting, root._ejectLabel), false)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
var e = root.entries[index]
|
|
227
|
+
if (!e || e.kind !== "volume" || !e.mounted)
|
|
228
|
+
return
|
|
229
|
+
if (unmountProcess.running) {
|
|
230
|
+
root.message(Format.fill(Mounts.WORDS.stillUnmounting, root._unmountLabel), false)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
root.quiesce(e.path)
|
|
234
|
+
root._unmountLabel = e.label
|
|
235
|
+
root._unmountDevice = e.device
|
|
236
|
+
root._unmountErr = ""
|
|
237
|
+
root._unmounting = true
|
|
238
|
+
unmountProcess.command = ["gio", "mount", "-u", e.path]
|
|
239
|
+
unmountProcess.running = true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// One eject in flight at a time; the verdict timeout below ends the wait it starts.
|
|
243
|
+
function armEject(e) {
|
|
244
|
+
root._ejectDevice = e.device
|
|
245
|
+
root._ejectLabel = e.label
|
|
246
|
+
root._ejectLoop = e.loop === true
|
|
247
|
+
root._ejectDetachFailed = false
|
|
248
|
+
root._verdictFromListing = 0
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Locks the container behind an unmounted or ejected crypt, from the listing an eject was judged on when it has one.
|
|
252
|
+
property var deviceLock: null
|
|
253
|
+
function lockCrypt(device, label, listing) {
|
|
254
|
+
if (deviceLock === null)
|
|
255
|
+
deviceLock = Qt.createComponent("DeviceLock.qml").createObject(root, { host: root })
|
|
256
|
+
return deviceLock.lockCryptAfter(listing !== undefined ? listing : root._listing, device, label)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Eject by mount point, never -f (an open write loses data) or -e -d (gio-tool-mount.c:1264 mounts); a loop row takes -u, as -e never returns without a Drive.
|
|
260
|
+
function eject(index) {
|
|
261
|
+
var e = root.entries[index]
|
|
262
|
+
if (!e || e.kind !== "volume" || !e.mounted)
|
|
263
|
+
return
|
|
264
|
+
if (ejectProcess.running || root._ejectDevice.length > 0) {
|
|
265
|
+
root.message(Format.fill(Mounts.WORDS.stillEjecting, root._ejectLabel), false)
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
// A non-removable USB disk is powered off, since an eject lets udiskie remount it; a loop row has no drive, so it takes -u below.
|
|
269
|
+
var disk = Devices.powerOffDisk(e.device)
|
|
270
|
+
if (!e.loop && disk.length > 0 && e.mediaRemovable !== true) {
|
|
271
|
+
root.powerOff(e, disk)
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
root.armEject(e)
|
|
275
|
+
root.quiesce(e.path)
|
|
276
|
+
root._ejectErr = ""
|
|
277
|
+
ejectProcess.command = Devices.ejectCommand(e)
|
|
278
|
+
ejectProcess.running = true
|
|
279
|
+
// Replaces the arm prompt, and a stick mid-flush can take a while to come unmounted.
|
|
280
|
+
root.message(Format.fill(Mounts.WORDS.ejecting, e.label), false)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Unmount every volume, then stop the drive, so nothing is left for udiskie to remount.
|
|
284
|
+
function powerOff(e, disk) {
|
|
285
|
+
// A user unmount already in flight owns unmountProcess; adopting it would skip the queue.
|
|
286
|
+
if (unmountProcess.running) {
|
|
287
|
+
root.message(Format.fill(Mounts.WORDS.stillUnmountingTryAgain, e.label), false)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
root.quiesce(e.path)
|
|
291
|
+
root.armEject(e)
|
|
292
|
+
root._powerOffDisk = disk
|
|
293
|
+
root._powerOffQueue = Devices.powerOffQueue(root._listing, disk, root.entries)
|
|
294
|
+
root._powerSectors = ""
|
|
295
|
+
root.message(Format.fill(Mounts.WORDS.ejecting, e.label), false)
|
|
296
|
+
root.powerOffNext()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function powerOffNext() {
|
|
300
|
+
// Every leg restarts the deadline, so 15 s bounds a stalled leg, not a flushing chain.
|
|
301
|
+
powerOffTimeout.restart()
|
|
302
|
+
if (root._powerOffQueue.length === 0) {
|
|
303
|
+
root._ejectErr = ""
|
|
304
|
+
ejectProcess.command = ["gio", "mount", "-t", root._powerOffDisk]
|
|
305
|
+
ejectProcess.running = true
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
var leg = root._powerOffQueue[0]
|
|
309
|
+
root._powerOffQueue = root._powerOffQueue.slice(1)
|
|
310
|
+
root._unmountErr = ""
|
|
311
|
+
unmountProcess.command = ["gio", "mount", "-u", leg.path]
|
|
312
|
+
unmountProcess.running = true
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// A grown write counter is progress, so the deadline restarts instead of ending the chain.
|
|
316
|
+
function notePowerSectors(body) {
|
|
317
|
+
if (root._powerOffDisk.length === 0)
|
|
318
|
+
return
|
|
319
|
+
var sectors = Devices.writtenSectors(body)
|
|
320
|
+
if (sectors.length === 0)
|
|
321
|
+
return
|
|
322
|
+
if (root._powerSectors.length > 0 && sectors !== root._powerSectors)
|
|
323
|
+
powerOffTimeout.restart()
|
|
324
|
+
root._powerSectors = sectors
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// The chain's guard state in one line, so a failed eject names its guard through ipc.
|
|
328
|
+
function ejectState() {
|
|
329
|
+
return [root._ejectDevice, root._ejectLabel, root._powerOffDisk, root._powerOffQueue.length, root._lastVerdict, unmountProcess.running, ejectProcess.running].join("|")
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Nothing is judged while gio still runs, and only a listing started after it exited counts.
|
|
333
|
+
// A listing that cannot be judged settles nothing, so the next poll tries again, until
|
|
334
|
+
// ejectVerdictTimeout ends the wait.
|
|
335
|
+
function judgeEject(body) {
|
|
336
|
+
if (root._ejectDevice.length === 0 || root._verdictFromListing === 0 || root._listingsStarted < root._verdictFromListing)
|
|
337
|
+
return
|
|
338
|
+
var verdict = Eject.verdict(body, root._ejectDevice)
|
|
339
|
+
if (verdict !== "unknown")
|
|
340
|
+
root.reportEject(verdict, Eject.blockers(body, root._ejectDevice), body)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// "Safe to unplug" is a safety claim: only a real drive's "safe" verdict earns it.
|
|
344
|
+
function reportEject(verdict, others, body) {
|
|
345
|
+
if (root._ejectDevice.length === 0)
|
|
346
|
+
return
|
|
347
|
+
powerOffTimeout.stop()
|
|
348
|
+
ejectVerdictTimeout.stop()
|
|
349
|
+
// An unmounted loop row reads as safe to the listing; a failed detach still has to say so.
|
|
350
|
+
var s = Eject.sentence(verdict === "safe" && root._ejectDetachFailed ? "attached" : verdict, root._ejectLabel, others, root._ejectLoop)
|
|
351
|
+
if (!s.isError)
|
|
352
|
+
root.lockCrypt(root._ejectDevice, root._ejectLabel, body)
|
|
353
|
+
root._ejectDevice = ""
|
|
354
|
+
// The newest verdict about this device is the true one, so it replaces the last one rather
|
|
355
|
+
// than queueing behind it: a refusal is an error and stands until dismissed, and without
|
|
356
|
+
// this the operator ejected the stick and went on reading "still mounted".
|
|
357
|
+
root.forgetMessage(root._lastVerdict)
|
|
358
|
+
root._lastVerdict = s.text
|
|
359
|
+
root.message(s.text, s.isError)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
Timer {
|
|
363
|
+
id: mountTimeout
|
|
364
|
+
interval: root.mountTimeoutMs
|
|
365
|
+
repeat: false
|
|
366
|
+
onTriggered: {
|
|
367
|
+
if (root._pendingOpenDevice.length === 0)
|
|
368
|
+
return
|
|
369
|
+
root._pendingOpenDevice = ""
|
|
370
|
+
root.message(Format.fill(Mounts.WORDS.noFolder, root._pendingOpenLabel), true)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
Timer {
|
|
375
|
+
id: ejectVerdictTimeout
|
|
376
|
+
interval: root.ejectVerdictMs
|
|
377
|
+
repeat: false
|
|
378
|
+
onTriggered: root.reportEject("unknown", [])
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// A hung chain leg ends here with the guard released and a sentence.
|
|
382
|
+
Timer {
|
|
383
|
+
id: powerOffTimeout
|
|
384
|
+
interval: root.powerOffWaitMs
|
|
385
|
+
repeat: false
|
|
386
|
+
onTriggered: {
|
|
387
|
+
if (root._powerOffDisk.length === 0 || (!unmountProcess.running && !ejectProcess.running))
|
|
388
|
+
return
|
|
389
|
+
// Only the leg the deadline ends is marked, so a user unmount is never swallowed.
|
|
390
|
+
if (unmountProcess.running) {
|
|
391
|
+
root._unmountEndedLate = true
|
|
392
|
+
unmountProcess.running = false
|
|
393
|
+
} else {
|
|
394
|
+
root._ejectEndedLate = true
|
|
395
|
+
ejectProcess.running = false
|
|
396
|
+
}
|
|
397
|
+
ejectVerdictTimeout.stop()
|
|
398
|
+
var label = root._ejectLabel
|
|
399
|
+
root._powerOffDisk = ""
|
|
400
|
+
root._powerOffQueue = []
|
|
401
|
+
root._ejectDevice = ""
|
|
402
|
+
// A verdict replaces the last one, so the next chain's safe sentence retires this error.
|
|
403
|
+
var text = Format.fill(Mounts.WORDS.ejectUnfinished, label)
|
|
404
|
+
root.forgetMessage(root._lastVerdict)
|
|
405
|
+
root._lastVerdict = text
|
|
406
|
+
root.message(text, true)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
Process {
|
|
411
|
+
id: listProcess
|
|
412
|
+
// PATH because a device-mapper leaf is not "/dev/" plus its kernel name, and MOUNTPOINTS
|
|
413
|
+
// because one btrfs device carries several and the plain column shows whichever it likes,
|
|
414
|
+
// which hid / behind /home here and left the system disk unidentifiable.
|
|
415
|
+
// TRAN is the transport, asked for because RM alone misses a USB bridge: a WD My Passport
|
|
416
|
+
// reports rm=false with tran=usb, and a drive you can unplug has to offer Eject (PR 74).
|
|
417
|
+
// PARTTYPE, PTTYPE, PARTN and UUID are the hide rule's own columns (a firmware type, an installer ISO keep, a recovery label, the dedupe key); FSVER beside FSTYPE names LUKS1 or LUKS2; RO keeps Flash image off a write-protected card.
|
|
418
|
+
command: ["lsblk", "--bytes", "--json", "-o", "NAME,PATH,LABEL,MOUNTPOINTS,RM,RO,TRAN,SIZE,TYPE,MODEL,FSTYPE,FSVER,PARTTYPENAME,PARTTYPE,PTTYPE,PARTN,UUID"]
|
|
419
|
+
stdout: StdioCollector {
|
|
420
|
+
id: listOut
|
|
421
|
+
waitForEnd: true
|
|
422
|
+
// The verdict is read here, from this run's own text, so it can never come off the
|
|
423
|
+
// stale fallback onExited keeps for the rail.
|
|
424
|
+
onStreamFinished: {
|
|
425
|
+
if (root.listTimedOut)
|
|
426
|
+
return
|
|
427
|
+
root._streamPending = false
|
|
428
|
+
root._listOutput = text
|
|
429
|
+
root.judgeEject(text)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
onExited: {
|
|
433
|
+
listTimeout.stop()
|
|
434
|
+
// A listing this timer ended collected nothing, and reading that as "no devices" would
|
|
435
|
+
// empty the rail, taking Eject with it exactly when a device is misbehaving. The stream
|
|
436
|
+
// guard is released here because a stream this timer cut off may never finish on its own.
|
|
437
|
+
if (root.listTimedOut) {
|
|
438
|
+
root._streamPending = false
|
|
439
|
+
} else {
|
|
440
|
+
root._listing = listOut.text || root._listOutput || ""
|
|
441
|
+
root.rebuild()
|
|
442
|
+
}
|
|
443
|
+
// Last, so anything waiting on the first answer reads the listing it answered with.
|
|
444
|
+
root.firstAnswered = true
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
Process {
|
|
449
|
+
id: mountProcess
|
|
450
|
+
objectName: "mountProcess"
|
|
451
|
+
stderr: StdioCollector { onStreamFinished: root._mountErr = this.text }
|
|
452
|
+
onExited: function (exitCode) {
|
|
453
|
+
// The wait starts here, not at launch: an admin polkit prompt longer than the
|
|
454
|
+
// wait is a slow prompt, not a mount that never reported a folder to open.
|
|
455
|
+
if (Devices.mountTimerStart(exitCode)) {
|
|
456
|
+
mountTimeout.restart()
|
|
457
|
+
root.poll()
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
var label = root._pendingOpenLabel
|
|
461
|
+
var sentence = Devices.mountError("mount", exitCode, root._mountErr, label)
|
|
462
|
+
// "" names the already-mounted refusal, so the poll below opens it like a success.
|
|
463
|
+
if (sentence.length === 0 && label.length > 0) {
|
|
464
|
+
mountTimeout.restart()
|
|
465
|
+
root.poll()
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
mountTimeout.stop()
|
|
469
|
+
root._pendingOpenDevice = ""
|
|
470
|
+
root.reportFailure(sentence.length > 0 ? sentence : Format.fill(Mounts.WORDS.mountFailed, label), root._mountErr)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
Process {
|
|
475
|
+
id: unmountProcess
|
|
476
|
+
objectName: "unmountProcess"
|
|
477
|
+
stderr: StdioCollector { onStreamFinished: root._unmountErr = this.text }
|
|
478
|
+
// A spawn that fails raises this and never exited, so the flag ends here with every other end.
|
|
479
|
+
onRunningChanged: if (!unmountProcess.running) root._unmounting = false
|
|
480
|
+
onExited: function (exitCode) {
|
|
481
|
+
// A power-off unmounts each volume first; a refusal stops the chain instead of
|
|
482
|
+
// powering off under a volume that is still mounted.
|
|
483
|
+
if (root._unmountEndedLate) {
|
|
484
|
+
root._unmountEndedLate = false
|
|
485
|
+
root.poll()
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
if (root._powerOffDisk.length > 0) {
|
|
489
|
+
if (exitCode !== 0) {
|
|
490
|
+
powerOffTimeout.stop()
|
|
491
|
+
root._powerOffDisk = ""
|
|
492
|
+
root._powerOffQueue = []
|
|
493
|
+
root._ejectDevice = ""
|
|
494
|
+
root.reportFailure(Devices.mountError("unmount", exitCode, root._unmountErr, root._ejectLabel), root._unmountErr)
|
|
495
|
+
} else {
|
|
496
|
+
root.powerOffNext()
|
|
497
|
+
}
|
|
498
|
+
root.poll()
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
if (exitCode !== 0)
|
|
502
|
+
root.reportFailure(Devices.mountError("unmount", exitCode, root._unmountErr, root._unmountLabel), root._unmountErr)
|
|
503
|
+
else if (root.lockCrypt(root._unmountDevice, root._unmountLabel))
|
|
504
|
+
return
|
|
505
|
+
root.poll()
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
Process {
|
|
510
|
+
id: ejectProcess
|
|
511
|
+
objectName: "ejectProcess"
|
|
512
|
+
stderr: StdioCollector { onStreamFinished: root._ejectErr = this.text }
|
|
513
|
+
onExited: function (exitCode) {
|
|
514
|
+
// A refused stop says so at once; a stop that ran is judged on the listing that
|
|
515
|
+
// follows, because gio has exited 0 over a volume that was still mounted.
|
|
516
|
+
if (root._ejectEndedLate) {
|
|
517
|
+
root._ejectEndedLate = false
|
|
518
|
+
root.poll()
|
|
519
|
+
return
|
|
520
|
+
}
|
|
521
|
+
if (exitCode !== 0 && root._powerOffDisk.length > 0) {
|
|
522
|
+
var sentence = Devices.mountError("eject", exitCode, root._ejectErr, root._ejectLabel)
|
|
523
|
+
powerOffTimeout.stop()
|
|
524
|
+
root._powerOffDisk = ""
|
|
525
|
+
root._powerOffQueue = []
|
|
526
|
+
root._ejectDevice = ""
|
|
527
|
+
ejectVerdictTimeout.stop()
|
|
528
|
+
root.reportFailure(sentence.length > 0 ? sentence : Format.fill(Mounts.WORDS.ejectFailed, root._ejectLabel), root._ejectErr)
|
|
529
|
+
root.poll()
|
|
530
|
+
return
|
|
531
|
+
}
|
|
532
|
+
if (root._powerOffDisk.length > 0)
|
|
533
|
+
root._powerOffDisk = ""
|
|
534
|
+
root._ejectDetachFailed = root._ejectLoop && exitCode === Devices.detachFailedExit
|
|
535
|
+
root._verdictFromListing = root._listingsStarted + 1
|
|
536
|
+
ejectVerdictTimeout.restart()
|
|
537
|
+
root.poll()
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|