buninu-browser 0.3.0 → 0.4.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 (239) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +19 -4
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +123 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +116 -0
  20. package/frontends/wayland/input.js +56 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +158 -0
  26. package/index.js +28 -1
  27. package/lib/clipboard-command.js +10 -0
  28. package/lib/clipboard-powershell.js +22 -0
  29. package/lib/clipboard.js +37 -2
  30. package/lib/document-types.js +11 -0
  31. package/lib/headless-shell.js +47 -8
  32. package/lib/layout/text-layout.js +17 -9
  33. package/lib/paint/display-list.js +5 -1
  34. package/lib/qml/README.md +971 -0
  35. package/lib/qml/builtins.js +700 -0
  36. package/lib/qml/canvas-dom.js +115 -0
  37. package/lib/qml/document.js +1176 -0
  38. package/lib/qml/format.js +9 -0
  39. package/lib/qml/host.js +324 -0
  40. package/lib/qml/images.js +199 -0
  41. package/lib/qml/launcher.js +132 -0
  42. package/lib/qml/loader.js +517 -0
  43. package/lib/qml/media-renderer.js +49 -0
  44. package/lib/qml/media.js +86 -0
  45. package/lib/qml/models.js +622 -0
  46. package/lib/qml/parser.js +381 -0
  47. package/lib/qml/qs +5 -0
  48. package/lib/qml/qscasty +3 -0
  49. package/lib/qml/qsheadless +3 -0
  50. package/lib/qml/qswayland +3 -0
  51. package/lib/qml/reactive.js +159 -0
  52. package/lib/qml/runtime.js +3381 -0
  53. package/lib/qml/shapes.js +243 -0
  54. package/lib/qml/text.js +109 -0
  55. package/lib/qml/textures.js +123 -0
  56. package/lib/qml/wayland-clipboard.js +32 -0
  57. package/lib/qml/worker-thread.js +70 -0
  58. package/lib/render-tree/index.js +16 -1
  59. package/lib/renderer/bindings.js +11 -0
  60. package/lib/renderer/canvas-2d.js +13 -0
  61. package/lib/renderer/host.js +31 -6
  62. package/lib/renderer/main.js +19 -6
  63. package/lib/renderer/page-realm.js +16 -1
  64. package/lib/renderer/page-renderer.js +230 -13
  65. package/lib/renderer/text-input-paint.js +54 -0
  66. package/lib/renderer/text-input.js +57 -0
  67. package/lib/style/computed-style.js +2 -2
  68. package/package.json +1 -1
  69. package/test/clipboard.test.js +19 -0
  70. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  71. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  72. package/test/fixtures/flea-helpers/bin/bat +3 -0
  73. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  74. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  75. package/test/fixtures/flea-helpers/bin/glow +3 -0
  76. package/test/fixtures/flea-helpers/bin/qs +3 -0
  77. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  78. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  79. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  80. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  81. package/test/fixtures/flea-helpers/check.mjs +36 -0
  82. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  83. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  84. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  85. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  86. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  87. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  88. package/test/fixtures/qml/flea/LICENSE +21 -0
  89. package/test/fixtures/qml/flea/README.md +25 -0
  90. package/test/fixtures/qml/flea/Theme.qml +8 -0
  91. package/test/fixtures/qml/flea/demo.qml +24 -0
  92. package/test/fixtures/qml/flea/qmldir +3 -0
  93. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  94. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  95. package/test/fixtures/qml/flea-theme/README.md +70 -0
  96. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  97. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  98. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  99. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  100. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  101. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  102. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  103. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  104. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  105. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  106. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  107. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  108. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  109. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  110. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  111. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  112. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  113. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  114. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  115. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  116. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  117. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  118. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  119. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  120. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  121. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  122. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  123. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  124. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  125. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  126. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  127. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  128. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  129. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  130. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  131. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  132. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  133. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  134. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  135. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  136. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  137. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  138. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  139. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  140. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  141. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  142. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  143. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  144. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  145. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  146. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  147. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  148. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  167. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  168. package/test/fixtures/qml/hello.qml +26 -0
  169. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  170. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  171. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  172. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  173. package/test/fixtures/qml/imports/logic/base.js +2 -0
  174. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  175. package/test/fixtures/qml/imports/logic/math.js +3 -0
  176. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  177. package/test/fixtures/qml/imports/main.qml +21 -0
  178. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  179. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  180. package/test/fixtures/qml/multiply.qml +61 -0
  181. package/test/fixtures/qml-canvas-dom.js +90 -0
  182. package/test/fixtures/qml-media-audio.js +39 -0
  183. package/test/helpers/flea-sandbox.js +48 -0
  184. package/test/helpers/quickshell-fixture.js +125 -0
  185. package/test/html-clipboard-wayland.test.js +47 -0
  186. package/test/index.test.js +13 -0
  187. package/test/manual-casty-without-qml.js +87 -0
  188. package/test/manual-flea-application.js +49 -0
  189. package/test/manual-flea-binary.js +62 -0
  190. package/test/manual-flea-qml.js +9 -0
  191. package/test/manual-flea-settings.js +40 -0
  192. package/test/manual-flea-theme.js +40 -0
  193. package/test/manual-qml-removal.js +46 -0
  194. package/test/manual-without-qml.js +45 -0
  195. package/test/qml-canvas-dom.test.js +37 -0
  196. package/test/qml-clipboard-bridge.test.js +25 -0
  197. package/test/qml-clipboard-wayland.test.js +40 -0
  198. package/test/qml-connections-timing.test.js +73 -0
  199. package/test/qml-dynamic.test.js +401 -0
  200. package/test/qml-flea-app.test.js +146 -0
  201. package/test/qml-flea-backend.test.js +78 -0
  202. package/test/qml-flea-columns-backend.test.js +38 -0
  203. package/test/qml-flea-compile.test.js +78 -0
  204. package/test/qml-flea-launcher.test.js +88 -0
  205. package/test/qml-flea-markdown-backend.test.js +31 -0
  206. package/test/qml-flea-media.test.js +28 -0
  207. package/test/qml-flea-navigation-backend.test.js +35 -0
  208. package/test/qml-flea-navigation-timing.test.js +147 -0
  209. package/test/qml-flea-preview-backend.test.js +53 -0
  210. package/test/qml-flea-previews.test.js +163 -0
  211. package/test/qml-flea-rows.test.js +101 -0
  212. package/test/qml-flea-textures.test.js +59 -0
  213. package/test/qml-flea-view-switch.test.js +33 -0
  214. package/test/qml-flea-wayland.test.js +68 -0
  215. package/test/qml-flea.test.js +34 -0
  216. package/test/qml-host.test.js +238 -0
  217. package/test/qml-imports.test.js +102 -0
  218. package/test/qml-inherited-handlers.test.js +51 -0
  219. package/test/qml-input-selection.test.js +66 -0
  220. package/test/qml-interactions.test.js +309 -0
  221. package/test/qml-language.test.js +167 -0
  222. package/test/qml-launcher.test.js +54 -0
  223. package/test/qml-layout-text.test.js +205 -0
  224. package/test/qml-media.test.js +63 -0
  225. package/test/qml-persisted-delegates.test.js +45 -0
  226. package/test/qml-quickshell-controls.test.js +65 -0
  227. package/test/qml-quickshell-settings.test.js +62 -0
  228. package/test/qml-repeater.test.js +72 -0
  229. package/test/qml-style-updates.test.js +124 -0
  230. package/test/qml-textures.test.js +66 -0
  231. package/test/qml-views.test.js +332 -0
  232. package/test/qml-worker.test.js +69 -0
  233. package/test/qml.test.js +83 -0
  234. package/test/renderer-host.test.js +11 -0
  235. package/test/text-input-pointer.test.js +31 -0
  236. package/test/text-input-scroll.test.js +29 -0
  237. package/test/text-input.test.js +47 -0
  238. package/test/wayland-frontend.test.js +83 -0
  239. package/test/wayland-urlbar.test.js +38 -0
@@ -0,0 +1,127 @@
1
+ .pragma library
2
+
3
+ .import "DirSizes.js" as DirSizes
4
+ .import "FolderSorts.js" as FolderSorts
5
+ .import "RecentMode.js" as RecentMode
6
+ .import "Thumbs.js" as Thumbs
7
+
8
+ // What the header's click and the s and S keys do, taking ui/Pane.qml's root the way Nav.js and
9
+ // Ops.js do: the pane holds the state, this holds what the state does. ui/Backend.qml records the
10
+ // order the listing is actually in, because list re-sorts by name ascending and only this file
11
+ // changes it after that.
12
+
13
+ // The orders the backend can actually produce, in the order s steps through them, and the only keys
14
+ // that may move the recorded order. It is not the list of what gets refused: docs/protocol.md "sort"
15
+ // refuses every other key by name, and the backend is the one that says so, see ui/js/Errors.js, aliased from FolderSorts.ORDERS, the one key table.
16
+ var ORDERS = FolderSorts.ORDERS
17
+
18
+ // The three sort decisions, kept apart from what a pane does with them: the chooser makes the same ones over its narrower list.
19
+
20
+ // The sorted column reverses; any other starts ascending, the order the canvas header draws beside "Name".
21
+ function columnOrder(orders, by, desc, key) {
22
+ if (orders.indexOf(key) < 0)
23
+ return null
24
+ return { key: key, desc: by === key ? !desc : false }
25
+ }
26
+
27
+ // Always ascending, column and direction separate; an outside order, a saved kind in the chooser, starts over at the first.
28
+ function nextOrder(orders, by) {
29
+ return { key: orders[(orders.indexOf(by) + 1) % orders.length], desc: false }
30
+ }
31
+
32
+ // Whichever order the listing is in, offered or inherited: the user asked for the reverse of what they have.
33
+ function reverseOrder(by, desc) {
34
+ return { key: by, desc: !desc }
35
+ }
36
+
37
+ // An edit open or a rename pending holds the sort and commits an open edit; it applies once the rename settles.
38
+ function holdForRename(pane, request) {
39
+ if (!(pane.renamingIndex >= 0 || pane.renamePending)) return false
40
+ pane.pendingSort = request
41
+ if (pane.renamingIndex >= 0 && !pane.renamePending) pane.commitOpenRename()
42
+ return true
43
+ }
44
+
45
+ // ui/Header.qml's click: only ORDERS leave this file except the forget row.
46
+ function column(pane, key) {
47
+ // The flyout's last row (issue 179) forgets the folder and lists it on the default.
48
+ // Recent stands on the root, so forgetting re-asks its history instead of listing the root over it.
49
+ if (key === "__default__") {
50
+ // Forgetting re-lists, so it holds like any other sort request.
51
+ if (holdForRename(pane, { forget: true })) return
52
+ if ((pane.recentMode || "").length > 0) {
53
+ RecentMode.run(pane, pane.recentPaths || [])
54
+ return
55
+ }
56
+ if (pane.backend && pane.backend.forgetFolderSort)
57
+ pane.backend.forgetFolderSort(pane.path)
58
+ // A preserved pane skips the reset a list would do, so refresh here or it keeps its old order.
59
+ if (pane.backend && pane.backend.resetSort)
60
+ pane.backend.resetSort(pane.path)
61
+ pane.openWithoutHistory(pane.path)
62
+ return
63
+ }
64
+ // Unsupported columns remain labels rather than sending a sort the backend must refuse.
65
+ var order = columnOrder(ORDERS, pane.backend.sortBy, pane.backend.sortDesc, key)
66
+ if (order)
67
+ resort(pane, order.key, order.desc)
68
+ }
69
+
70
+ // s walks ORDERS so it never lands on a refusal-only column; an aimed click earns the reason, a walking key only noise.
71
+ function next(pane) {
72
+ var order = nextOrder(ORDERS, pane.backend.sortBy)
73
+ resort(pane, order.key, order.desc)
74
+ }
75
+
76
+ // S: the capital-is-the-variant pair g/G and j/J use.
77
+ function reverse(pane) {
78
+ var order = reverseOrder(pane.backend.sortBy, pane.backend.sortDesc)
79
+ resort(pane, order.key, order.desc)
80
+ }
81
+
82
+ // The request goes out for every key, so the refusal is the backend's alone. Only an order it will
83
+ // really produce moves the recorded one, or the mark would describe a listing that never changed.
84
+ function resort(pane, key, desc) {
85
+ if (holdForRename(pane, { key: key, desc: desc })) return
86
+ // Asking for the order the listing is already in would drop every row-indexed cache and put the
87
+ // cursor back to redraw the rows already on screen, so it is not asked for at all.
88
+ if (pane.backend.sortBy === key && pane.backend.sortDesc === desc) {
89
+ return
90
+ }
91
+ pane.backend.sort(key, desc)
92
+ if (ORDERS.indexOf(key) < 0) {
93
+ return
94
+ }
95
+ pane.backend.sortBy = key
96
+ pane.backend.sortDesc = desc
97
+ // A walk's rows are matches, not the folder's, so sorting them writes nothing. Recent stands
98
+ // on the root, so its order is never remembered as the root's own.
99
+ if (pane.backend && pane.backend.rememberFolderSort
100
+ && (pane.searchMode || "") === "" && (pane.recentMode || "") === "")
101
+ pane.backend.rememberFolderSort(pane.path, key, desc)
102
+ // A reorder moves every row, so the caches keyed by a row index are as stale as a new listing's,
103
+ // and a selection of row indices would silently come to name different files.
104
+ pane.thumbState = Thumbs.empty()
105
+ pane.dirSizeState = DirSizes.empty()
106
+ pane.clearSelection()
107
+ pane.setCursor(0)
108
+ // sort emits no rows of its own, so the reordered window is asked for here; see docs/protocol.md.
109
+ pane.backend.window(0, pane.windowSize)
110
+ }
111
+
112
+ // The held sort a resort deferred; runs when the edit ends or the listing lands.
113
+ function applyPending(pane) {
114
+ var held = pane.pendingSort
115
+ if (!held) return
116
+ // Not settled yet, or nowhere to send it: the hold stands.
117
+ if (pane.listInFlight || pane.renamingIndex >= 0 || pane.renamePending) return
118
+ if (!pane.backend || pane.backend.quitting || !pane.backend.running) { pane.pendingSort = null; return }
119
+ pane.pendingSort = null
120
+ if (held.forget) { column(pane, "__default__"); return }
121
+ resort(pane, held.key, held.desc)
122
+ }
123
+
124
+ // A sorted column's title: the label and the geometric mark of the direction, characters the stock monospace has, so the mark scales with the font like the label.
125
+ function title(label, descending) {
126
+ return label + " " + (descending ? "\u25be" : "\u25b4")
127
+ }
@@ -0,0 +1,42 @@
1
+ .pragma library
2
+
3
+ .import "Format.js" as Format
4
+
5
+ // The state word an errored menu row says in its hint slot: a fixed value here, never a provider's reason.
6
+ var WORDS = {
7
+ taildropFolder: "files only",
8
+ taildropSignedOut: "signed out",
9
+ taildropNoPeers: "no devices",
10
+ taildropDown: "offline",
11
+ localsendFolder: "files only",
12
+ localsendNoPeers: "no devices",
13
+ dropboxSignedOut: "signed out",
14
+ dropboxDown: "not running",
15
+ permissionsLink: "symlink",
16
+ permissionsSpecial: "special file",
17
+ permissionsNotYours: "not yours"
18
+ }
19
+
20
+ // Sample input: { rowIsFile: false, taildropCode: "" }, or { rowIsFile: true, taildropCode: "signedOut" }; codes are ui/js/Taildrop.js status().
21
+ function taildrop(p) {
22
+ if (p.rowIsFile === false) return WORDS.taildropFolder
23
+ if (p.taildropCode === "signedOut") return WORDS.taildropSignedOut
24
+ return p.taildropCode === "noPeers" ? WORDS.taildropNoPeers : WORDS.taildropDown
25
+ }
26
+
27
+ // Sample input: { rowIsFile: false }. The send finds nobody on the network for any other cause.
28
+ function localsend(p) {
29
+ return p.rowIsFile === false ? WORDS.localsendFolder : WORDS.localsendNoPeers
30
+ }
31
+
32
+ // Sample input: { dropboxCode: "signedOut" }; codes are ui/js/Dropbox.js account() and status().
33
+ function dropbox(p) {
34
+ return p.dropboxCode === "signedOut" ? WORDS.dropboxSignedOut : WORDS.dropboxDown
35
+ }
36
+
37
+ // A link names itself; any other kind that is neither a file nor a folder is special, and a mode never read names nothing.
38
+ function permissions(mode) {
39
+ var kind = (Number(mode) || 0) & Format.S_IFMT
40
+ if (kind === 0) return ""
41
+ return kind === Format.S_IFLNK ? WORDS.permissionsLink : WORDS.permissionsSpecial
42
+ }
@@ -0,0 +1,166 @@
1
+ .pragma library
2
+
3
+ // The listing swap's decisions, see AGENTS.md "The listing swap"; ui/PaneSwap.qml holds the state and the timer.
4
+
5
+ // Both swaps' cap, the pane mark's hold-off and a PDF page render's cap: with four 120 Hz frames for input, the Timer and the draw, the mark shows by 100 ms.
6
+ var CAP_MS = 66
7
+
8
+ // The hold-off of a loading state nothing keeps MIN_SHOW_MS: LoadingState outside the pane, the folder fallback, a search walk's mark.
9
+ var HOLD_MS = 150
10
+
11
+ // A drawn loading state stands this long before content replaces it; under 100 ms the two changes read as one blink.
12
+ var MIN_SHOW_MS = 100
13
+
14
+ // Answered while a listing is out: the navigations take its place, escape touches no row, the rest change the window.
15
+ var ANSWERED_WHILE_LISTING = ["open", "parent", "historyBack", "historyForward", "escape",
16
+ "viewList", "viewColumns", "viewGrid", "viewDual", "sidebar", "windowNew", "togglePreview",
17
+ "reload"]
18
+
19
+ // What a swallowed key says, the sentence every refused navigation already gives.
20
+ var LOADING = "A directory is already loading."
21
+
22
+ // What a listed or rows line does: dropped, kept for the rows behind it, applied alone, or landed with the kept line.
23
+ var DROP = "drop"
24
+ var KEEP = "keep"
25
+ var APPLY = "apply"
26
+ var LAND = "land"
27
+ var WAIT = "wait"
28
+
29
+ function idle() {
30
+ return { holding: false, listed: null, fellBack: false, waits: false, walk: false, query: "", since: 0, relist: false }
31
+ }
32
+
33
+ // Only a settled listing is held, plus a walk's drawn state for a listing after it; see ui/PaneSwap.qml hold.
34
+ function begin(state, listingState, searchMode, ask, now) {
35
+ // The exit holds the whole search state, strip and rows, until the folder lands in one turn.
36
+ var exit = ask.holdWalk === true
37
+ var afterWalk = state.walk === true && !exit
38
+ var holding = ask.clearAtOnce !== true && listingState !== "loading" && (searchMode === "" || exit) && !afterWalk
39
+ // An unheld Back or Forward still keeps its listed line, so the count, the rows and the cursor land in one turn.
40
+ var waits = !holding && !!ask.restore && String(ask.restore.name || "").length > 0
41
+ return { holding: holding, listed: null, fellBack: false, waits: waits, walk: state.walk, query: ask.keptQuery || "", since: now,
42
+ relist: ask.relist === true }
43
+ }
44
+
45
+ // A session line lands only on its open session's newest sent gen; a closed exit's lines never land either.
46
+ function dropSessionLine(searchMode, gen, searchGen, leaving, openedGen) {
47
+ if (gen > 0 && leaving === true) {
48
+ return true
49
+ }
50
+ return gen > 0 && ((searchMode || "").length === 0 || gen !== searchGen || gen <= (openedGen || 0))
51
+ }
52
+
53
+ // A live answer holds old rows until its own rows land, no listing out.
54
+ function keepLive(searchMode, inFlight) {
55
+ return (searchMode || "").length > 0 && inFlight !== true
56
+ }
57
+
58
+ // While a list is out, only a listed line naming the directory asked for answers it; an earlier sort's or search's is dropped.
59
+ // Held rows, a fallback or a loading mark already drawn keep it for the rows, so the mark never gives way to a count over none.
60
+ function onListed(state, inFlight, path, asked, marked) {
61
+ if (inFlight === true && path.length > 0 && path !== asked)
62
+ return DROP
63
+ return state.holding || state.fellBack || state.waits || marked === true ? KEEP : APPLY
64
+ }
65
+
66
+ // A rows line ahead of its listed line answers a listing already replaced; one behind a kept listed line lands with it,
67
+ // once the loading mark it replaces has stood waitMs more.
68
+ function onRows(state, inFlight, listedSeen, waitMs) {
69
+ if (inFlight === true && listedSeen !== true)
70
+ return DROP
71
+ if (state.listed === null)
72
+ return APPLY
73
+ return waitMs > 0 ? WAIT : LAND
74
+ }
75
+
76
+ // How much longer a loading state drawn at since stands before it may go; 0 when none is drawn.
77
+ function showLeft(since, now) {
78
+ return since > 0 ? Math.max(0, since + MIN_SHOW_MS - now) : 0
79
+ }
80
+
81
+ // How long a held listing begun at state.since still holds before it falls back; -1 when nothing is held.
82
+ function capLeft(state, now) {
83
+ return state.holding === true ? Math.max(0, state.since + CAP_MS - now) : -1
84
+ }
85
+
86
+ // The pane mark's hold-off: a directory listing keeps its mark MIN_SHOW_MS, so it draws at the cap; a walk's does not.
87
+ function markHoldOff(searchMode) {
88
+ return (searchMode || "") === "" ? CAP_MS : HOLD_MS
89
+ }
90
+
91
+ // A directory listing out under a drawn mark; a walk's or a search's matches stream into a loading pane, never waiting on it.
92
+ function marked(since, inFlight, searchMode, walk) {
93
+ return since > 0 && inFlight === true && (searchMode || "") === "" && walk !== true
94
+ }
95
+
96
+ function kept(state, listed) {
97
+ return Object.assign({}, state, { listed: listed })
98
+ }
99
+
100
+ // Every listed line that is applied rather than kept says whether the rows it brings are a walk's.
101
+ function heard(state, walk) {
102
+ return Object.assign({}, state, { walk: walk === true })
103
+ }
104
+
105
+ // A held listing is only ever a directory's, which is what begin() asked for.
106
+ function landed(state) {
107
+ return Object.assign({}, state, { holding: false, listed: null, fellBack: false, waits: false, walk: false, relist: false })
108
+ }
109
+
110
+ // The cap ran out: the pane shows the loading state until the rows land, and a listed line already kept waits for them.
111
+ function expired(state) {
112
+ return Object.assign({}, state, { holding: false, fellBack: true })
113
+ }
114
+
115
+ // A failed or refused listing ends the hold without a swap; the failure draws what it always did.
116
+ function dropped(state) {
117
+ return Object.assign({}, state, { holding: false, listed: null, fellBack: false, waits: false, relist: false })
118
+ }
119
+
120
+ // The listing is no longer out, however it ended.
121
+ function ended(state) {
122
+ return Object.assign({}, state, { holding: false, listed: null, fellBack: false, waits: false, relist: false })
123
+ }
124
+
125
+ // A stale or window refusal ended only its own request, and so does a scan failure naming a folder no longer asked for; any other failure ends the listing, letting held rows go while it is still out.
126
+ function failListing(pane, where, path) {
127
+ if (where === "stale" || where === "window")
128
+ return false
129
+ if (where === "scan" && !!path && path !== pane.listingPath)
130
+ return false
131
+ // An Esc relist that died leaves no cursor file armed for whatever listing comes next.
132
+ if (pane.swap.phase.relist === true)
133
+ pane.pendingSelect = ""
134
+ pane.swap.drop()
135
+ pane.listInFlight = false
136
+ pane.listedSeen = false
137
+ return true
138
+ }
139
+
140
+ // Sample input: {"c":"transfer","op":"move","rows":[3],"dest":"/home/gm/omarchy"} with held 7 gains "listing":7, unless it names one.
141
+ function named(request, held) {
142
+ if (request.rows === undefined || request.listing !== undefined || !(held > 0))
143
+ return request
144
+ return Object.assign({}, request, { listing: held })
145
+ }
146
+
147
+ // A key swallowed while a listing is out, held or loading: the backend already numbers rows for the directory asked for.
148
+ function swallows(inFlight, action) {
149
+ return inFlight === true && action.length > 0 && ANSWERED_WHILE_LISTING.indexOf(action) < 0
150
+ }
151
+
152
+ // A re-sort renumbers every row without a new list, so a selection made while it was in flight names other files once the new numbering lands.
153
+ function clearsSelection(oldHeld, listing) {
154
+ return oldHeld > 0 && listing > 0 && listing !== oldHeld
155
+ }
156
+
157
+ // A blank frame: a listing out and loading before the cap, or no row drawn while a live count still names rows.
158
+ function frameKind(inFlight, listingState, fellBack, drawn, named, live) {
159
+ if (inFlight === true && listingState === "loading") {
160
+ return fellBack === true ? "loading" : "blank"
161
+ }
162
+ if (live === true && drawn === 0 && named > 0) {
163
+ return "blank"
164
+ }
165
+ return ""
166
+ }
@@ -0,0 +1,107 @@
1
+ .pragma library
2
+
3
+ .import "Format.js" as Format
4
+ .import "KeyName.js" as KeyName
5
+
6
+ // Flea's text size, which is the Display section's one control. The SettingsScale board rules the
7
+ // shape: Omarchy owns the size by default, and an override pins one stop from Omarchy's own range
8
+ // rather than a free number or a multiplier of its own. The monitor scale beside it belongs to the
9
+ // compositor, which is why nothing here steps or cycles one.
10
+
11
+ // Whole sentences with "%1" for a value, filled by fill so each translates as one.
12
+ var WORDS = {
13
+ followsOmarchy: "Text size follows Omarchy, %1",
14
+ override: "Text size %1. %2 follows Omarchy again",
15
+ overrideAlone: "Text size %1"
16
+ }
17
+
18
+ // The board's seven documented stops, in px, read off its own layout table. They are not
19
+ // contiguous: 13, 15, 17, 18 and 19 are not stops. The range is the OEM one, 9 to 20, so Flea
20
+ // offers no size Omarchy does not.
21
+ var STOPS = [9, 10, 11, 12, 14, 16, 20]
22
+
23
+ // Omarchy's own type ladder, from Commons/Style.qml, whose bodySmall is fontPx(0.917) and caption
24
+ // fontPx(0.833) over the base size. Running the same two ratios at a stop is what makes an override
25
+ // the size Omarchy itself would have drawn, rather than a second ladder that can disagree with it.
26
+ var BODY_RATIO = 1.0
27
+ var BODY_SMALL_RATIO = 0.917
28
+ var CAPTION_RATIO = 0.833
29
+
30
+ var FOLLOW = "system"
31
+
32
+ function follow() {
33
+ return { mode: FOLLOW }
34
+ }
35
+
36
+ // Follow Omarchy is the default, and the board names its stored shape: {"mode":"system"}. An
37
+ // override is the same key holding a number instead, which is src/uischema.rs's own display.textSize
38
+ // rule: "system", or one stop the OEM panel could have produced. There is no second stored shape.
39
+ function following(stored) {
40
+ return !stored || !(Number(stored.mode) > 0)
41
+ }
42
+
43
+ // The nearest stop, so a base Omarchy invented that is not on the list still enters the override on
44
+ // a real stop, and a hand-edited file cannot pin a free number. A tie takes the smaller stop, which
45
+ // is what the strict comparison below does: 13 enters at 12 and 15 at 14, so nothing grows unasked.
46
+ function nearest(px) {
47
+ var best = STOPS[0]
48
+ for (var i = 1; i < STOPS.length; i++) {
49
+ if (Math.abs(STOPS[i] - px) < Math.abs(best - px))
50
+ best = STOPS[i]
51
+ }
52
+ return best
53
+ }
54
+
55
+ // The size Flea actually draws at: Omarchy's own while following, the stored stop while overriding.
56
+ function effective(stored, omarchyBase) {
57
+ return following(stored) ? omarchyBase : nearest(Number(stored.mode))
58
+ }
59
+
60
+ // Switching to Override starts on the stop beside the size already on screen, so the switch itself
61
+ // changes nothing and only a step does.
62
+ function pin(stored, omarchyBase) {
63
+ return { mode: nearest(effective(stored, omarchyBase)) }
64
+ }
65
+
66
+ // One stop along the list, clamped at both ends. Stepping while following becomes an override,
67
+ // which is what the first press of Ctrl+Shift+Plus should do.
68
+ function stepped(stored, omarchyBase, direction) {
69
+ var at = STOPS.indexOf(pin(stored, omarchyBase).mode)
70
+ var to = Math.max(0, Math.min(STOPS.length - 1, at + direction))
71
+ return { mode: STOPS[to] }
72
+ }
73
+
74
+ // A stored value another hand wrote, and 0.1.3's own {"mode":"override","px":N}: anything this
75
+ // cannot read as a size follows Omarchy, which is both the default and the state deleting ui.json
76
+ // restores, and a number that is not a stop lands on the nearest one rather than pinning a size the
77
+ // OEM panel could not produce. src/uistate.rs refuses the same shapes on the way in.
78
+ function parse(stored) {
79
+ var px = stored ? Number(stored.mode) : NaN
80
+ return px > 0 ? { mode: nearest(px) } : follow()
81
+ }
82
+
83
+ // One action-to-delta mapping for every caller, so Focus and the Trash host cannot drift apart.
84
+ function direction(action) {
85
+ return action === "textSizeReset" ? 0 : (action === "textSizeUp" ? 1 : -1)
86
+ }
87
+
88
+ function body(base) {
89
+ return Math.max(1, Math.round(base * BODY_RATIO))
90
+ }
91
+
92
+ function bodySmall(base) {
93
+ return Math.max(1, Math.round(base * BODY_SMALL_RATIO))
94
+ }
95
+
96
+ function caption(base) {
97
+ return Math.max(1, Math.round(base * CAPTION_RATIO))
98
+ }
99
+
100
+ // The sentence the status bar shows, because a size changed by a chord with the panel shut is
101
+ // otherwise a state with no readout and no named way back.
102
+ function announce(stored, omarchyBase) {
103
+ var px = effective(stored, omarchyBase) + "px"
104
+ if (following(stored)) return Format.fill(WORDS.followsOmarchy, px)
105
+ var reset = KeyName.keyFor("textSizeReset")
106
+ return reset.length > 0 ? Format.fill(WORDS.override, px, reset) : Format.fill(WORDS.overrideAlone, px)
107
+ }
@@ -0,0 +1,158 @@
1
+ .pragma library
2
+
3
+ // A row this listing has dealt with: null means asked and waiting, a string is the answer.
4
+ // A cache-only ask carries a mark, so its empty answer means "not tried" and a class switch forgets it.
5
+ var ASKED = null
6
+ var CACHE_ASKED = "cache-asked"
7
+ var CACHE_MISS = "cache-missed"
8
+
9
+ // Asked and not answered yet, a plain ask or a cache-only one; only these have a job to cancel.
10
+ function pending(value) {
11
+ return value === ASKED || value === CACHE_ASKED
12
+ }
13
+
14
+ function empty() {
15
+ return { file: {}, order: [] }
16
+ }
17
+
18
+ // The rows a request may name: the viewport in row indices, clamped to the last row of the listing.
19
+ function viewport(contentY, rowHeight, visibleRows, total) {
20
+ var first = Math.floor(contentY / rowHeight)
21
+ // A window that is not row aligned straddles one more row than it holds, so the bottom comes off its pixel extent.
22
+ var bottom = contentY + visibleRows * rowHeight
23
+ return { first: first, last: Math.min(total - 1, Math.ceil(bottom / rowHeight) - 1) }
24
+ }
25
+
26
+ // Only visible rows, only rows the backend called thumbnailable, only rows never asked.
27
+ function plan(state, rows, held, first, last, mode) {
28
+ var ask = []
29
+ for (var i = first; i <= last; i++) {
30
+ if (state.file[i] !== undefined) {
31
+ continue
32
+ }
33
+ var row = rows[i - held]
34
+ if (allowed(row, mode)) {
35
+ ask.push(i)
36
+ }
37
+ }
38
+ var drop = []
39
+ for (var key in state.file) {
40
+ if (!pending(state.file[key])) {
41
+ continue
42
+ }
43
+ var at = Number(key)
44
+ if (at < first || at > last || !allowed(rows[at - held], mode)) {
45
+ drop.push(at)
46
+ }
47
+ }
48
+ return { ask: ask, drop: drop }
49
+ }
50
+
51
+ // A cancelled row is forgotten here too, because the backend forgets it when it drops the job.
52
+ function forget(state, row) {
53
+ delete state.file[row]
54
+ var at = state.order.indexOf(row)
55
+ if (at >= 0) {
56
+ state.order.splice(at, 1)
57
+ }
58
+ }
59
+
60
+ // A miss on a generating class returns to unasked; on a cache-only class it stays a miss.
61
+ function miss(state, row, generating, cap) {
62
+ if (generating === true) {
63
+ forget(state, row)
64
+ return { file: state.file, order: state.order }
65
+ }
66
+ return remember(state, row, "", cap)
67
+ }
68
+
69
+ // The wrapper is new so a QML binding on it re-evaluates; the map inside is mutated in place.
70
+ function applied(state, work) {
71
+ for (var i = 0; i < work.drop.length; i++) {
72
+ forget(state, work.drop[i])
73
+ }
74
+ var mark = work.cacheOnly === true ? CACHE_ASKED : ASKED
75
+ for (var j = 0; j < work.ask.length; j++) {
76
+ state.file[work.ask[j]] = mark
77
+ state.order.push(work.ask[j])
78
+ }
79
+ return { file: state.file, order: state.order }
80
+ }
81
+
82
+ // A live answer re-lists some rows: each finished thumbnail follows its row, by name, to that row's new index. Asked and missed rows do not follow.
83
+ function carried(state, oldRows, oldHeld, newRows, newHeld) {
84
+ var at = Object.create(null)
85
+ for (var j = 0; j < newRows.length; j++) {
86
+ if (newRows[j].t) {
87
+ at[newRows[j].n] = newHeld + j
88
+ }
89
+ }
90
+ var next = empty()
91
+ for (var k = 0; k < state.order.length; k++) {
92
+ var file = state.file[state.order[k]]
93
+ var row = oldRows[state.order[k] - oldHeld]
94
+ if (file && file !== CACHE_ASKED && file !== CACHE_MISS && row && at[row.n] !== undefined) {
95
+ next.file[at[row.n]] = file
96
+ next.order.push(at[row.n])
97
+ }
98
+ }
99
+ return next
100
+ }
101
+
102
+ // The cap bounds a policy bug, not normal use: only the rows a viewport held are ever recorded.
103
+ function remember(state, row, file, cap) {
104
+ if (state.file[row] === undefined) {
105
+ state.order.push(row)
106
+ }
107
+ // An empty answer to a cache-only ask was never a decode, so it keeps its mark.
108
+ state.file[row] = (file === "" && state.file[row] === CACHE_ASKED) ? CACHE_MISS : file
109
+ while (state.order.length > cap) {
110
+ delete state.file[state.order.shift()]
111
+ }
112
+ return { file: state.file, order: state.order }
113
+ }
114
+
115
+ function fileFor(state, row) {
116
+ var value = state.file[row]
117
+ if (value === CACHE_ASKED || value === CACHE_MISS) return ""
118
+ return typeof value === "string" ? value : ""
119
+ }
120
+
121
+ // Answered with nothing, which is not the same as not answered yet. A thumbnailer that could not
122
+ // read the file reports an empty name, and only once that has arrived is it honest to say so.
123
+ function refused(state, row) {
124
+ return state.file[row] === ""
125
+ }
126
+
127
+ // src/backend/thumbs.rs THUMB_SIZE: the longest side of a cache file made from a larger original.
128
+ var CACHE_SIZE = 256
129
+
130
+ // How much a picture may be enlarged to fit its box, and never past limit: 1 for an original, which
131
+ // draws at its own size when it is smaller than the box (decided 2026-09-24). Sample input: (754, 471, 120, 68, 1) is 1.
132
+ function fitScale(boxWidth, boxHeight, width, height, limit) {
133
+ return Math.min(limit, boxWidth / Math.max(1, width), boxHeight / Math.max(1, height))
134
+ }
135
+
136
+ // A cache file stands in for its original, so it is enlarged at most to the original's own size. The
137
+ // longest side is compared because EXIF can turn either one. Unknown dimensions leave a full-size cache
138
+ // file free to fill the box, while a smaller one already is the original. Sample input: (256, 171, 300, 200) is 300/256.
139
+ function thumbLimit(thumbWidth, thumbHeight, originalWidth, originalHeight) {
140
+ var thumbLongest = Math.max(1, thumbWidth, thumbHeight)
141
+ var originalLongest = Math.max(Number(originalWidth) || 0, Number(originalHeight) || 0)
142
+ if (originalLongest > 0)
143
+ return originalLongest / thumbLongest
144
+ return thumbLongest < CACHE_SIZE ? 1 : Infinity
145
+ }
146
+
147
+ // A video poster fills the frame; an image keeps the never-enlarge cap.
148
+ function posterLimit(isVideo, thumbWidth, thumbHeight, originalWidth, originalHeight) {
149
+ if (isVideo)
150
+ return Infinity
151
+ return thumbLimit(thumbWidth, thumbHeight, originalWidth, originalHeight)
152
+ }
153
+
154
+ // Backend icon identity distinguishes image thumbnails from video without opening any extra file; an AppImage's own icon is its identity, so Images draws a row the backend flagged a.
155
+ function allowed(row, mode) {
156
+ if (!row || !row.t || mode === "off") return false
157
+ return mode !== "images" || row.i === "image-x-generic" || row.a === true
158
+ }