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.
Files changed (637) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +511 -2
  3. package/THIRD-PARTY-NOTICES.md +29 -0
  4. package/demos/App.java +5 -0
  5. package/demos/Dockerfile +4 -0
  6. package/demos/Main.kt +3 -0
  7. package/demos/Program.cs +1 -0
  8. package/demos/README.md +12 -0
  9. package/demos/app.dart +3 -0
  10. package/demos/app.deb +0 -0
  11. package/demos/app.js +1 -0
  12. package/demos/backup.tar +0 -0
  13. package/demos/budget.xlsx +0 -0
  14. package/demos/build.sh +2 -0
  15. package/demos/bundle.zip +0 -0
  16. package/demos/clip.mp4 +0 -0
  17. package/demos/data.sqlite +0 -0
  18. package/demos/disk.iso +0 -0
  19. package/demos/engine.cpp +5 -0
  20. package/demos/firmware.bin +0 -0
  21. package/demos/flake.nix +4 -0
  22. package/demos/folder/inside.txt +1 -0
  23. package/demos/font.ttf +0 -0
  24. package/demos/index.html +5 -0
  25. package/demos/index.php +2 -0
  26. package/demos/init.lua +1 -0
  27. package/demos/letter.docx +0 -0
  28. package/demos/lib.rs +3 -0
  29. package/demos/main.c +6 -0
  30. package/demos/main.h +3 -0
  31. package/demos/math-and-diagrams.md +200 -0
  32. package/demos/notes.tar.gz +0 -0
  33. package/demos/notes.txt +3 -0
  34. package/demos/photo.png +0 -0
  35. package/demos/program +2 -0
  36. package/demos/report.pdf +32 -0
  37. package/demos/script.py +1 -0
  38. package/demos/server.go +7 -0
  39. package/demos/settings.json +4 -0
  40. package/demos/slides.pptx +0 -0
  41. package/demos/song.mp3 +0 -0
  42. package/demos/style.css +4 -0
  43. package/demos/tool.rb +1 -0
  44. package/demos/types.ts +2 -0
  45. package/docs/pele.md +23 -0
  46. package/docs/protocol.md +3420 -0
  47. package/docs/temml.md +63 -0
  48. package/index.js +107 -0
  49. package/package.json +34 -4
  50. package/src/archive.js +407 -0
  51. package/src/archiveops.js +728 -0
  52. package/src/backend.js +369 -0
  53. package/src/bulkrename.js +95 -0
  54. package/src/clip-host.js +76 -0
  55. package/src/clip.js +462 -0
  56. package/src/code.js +168 -0
  57. package/src/compare.js +74 -0
  58. package/src/create.js +128 -0
  59. package/src/dirsize.js +147 -0
  60. package/src/figure-svg.js +530 -0
  61. package/src/figure-thread.js +23 -0
  62. package/src/figure.js +132 -0
  63. package/src/find.js +55 -0
  64. package/src/flea.js +39 -0
  65. package/src/fsinfo.js +39 -0
  66. package/src/highlight.js +141 -0
  67. package/src/html.js +52 -0
  68. package/src/kinds.js +89 -0
  69. package/src/listing.js +130 -0
  70. package/src/markdown-html.js +317 -0
  71. package/src/mathml.js +186 -0
  72. package/src/menu.js +180 -0
  73. package/src/meta.js +135 -0
  74. package/src/office.js +810 -0
  75. package/src/order.js +100 -0
  76. package/src/peek.js +49 -0
  77. package/src/pele-wrapper.js +695 -0
  78. package/src/permissions.js +180 -0
  79. package/src/places.js +313 -0
  80. package/src/preview-language.js +10 -0
  81. package/src/terminal-server.js +95 -0
  82. package/src/terminal-shell.js +26 -0
  83. package/src/transfer.js +669 -0
  84. package/src/trash-record.js +41 -0
  85. package/src/trash.js +210 -0
  86. package/src/trashbrowse.js +327 -0
  87. package/src/uistate.js +393 -0
  88. package/src/uistore.js +168 -0
  89. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  90. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  91. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  92. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  93. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  94. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  95. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  96. package/src/vendor/pele.js +145 -0
  97. package/src/vendor/temml.mjs +14708 -0
  98. package/src/watch.js +98 -0
  99. package/src/wayland/client.js +109 -0
  100. package/src/wayland/protocol.js +55 -0
  101. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  102. package/src/wayland/protocols/wayland.xml +3248 -0
  103. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  104. package/src/wayland/resource-io.js +6 -0
  105. package/src/wayland/socket-discovery.js +26 -0
  106. package/src/wayland/transport-posix.js +120 -0
  107. package/src/wayland/transport-win32.js +11 -0
  108. package/src/wayland/transport.js +3 -0
  109. package/src/wayland/win32-resources.js +80 -0
  110. package/src/wayland/win32-wire.js +62 -0
  111. package/test/archive.test.js +80 -0
  112. package/test/bench-markdown-scroll.mjs +190 -0
  113. package/test/clip-host.test.js +113 -0
  114. package/test/code-preview.test.js +35 -0
  115. package/test/compare-ui-paths.test.js +24 -0
  116. package/test/compare.test.js +54 -0
  117. package/test/delete-trash.test.js +67 -0
  118. package/test/dirsize.test.js +138 -0
  119. package/test/file-ops.test.js +307 -0
  120. package/test/file-url.test.js +24 -0
  121. package/test/find-contents.test.js +161 -0
  122. package/test/find-input.test.js +66 -0
  123. package/test/fixtures/pele-parity.json +1006 -0
  124. package/test/highlight-launcher.test.js +35 -0
  125. package/test/html-preview.test.js +22 -0
  126. package/test/input.test.js +15 -0
  127. package/test/keymap.test.js +26 -0
  128. package/test/language-icons.test.js +36 -0
  129. package/test/launcher.test.js +51 -0
  130. package/test/manual-archive-here-ui.mjs +47 -0
  131. package/test/manual-code-scroll.mjs +28 -0
  132. package/test/manual-column-scroll.mjs +24 -0
  133. package/test/manual-compare-ui.mjs +20 -0
  134. package/test/manual-find-status.mjs +37 -0
  135. package/test/manual-folder-focus.mjs +63 -0
  136. package/test/manual-html-security.mjs +23 -0
  137. package/test/manual-html-ui.mjs +30 -0
  138. package/test/manual-syntax-preview.mjs +26 -0
  139. package/test/manual-terminal-lifecycle.mjs +42 -0
  140. package/test/manual-terminal-navigation.mjs +48 -0
  141. package/test/manual-terminal-ui.mjs +47 -0
  142. package/test/manual-trash-ui.mjs +86 -0
  143. package/test/markdown-diagrams.test.js +93 -0
  144. package/test/markdown-html.test.js +105 -0
  145. package/test/mathml.test.js +92 -0
  146. package/test/menu-transfer.test.js +57 -0
  147. package/test/office-preview.test.js +154 -0
  148. package/test/pele-parity.test.js +28 -0
  149. package/test/places.test.js +134 -0
  150. package/test/preview-keys.test.js +60 -0
  151. package/test/preview-language.test.js +22 -0
  152. package/test/status-filename.test.js +20 -0
  153. package/test/transfer-replace-merge.test.js +187 -0
  154. package/test/trash-address.test.js +23 -0
  155. package/test/trash-view.test.js +37 -0
  156. package/test/trashbrowse.test.js +135 -0
  157. package/ui/AboutFacts.qml +78 -0
  158. package/ui/ArchiveCards.qml +95 -0
  159. package/ui/ArchiveCompressDialog.qml +378 -0
  160. package/ui/ArchiveHereConfirm.qml +127 -0
  161. package/ui/ArchivePasswordDialog.qml +257 -0
  162. package/ui/Backend.qml +775 -0
  163. package/ui/BulkOldNames.qml +47 -0
  164. package/ui/BulkRenameDialog.qml +380 -0
  165. package/ui/CardFrame.qml +67 -0
  166. package/ui/CardScroll.qml +79 -0
  167. package/ui/CheckBox.qml +73 -0
  168. package/ui/ChromeBar.qml +360 -0
  169. package/ui/ChromeButton.qml +131 -0
  170. package/ui/ChromeModes.qml +43 -0
  171. package/ui/CodeLines.qml +256 -0
  172. package/ui/CodeRefusal.qml +52 -0
  173. package/ui/CollideConfirm.qml +147 -0
  174. package/ui/CollideHost.qml +167 -0
  175. package/ui/ColumnPane.qml +525 -0
  176. package/ui/ColumnRow.qml +380 -0
  177. package/ui/ColumnsArea.qml +594 -0
  178. package/ui/ColumnsLanding.qml +147 -0
  179. package/ui/CompareView.qml +321 -0
  180. package/ui/ContextMenu.qml +743 -0
  181. package/ui/ConvertDialog.qml +456 -0
  182. package/ui/CountedWorker.qml +7 -0
  183. package/ui/Crumb.qml +64 -0
  184. package/ui/CrumbDrop.qml +25 -0
  185. package/ui/CsvTable.qml +394 -0
  186. package/ui/CursorMark.qml +41 -0
  187. package/ui/DefaultClaim.qml +130 -0
  188. package/ui/DeviceLock.qml +47 -0
  189. package/ui/DeviceMounts.qml +540 -0
  190. package/ui/DialogButton.qml +150 -0
  191. package/ui/DialogField.qml +147 -0
  192. package/ui/DialogHeader.qml +31 -0
  193. package/ui/DialogScrim.qml +20 -0
  194. package/ui/DialogTitle.qml +37 -0
  195. package/ui/Divider.qml +9 -0
  196. package/ui/DropInto.qml +126 -0
  197. package/ui/DropTarget.qml +35 -0
  198. package/ui/DropboxMark.qml +60 -0
  199. package/ui/EmptyState.qml +150 -0
  200. package/ui/EscapeHint.qml +11 -0
  201. package/ui/FactsTable.qml +54 -0
  202. package/ui/FastScrollHandler.qml +476 -0
  203. package/ui/FatNamesConfirm.qml +157 -0
  204. package/ui/Favourites.qml +143 -0
  205. package/ui/FigureService.qml +120 -0
  206. package/ui/FileDrag.qml +267 -0
  207. package/ui/FilterStrip.qml +133 -0
  208. package/ui/FitMetrics.qml +22 -0
  209. package/ui/FlashDriveRows.qml +137 -0
  210. package/ui/FlashHost.qml +72 -0
  211. package/ui/FlashImageRow.qml +104 -0
  212. package/ui/FlashRunner.qml +119 -0
  213. package/ui/FlashStepRow.qml +172 -0
  214. package/ui/FlashStrip.qml +38 -0
  215. package/ui/FlashView.qml +397 -0
  216. package/ui/FleaMark.qml +88 -0
  217. package/ui/FlyoutFade.qml +23 -0
  218. package/ui/FontPane.qml +170 -0
  219. package/ui/Glass.qml +114 -0
  220. package/ui/GlassTagger.qml +59 -0
  221. package/ui/Glyph.qml +64 -0
  222. package/ui/GlyphSolid.qml +22 -0
  223. package/ui/GridArea.qml +664 -0
  224. package/ui/GridTile.qml +325 -0
  225. package/ui/GvfsBridge.qml +164 -0
  226. package/ui/Header.qml +433 -0
  227. package/ui/HoverTimers.qml +39 -0
  228. package/ui/HtmlClip.qml +30 -0
  229. package/ui/HtmlPane.qml +63 -0
  230. package/ui/HyprlandMark.qml +45 -0
  231. package/ui/Ipc.qml +1005 -0
  232. package/ui/IpcControls.qml +7 -0
  233. package/ui/IpcMenuState.qml +48 -0
  234. package/ui/IpcPreviewState.qml +185 -0
  235. package/ui/JumpPath.qml +51 -0
  236. package/ui/KeymapSheet.qml +561 -0
  237. package/ui/KeymapSheetResult.qml +89 -0
  238. package/ui/KindStack.qml +40 -0
  239. package/ui/LangMark.qml +51 -0
  240. package/ui/LangMarkLoader.qml +26 -0
  241. package/ui/List.qml +480 -0
  242. package/ui/LoadingState.qml +54 -0
  243. package/ui/LocalSend.qml +56 -0
  244. package/ui/LocalSendMark.qml +73 -0
  245. package/ui/MarkdownBlockView.qml +311 -0
  246. package/ui/MarkdownBlocks.qml +35 -0
  247. package/ui/MarkdownFigure.qml +254 -0
  248. package/ui/MarkdownImages.qml +72 -0
  249. package/ui/MarkdownList.qml +81 -0
  250. package/ui/MarkdownMathsText.qml +97 -0
  251. package/ui/MarkdownPane.qml +83 -0
  252. package/ui/MarkdownPictures.qml +41 -0
  253. package/ui/MarkdownQuote.qml +52 -0
  254. package/ui/MarkdownRemote.qml +82 -0
  255. package/ui/MarkdownSource.qml +155 -0
  256. package/ui/MarkdownTable.qml +222 -0
  257. package/ui/MarkdownTableScroll.qml +64 -0
  258. package/ui/MarkdownTableWheel.qml +126 -0
  259. package/ui/MarkdownText.qml +87 -0
  260. package/ui/MarkdownWorker.js +132 -0
  261. package/ui/MatchText.qml +99 -0
  262. package/ui/MediaSession.qml +38 -0
  263. package/ui/MediaSound.qml +26 -0
  264. package/ui/MediaStrip.qml +191 -0
  265. package/ui/MediaTrack.qml +36 -0
  266. package/ui/MediaVolume.qml +54 -0
  267. package/ui/MenuActionDialog.qml +396 -0
  268. package/ui/MenuEdgeFade.qml +24 -0
  269. package/ui/MenuHost.qml +227 -0
  270. package/ui/MenuRow.qml +351 -0
  271. package/ui/MountListing.qml +100 -0
  272. package/ui/NetworkDialog.qml +435 -0
  273. package/ui/NetworkForm.qml +426 -0
  274. package/ui/NetworkMounts.qml +913 -0
  275. package/ui/NetworkPlaces.qml +127 -0
  276. package/ui/OpenWithDialog.qml +613 -0
  277. package/ui/Opener.qml +95 -0
  278. package/ui/Pane.qml +1320 -0
  279. package/ui/PaneMenuActions.qml +527 -0
  280. package/ui/PanePath.qml +90 -0
  281. package/ui/PaneRail.qml +268 -0
  282. package/ui/PaneStates.qml +88 -0
  283. package/ui/PaneSwap.qml +339 -0
  284. package/ui/PaneWire.qml +601 -0
  285. package/ui/ParkedWait.qml +9 -0
  286. package/ui/PathEntry.qml +186 -0
  287. package/ui/PathJump.qml +400 -0
  288. package/ui/PdfViewer.qml +297 -0
  289. package/ui/PermissionsDialog.qml +658 -0
  290. package/ui/PermissionsHeadings.qml +31 -0
  291. package/ui/PhoneMounts.qml +67 -0
  292. package/ui/PickerAnswers.qml +75 -0
  293. package/ui/PickerBox.qml +26 -0
  294. package/ui/PickerChrome.qml +295 -0
  295. package/ui/PickerFilter.qml +97 -0
  296. package/ui/PickerGrid.qml +308 -0
  297. package/ui/PickerHeader.qml +28 -0
  298. package/ui/PickerLifecycle.qml +27 -0
  299. package/ui/PickerList.qml +218 -0
  300. package/ui/PickerListing.qml +149 -0
  301. package/ui/PickerPathBar.qml +127 -0
  302. package/ui/PickerPlaces.qml +189 -0
  303. package/ui/PickerRecent.qml +92 -0
  304. package/ui/PickerSave.qml +101 -0
  305. package/ui/PickerSelection.qml +155 -0
  306. package/ui/PickerWindow.qml +846 -0
  307. package/ui/PowerSectorsReader.qml +10 -0
  308. package/ui/PrebuildDriver.qml +146 -0
  309. package/ui/Preview.qml +1005 -0
  310. package/ui/PreviewAnimation.qml +24 -0
  311. package/ui/PreviewArchive.qml +91 -0
  312. package/ui/PreviewColumn.qml +553 -0
  313. package/ui/PreviewEdge.qml +63 -0
  314. package/ui/PreviewFacts.qml +30 -0
  315. package/ui/PreviewFactsLine.qml +34 -0
  316. package/ui/PreviewHtml.qml +233 -0
  317. package/ui/PreviewImage.qml +253 -0
  318. package/ui/PreviewImagePan.qml +13 -0
  319. package/ui/PreviewImageSharp.qml +32 -0
  320. package/ui/PreviewLines.qml +106 -0
  321. package/ui/PreviewMarkdown.qml +706 -0
  322. package/ui/PreviewMedia.qml +250 -0
  323. package/ui/PreviewPdf.qml +482 -0
  324. package/ui/PreviewScroll.qml +201 -0
  325. package/ui/PreviewSwap.qml +335 -0
  326. package/ui/PreviewText.qml +149 -0
  327. package/ui/ProtocolChip.qml +18 -0
  328. package/ui/QuickLookArchive.qml +41 -0
  329. package/ui/QuickLookBar.qml +103 -0
  330. package/ui/QuickLookPrepare.qml +355 -0
  331. package/ui/QuickLookSwap.qml +10 -0
  332. package/ui/QuickLookWarm.qml +65 -0
  333. package/ui/RailDrop.qml +155 -0
  334. package/ui/RailHeading.qml +19 -0
  335. package/ui/RailPlaces.qml +38 -0
  336. package/ui/RailTrashRow.qml +67 -0
  337. package/ui/RenameField.qml +353 -0
  338. package/ui/RendererRetry.qml +11 -0
  339. package/ui/ResizeHandle.qml +52 -0
  340. package/ui/Reveal.qml +54 -0
  341. package/ui/Row.qml +476 -0
  342. package/ui/RowDrag.qml +213 -0
  343. package/ui/RowDropTarget.qml +27 -0
  344. package/ui/RunCard.qml +286 -0
  345. package/ui/Scripts.qml +65 -0
  346. package/ui/SearchStrip.qml +232 -0
  347. package/ui/SelectionBand.qml +140 -0
  348. package/ui/SelectionPreview.qml +370 -0
  349. package/ui/SettingsFavourite.qml +122 -0
  350. package/ui/SettingsGroup.qml +96 -0
  351. package/ui/SettingsHint.qml +12 -0
  352. package/ui/SettingsMetrics.qml +29 -0
  353. package/ui/SettingsPane.qml +178 -0
  354. package/ui/SettingsPanel.qml +547 -0
  355. package/ui/SettingsRail.qml +128 -0
  356. package/ui/SettingsRow.qml +409 -0
  357. package/ui/SettingsRuler.qml +85 -0
  358. package/ui/SettingsSegment.qml +85 -0
  359. package/ui/ShareBrowser.qml +113 -0
  360. package/ui/ShareLink.qml +67 -0
  361. package/ui/ShelfPins.qml +101 -0
  362. package/ui/Sidebar.qml +728 -0
  363. package/ui/SidebarRow.qml +358 -0
  364. package/ui/Spinner.qml +60 -0
  365. package/ui/StableModel.qml +89 -0
  366. package/ui/StateMessage.qml +84 -0
  367. package/ui/StatusBar.qml +512 -0
  368. package/ui/StatusFileName.qml +27 -0
  369. package/ui/TabBar.qml +673 -0
  370. package/ui/TabDragGeometry.qml +60 -0
  371. package/ui/TagMenuMark.qml +52 -0
  372. package/ui/TagRailSection.qml +53 -0
  373. package/ui/TagSlot.qml +40 -0
  374. package/ui/TagState.qml +150 -0
  375. package/ui/TagSwatch.qml +28 -0
  376. package/ui/Taildrop.qml +86 -0
  377. package/ui/TailscaleMark.qml +50 -0
  378. package/ui/TerminalService.qml +69 -0
  379. package/ui/TerminalTab.qml +48 -0
  380. package/ui/TerminalTabs.qml +27 -0
  381. package/ui/Theme.qml +460 -0
  382. package/ui/ThemeColors.qml +43 -0
  383. package/ui/TransferCard.qml +239 -0
  384. package/ui/TrashConfirm.qml +129 -0
  385. package/ui/TrashDropHandler.qml +11 -0
  386. package/ui/TrashHost.qml +202 -0
  387. package/ui/TrashMonitor.qml +30 -0
  388. package/ui/TrashToolbar.qml +19 -0
  389. package/ui/TrashView.qml +492 -0
  390. package/ui/UnlockDialog.qml +450 -0
  391. package/ui/UnlockHost.qml +206 -0
  392. package/ui/UpdateCheck.qml +97 -0
  393. package/ui/UserDirs.qml +25 -0
  394. package/ui/ViewState.qml +565 -0
  395. package/ui/ViewportScrollBar.qml +195 -0
  396. package/ui/ViewportScrollBars.qml +25 -0
  397. package/ui/WebPreview.qml +33 -0
  398. package/ui/WebPreviewView.qml +26 -0
  399. package/ui/WindowBody.qml +829 -0
  400. package/ui/WindowDrag.qml +35 -0
  401. package/ui/boot/Commons/AudioNodes.qml +50 -0
  402. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  403. package/ui/boot/Commons/Border.qml +243 -0
  404. package/ui/boot/Commons/BorderGeometry.js +373 -0
  405. package/ui/boot/Commons/Color.qml +258 -0
  406. package/ui/boot/Commons/I18n.qml +118 -0
  407. package/ui/boot/Commons/InputDevices.qml +112 -0
  408. package/ui/boot/Commons/InputMethodState.qml +38 -0
  409. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  410. package/ui/boot/Commons/Numbers.js +19 -0
  411. package/ui/boot/Commons/Plural.js +91 -0
  412. package/ui/boot/Commons/ShellIpc.qml +12 -0
  413. package/ui/boot/Commons/Style.qml +561 -0
  414. package/ui/boot/Commons/Translations.qml +100 -0
  415. package/ui/boot/Commons/UntypedInput.qml +152 -0
  416. package/ui/boot/Commons/Util.qml +188 -0
  417. package/ui/boot/Commons/qmldir +13 -0
  418. package/ui/boot/LICENSE-omarchy +20 -0
  419. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  420. package/ui/boot/Ui/BarIconButton.qml +106 -0
  421. package/ui/boot/Ui/BarIconContent.qml +38 -0
  422. package/ui/boot/Ui/BarIndicator.qml +53 -0
  423. package/ui/boot/Ui/BarWidget.qml +50 -0
  424. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  425. package/ui/boot/Ui/BorderSurface.qml +40 -0
  426. package/ui/boot/Ui/Button.qml +224 -0
  427. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  428. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  429. package/ui/boot/Ui/CursorSurface.qml +42 -0
  430. package/ui/boot/Ui/Dropdown.qml +245 -0
  431. package/ui/boot/Ui/FilterInput.qml +26 -0
  432. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  433. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  434. package/ui/boot/Ui/MultiSelect.qml +624 -0
  435. package/ui/boot/Ui/NumberField.qml +86 -0
  436. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  437. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  438. package/ui/boot/Ui/Panel.qml +60 -0
  439. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  440. package/ui/boot/Ui/PanelController.qml +16 -0
  441. package/ui/boot/Ui/PanelHero.qml +112 -0
  442. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  443. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  444. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  445. package/ui/boot/Ui/PanelSlider.qml +156 -0
  446. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  447. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  448. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  449. package/ui/boot/Ui/PopupCard.qml +181 -0
  450. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  451. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  452. package/ui/boot/Ui/SizedImage.qml +125 -0
  453. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  454. package/ui/boot/Ui/TextField.qml +58 -0
  455. package/ui/boot/Ui/Toggle.qml +118 -0
  456. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  457. package/ui/boot/Ui/WidgetButton.qml +172 -0
  458. package/ui/boot/Ui/qmldir +41 -0
  459. package/ui/boot/fleatab.qml +50 -0
  460. package/ui/boot/picker.qml +39 -0
  461. package/ui/boot/shell.qml +131 -0
  462. package/ui/boot/tabtearoff.qml +51 -0
  463. package/ui/js/Anchor.js +379 -0
  464. package/ui/js/AnchorHold.js +162 -0
  465. package/ui/js/Archive.js +157 -0
  466. package/ui/js/ArchiveCard.js +44 -0
  467. package/ui/js/ArchiveOps.js +44 -0
  468. package/ui/js/BackReturn.js +118 -0
  469. package/ui/js/BulkRename.js +100 -0
  470. package/ui/js/Buttons.js +54 -0
  471. package/ui/js/ChromeKeys.js +116 -0
  472. package/ui/js/ClipMarks.js +73 -0
  473. package/ui/js/ClipSelection.js +67 -0
  474. package/ui/js/Clipboard.js +179 -0
  475. package/ui/js/Cloud.js +97 -0
  476. package/ui/js/CodeRuns.js +72 -0
  477. package/ui/js/Collide.js +212 -0
  478. package/ui/js/ColumnFit.js +28 -0
  479. package/ui/js/ColumnMenu.js +111 -0
  480. package/ui/js/Columns.js +298 -0
  481. package/ui/js/ColumnsCarry.js +103 -0
  482. package/ui/js/Compare.js +74 -0
  483. package/ui/js/Contrast.js +157 -0
  484. package/ui/js/ControlReader.js +42 -0
  485. package/ui/js/Convert.js +52 -0
  486. package/ui/js/CopyAs.js +52 -0
  487. package/ui/js/Crumbs.js +178 -0
  488. package/ui/js/Csv.js +231 -0
  489. package/ui/js/Density.js +44 -0
  490. package/ui/js/Devices.js +440 -0
  491. package/ui/js/DirSizes.js +180 -0
  492. package/ui/js/Drag.js +360 -0
  493. package/ui/js/DragOut.js +166 -0
  494. package/ui/js/DropDev.js +52 -0
  495. package/ui/js/Dropbox.js +41 -0
  496. package/ui/js/Eject.js +217 -0
  497. package/ui/js/Errors.js +188 -0
  498. package/ui/js/ExtThumbs.js +115 -0
  499. package/ui/js/Facts.js +402 -0
  500. package/ui/js/FigureDate.js +14 -0
  501. package/ui/js/Filter.js +285 -0
  502. package/ui/js/Find.js +46 -0
  503. package/ui/js/Flash.js +184 -0
  504. package/ui/js/FlashCard.js +79 -0
  505. package/ui/js/FlashEndings.js +125 -0
  506. package/ui/js/FlashPane.js +116 -0
  507. package/ui/js/FleaBin.js +12 -0
  508. package/ui/js/Focus.js +504 -0
  509. package/ui/js/FolderSorts.js +108 -0
  510. package/ui/js/FolderViews.js +44 -0
  511. package/ui/js/Format.js +314 -0
  512. package/ui/js/Fuzzy.js +141 -0
  513. package/ui/js/GlassTag.js +72 -0
  514. package/ui/js/Grid.js +33 -0
  515. package/ui/js/GridGeometry.js +90 -0
  516. package/ui/js/GridNames.js +153 -0
  517. package/ui/js/GvfsBridge.js +200 -0
  518. package/ui/js/HostClipboard.js +29 -0
  519. package/ui/js/Icons.js +218 -0
  520. package/ui/js/Input.js +50 -0
  521. package/ui/js/Jump.js +200 -0
  522. package/ui/js/KeyName.js +64 -0
  523. package/ui/js/Keymap.js +401 -0
  524. package/ui/js/Kinds.js +164 -0
  525. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  526. package/ui/js/LangMarkPaths.js +47 -0
  527. package/ui/js/LangMarks.js +120 -0
  528. package/ui/js/Lists.js +11 -0
  529. package/ui/js/LocalSend.js +39 -0
  530. package/ui/js/LockedMenu.js +63 -0
  531. package/ui/js/MakeDefault.js +186 -0
  532. package/ui/js/MarkBox.js +7 -0
  533. package/ui/js/Markdown.js +119 -0
  534. package/ui/js/MarkdownLists.js +86 -0
  535. package/ui/js/MarkdownMaths.js +58 -0
  536. package/ui/js/MarkdownPictures.js +32 -0
  537. package/ui/js/MarkdownPrepared.js +139 -0
  538. package/ui/js/MarkdownTableFit.js +34 -0
  539. package/ui/js/Marks.js +148 -0
  540. package/ui/js/Marquee.js +96 -0
  541. package/ui/js/Match.js +93 -0
  542. package/ui/js/Material.js +109 -0
  543. package/ui/js/MdBlocks.js +437 -0
  544. package/ui/js/MdBreak.js +71 -0
  545. package/ui/js/MdChunks.js +70 -0
  546. package/ui/js/MdContainer.js +137 -0
  547. package/ui/js/MdDocument.js +337 -0
  548. package/ui/js/MdEmph.js +97 -0
  549. package/ui/js/MdEntity.js +108 -0
  550. package/ui/js/MdEntityTable.js +223 -0
  551. package/ui/js/MdEscape.js +59 -0
  552. package/ui/js/MdFront.js +37 -0
  553. package/ui/js/MdHold.js +32 -0
  554. package/ui/js/MdHtml.js +307 -0
  555. package/ui/js/MdHtmlBlock.js +290 -0
  556. package/ui/js/MdHtmlHeading.js +53 -0
  557. package/ui/js/MdHtmlImage.js +179 -0
  558. package/ui/js/MdInline.js +154 -0
  559. package/ui/js/MdItems.js +212 -0
  560. package/ui/js/MdLeaf.js +309 -0
  561. package/ui/js/MdLink.js +196 -0
  562. package/ui/js/MdMath.js +67 -0
  563. package/ui/js/MdParagraphs.js +138 -0
  564. package/ui/js/MdRefs.js +232 -0
  565. package/ui/js/MdResolve.js +226 -0
  566. package/ui/js/MdRun.js +300 -0
  567. package/ui/js/MdUrl.js +226 -0
  568. package/ui/js/Menu.js +514 -0
  569. package/ui/js/MenuContext.js +85 -0
  570. package/ui/js/MenuFit.js +13 -0
  571. package/ui/js/MenuRefresh.js +74 -0
  572. package/ui/js/MenuWheel.js +35 -0
  573. package/ui/js/Messages.js +186 -0
  574. package/ui/js/Motion.js +16 -0
  575. package/ui/js/Mounts.js +320 -0
  576. package/ui/js/MouseNav.js +13 -0
  577. package/ui/js/Nav.js +329 -0
  578. package/ui/js/NetFs.js +26 -0
  579. package/ui/js/OpenWith.js +83 -0
  580. package/ui/js/Ops.js +585 -0
  581. package/ui/js/Palette.js +33 -0
  582. package/ui/js/PathBar.js +260 -0
  583. package/ui/js/Permissions.js +179 -0
  584. package/ui/js/Phones.js +112 -0
  585. package/ui/js/Picker.js +353 -0
  586. package/ui/js/PlaceMenu.js +96 -0
  587. package/ui/js/Places.js +265 -0
  588. package/ui/js/PreviewAsk.js +21 -0
  589. package/ui/js/PreviewKeys.js +155 -0
  590. package/ui/js/PreviewSettle.js +29 -0
  591. package/ui/js/PreviewSwap.js +101 -0
  592. package/ui/js/PropertyFacts.js +144 -0
  593. package/ui/js/Protocols.js +296 -0
  594. package/ui/js/QuickLookKinds.js +27 -0
  595. package/ui/js/QuickLookRect.js +24 -0
  596. package/ui/js/RailDrop.js +51 -0
  597. package/ui/js/RailKeys.js +87 -0
  598. package/ui/js/RailMenu.js +133 -0
  599. package/ui/js/Recent.js +135 -0
  600. package/ui/js/RecentMode.js +156 -0
  601. package/ui/js/Reload.js +60 -0
  602. package/ui/js/Renderer.js +9 -0
  603. package/ui/js/RunFile.js +143 -0
  604. package/ui/js/Scripts.js +28 -0
  605. package/ui/js/Scroll.js +304 -0
  606. package/ui/js/ScrollOff.js +69 -0
  607. package/ui/js/Search.js +478 -0
  608. package/ui/js/Selection.js +97 -0
  609. package/ui/js/Settings.js +476 -0
  610. package/ui/js/SettingsAbout.js +97 -0
  611. package/ui/js/SettingsShelf.js +52 -0
  612. package/ui/js/SheetKeys.js +42 -0
  613. package/ui/js/SheetQuery.js +281 -0
  614. package/ui/js/ShelfPile.js +27 -0
  615. package/ui/js/SlowClick.js +53 -0
  616. package/ui/js/SlowOp.js +6 -0
  617. package/ui/js/Sort.js +134 -0
  618. package/ui/js/Startup.js +63 -0
  619. package/ui/js/StateWords.js +17 -0
  620. package/ui/js/Status.js +238 -0
  621. package/ui/js/Swap.js +166 -0
  622. package/ui/js/TabMove.js +39 -0
  623. package/ui/js/Tabs.js +751 -0
  624. package/ui/js/Tags.js +134 -0
  625. package/ui/js/Taildrop.js +128 -0
  626. package/ui/js/Tap.js +180 -0
  627. package/ui/js/TextSize.js +107 -0
  628. package/ui/js/Thumbs.js +158 -0
  629. package/ui/js/Transfer.js +172 -0
  630. package/ui/js/Trash.js +84 -0
  631. package/ui/js/TrashDates.js +47 -0
  632. package/ui/js/TrashDrop.js +64 -0
  633. package/ui/js/UiState.js +438 -0
  634. package/ui/js/Unlock.js +152 -0
  635. package/ui/js/Update.js +130 -0
  636. package/ui/js/UserDirs.js +56 -0
  637. 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
+ }