flea-qml 0.0.0-stage → 0.1.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 (625) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +478 -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 +402 -0
  51. package/src/archiveops.js +697 -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 +129 -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/transfer.js +669 -0
  82. package/src/trash-record.js +41 -0
  83. package/src/trash.js +210 -0
  84. package/src/trashbrowse.js +326 -0
  85. package/src/uistate.js +393 -0
  86. package/src/uistore.js +168 -0
  87. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  88. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  89. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  90. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  91. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  92. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  93. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  94. package/src/vendor/pele.js +145 -0
  95. package/src/vendor/temml.mjs +14708 -0
  96. package/src/watch.js +98 -0
  97. package/src/wayland/client.js +109 -0
  98. package/src/wayland/protocol.js +55 -0
  99. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  100. package/src/wayland/protocols/wayland.xml +3248 -0
  101. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  102. package/src/wayland/resource-io.js +6 -0
  103. package/src/wayland/socket-discovery.js +26 -0
  104. package/src/wayland/transport-posix.js +120 -0
  105. package/src/wayland/transport-win32.js +11 -0
  106. package/src/wayland/transport.js +3 -0
  107. package/src/wayland/win32-resources.js +80 -0
  108. package/src/wayland/win32-wire.js +62 -0
  109. package/test/archive.test.js +80 -0
  110. package/test/bench-markdown-scroll.mjs +190 -0
  111. package/test/clip-host.test.js +113 -0
  112. package/test/code-preview.test.js +35 -0
  113. package/test/compare-ui-paths.test.js +24 -0
  114. package/test/compare.test.js +54 -0
  115. package/test/delete-trash.test.js +67 -0
  116. package/test/dirsize.test.js +138 -0
  117. package/test/file-ops.test.js +241 -0
  118. package/test/find-contents.test.js +161 -0
  119. package/test/find-input.test.js +66 -0
  120. package/test/fixtures/pele-parity.json +1006 -0
  121. package/test/highlight-launcher.test.js +35 -0
  122. package/test/html-preview.test.js +22 -0
  123. package/test/input.test.js +15 -0
  124. package/test/keymap.test.js +26 -0
  125. package/test/language-icons.test.js +36 -0
  126. package/test/launcher.test.js +51 -0
  127. package/test/manual-code-scroll.mjs +28 -0
  128. package/test/manual-column-scroll.mjs +24 -0
  129. package/test/manual-compare-ui.mjs +20 -0
  130. package/test/manual-find-status.mjs +37 -0
  131. package/test/manual-html-security.mjs +23 -0
  132. package/test/manual-html-ui.mjs +30 -0
  133. package/test/manual-syntax-preview.mjs +26 -0
  134. package/test/manual-trash-ui.mjs +86 -0
  135. package/test/markdown-diagrams.test.js +93 -0
  136. package/test/markdown-html.test.js +105 -0
  137. package/test/mathml.test.js +92 -0
  138. package/test/menu-transfer.test.js +57 -0
  139. package/test/office-preview.test.js +154 -0
  140. package/test/pele-parity.test.js +28 -0
  141. package/test/places.test.js +134 -0
  142. package/test/preview-keys.test.js +60 -0
  143. package/test/preview-language.test.js +22 -0
  144. package/test/status-filename.test.js +20 -0
  145. package/test/transfer-replace-merge.test.js +187 -0
  146. package/test/trash-address.test.js +23 -0
  147. package/test/trash-view.test.js +37 -0
  148. package/test/trashbrowse.test.js +135 -0
  149. package/ui/AboutFacts.qml +78 -0
  150. package/ui/ArchiveCards.qml +68 -0
  151. package/ui/ArchiveCompressDialog.qml +378 -0
  152. package/ui/ArchivePasswordDialog.qml +257 -0
  153. package/ui/Backend.qml +771 -0
  154. package/ui/BulkOldNames.qml +47 -0
  155. package/ui/BulkRenameDialog.qml +380 -0
  156. package/ui/CardFrame.qml +67 -0
  157. package/ui/CardScroll.qml +79 -0
  158. package/ui/CheckBox.qml +73 -0
  159. package/ui/ChromeBar.qml +360 -0
  160. package/ui/ChromeButton.qml +131 -0
  161. package/ui/ChromeModes.qml +43 -0
  162. package/ui/CodeLines.qml +256 -0
  163. package/ui/CodeRefusal.qml +52 -0
  164. package/ui/CollideConfirm.qml +147 -0
  165. package/ui/CollideHost.qml +167 -0
  166. package/ui/ColumnPane.qml +523 -0
  167. package/ui/ColumnRow.qml +380 -0
  168. package/ui/ColumnsArea.qml +594 -0
  169. package/ui/ColumnsLanding.qml +147 -0
  170. package/ui/CompareView.qml +321 -0
  171. package/ui/ContextMenu.qml +743 -0
  172. package/ui/ConvertDialog.qml +456 -0
  173. package/ui/CountedWorker.qml +7 -0
  174. package/ui/Crumb.qml +64 -0
  175. package/ui/CrumbDrop.qml +25 -0
  176. package/ui/CsvTable.qml +394 -0
  177. package/ui/CursorMark.qml +41 -0
  178. package/ui/DefaultClaim.qml +130 -0
  179. package/ui/DeviceLock.qml +47 -0
  180. package/ui/DeviceMounts.qml +540 -0
  181. package/ui/DialogButton.qml +150 -0
  182. package/ui/DialogField.qml +147 -0
  183. package/ui/DialogHeader.qml +31 -0
  184. package/ui/DialogScrim.qml +20 -0
  185. package/ui/DialogTitle.qml +37 -0
  186. package/ui/Divider.qml +9 -0
  187. package/ui/DropInto.qml +126 -0
  188. package/ui/DropTarget.qml +35 -0
  189. package/ui/DropboxMark.qml +60 -0
  190. package/ui/EmptyState.qml +150 -0
  191. package/ui/EscapeHint.qml +11 -0
  192. package/ui/FactsTable.qml +54 -0
  193. package/ui/FastScrollHandler.qml +476 -0
  194. package/ui/FatNamesConfirm.qml +157 -0
  195. package/ui/Favourites.qml +143 -0
  196. package/ui/FigureService.qml +120 -0
  197. package/ui/FileDrag.qml +267 -0
  198. package/ui/FilterStrip.qml +133 -0
  199. package/ui/FitMetrics.qml +22 -0
  200. package/ui/FlashDriveRows.qml +137 -0
  201. package/ui/FlashHost.qml +72 -0
  202. package/ui/FlashImageRow.qml +104 -0
  203. package/ui/FlashRunner.qml +119 -0
  204. package/ui/FlashStepRow.qml +172 -0
  205. package/ui/FlashStrip.qml +38 -0
  206. package/ui/FlashView.qml +397 -0
  207. package/ui/FleaMark.qml +88 -0
  208. package/ui/FlyoutFade.qml +23 -0
  209. package/ui/FontPane.qml +170 -0
  210. package/ui/Glass.qml +114 -0
  211. package/ui/GlassTagger.qml +59 -0
  212. package/ui/Glyph.qml +64 -0
  213. package/ui/GlyphSolid.qml +22 -0
  214. package/ui/GridArea.qml +664 -0
  215. package/ui/GridTile.qml +325 -0
  216. package/ui/GvfsBridge.qml +164 -0
  217. package/ui/Header.qml +433 -0
  218. package/ui/HoverTimers.qml +39 -0
  219. package/ui/HtmlClip.qml +30 -0
  220. package/ui/HtmlPane.qml +63 -0
  221. package/ui/HyprlandMark.qml +45 -0
  222. package/ui/Ipc.qml +1005 -0
  223. package/ui/IpcControls.qml +7 -0
  224. package/ui/IpcMenuState.qml +48 -0
  225. package/ui/IpcPreviewState.qml +185 -0
  226. package/ui/JumpPath.qml +51 -0
  227. package/ui/KeymapSheet.qml +561 -0
  228. package/ui/KeymapSheetResult.qml +89 -0
  229. package/ui/KindStack.qml +40 -0
  230. package/ui/LangMark.qml +51 -0
  231. package/ui/LangMarkLoader.qml +26 -0
  232. package/ui/List.qml +480 -0
  233. package/ui/LoadingState.qml +54 -0
  234. package/ui/LocalSend.qml +56 -0
  235. package/ui/LocalSendMark.qml +73 -0
  236. package/ui/MarkdownBlockView.qml +311 -0
  237. package/ui/MarkdownBlocks.qml +35 -0
  238. package/ui/MarkdownFigure.qml +254 -0
  239. package/ui/MarkdownImages.qml +72 -0
  240. package/ui/MarkdownList.qml +81 -0
  241. package/ui/MarkdownMathsText.qml +97 -0
  242. package/ui/MarkdownPane.qml +83 -0
  243. package/ui/MarkdownPictures.qml +41 -0
  244. package/ui/MarkdownQuote.qml +52 -0
  245. package/ui/MarkdownRemote.qml +82 -0
  246. package/ui/MarkdownSource.qml +155 -0
  247. package/ui/MarkdownTable.qml +222 -0
  248. package/ui/MarkdownTableScroll.qml +64 -0
  249. package/ui/MarkdownTableWheel.qml +126 -0
  250. package/ui/MarkdownText.qml +87 -0
  251. package/ui/MarkdownWorker.js +132 -0
  252. package/ui/MatchText.qml +99 -0
  253. package/ui/MediaSession.qml +38 -0
  254. package/ui/MediaSound.qml +26 -0
  255. package/ui/MediaStrip.qml +191 -0
  256. package/ui/MediaTrack.qml +36 -0
  257. package/ui/MediaVolume.qml +54 -0
  258. package/ui/MenuActionDialog.qml +396 -0
  259. package/ui/MenuEdgeFade.qml +24 -0
  260. package/ui/MenuHost.qml +227 -0
  261. package/ui/MenuRow.qml +351 -0
  262. package/ui/MountListing.qml +100 -0
  263. package/ui/NetworkDialog.qml +435 -0
  264. package/ui/NetworkForm.qml +426 -0
  265. package/ui/NetworkMounts.qml +913 -0
  266. package/ui/NetworkPlaces.qml +127 -0
  267. package/ui/OpenWithDialog.qml +613 -0
  268. package/ui/Opener.qml +115 -0
  269. package/ui/Pane.qml +1296 -0
  270. package/ui/PaneMenuActions.qml +527 -0
  271. package/ui/PanePath.qml +90 -0
  272. package/ui/PaneRail.qml +239 -0
  273. package/ui/PaneStates.qml +88 -0
  274. package/ui/PaneSwap.qml +339 -0
  275. package/ui/PaneWire.qml +605 -0
  276. package/ui/ParkedWait.qml +9 -0
  277. package/ui/PathEntry.qml +186 -0
  278. package/ui/PathJump.qml +400 -0
  279. package/ui/PdfViewer.qml +297 -0
  280. package/ui/PermissionsDialog.qml +658 -0
  281. package/ui/PermissionsHeadings.qml +31 -0
  282. package/ui/PhoneMounts.qml +67 -0
  283. package/ui/PickerAnswers.qml +75 -0
  284. package/ui/PickerBox.qml +26 -0
  285. package/ui/PickerChrome.qml +295 -0
  286. package/ui/PickerFilter.qml +97 -0
  287. package/ui/PickerGrid.qml +308 -0
  288. package/ui/PickerHeader.qml +28 -0
  289. package/ui/PickerLifecycle.qml +27 -0
  290. package/ui/PickerList.qml +218 -0
  291. package/ui/PickerListing.qml +149 -0
  292. package/ui/PickerPathBar.qml +127 -0
  293. package/ui/PickerPlaces.qml +189 -0
  294. package/ui/PickerRecent.qml +92 -0
  295. package/ui/PickerSave.qml +101 -0
  296. package/ui/PickerSelection.qml +155 -0
  297. package/ui/PickerWindow.qml +846 -0
  298. package/ui/PowerSectorsReader.qml +10 -0
  299. package/ui/PrebuildDriver.qml +146 -0
  300. package/ui/Preview.qml +1004 -0
  301. package/ui/PreviewAnimation.qml +24 -0
  302. package/ui/PreviewArchive.qml +66 -0
  303. package/ui/PreviewColumn.qml +551 -0
  304. package/ui/PreviewEdge.qml +63 -0
  305. package/ui/PreviewFacts.qml +30 -0
  306. package/ui/PreviewFactsLine.qml +34 -0
  307. package/ui/PreviewHtml.qml +233 -0
  308. package/ui/PreviewImage.qml +253 -0
  309. package/ui/PreviewImagePan.qml +13 -0
  310. package/ui/PreviewImageSharp.qml +32 -0
  311. package/ui/PreviewLines.qml +106 -0
  312. package/ui/PreviewMarkdown.qml +706 -0
  313. package/ui/PreviewMedia.qml +250 -0
  314. package/ui/PreviewPdf.qml +482 -0
  315. package/ui/PreviewScroll.qml +201 -0
  316. package/ui/PreviewSwap.qml +335 -0
  317. package/ui/PreviewText.qml +149 -0
  318. package/ui/ProtocolChip.qml +18 -0
  319. package/ui/QuickLookArchive.qml +38 -0
  320. package/ui/QuickLookBar.qml +103 -0
  321. package/ui/QuickLookPrepare.qml +355 -0
  322. package/ui/QuickLookSwap.qml +10 -0
  323. package/ui/QuickLookWarm.qml +65 -0
  324. package/ui/RailDrop.qml +155 -0
  325. package/ui/RailHeading.qml +19 -0
  326. package/ui/RailPlaces.qml +38 -0
  327. package/ui/RailTrashRow.qml +67 -0
  328. package/ui/RenameField.qml +353 -0
  329. package/ui/RendererRetry.qml +11 -0
  330. package/ui/ResizeHandle.qml +52 -0
  331. package/ui/Reveal.qml +54 -0
  332. package/ui/Row.qml +476 -0
  333. package/ui/RowDrag.qml +213 -0
  334. package/ui/RowDropTarget.qml +27 -0
  335. package/ui/RunCard.qml +286 -0
  336. package/ui/Scripts.qml +65 -0
  337. package/ui/SearchStrip.qml +232 -0
  338. package/ui/SelectionBand.qml +140 -0
  339. package/ui/SelectionPreview.qml +370 -0
  340. package/ui/SettingsFavourite.qml +122 -0
  341. package/ui/SettingsGroup.qml +96 -0
  342. package/ui/SettingsHint.qml +12 -0
  343. package/ui/SettingsMetrics.qml +29 -0
  344. package/ui/SettingsPane.qml +178 -0
  345. package/ui/SettingsPanel.qml +547 -0
  346. package/ui/SettingsRail.qml +128 -0
  347. package/ui/SettingsRow.qml +409 -0
  348. package/ui/SettingsRuler.qml +85 -0
  349. package/ui/SettingsSegment.qml +85 -0
  350. package/ui/ShareBrowser.qml +113 -0
  351. package/ui/ShareLink.qml +67 -0
  352. package/ui/ShelfPins.qml +101 -0
  353. package/ui/Sidebar.qml +732 -0
  354. package/ui/SidebarRow.qml +358 -0
  355. package/ui/Spinner.qml +60 -0
  356. package/ui/StableModel.qml +89 -0
  357. package/ui/StateMessage.qml +84 -0
  358. package/ui/StatusBar.qml +512 -0
  359. package/ui/StatusFileName.qml +27 -0
  360. package/ui/TabBar.qml +673 -0
  361. package/ui/TabDragGeometry.qml +60 -0
  362. package/ui/TagMenuMark.qml +52 -0
  363. package/ui/TagRailSection.qml +53 -0
  364. package/ui/TagSlot.qml +40 -0
  365. package/ui/TagState.qml +150 -0
  366. package/ui/TagSwatch.qml +28 -0
  367. package/ui/Taildrop.qml +86 -0
  368. package/ui/TailscaleMark.qml +50 -0
  369. package/ui/Theme.qml +460 -0
  370. package/ui/ThemeColors.qml +43 -0
  371. package/ui/TransferCard.qml +239 -0
  372. package/ui/TrashConfirm.qml +129 -0
  373. package/ui/TrashDropHandler.qml +11 -0
  374. package/ui/TrashHost.qml +202 -0
  375. package/ui/TrashMonitor.qml +30 -0
  376. package/ui/TrashToolbar.qml +19 -0
  377. package/ui/TrashView.qml +492 -0
  378. package/ui/UnlockDialog.qml +450 -0
  379. package/ui/UnlockHost.qml +206 -0
  380. package/ui/UpdateCheck.qml +97 -0
  381. package/ui/UserDirs.qml +25 -0
  382. package/ui/ViewState.qml +565 -0
  383. package/ui/ViewportScrollBar.qml +195 -0
  384. package/ui/ViewportScrollBars.qml +25 -0
  385. package/ui/WebPreview.qml +33 -0
  386. package/ui/WebPreviewView.qml +26 -0
  387. package/ui/WindowBody.qml +827 -0
  388. package/ui/WindowDrag.qml +35 -0
  389. package/ui/boot/Commons/AudioNodes.qml +50 -0
  390. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  391. package/ui/boot/Commons/Border.qml +243 -0
  392. package/ui/boot/Commons/BorderGeometry.js +373 -0
  393. package/ui/boot/Commons/Color.qml +258 -0
  394. package/ui/boot/Commons/I18n.qml +118 -0
  395. package/ui/boot/Commons/InputDevices.qml +112 -0
  396. package/ui/boot/Commons/InputMethodState.qml +38 -0
  397. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  398. package/ui/boot/Commons/Numbers.js +19 -0
  399. package/ui/boot/Commons/Plural.js +91 -0
  400. package/ui/boot/Commons/ShellIpc.qml +12 -0
  401. package/ui/boot/Commons/Style.qml +561 -0
  402. package/ui/boot/Commons/Translations.qml +100 -0
  403. package/ui/boot/Commons/UntypedInput.qml +152 -0
  404. package/ui/boot/Commons/Util.qml +188 -0
  405. package/ui/boot/Commons/qmldir +13 -0
  406. package/ui/boot/LICENSE-omarchy +20 -0
  407. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  408. package/ui/boot/Ui/BarIconButton.qml +106 -0
  409. package/ui/boot/Ui/BarIconContent.qml +38 -0
  410. package/ui/boot/Ui/BarIndicator.qml +53 -0
  411. package/ui/boot/Ui/BarWidget.qml +50 -0
  412. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  413. package/ui/boot/Ui/BorderSurface.qml +40 -0
  414. package/ui/boot/Ui/Button.qml +224 -0
  415. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  416. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  417. package/ui/boot/Ui/CursorSurface.qml +42 -0
  418. package/ui/boot/Ui/Dropdown.qml +245 -0
  419. package/ui/boot/Ui/FilterInput.qml +26 -0
  420. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  421. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  422. package/ui/boot/Ui/MultiSelect.qml +624 -0
  423. package/ui/boot/Ui/NumberField.qml +86 -0
  424. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  425. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  426. package/ui/boot/Ui/Panel.qml +60 -0
  427. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  428. package/ui/boot/Ui/PanelController.qml +16 -0
  429. package/ui/boot/Ui/PanelHero.qml +112 -0
  430. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  431. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  432. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  433. package/ui/boot/Ui/PanelSlider.qml +156 -0
  434. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  435. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  436. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  437. package/ui/boot/Ui/PopupCard.qml +181 -0
  438. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  439. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  440. package/ui/boot/Ui/SizedImage.qml +125 -0
  441. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  442. package/ui/boot/Ui/TextField.qml +58 -0
  443. package/ui/boot/Ui/Toggle.qml +118 -0
  444. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  445. package/ui/boot/Ui/WidgetButton.qml +172 -0
  446. package/ui/boot/Ui/qmldir +41 -0
  447. package/ui/boot/fleatab.qml +50 -0
  448. package/ui/boot/picker.qml +39 -0
  449. package/ui/boot/shell.qml +122 -0
  450. package/ui/boot/tabtearoff.qml +51 -0
  451. package/ui/js/Anchor.js +379 -0
  452. package/ui/js/AnchorHold.js +162 -0
  453. package/ui/js/Archive.js +157 -0
  454. package/ui/js/ArchiveCard.js +44 -0
  455. package/ui/js/ArchiveOps.js +44 -0
  456. package/ui/js/BackReturn.js +118 -0
  457. package/ui/js/BulkRename.js +100 -0
  458. package/ui/js/Buttons.js +54 -0
  459. package/ui/js/ChromeKeys.js +116 -0
  460. package/ui/js/ClipMarks.js +73 -0
  461. package/ui/js/ClipSelection.js +67 -0
  462. package/ui/js/Clipboard.js +179 -0
  463. package/ui/js/Cloud.js +97 -0
  464. package/ui/js/CodeRuns.js +72 -0
  465. package/ui/js/Collide.js +212 -0
  466. package/ui/js/ColumnFit.js +28 -0
  467. package/ui/js/ColumnMenu.js +111 -0
  468. package/ui/js/Columns.js +298 -0
  469. package/ui/js/ColumnsCarry.js +103 -0
  470. package/ui/js/Compare.js +74 -0
  471. package/ui/js/Contrast.js +157 -0
  472. package/ui/js/ControlReader.js +42 -0
  473. package/ui/js/Convert.js +52 -0
  474. package/ui/js/CopyAs.js +52 -0
  475. package/ui/js/Crumbs.js +178 -0
  476. package/ui/js/Csv.js +231 -0
  477. package/ui/js/Density.js +44 -0
  478. package/ui/js/Devices.js +440 -0
  479. package/ui/js/DirSizes.js +180 -0
  480. package/ui/js/Drag.js +364 -0
  481. package/ui/js/DragOut.js +166 -0
  482. package/ui/js/DropDev.js +52 -0
  483. package/ui/js/Dropbox.js +41 -0
  484. package/ui/js/Eject.js +217 -0
  485. package/ui/js/Errors.js +188 -0
  486. package/ui/js/ExtThumbs.js +115 -0
  487. package/ui/js/Facts.js +402 -0
  488. package/ui/js/FigureDate.js +14 -0
  489. package/ui/js/Filter.js +285 -0
  490. package/ui/js/Find.js +46 -0
  491. package/ui/js/Flash.js +184 -0
  492. package/ui/js/FlashCard.js +79 -0
  493. package/ui/js/FlashEndings.js +125 -0
  494. package/ui/js/FlashPane.js +116 -0
  495. package/ui/js/FleaBin.js +12 -0
  496. package/ui/js/Focus.js +502 -0
  497. package/ui/js/FolderSorts.js +108 -0
  498. package/ui/js/FolderViews.js +44 -0
  499. package/ui/js/Format.js +305 -0
  500. package/ui/js/Fuzzy.js +141 -0
  501. package/ui/js/GlassTag.js +72 -0
  502. package/ui/js/Grid.js +33 -0
  503. package/ui/js/GridGeometry.js +90 -0
  504. package/ui/js/GridNames.js +153 -0
  505. package/ui/js/GvfsBridge.js +200 -0
  506. package/ui/js/HostClipboard.js +29 -0
  507. package/ui/js/Icons.js +218 -0
  508. package/ui/js/Input.js +50 -0
  509. package/ui/js/Jump.js +200 -0
  510. package/ui/js/KeyName.js +64 -0
  511. package/ui/js/Keymap.js +401 -0
  512. package/ui/js/Kinds.js +164 -0
  513. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  514. package/ui/js/LangMarkPaths.js +47 -0
  515. package/ui/js/LangMarks.js +120 -0
  516. package/ui/js/Lists.js +11 -0
  517. package/ui/js/LocalSend.js +39 -0
  518. package/ui/js/LockedMenu.js +63 -0
  519. package/ui/js/MakeDefault.js +186 -0
  520. package/ui/js/MarkBox.js +7 -0
  521. package/ui/js/Markdown.js +119 -0
  522. package/ui/js/MarkdownLists.js +86 -0
  523. package/ui/js/MarkdownMaths.js +58 -0
  524. package/ui/js/MarkdownPictures.js +32 -0
  525. package/ui/js/MarkdownPrepared.js +139 -0
  526. package/ui/js/MarkdownTableFit.js +34 -0
  527. package/ui/js/Marks.js +148 -0
  528. package/ui/js/Marquee.js +96 -0
  529. package/ui/js/Match.js +93 -0
  530. package/ui/js/Material.js +109 -0
  531. package/ui/js/MdBlocks.js +437 -0
  532. package/ui/js/MdBreak.js +71 -0
  533. package/ui/js/MdChunks.js +70 -0
  534. package/ui/js/MdContainer.js +137 -0
  535. package/ui/js/MdDocument.js +337 -0
  536. package/ui/js/MdEmph.js +97 -0
  537. package/ui/js/MdEntity.js +108 -0
  538. package/ui/js/MdEntityTable.js +223 -0
  539. package/ui/js/MdEscape.js +59 -0
  540. package/ui/js/MdFront.js +37 -0
  541. package/ui/js/MdHold.js +32 -0
  542. package/ui/js/MdHtml.js +307 -0
  543. package/ui/js/MdHtmlBlock.js +290 -0
  544. package/ui/js/MdHtmlHeading.js +53 -0
  545. package/ui/js/MdHtmlImage.js +179 -0
  546. package/ui/js/MdInline.js +154 -0
  547. package/ui/js/MdItems.js +212 -0
  548. package/ui/js/MdLeaf.js +309 -0
  549. package/ui/js/MdLink.js +196 -0
  550. package/ui/js/MdMath.js +67 -0
  551. package/ui/js/MdParagraphs.js +138 -0
  552. package/ui/js/MdRefs.js +232 -0
  553. package/ui/js/MdResolve.js +226 -0
  554. package/ui/js/MdRun.js +300 -0
  555. package/ui/js/MdUrl.js +226 -0
  556. package/ui/js/Menu.js +514 -0
  557. package/ui/js/MenuContext.js +85 -0
  558. package/ui/js/MenuFit.js +13 -0
  559. package/ui/js/MenuRefresh.js +74 -0
  560. package/ui/js/MenuWheel.js +35 -0
  561. package/ui/js/Messages.js +186 -0
  562. package/ui/js/Motion.js +16 -0
  563. package/ui/js/Mounts.js +320 -0
  564. package/ui/js/MouseNav.js +13 -0
  565. package/ui/js/Nav.js +329 -0
  566. package/ui/js/NetFs.js +26 -0
  567. package/ui/js/OpenWith.js +83 -0
  568. package/ui/js/Ops.js +585 -0
  569. package/ui/js/Palette.js +33 -0
  570. package/ui/js/PathBar.js +260 -0
  571. package/ui/js/Permissions.js +179 -0
  572. package/ui/js/Phones.js +112 -0
  573. package/ui/js/Picker.js +353 -0
  574. package/ui/js/PlaceMenu.js +96 -0
  575. package/ui/js/Places.js +265 -0
  576. package/ui/js/PreviewAsk.js +21 -0
  577. package/ui/js/PreviewKeys.js +155 -0
  578. package/ui/js/PreviewSettle.js +29 -0
  579. package/ui/js/PreviewSwap.js +101 -0
  580. package/ui/js/PropertyFacts.js +144 -0
  581. package/ui/js/Protocols.js +296 -0
  582. package/ui/js/QuickLookKinds.js +27 -0
  583. package/ui/js/QuickLookRect.js +24 -0
  584. package/ui/js/RailDrop.js +51 -0
  585. package/ui/js/RailKeys.js +87 -0
  586. package/ui/js/RailMenu.js +133 -0
  587. package/ui/js/Recent.js +135 -0
  588. package/ui/js/RecentMode.js +156 -0
  589. package/ui/js/Reload.js +60 -0
  590. package/ui/js/Renderer.js +9 -0
  591. package/ui/js/RunFile.js +143 -0
  592. package/ui/js/Scripts.js +28 -0
  593. package/ui/js/Scroll.js +304 -0
  594. package/ui/js/ScrollOff.js +69 -0
  595. package/ui/js/Search.js +478 -0
  596. package/ui/js/Selection.js +97 -0
  597. package/ui/js/Settings.js +476 -0
  598. package/ui/js/SettingsAbout.js +97 -0
  599. package/ui/js/SettingsShelf.js +52 -0
  600. package/ui/js/SheetKeys.js +42 -0
  601. package/ui/js/SheetQuery.js +281 -0
  602. package/ui/js/ShelfPile.js +27 -0
  603. package/ui/js/SlowClick.js +53 -0
  604. package/ui/js/SlowOp.js +6 -0
  605. package/ui/js/Sort.js +134 -0
  606. package/ui/js/Startup.js +63 -0
  607. package/ui/js/StateWords.js +17 -0
  608. package/ui/js/Status.js +238 -0
  609. package/ui/js/Swap.js +166 -0
  610. package/ui/js/TabMove.js +39 -0
  611. package/ui/js/Tabs.js +736 -0
  612. package/ui/js/Tags.js +134 -0
  613. package/ui/js/Taildrop.js +128 -0
  614. package/ui/js/Tap.js +174 -0
  615. package/ui/js/TextSize.js +107 -0
  616. package/ui/js/Thumbs.js +158 -0
  617. package/ui/js/Transfer.js +172 -0
  618. package/ui/js/Trash.js +81 -0
  619. package/ui/js/TrashDates.js +47 -0
  620. package/ui/js/TrashDrop.js +64 -0
  621. package/ui/js/UiState.js +438 -0
  622. package/ui/js/Unlock.js +152 -0
  623. package/ui/js/Update.js +130 -0
  624. package/ui/js/UserDirs.js +56 -0
  625. package/ui/qmldir +228 -0
@@ -0,0 +1,540 @@
1
+ import QtQuick
2
+ import Quickshell.Io
3
+ import "js/Devices.js" as Devices
4
+ import "js/Format.js" as Format
5
+ import "js/Mounts.js" as Mounts
6
+ import "js/Eject.js" as Eject
7
+
8
+ // The DEVICES group's own Service, the shape ui/NetworkMounts.qml has for NETWORK: the only thing
9
+ // here that runs a process, while ui/Sidebar.qml reads "entries" and renders it. Enumeration is
10
+ // lsblk, which any user may run; acting on a volume is gio, the tool the shares already drive.
11
+ Item {
12
+ id: root
13
+
14
+ property var entries: []
15
+ // RailAdditions rule 1's switch, on by default from 0.3.3; off answers 0.2.1's rows, and the state document is read elsewhere.
16
+ property bool showUnmounted: true
17
+
18
+ signal opened(string path)
19
+ signal message(string text, bool isError)
20
+ // A failed mount, unmount or eject: the headline plus gio's own reason for the status bar's detail line.
21
+ signal operationResult(string headline, string detail, bool isError)
22
+ // An eject is about to release this mountpoint: readers on it stop first (#232).
23
+ signal quiesce(string path)
24
+ signal unlockRequested(string device, string label, double size, string fstype)
25
+ // The verdict this surface last posted, so a newer one replaces it and nothing else.
26
+ signal forgetMessage(string text)
27
+ property string _lastVerdict: ""
28
+
29
+ // lsblk costs 5 ms on this box where gio mount -l costs 513 ms, so the rail's own five second
30
+ // rhythm carries this too rather than earning a slower clock of its own.
31
+ readonly property int pollMs: 5000
32
+ // A listing is bounded the same way a mount is below, and for the same reason ui/NetworkMounts.qml
33
+ // bounds its own: an lsblk that never answers left poll() refusing forever and froze the rail.
34
+ readonly property int listTimeoutMs: 10000
35
+ // A mount that never grows a mountpoint has to stop waiting, or the row waits on the poll forever.
36
+ readonly property int mountTimeoutMs: 15000
37
+ // How long an eject waits for a listing it can judge after gio exits, before the user is told
38
+ // that nothing was confirmed: three polls, the same bound the mount above gets.
39
+ readonly property int ejectVerdictMs: 15000
40
+ // How long a power-off chain may hold the eject guard: a hung unmount leg ends here instead.
41
+ readonly property int powerOffWaitMs: 15000
42
+
43
+ property string _listing: ""
44
+ // A Process's own onExited can race its StdioCollector's text property, the same guard every
45
+ // stdout-reading process in ui/NetworkMounts.qml already carries.
46
+ property string _listOutput: ""
47
+ // The device a mount was asked for; rebuild() opens it the moment lsblk reports its mountpoint.
48
+ property string _pendingOpenDevice: ""
49
+ property string _pendingOpenLabel: ""
50
+ // The volume an unmount was asked for, so its refusal can name it.
51
+ property string _unmountLabel: ""
52
+ // The last written-sector count the chain saw, so a flushing leg restarts the deadline.
53
+ property string _powerSectors: ""
54
+ // A power-off in flight: the disk to stop and the nodes still to unmount.
55
+ property string _powerOffDisk: ""
56
+ property var _powerOffQueue: []
57
+ // gio's own stderr per action, so a refusal names its cause instead of a guess.
58
+ property string _mountErr: ""
59
+ property string _unmountErr: ""
60
+ property string _ejectErr: ""
61
+ property string _unmountDevice: ""
62
+ // The device whose eject awaits its verdict, "" when none. The listing taken after gio exits is
63
+ // the only witness: gio's own exit code has been 0 over a volume that was still mounted.
64
+ property string _ejectDevice: ""
65
+ property string _ejectLabel: ""
66
+ property bool _ejectLoop: false
67
+ // A loop detach failed; the next listing judges whether the unmount succeeded.
68
+ property bool _ejectDetachFailed: false
69
+ // Listings are counted as they start. _verdictFromListing is 0 while gio runs, so nothing is
70
+ // judged before it exits; at exit it becomes one past the count, so the listing in flight at
71
+ // that moment, taken before the eject finished, is never the witness.
72
+ property int _listingsStarted: 0
73
+ property int _verdictFromListing: 0
74
+ // poll() refuses to start a listing until the previous one's stream has finished, so the body a
75
+ // verdict is read from and the count it is gated on always belong to the same run.
76
+ property bool _streamPending: false
77
+ // Cleared only when the next listing starts, which onExited's own release of _streamPending
78
+ // guarantees cannot happen until the ended listing is fully done with.
79
+ property bool listTimedOut: false
80
+ // Set when the chain deadline ends that leg, so its own onExited is swallowed once.
81
+ property bool _unmountEndedLate: false
82
+ // The -t leg's own ended-late flag, never set by an unmount the deadline ends.
83
+ property bool _ejectEndedLate: false
84
+ // The rail's DEVICES gate: true once the first listing answered, timed out or not, without replacing anything.
85
+ property bool firstAnswered: false
86
+ // Set on the unmount's first turn, since a binding on Process.running would still say idle when PaneRail decides whether to unload this host.
87
+ property bool _unmounting: false
88
+ // True while a release is in flight: an eject awaiting its verdict, which a power-off chain holds too, or a user unmount.
89
+ readonly property bool releasing: root._ejectDevice.length > 0 || root._unmounting
90
+
91
+ // The internal disk row reads the hostname alone (decided 2026-09-08; the canvas drew "<host> · <kernel name>"), and /etc/hostname is the
92
+ // one source for that host name that costs no process.
93
+ FileView {
94
+ id: hostnameFile
95
+ path: "/etc/hostname"
96
+ blockLoading: true
97
+ printErrors: false
98
+ onLoaded: root.rebuild()
99
+ onLoadFailed: root.rebuild()
100
+ }
101
+
102
+ // The write counter is built by the first power-off and retained for later chains.
103
+ Loader {
104
+ id: powerSectorsFile
105
+ active: false
106
+ source: "PowerSectorsReader.qml"
107
+ onLoaded: item.owner = root
108
+ }
109
+ on_PowerOffDiskChanged: if (_powerOffDisk.length > 0) powerSectorsFile.active = true
110
+
111
+ // False parks the poll once the first listing answered; a mount or a release in flight reads the next listing, so it keeps polling.
112
+ property bool polling: true
113
+ Timer {
114
+ interval: root.pollMs
115
+ running: root.polling || !root.firstAnswered || root.releasing || root._pendingOpenDevice.length > 0 || root._powerOffDisk.length > 0
116
+ repeat: true
117
+ triggeredOnStart: true
118
+ onTriggered: root.poll()
119
+ }
120
+
121
+ function poll() {
122
+ if (listProcess.running || root._streamPending)
123
+ return
124
+ root.listTimedOut = false
125
+ root._streamPending = true
126
+ root._listingsStarted += 1
127
+ listProcess.running = true
128
+ listTimeout.restart()
129
+ if (root._powerOffDisk.length > 0)
130
+ powerSectorsFile.item.reload()
131
+ }
132
+
133
+ Timer {
134
+ id: listTimeout
135
+ interval: root.listTimeoutMs
136
+ repeat: false
137
+ onTriggered: {
138
+ if (!listProcess.running)
139
+ return
140
+ // Ending it is what lets the next poll run at all; a listing nobody can end froze the rail.
141
+ root.listTimedOut = true
142
+ listProcess.running = false
143
+ }
144
+ }
145
+
146
+ // Decided 2026-09-08: the machine's row is its hostname alone; the kernel name stays the fallback for a box with no hostname, and r.device still names the disk for the actions.
147
+ function hostLabel(fallback) {
148
+ var host = String(hostnameFile.text() || "").trim()
149
+ return host.length > 0 ? host : fallback
150
+ }
151
+
152
+ function rebuild() {
153
+ var rows = Devices.parseDevices(root._listing, root.showUnmounted)
154
+ var out = []
155
+ for (var i = 0; i < rows.length; i++) {
156
+ var r = rows[i]
157
+ var label = r.kind === "disk" ? root.hostLabel(r.label) : r.label
158
+ out.push({ path: r.path, label: label, group: "device", kind: r.kind,
159
+ device: r.device, mounted: r.mounted, removable: r.removable,
160
+ mediaRemovable: r.mediaRemovable === true, readOnly: r.readOnly === true, size: r.size,
161
+ glyph: r.locked === true ? "lock" : "drive", locked: r.locked === true,
162
+ fstype: r.fstype || "", fsver: r.fsver || "", loop: !!r.loop })
163
+ }
164
+ // Same rule as ui/NetworkMounts.qml's: an unchanged poll assigns nothing, see Mounts.sameEntries.
165
+ if (!Mounts.sameEntries(root.entries, out))
166
+ root.entries = out
167
+ root.openWhenMounted()
168
+ }
169
+
170
+ // gio returns before udisks has published the mountpoint, so the poll after it is what
171
+ // resolves the path; this is the one place a just-mounted volume is opened.
172
+ function openWhenMounted() {
173
+ if (root._pendingOpenDevice.length === 0)
174
+ return
175
+ for (var i = 0; i < root.entries.length; i++) {
176
+ var e = root.entries[i]
177
+ if (e.device === root._pendingOpenDevice && e.mounted) {
178
+ root._pendingOpenDevice = ""
179
+ mountTimeout.stop()
180
+ root.opened(e.path)
181
+ return
182
+ }
183
+ }
184
+ }
185
+
186
+ // The disk row and a mounted volume open directly; an unmounted volume mounts first, then
187
+ // opens, because a user who picks a disk means to look inside it.
188
+ function activate(index) {
189
+ var e = root.entries[index]
190
+ if (!e)
191
+ return
192
+ if (e.locked === true) {
193
+ root.unlockRequested(e.device, e.label, Number(e.size) || 0, String(e.fstype || ""))
194
+ return
195
+ }
196
+ if (e.mounted) {
197
+ root.opened(e.path)
198
+ return
199
+ }
200
+ root.mountVolume(e)
201
+ }
202
+
203
+ function reportFailure(headline, stderr) { root.operationResult(headline, Devices.mountDetail(stderr), true) }
204
+
205
+ function mountVolume(e) {
206
+ if (mountProcess.running) {
207
+ root.message(Mounts.WORDS.anotherMounting, false)
208
+ return
209
+ }
210
+ root._pendingOpenDevice = e.device
211
+ root._pendingOpenLabel = e.label
212
+ root._mountErr = ""
213
+ mountProcess.command = ["gio", "mount", "-d", e.device]
214
+ mountProcess.running = true
215
+ }
216
+
217
+ // RailAdditions rule 2's Unmount, which is not Eject: a fixed disk stays where it is and only
218
+ // its filesystem goes away. gio is handed the mount point, the way eject below is, and the poll
219
+ // that follows is what redraws the row as unmounted.
220
+ function unmount(index) {
221
+ // A chain owns unmountProcess for its legs, so a user unmount waits for the eject.
222
+ if (root._powerOffDisk.length > 0) {
223
+ root.message(Format.fill(Mounts.WORDS.stillEjecting, root._ejectLabel), false)
224
+ return
225
+ }
226
+ var e = root.entries[index]
227
+ if (!e || e.kind !== "volume" || !e.mounted)
228
+ return
229
+ if (unmountProcess.running) {
230
+ root.message(Format.fill(Mounts.WORDS.stillUnmounting, root._unmountLabel), false)
231
+ return
232
+ }
233
+ root.quiesce(e.path)
234
+ root._unmountLabel = e.label
235
+ root._unmountDevice = e.device
236
+ root._unmountErr = ""
237
+ root._unmounting = true
238
+ unmountProcess.command = ["gio", "mount", "-u", e.path]
239
+ unmountProcess.running = true
240
+ }
241
+
242
+ // One eject in flight at a time; the verdict timeout below ends the wait it starts.
243
+ function armEject(e) {
244
+ root._ejectDevice = e.device
245
+ root._ejectLabel = e.label
246
+ root._ejectLoop = e.loop === true
247
+ root._ejectDetachFailed = false
248
+ root._verdictFromListing = 0
249
+ }
250
+
251
+ // Locks the container behind an unmounted or ejected crypt, from the listing an eject was judged on when it has one.
252
+ property var deviceLock: null
253
+ function lockCrypt(device, label, listing) {
254
+ if (deviceLock === null)
255
+ deviceLock = Qt.createComponent("DeviceLock.qml").createObject(root, { host: root })
256
+ return deviceLock.lockCryptAfter(listing !== undefined ? listing : root._listing, device, label)
257
+ }
258
+
259
+ // Eject by mount point, never -f (an open write loses data) or -e -d (gio-tool-mount.c:1264 mounts); a loop row takes -u, as -e never returns without a Drive.
260
+ function eject(index) {
261
+ var e = root.entries[index]
262
+ if (!e || e.kind !== "volume" || !e.mounted)
263
+ return
264
+ if (ejectProcess.running || root._ejectDevice.length > 0) {
265
+ root.message(Format.fill(Mounts.WORDS.stillEjecting, root._ejectLabel), false)
266
+ return
267
+ }
268
+ // A non-removable USB disk is powered off, since an eject lets udiskie remount it; a loop row has no drive, so it takes -u below.
269
+ var disk = Devices.powerOffDisk(e.device)
270
+ if (!e.loop && disk.length > 0 && e.mediaRemovable !== true) {
271
+ root.powerOff(e, disk)
272
+ return
273
+ }
274
+ root.armEject(e)
275
+ root.quiesce(e.path)
276
+ root._ejectErr = ""
277
+ ejectProcess.command = Devices.ejectCommand(e)
278
+ ejectProcess.running = true
279
+ // Replaces the arm prompt, and a stick mid-flush can take a while to come unmounted.
280
+ root.message(Format.fill(Mounts.WORDS.ejecting, e.label), false)
281
+ }
282
+
283
+ // Unmount every volume, then stop the drive, so nothing is left for udiskie to remount.
284
+ function powerOff(e, disk) {
285
+ // A user unmount already in flight owns unmountProcess; adopting it would skip the queue.
286
+ if (unmountProcess.running) {
287
+ root.message(Format.fill(Mounts.WORDS.stillUnmountingTryAgain, e.label), false)
288
+ return
289
+ }
290
+ root.quiesce(e.path)
291
+ root.armEject(e)
292
+ root._powerOffDisk = disk
293
+ root._powerOffQueue = Devices.powerOffQueue(root._listing, disk, root.entries)
294
+ root._powerSectors = ""
295
+ root.message(Format.fill(Mounts.WORDS.ejecting, e.label), false)
296
+ root.powerOffNext()
297
+ }
298
+
299
+ function powerOffNext() {
300
+ // Every leg restarts the deadline, so 15 s bounds a stalled leg, not a flushing chain.
301
+ powerOffTimeout.restart()
302
+ if (root._powerOffQueue.length === 0) {
303
+ root._ejectErr = ""
304
+ ejectProcess.command = ["gio", "mount", "-t", root._powerOffDisk]
305
+ ejectProcess.running = true
306
+ return
307
+ }
308
+ var leg = root._powerOffQueue[0]
309
+ root._powerOffQueue = root._powerOffQueue.slice(1)
310
+ root._unmountErr = ""
311
+ unmountProcess.command = ["gio", "mount", "-u", leg.path]
312
+ unmountProcess.running = true
313
+ }
314
+
315
+ // A grown write counter is progress, so the deadline restarts instead of ending the chain.
316
+ function notePowerSectors(body) {
317
+ if (root._powerOffDisk.length === 0)
318
+ return
319
+ var sectors = Devices.writtenSectors(body)
320
+ if (sectors.length === 0)
321
+ return
322
+ if (root._powerSectors.length > 0 && sectors !== root._powerSectors)
323
+ powerOffTimeout.restart()
324
+ root._powerSectors = sectors
325
+ }
326
+
327
+ // The chain's guard state in one line, so a failed eject names its guard through ipc.
328
+ function ejectState() {
329
+ return [root._ejectDevice, root._ejectLabel, root._powerOffDisk, root._powerOffQueue.length, root._lastVerdict, unmountProcess.running, ejectProcess.running].join("|")
330
+ }
331
+
332
+ // Nothing is judged while gio still runs, and only a listing started after it exited counts.
333
+ // A listing that cannot be judged settles nothing, so the next poll tries again, until
334
+ // ejectVerdictTimeout ends the wait.
335
+ function judgeEject(body) {
336
+ if (root._ejectDevice.length === 0 || root._verdictFromListing === 0 || root._listingsStarted < root._verdictFromListing)
337
+ return
338
+ var verdict = Eject.verdict(body, root._ejectDevice)
339
+ if (verdict !== "unknown")
340
+ root.reportEject(verdict, Eject.blockers(body, root._ejectDevice), body)
341
+ }
342
+
343
+ // "Safe to unplug" is a safety claim: only a real drive's "safe" verdict earns it.
344
+ function reportEject(verdict, others, body) {
345
+ if (root._ejectDevice.length === 0)
346
+ return
347
+ powerOffTimeout.stop()
348
+ ejectVerdictTimeout.stop()
349
+ // An unmounted loop row reads as safe to the listing; a failed detach still has to say so.
350
+ var s = Eject.sentence(verdict === "safe" && root._ejectDetachFailed ? "attached" : verdict, root._ejectLabel, others, root._ejectLoop)
351
+ if (!s.isError)
352
+ root.lockCrypt(root._ejectDevice, root._ejectLabel, body)
353
+ root._ejectDevice = ""
354
+ // The newest verdict about this device is the true one, so it replaces the last one rather
355
+ // than queueing behind it: a refusal is an error and stands until dismissed, and without
356
+ // this the operator ejected the stick and went on reading "still mounted".
357
+ root.forgetMessage(root._lastVerdict)
358
+ root._lastVerdict = s.text
359
+ root.message(s.text, s.isError)
360
+ }
361
+
362
+ Timer {
363
+ id: mountTimeout
364
+ interval: root.mountTimeoutMs
365
+ repeat: false
366
+ onTriggered: {
367
+ if (root._pendingOpenDevice.length === 0)
368
+ return
369
+ root._pendingOpenDevice = ""
370
+ root.message(Format.fill(Mounts.WORDS.noFolder, root._pendingOpenLabel), true)
371
+ }
372
+ }
373
+
374
+ Timer {
375
+ id: ejectVerdictTimeout
376
+ interval: root.ejectVerdictMs
377
+ repeat: false
378
+ onTriggered: root.reportEject("unknown", [])
379
+ }
380
+
381
+ // A hung chain leg ends here with the guard released and a sentence.
382
+ Timer {
383
+ id: powerOffTimeout
384
+ interval: root.powerOffWaitMs
385
+ repeat: false
386
+ onTriggered: {
387
+ if (root._powerOffDisk.length === 0 || (!unmountProcess.running && !ejectProcess.running))
388
+ return
389
+ // Only the leg the deadline ends is marked, so a user unmount is never swallowed.
390
+ if (unmountProcess.running) {
391
+ root._unmountEndedLate = true
392
+ unmountProcess.running = false
393
+ } else {
394
+ root._ejectEndedLate = true
395
+ ejectProcess.running = false
396
+ }
397
+ ejectVerdictTimeout.stop()
398
+ var label = root._ejectLabel
399
+ root._powerOffDisk = ""
400
+ root._powerOffQueue = []
401
+ root._ejectDevice = ""
402
+ // A verdict replaces the last one, so the next chain's safe sentence retires this error.
403
+ var text = Format.fill(Mounts.WORDS.ejectUnfinished, label)
404
+ root.forgetMessage(root._lastVerdict)
405
+ root._lastVerdict = text
406
+ root.message(text, true)
407
+ }
408
+ }
409
+
410
+ Process {
411
+ id: listProcess
412
+ // PATH because a device-mapper leaf is not "/dev/" plus its kernel name, and MOUNTPOINTS
413
+ // because one btrfs device carries several and the plain column shows whichever it likes,
414
+ // which hid / behind /home here and left the system disk unidentifiable.
415
+ // TRAN is the transport, asked for because RM alone misses a USB bridge: a WD My Passport
416
+ // reports rm=false with tran=usb, and a drive you can unplug has to offer Eject (PR 74).
417
+ // PARTTYPE, PTTYPE, PARTN and UUID are the hide rule's own columns (a firmware type, an installer ISO keep, a recovery label, the dedupe key); FSVER beside FSTYPE names LUKS1 or LUKS2; RO keeps Flash image off a write-protected card.
418
+ command: ["lsblk", "--bytes", "--json", "-o", "NAME,PATH,LABEL,MOUNTPOINTS,RM,RO,TRAN,SIZE,TYPE,MODEL,FSTYPE,FSVER,PARTTYPENAME,PARTTYPE,PTTYPE,PARTN,UUID"]
419
+ stdout: StdioCollector {
420
+ id: listOut
421
+ waitForEnd: true
422
+ // The verdict is read here, from this run's own text, so it can never come off the
423
+ // stale fallback onExited keeps for the rail.
424
+ onStreamFinished: {
425
+ if (root.listTimedOut)
426
+ return
427
+ root._streamPending = false
428
+ root._listOutput = text
429
+ root.judgeEject(text)
430
+ }
431
+ }
432
+ onExited: {
433
+ listTimeout.stop()
434
+ // A listing this timer ended collected nothing, and reading that as "no devices" would
435
+ // empty the rail, taking Eject with it exactly when a device is misbehaving. The stream
436
+ // guard is released here because a stream this timer cut off may never finish on its own.
437
+ if (root.listTimedOut) {
438
+ root._streamPending = false
439
+ } else {
440
+ root._listing = listOut.text || root._listOutput || ""
441
+ root.rebuild()
442
+ }
443
+ // Last, so anything waiting on the first answer reads the listing it answered with.
444
+ root.firstAnswered = true
445
+ }
446
+ }
447
+
448
+ Process {
449
+ id: mountProcess
450
+ objectName: "mountProcess"
451
+ stderr: StdioCollector { onStreamFinished: root._mountErr = this.text }
452
+ onExited: function (exitCode) {
453
+ // The wait starts here, not at launch: an admin polkit prompt longer than the
454
+ // wait is a slow prompt, not a mount that never reported a folder to open.
455
+ if (Devices.mountTimerStart(exitCode)) {
456
+ mountTimeout.restart()
457
+ root.poll()
458
+ return
459
+ }
460
+ var label = root._pendingOpenLabel
461
+ var sentence = Devices.mountError("mount", exitCode, root._mountErr, label)
462
+ // "" names the already-mounted refusal, so the poll below opens it like a success.
463
+ if (sentence.length === 0 && label.length > 0) {
464
+ mountTimeout.restart()
465
+ root.poll()
466
+ return
467
+ }
468
+ mountTimeout.stop()
469
+ root._pendingOpenDevice = ""
470
+ root.reportFailure(sentence.length > 0 ? sentence : Format.fill(Mounts.WORDS.mountFailed, label), root._mountErr)
471
+ }
472
+ }
473
+
474
+ Process {
475
+ id: unmountProcess
476
+ objectName: "unmountProcess"
477
+ stderr: StdioCollector { onStreamFinished: root._unmountErr = this.text }
478
+ // A spawn that fails raises this and never exited, so the flag ends here with every other end.
479
+ onRunningChanged: if (!unmountProcess.running) root._unmounting = false
480
+ onExited: function (exitCode) {
481
+ // A power-off unmounts each volume first; a refusal stops the chain instead of
482
+ // powering off under a volume that is still mounted.
483
+ if (root._unmountEndedLate) {
484
+ root._unmountEndedLate = false
485
+ root.poll()
486
+ return
487
+ }
488
+ if (root._powerOffDisk.length > 0) {
489
+ if (exitCode !== 0) {
490
+ powerOffTimeout.stop()
491
+ root._powerOffDisk = ""
492
+ root._powerOffQueue = []
493
+ root._ejectDevice = ""
494
+ root.reportFailure(Devices.mountError("unmount", exitCode, root._unmountErr, root._ejectLabel), root._unmountErr)
495
+ } else {
496
+ root.powerOffNext()
497
+ }
498
+ root.poll()
499
+ return
500
+ }
501
+ if (exitCode !== 0)
502
+ root.reportFailure(Devices.mountError("unmount", exitCode, root._unmountErr, root._unmountLabel), root._unmountErr)
503
+ else if (root.lockCrypt(root._unmountDevice, root._unmountLabel))
504
+ return
505
+ root.poll()
506
+ }
507
+ }
508
+
509
+ Process {
510
+ id: ejectProcess
511
+ objectName: "ejectProcess"
512
+ stderr: StdioCollector { onStreamFinished: root._ejectErr = this.text }
513
+ onExited: function (exitCode) {
514
+ // A refused stop says so at once; a stop that ran is judged on the listing that
515
+ // follows, because gio has exited 0 over a volume that was still mounted.
516
+ if (root._ejectEndedLate) {
517
+ root._ejectEndedLate = false
518
+ root.poll()
519
+ return
520
+ }
521
+ if (exitCode !== 0 && root._powerOffDisk.length > 0) {
522
+ var sentence = Devices.mountError("eject", exitCode, root._ejectErr, root._ejectLabel)
523
+ powerOffTimeout.stop()
524
+ root._powerOffDisk = ""
525
+ root._powerOffQueue = []
526
+ root._ejectDevice = ""
527
+ ejectVerdictTimeout.stop()
528
+ root.reportFailure(sentence.length > 0 ? sentence : Format.fill(Mounts.WORDS.ejectFailed, root._ejectLabel), root._ejectErr)
529
+ root.poll()
530
+ return
531
+ }
532
+ if (root._powerOffDisk.length > 0)
533
+ root._powerOffDisk = ""
534
+ root._ejectDetachFailed = root._ejectLoop && exitCode === Devices.detachFailedExit
535
+ root._verdictFromListing = root._listingsStarted + 1
536
+ ejectVerdictTimeout.restart()
537
+ root.poll()
538
+ }
539
+ }
540
+ }