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,450 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell.Io
|
|
3
|
+
import qs.Commons
|
|
4
|
+
import "." as Flea
|
|
5
|
+
import "js/Buttons.js" as Buttons
|
|
6
|
+
import "js/Format.js" as Format
|
|
7
|
+
import "js/Unlock.js" as Unlock
|
|
8
|
+
|
|
9
|
+
// Unlock (U039): the encrypted-drive dialog the board draws; the passphrase goes to udisksctl unlock on stdin, never argv.
|
|
10
|
+
FocusScope {
|
|
11
|
+
id: root
|
|
12
|
+
anchors.fill: parent
|
|
13
|
+
visible: opened || opacity > 0
|
|
14
|
+
// Closed means no input, even while the fade-out is still drawn.
|
|
15
|
+
enabled: opened
|
|
16
|
+
opacity: 0
|
|
17
|
+
property bool opened: false
|
|
18
|
+
property string device: ""
|
|
19
|
+
property string driveLabel: ""
|
|
20
|
+
property double driveSize: 0
|
|
21
|
+
property string crypto: "LUKS"
|
|
22
|
+
// typing is the default, unlocking dims the field, wrong carries the error.
|
|
23
|
+
property string phase: "typing"
|
|
24
|
+
property string noteText: ""
|
|
25
|
+
property bool noteIsError: false
|
|
26
|
+
property string _secret: ""
|
|
27
|
+
property string _unlockOutput: ""
|
|
28
|
+
property string _unlockErr: ""
|
|
29
|
+
property string _mountOutput: ""
|
|
30
|
+
property bool _started: false
|
|
31
|
+
// The child in flight: unlock, mount; relock is a failed mount's lock, abandon a Cancel the late exit still settles.
|
|
32
|
+
property string _stage: "idle"
|
|
33
|
+
property string _lockLabel: ""
|
|
34
|
+
// A late lock from an abandoned attempt never refuses the dialog, reopened or not.
|
|
35
|
+
property bool _quietLock: false
|
|
36
|
+
// The drive a Cancelled attempt was unlocking, so its late lock closes that drive and names it, whatever the dialog opened since.
|
|
37
|
+
property string _abandonDevice: ""
|
|
38
|
+
property string _abandonLabel: ""
|
|
39
|
+
// A submit made while an earlier attempt's client or its late lock still runs; it starts when they end.
|
|
40
|
+
property bool _queued: false
|
|
41
|
+
property int unlockTimeoutMs: 15000
|
|
42
|
+
readonly property var cardItem: card
|
|
43
|
+
// Read by the capture harness: the controls unlockState names, no look change.
|
|
44
|
+
readonly property var fieldItem: field
|
|
45
|
+
function buttonItem(name) { return ({cancel: cancelButton, unlock: unlockButton})[name] || null }
|
|
46
|
+
signal unlocked(string path, string label, string note)
|
|
47
|
+
signal closed()
|
|
48
|
+
signal lockFailed(string note)
|
|
49
|
+
|
|
50
|
+
function openFor(devicePath, label, size, fstype, fsver) {
|
|
51
|
+
root.device = String(devicePath || "")
|
|
52
|
+
root.describe(label, size, fstype, fsver)
|
|
53
|
+
root.phase = "typing"
|
|
54
|
+
root.noteText = ""
|
|
55
|
+
root.noteIsError = false
|
|
56
|
+
root._secret = ""
|
|
57
|
+
field.text = ""
|
|
58
|
+
field.heldMask = 0
|
|
59
|
+
field.error = false
|
|
60
|
+
root.opened = true
|
|
61
|
+
field.takeFocus()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The name row and every sentence name the drive through this, so a later rail row can name it for flea --unlock.
|
|
65
|
+
function describe(label, size, fstype, fsver) {
|
|
66
|
+
root.driveLabel = String(label || root.device)
|
|
67
|
+
root.driveSize = Number(size) || 0
|
|
68
|
+
root.crypto = Unlock.cryptoName(String(fstype || ""), String(fsver || ""))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// The tab order: the field, then Cancel, then Unlock; while Unlocking, Cancel is the one live control.
|
|
72
|
+
function tabStops() {
|
|
73
|
+
return root.phase === "unlocking" ? [cancelButton] : [field.input, cancelButton, unlockButton]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function stepTab(back) {
|
|
77
|
+
var stops = root.tabStops()
|
|
78
|
+
var at = -1
|
|
79
|
+
for (var i = 0; i < stops.length; i++) {
|
|
80
|
+
if (stops[i].activeFocus) { at = i; break }
|
|
81
|
+
}
|
|
82
|
+
var next = at < 0 ? (back ? stops.length - 1 : 0) : (at + (back ? stops.length - 1 : 1)) % stops.length
|
|
83
|
+
stops[next].forceActiveFocus()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Read by the capture harness and ui/UnlockHost.qml: which stop holds the keyboard, and whether the field holds typed or held keys.
|
|
87
|
+
function focusPart() {
|
|
88
|
+
return field.input.activeFocus ? "field" : cancelButton.activeFocus ? "cancel" : unlockButton.activeFocus ? "unlock" : ""
|
|
89
|
+
}
|
|
90
|
+
readonly property bool masked: field.text.length > 0 || field.heldMask > 0
|
|
91
|
+
readonly property string subtitleText: Unlock.subtitle(root.driveLabel, root.driveSize > 0 ? Format.size(root.driveSize) : "", root.crypto)
|
|
92
|
+
|
|
93
|
+
function close() {
|
|
94
|
+
unlockTimeout.stop()
|
|
95
|
+
root._queued = false
|
|
96
|
+
if (root._stage === "unlock") {
|
|
97
|
+
// A Cancelled unlock may finish on the daemon after its client is gone, so the client runs on and the late exit locks back.
|
|
98
|
+
root._stage = "abandon"
|
|
99
|
+
root._abandonDevice = root.device
|
|
100
|
+
root._abandonLabel = root.driveLabel
|
|
101
|
+
} else if (root._stage === "relock") {
|
|
102
|
+
root._stage = "idle"
|
|
103
|
+
} else if (root._stage === "mount") {
|
|
104
|
+
// The late exit of the killed mount locks the container, so the dialog leaves it as it found it.
|
|
105
|
+
root._stage = "abandon"
|
|
106
|
+
root._abandonDevice = root.device
|
|
107
|
+
root._abandonLabel = root.driveLabel
|
|
108
|
+
if (mountProcess.running)
|
|
109
|
+
mountProcess.running = false
|
|
110
|
+
else
|
|
111
|
+
root.lockContainer(false, root._abandonDevice, root._abandonLabel)
|
|
112
|
+
}
|
|
113
|
+
field.text = ""
|
|
114
|
+
field.heldMask = 0
|
|
115
|
+
root._secret = ""
|
|
116
|
+
root.opened = false
|
|
117
|
+
root.closed()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Ends an attempt with a sentence the user can act on; no path leaves the passphrase held.
|
|
121
|
+
function refuse(note) {
|
|
122
|
+
unlockTimeout.stop()
|
|
123
|
+
root._stage = "idle"
|
|
124
|
+
root._secret = ""
|
|
125
|
+
root.showRefusal(note)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function showRefusal(note) {
|
|
129
|
+
root.phase = "wrong"
|
|
130
|
+
field.error = true
|
|
131
|
+
root.noteText = note
|
|
132
|
+
root.noteIsError = true
|
|
133
|
+
field.takeFocus()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// A dialog that ends without a mounted volume leaves the container locked; relock keeps the dialog open on the mount sentence.
|
|
137
|
+
function lockContainer(relock, device, label) {
|
|
138
|
+
root._stage = relock ? "relock" : "idle"
|
|
139
|
+
root._lockLabel = label || root.driveLabel
|
|
140
|
+
lockProcess.command = ["udisksctl", "lock", "--block-device", device || root.device]
|
|
141
|
+
lockProcess.running = true
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function submit() {
|
|
145
|
+
if (!root.opened || root.phase === "unlocking")
|
|
146
|
+
return
|
|
147
|
+
if (field.text.length === 0) {
|
|
148
|
+
// An empty field refuses without touching _stage, so a Cancelled attempt's late exit still locks back.
|
|
149
|
+
root.showRefusal(Unlock.wrongNote(root.driveLabel))
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
root._secret = field.text
|
|
153
|
+
// Only the submitted length is kept, so the dots draw from a count.
|
|
154
|
+
field.heldMask = field.text.length
|
|
155
|
+
field.text = ""
|
|
156
|
+
root.phase = "unlocking"
|
|
157
|
+
cancelButton.forceActiveFocus()
|
|
158
|
+
field.error = false
|
|
159
|
+
root.noteText = Unlock.unlockingNote(root.driveLabel)
|
|
160
|
+
root.noteIsError = false
|
|
161
|
+
// An earlier attempt's client, its killed mount or its late lock still runs, so this one waits under the same bound.
|
|
162
|
+
if (unlockProcess.running || lockProcess.running || root._stage === "abandon") {
|
|
163
|
+
root._queued = true
|
|
164
|
+
unlockTimeout.restart()
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
root._startUnlock()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function _startQueued() {
|
|
171
|
+
if (!root._queued)
|
|
172
|
+
return
|
|
173
|
+
root._queued = false
|
|
174
|
+
unlockTimeout.stop()
|
|
175
|
+
root._startUnlock()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function _startUnlock() {
|
|
179
|
+
root._unlockOutput = ""
|
|
180
|
+
root._unlockErr = ""
|
|
181
|
+
root._started = false
|
|
182
|
+
root._stage = "unlock"
|
|
183
|
+
root._quietLock = false
|
|
184
|
+
unlockProcess.command = Unlock.unlockArgv(root.device)
|
|
185
|
+
// The last attempt closed stdin to send EOF, so a retry opens it again before starting.
|
|
186
|
+
unlockProcess.stdinEnabled = true
|
|
187
|
+
unlockProcess.running = true
|
|
188
|
+
unlockTimeout.restart()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Flea.Reveal { target: root; shown: root.opened; lift: card }
|
|
192
|
+
Keys.onTabPressed: function (event) { root.stepTab((event.modifiers & Qt.ShiftModifier) !== 0); event.accepted = true }
|
|
193
|
+
Keys.onBacktabPressed: function (event) { root.stepTab(true); event.accepted = true }
|
|
194
|
+
Keys.onPressed: function(event) {
|
|
195
|
+
if (event.key === Qt.Key_Escape) {
|
|
196
|
+
root.close()
|
|
197
|
+
event.accepted = true
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
|
201
|
+
root.submit()
|
|
202
|
+
event.accepted = true
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
Timer {
|
|
207
|
+
id: unlockTimeout
|
|
208
|
+
interval: root.unlockTimeoutMs
|
|
209
|
+
repeat: false
|
|
210
|
+
// Only a child that never started is ended here: a slow KDF has no bound, and Cancel leaves a running one to its late exit.
|
|
211
|
+
onTriggered: {
|
|
212
|
+
// A queued submit never reached its client: it is refused and the passphrase dropped, and the earlier attempt still settles.
|
|
213
|
+
if (root._queued) {
|
|
214
|
+
root._queued = false
|
|
215
|
+
root._secret = ""
|
|
216
|
+
root.showRefusal(Unlock.unlockFailedNote(root.driveLabel))
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
if (root._started || root._stage !== "unlock")
|
|
220
|
+
return
|
|
221
|
+
unlockProcess.running = false
|
|
222
|
+
root.refuse(Unlock.unlockFailedNote(root.driveLabel))
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
Flea.DialogScrim {
|
|
227
|
+
onDismissed: root.close()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
Rectangle {
|
|
231
|
+
id: card
|
|
232
|
+
x: Theme.cardOrigin(root.width, width)
|
|
233
|
+
y: Theme.cardOrigin(root.height, height)
|
|
234
|
+
width: Theme.cardSpan(448, root.width - 2 * Theme.spacing.gap)
|
|
235
|
+
height: Theme.cardSpan(body.wanted + card.cardPadding + card.cardBottomPadding, root.height - 2 * Theme.spacing.gap)
|
|
236
|
+
color: Theme.color.surface
|
|
237
|
+
border.color: Theme.color.mutedFrame
|
|
238
|
+
border.width: Theme.spacing.hairline
|
|
239
|
+
radius: Style.cornerRadius
|
|
240
|
+
// The Unlock board's padding 16 16 12 and 6 px of air over the buttons, at its 13 px reference.
|
|
241
|
+
readonly property real referenceScale: Theme.font.bodySmall / 13
|
|
242
|
+
readonly property real cardPadding: Math.round(16 * referenceScale)
|
|
243
|
+
readonly property real cardBottomPadding: Math.round(12 * referenceScale)
|
|
244
|
+
readonly property real buttonsAir: Math.round(6 * referenceScale)
|
|
245
|
+
MouseArea {
|
|
246
|
+
anchors.fill: parent
|
|
247
|
+
hoverEnabled: true
|
|
248
|
+
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
249
|
+
// Without this sink a press on the card padding falls to the ground, which closes the dialog.
|
|
250
|
+
onClicked: {}
|
|
251
|
+
onWheel: function(wheel) { wheel.accepted = true }
|
|
252
|
+
}
|
|
253
|
+
Flea.CardFrame {
|
|
254
|
+
id: body
|
|
255
|
+
anchors.fill: parent
|
|
256
|
+
anchors.leftMargin: card.cardPadding
|
|
257
|
+
anchors.rightMargin: card.cardPadding
|
|
258
|
+
anchors.topMargin: card.cardPadding
|
|
259
|
+
anchors.bottomMargin: card.cardBottomPadding
|
|
260
|
+
title: Flea.DialogHeader {
|
|
261
|
+
width: parent.width
|
|
262
|
+
text: Unlock.title(root.driveLabel)
|
|
263
|
+
}
|
|
264
|
+
Column {
|
|
265
|
+
width: parent.width
|
|
266
|
+
spacing: Theme.spacing.gap
|
|
267
|
+
Row {
|
|
268
|
+
width: parent.width
|
|
269
|
+
spacing: Theme.spacing.gap
|
|
270
|
+
Flea.Glyph {
|
|
271
|
+
width: Theme.markSize
|
|
272
|
+
height: Theme.fileRowHeight
|
|
273
|
+
name: "drive"
|
|
274
|
+
color: Theme.color.foreground
|
|
275
|
+
}
|
|
276
|
+
Text {
|
|
277
|
+
width: parent.width - Theme.markSize - parent.spacing
|
|
278
|
+
anchors.verticalCenter: parent.verticalCenter
|
|
279
|
+
text: root.subtitleText
|
|
280
|
+
textFormat: Text.PlainText
|
|
281
|
+
elide: Text.ElideRight
|
|
282
|
+
color: Theme.color.foreground
|
|
283
|
+
font { family: Theme.font.family; pixelSize: Theme.font.body }
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
Flea.DialogField {
|
|
287
|
+
id: field
|
|
288
|
+
width: parent.width
|
|
289
|
+
label: "Passphrase"
|
|
290
|
+
secret: true
|
|
291
|
+
enabled: root.phase !== "unlocking"
|
|
292
|
+
opacity: root.phase === "unlocking" ? Buttons.DISABLED_OPACITY : 1
|
|
293
|
+
onAccepted: root.submit()
|
|
294
|
+
onTabbed: function (from, back) { root.stepTab(back) }
|
|
295
|
+
// A retry starts clean, as the archive card does.
|
|
296
|
+
onEdited: {
|
|
297
|
+
if (field.error || root.noteIsError) {
|
|
298
|
+
field.error = false
|
|
299
|
+
root.noteText = ""
|
|
300
|
+
root.noteIsError = false
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
Text {
|
|
305
|
+
width: parent.width
|
|
306
|
+
visible: root.noteText.length > 0
|
|
307
|
+
text: root.noteText
|
|
308
|
+
textFormat: Text.PlainText
|
|
309
|
+
wrapMode: Text.Wrap
|
|
310
|
+
color: root.noteIsError ? Theme.color.errorOnSurface : Theme.color.muted
|
|
311
|
+
font { family: Theme.font.family; pixelSize: Theme.font.caption }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
buttons: Item {
|
|
315
|
+
width: parent.width
|
|
316
|
+
height: buttonRow.height
|
|
317
|
+
Row {
|
|
318
|
+
id: buttonRow
|
|
319
|
+
anchors.right: parent.right
|
|
320
|
+
topPadding: card.buttonsAir
|
|
321
|
+
spacing: Theme.spacing.gap
|
|
322
|
+
Flea.DialogButton {
|
|
323
|
+
id: cancelButton
|
|
324
|
+
label: "Cancel"
|
|
325
|
+
focused: activeFocus
|
|
326
|
+
onActivated: root.close()
|
|
327
|
+
}
|
|
328
|
+
Flea.DialogButton {
|
|
329
|
+
id: unlockButton
|
|
330
|
+
label: "Unlock"
|
|
331
|
+
primary: true
|
|
332
|
+
// Disabled with the field, so a keyboard that Tabbed here drops to the card and Cancel keeps the one ring.
|
|
333
|
+
enabled: root.phase !== "unlocking"
|
|
334
|
+
opacity: root.phase === "unlocking" ? Buttons.DISABLED_OPACITY : 1
|
|
335
|
+
onActivated: root.submit()
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
Process {
|
|
343
|
+
id: unlockProcess
|
|
344
|
+
environment: ({ "LC_ALL": "C" })
|
|
345
|
+
stdinEnabled: true
|
|
346
|
+
stdout: StdioCollector { id: unlockOut; waitForEnd: true; onStreamFinished: root._unlockOutput = text }
|
|
347
|
+
stderr: StdioCollector { id: unlockErr; waitForEnd: true; onStreamFinished: root._unlockErr = text }
|
|
348
|
+
onStarted: {
|
|
349
|
+
root._started = true
|
|
350
|
+
if (root._stage !== "unlock") {
|
|
351
|
+
// A Cancel won the race: close stdin so the waiting --key-file /dev/stdin reads EOF and exits.
|
|
352
|
+
unlockProcess.stdinEnabled = false
|
|
353
|
+
return
|
|
354
|
+
}
|
|
355
|
+
var secret = root._secret
|
|
356
|
+
root._secret = ""
|
|
357
|
+
// --key-file /dev/stdin reads every byte to EOF as the key, so no newline and stdin closes after it.
|
|
358
|
+
unlockProcess.write(secret)
|
|
359
|
+
unlockProcess.stdinEnabled = false
|
|
360
|
+
secret = ""
|
|
361
|
+
}
|
|
362
|
+
onExited: function(exitCode) {
|
|
363
|
+
unlockTimeout.stop()
|
|
364
|
+
// A Cancelled unlock that still succeeded left the container open with nobody waiting: lock it back quietly.
|
|
365
|
+
if (root._stage === "abandon") {
|
|
366
|
+
if (exitCode === 0) {
|
|
367
|
+
var late = Unlock.parseUnlocked(String(unlockOut.text || root._unlockOutput || ""))
|
|
368
|
+
if (late.length > 0) {
|
|
369
|
+
root._quietLock = true
|
|
370
|
+
root.lockContainer(true, root._abandonDevice, root._abandonLabel)
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
root._stage = "idle"
|
|
375
|
+
root._startQueued()
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
// Nothing waits on this exit, so a late one does nothing.
|
|
379
|
+
if (root._stage !== "unlock")
|
|
380
|
+
return
|
|
381
|
+
if (exitCode !== 0) {
|
|
382
|
+
// Wrong passphrase versus any other refusal is read off the D-Bus error name, never off udisksctl's own sentences.
|
|
383
|
+
root.refuse(Unlock.refusalNote(String(unlockErr.text || root._unlockErr || ""), root.driveLabel))
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
var mapped = Unlock.parseUnlocked(String(unlockOut.text || root._unlockOutput || ""))
|
|
387
|
+
if (mapped.length === 0) {
|
|
388
|
+
root.refuse(Unlock.wrongNote(root.driveLabel))
|
|
389
|
+
return
|
|
390
|
+
}
|
|
391
|
+
root._mountOutput = ""
|
|
392
|
+
root._stage = "mount"
|
|
393
|
+
mountProcess.command = ["udisksctl", "mount", "--block-device", mapped]
|
|
394
|
+
mountProcess.running = true
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
Process {
|
|
399
|
+
id: mountProcess
|
|
400
|
+
environment: ({ "LC_ALL": "C" })
|
|
401
|
+
stdout: StdioCollector { id: mountOut; waitForEnd: true; onStreamFinished: root._mountOutput = text }
|
|
402
|
+
stderr: StdioCollector { waitForEnd: true }
|
|
403
|
+
onExited: function(exitCode) {
|
|
404
|
+
if (root._stage === "abandon") {
|
|
405
|
+
root.lockContainer(false, root._abandonDevice, root._abandonLabel)
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
if (root._stage !== "mount")
|
|
409
|
+
return
|
|
410
|
+
var folder = exitCode === 0 ? Unlock.parseMounted(String(mountOut.text || root._mountOutput || "")) : ""
|
|
411
|
+
if (folder.length === 0) {
|
|
412
|
+
root.lockContainer(true)
|
|
413
|
+
return
|
|
414
|
+
}
|
|
415
|
+
var label = root.driveLabel
|
|
416
|
+
var note = Unlock.unlockedNote(label)
|
|
417
|
+
root._stage = "idle"
|
|
418
|
+
field.text = ""
|
|
419
|
+
field.heldMask = 0
|
|
420
|
+
root.opened = false
|
|
421
|
+
root.closed()
|
|
422
|
+
root.unlocked(folder, label, note)
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
Process {
|
|
427
|
+
id: lockProcess
|
|
428
|
+
environment: ({ "LC_ALL": "C" })
|
|
429
|
+
// Every lock exit frees the drive for a submit that queued behind it.
|
|
430
|
+
onExited: function(exitCode) {
|
|
431
|
+
root._lockExited(exitCode)
|
|
432
|
+
root._startQueued()
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function _lockExited(exitCode) {
|
|
437
|
+
var quiet = root._quietLock
|
|
438
|
+
root._quietLock = false
|
|
439
|
+
if (root._stage === "relock") {
|
|
440
|
+
// Open on a relock, the mount failure is what the user reads; a quiet lock or one nobody waits on takes no focus.
|
|
441
|
+
if (root.opened && !quiet)
|
|
442
|
+
root.refuse(Unlock.mountNote(root._lockLabel))
|
|
443
|
+
else
|
|
444
|
+
root._stage = "idle"
|
|
445
|
+
}
|
|
446
|
+
// Every refused lock is told, beside the mount failure or alone.
|
|
447
|
+
if (exitCode !== 0)
|
|
448
|
+
root.lockFailed(Unlock.lockNote(root._lockLabel))
|
|
449
|
+
}
|
|
450
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import "." as Flea
|
|
3
|
+
import "js/Unlock.js" as Unlock
|
|
4
|
+
|
|
5
|
+
// Unlock (U039): the encrypted-drive dialog's host, over the whole window.
|
|
6
|
+
// Created once per window on first use, so no placeholder object stands before it; the unlocked-drive follow lives here.
|
|
7
|
+
Item {
|
|
8
|
+
id: root
|
|
9
|
+
anchors.fill: parent
|
|
10
|
+
property var window: null
|
|
11
|
+
property var footer: null
|
|
12
|
+
property var sidebar: null
|
|
13
|
+
property string pendingPath: ""
|
|
14
|
+
property int lockedCursor: -1
|
|
15
|
+
// The device flea --unlock opened on, until a rail row names it.
|
|
16
|
+
property string unnamedDevice: ""
|
|
17
|
+
// The pending poller answered once, so its Loader never runs again for this card.
|
|
18
|
+
property bool _namePollDone: false
|
|
19
|
+
// The poller's last listing, kept after its Loader ends, so the first row still names the card.
|
|
20
|
+
property var _pollEntries: []
|
|
21
|
+
readonly property bool opened: loader.item !== null && loader.item.opened
|
|
22
|
+
// The test seam reads the card through this, so no writer ever reaches past the host.
|
|
23
|
+
readonly property var dialog: loader.item
|
|
24
|
+
// The test seam reads the poller's life through this, never its item.
|
|
25
|
+
readonly property bool namePolling: namePoller.active
|
|
26
|
+
|
|
27
|
+
function openFor(device, label, size, fstype, rail) {
|
|
28
|
+
if (rail !== undefined && rail !== null)
|
|
29
|
+
root.sidebar = rail
|
|
30
|
+
root.lockedCursor = root.cursorOnRow(device)
|
|
31
|
+
loader.active = true
|
|
32
|
+
loader.item.openFor(device, label, size, fstype, root.versionFor(root.sidebar, device))
|
|
33
|
+
}
|
|
34
|
+
// The rail row carries lsblk's FSVER, so the card reads LUKS1 or LUKS2 with no signal change.
|
|
35
|
+
function versionFor(rail, device) {
|
|
36
|
+
var row = Unlock.namingRow(rail ? rail.deviceEntries : [], root.fallbackEntries(), device)
|
|
37
|
+
return row ? String(row.fsver || "") : ""
|
|
38
|
+
}
|
|
39
|
+
// The locked row wherever the device list lives, the rail first; a hidden rail unloads its own list with the Sidebar.
|
|
40
|
+
function lookupRow(device) {
|
|
41
|
+
return Unlock.namingRow(root.sidebar ? root.sidebar.deviceEntries : [], root.fallbackEntries(), device)
|
|
42
|
+
}
|
|
43
|
+
// A hidden rail unloads its poll with the Sidebar, so this one-shot lister names the CLI card instead.
|
|
44
|
+
function fallbackEntries() {
|
|
45
|
+
return namePoller.item ? namePoller.item.entries : root._pollEntries
|
|
46
|
+
}
|
|
47
|
+
// flea --unlock may open before the rail's first lsblk, so the card shows the device's leaf until its locked row names it.
|
|
48
|
+
function openForDevice(devicePath, rail) {
|
|
49
|
+
var leaf = String(devicePath || "").split("/").pop()
|
|
50
|
+
openFor(devicePath, leaf, 0, "LUKS", rail)
|
|
51
|
+
root._namePollDone = false
|
|
52
|
+
root._pollEntries = []
|
|
53
|
+
root.unnamedDevice = String(devicePath || "")
|
|
54
|
+
root.nameDevice()
|
|
55
|
+
}
|
|
56
|
+
// A card the user has started on never takes a name, so its poller never starts.
|
|
57
|
+
function cardTouched() {
|
|
58
|
+
var unlockDialog = loader.item
|
|
59
|
+
return !!unlockDialog && (unlockDialog.phase !== "typing" || unlockDialog.noteText.length > 0 || unlockDialog.masked)
|
|
60
|
+
}
|
|
61
|
+
// The first touch latches the one-shot shut, so erasing never restarts the poller.
|
|
62
|
+
readonly property bool _touched: root.cardTouched()
|
|
63
|
+
on_TouchedChanged: if (root._touched) root._namePollDone = true
|
|
64
|
+
// Only an untouched card takes the name, so nothing the user has started on changes the drive it names.
|
|
65
|
+
function nameDevice() {
|
|
66
|
+
var unlockDialog = loader.item
|
|
67
|
+
if (root.unnamedDevice.length === 0 || !unlockDialog || !unlockDialog.opened || unlockDialog.device !== root.unnamedDevice) {
|
|
68
|
+
root.unnamedDevice = ""
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
// A touched card ends the pending name for good, so no later listing renames it.
|
|
72
|
+
if (root.cardTouched()) {
|
|
73
|
+
root._namePollDone = true
|
|
74
|
+
root.unnamedDevice = ""
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
var row = root.lookupRow(root.unnamedDevice)
|
|
78
|
+
// One-shot: the poller's first listing ends the wait whether it named the card or not.
|
|
79
|
+
if (!row) {
|
|
80
|
+
if (root._namePollDone)
|
|
81
|
+
root.unnamedDevice = ""
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
root.unnamedDevice = ""
|
|
85
|
+
unlockDialog.describe(row.label, row.size, row.fstype, row.fsver)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// After an unlock the locked row is a crypt row in the same place; put the cursor on it,
|
|
89
|
+
// so the unlocked drive stays current. Matched by mountpoint: udisksctl names /dev/dm-N, lsblk /dev/mapper/.
|
|
90
|
+
function followUnlocked(path) {
|
|
91
|
+
root.pendingPath = String(path || "")
|
|
92
|
+
root.landCursor(true)
|
|
93
|
+
}
|
|
94
|
+
// Answers whether the cursor landed; stashes for the next rebuild when asked.
|
|
95
|
+
function landCursor(stash) {
|
|
96
|
+
var rail = root.sidebar
|
|
97
|
+
var want = root.pendingPath
|
|
98
|
+
if (!rail || want.length === 0) {
|
|
99
|
+
root.pendingPath = ""
|
|
100
|
+
return false
|
|
101
|
+
}
|
|
102
|
+
var at = rail.placesEntries.length + rail.networkEntries.length
|
|
103
|
+
for (var i = 0; i < rail.deviceEntries.length; i++) {
|
|
104
|
+
if (String(rail.deviceEntries[i].path || "") === want) {
|
|
105
|
+
root.pendingPath = ""
|
|
106
|
+
rail.cursorIndex = at + i
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (stash !== true)
|
|
111
|
+
root.pendingPath = ""
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// The rail index of the cursor when it sits on this device's own row, else -1: the locked row the unlock was asked on.
|
|
116
|
+
function cursorOnRow(device) {
|
|
117
|
+
var rail = root.sidebar
|
|
118
|
+
if (!rail || rail.cursorIndex < 0)
|
|
119
|
+
return -1
|
|
120
|
+
var row = rail.deviceEntries[rail.cursorIndex - rail.placesEntries.length - rail.networkEntries.length]
|
|
121
|
+
return row && String(row.device || "") === device ? rail.cursorIndex : -1
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// A rebuilt rail ends the follow when the cursor moved off the locked row, and lands it otherwise.
|
|
125
|
+
function settleFollow() {
|
|
126
|
+
if (root.pendingPath.length === 0)
|
|
127
|
+
return
|
|
128
|
+
var rail = root.sidebar
|
|
129
|
+
if (!rail || (rail.cursorIndex !== -1 && rail.cursorIndex !== root.lockedCursor)) {
|
|
130
|
+
root.pendingPath = ""
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
root.landCursor(false)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The CLI card's own poll while its name is pending, one listing only; it overlaps the rail's poll because the rail's first lsblk can lag the CLI open.
|
|
137
|
+
Loader {
|
|
138
|
+
id: namePoller
|
|
139
|
+
active: root.unnamedDevice.length > 0 && root.opened && !root._namePollDone && !root.cardTouched()
|
|
140
|
+
sourceComponent: pollerComponent
|
|
141
|
+
}
|
|
142
|
+
Component {
|
|
143
|
+
id: pollerComponent
|
|
144
|
+
Flea.DeviceMounts {}
|
|
145
|
+
}
|
|
146
|
+
Connections {
|
|
147
|
+
target: namePoller.item
|
|
148
|
+
// The first answer ends the Loader below, so its listing is kept here first.
|
|
149
|
+
function onEntriesChanged() { root._pollEntries = namePoller.item.entries }
|
|
150
|
+
// An empty first listing changes no entries, so its answer latches here instead.
|
|
151
|
+
function onFirstAnsweredChanged() {
|
|
152
|
+
if (namePoller.item && namePoller.item.firstAnswered) {
|
|
153
|
+
// A listing the timeout ended answered nothing, so the one-shot polls again for one that did.
|
|
154
|
+
if (namePoller.item.listTimedOut) {
|
|
155
|
+
namePoller.item.firstAnswered = false
|
|
156
|
+
namePoller.item.poll()
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
root._namePollDone = true
|
|
160
|
+
root.nameDevice()
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
Loader {
|
|
165
|
+
id: loader
|
|
166
|
+
anchors.fill: parent
|
|
167
|
+
z: 2
|
|
168
|
+
active: false
|
|
169
|
+
source: "UnlockDialog.qml"
|
|
170
|
+
onLoaded: {
|
|
171
|
+
item.closed.connect(root.onDialogClosed)
|
|
172
|
+
item.unlocked.connect(root.onDialogUnlocked)
|
|
173
|
+
item.lockFailed.connect(root.onDialogLockFailed)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
Connections {
|
|
177
|
+
// A plain test rail emits nothing, so only a rail with the signal is the target.
|
|
178
|
+
target: root.sidebar && root.sidebar.entriesChanged ? root.sidebar : null
|
|
179
|
+
function onEntriesChanged() { root.settleFollow(); root.nameDevice() }
|
|
180
|
+
}
|
|
181
|
+
function onDialogClosed() {
|
|
182
|
+
if (root.window)
|
|
183
|
+
root.window.currentPane.forceActiveFocus()
|
|
184
|
+
}
|
|
185
|
+
// A dialog that ended without a volume could not put the container back; the drive is named.
|
|
186
|
+
function onDialogLockFailed(note) {
|
|
187
|
+
if (root.footer)
|
|
188
|
+
root.footer.say(note, true)
|
|
189
|
+
}
|
|
190
|
+
// The rail's own open, so a rail Enter's focus claim lands in the folder as any mount-first open does (RailKeys.landed).
|
|
191
|
+
function onDialogUnlocked(path, label, note) {
|
|
192
|
+
if (!root.window)
|
|
193
|
+
return
|
|
194
|
+
if (!root.sidebar) {
|
|
195
|
+
// No rail to open through, so the current pane opens the folder itself, with the note said below.
|
|
196
|
+
root.window.currentPane.open(path)
|
|
197
|
+
} else {
|
|
198
|
+
root.sidebar.opened(path)
|
|
199
|
+
// The unlocked row and its eject mark land now, not at the rail's next five second poll.
|
|
200
|
+
root.sidebar.pollDevices()
|
|
201
|
+
root.followUnlocked(path)
|
|
202
|
+
}
|
|
203
|
+
if (root.footer)
|
|
204
|
+
root.footer.say(note, false)
|
|
205
|
+
}
|
|
206
|
+
}
|