buninu-browser 0.3.0 → 0.5.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 (296) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +40 -4
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +152 -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 +130 -0
  20. package/frontends/wayland/input.js +75 -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 +162 -0
  26. package/index.js +28 -1
  27. package/lib/audio/shared-connection.js +31 -0
  28. package/lib/cdp-server.js +15 -2
  29. package/lib/clipboard-command.js +10 -0
  30. package/lib/clipboard-powershell.js +22 -0
  31. package/lib/clipboard.js +37 -2
  32. package/lib/document-types.js +11 -0
  33. package/lib/headless-shell.js +54 -12
  34. package/lib/layout/text-layout.js +17 -9
  35. package/lib/paint/display-list.js +6 -1
  36. package/lib/qml/README.md +1427 -0
  37. package/lib/qml/builtins.js +771 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/canvas.js +76 -0
  40. package/lib/qml/document.js +1199 -0
  41. package/lib/qml/fonts.js +69 -0
  42. package/lib/qml/format.js +9 -0
  43. package/lib/qml/host.js +352 -0
  44. package/lib/qml/images.js +199 -0
  45. package/lib/qml/launcher.js +132 -0
  46. package/lib/qml/loader.js +521 -0
  47. package/lib/qml/media-renderer.js +49 -0
  48. package/lib/qml/media.js +86 -0
  49. package/lib/qml/models.js +622 -0
  50. package/lib/qml/parser.js +381 -0
  51. package/lib/qml/qs +5 -0
  52. package/lib/qml/qscasty +3 -0
  53. package/lib/qml/qsheadless +3 -0
  54. package/lib/qml/qswayland +3 -0
  55. package/lib/qml/reactive.js +182 -0
  56. package/lib/qml/runtime.js +3603 -0
  57. package/lib/qml/shader/fragment.js +83 -0
  58. package/lib/qml/shader/qsb.js +27 -0
  59. package/lib/qml/shader/renderer.js +39 -0
  60. package/lib/qml/shader/runtime.js +61 -0
  61. package/lib/qml/shapes.js +251 -0
  62. package/lib/qml/shortcuts.js +65 -0
  63. package/lib/qml/text.js +109 -0
  64. package/lib/qml/textures.js +126 -0
  65. package/lib/qml/wayland-clipboard.js +32 -0
  66. package/lib/qml/window-constraints.js +16 -0
  67. package/lib/qml/worker-thread.js +70 -0
  68. package/lib/render-tree/index.js +16 -1
  69. package/lib/renderer/bindings.js +16 -1
  70. package/lib/renderer/canvas-2d.js +29 -0
  71. package/lib/renderer/document-selection.js +19 -0
  72. package/lib/renderer/host.js +45 -17
  73. package/lib/renderer/main.js +21 -6
  74. package/lib/renderer/page-realm.js +20 -10
  75. package/lib/renderer/page-renderer.js +497 -42
  76. package/lib/renderer/pointer-move.js +5 -3
  77. package/lib/renderer/render-interval.js +9 -0
  78. package/lib/renderer/text-input-paint.js +54 -0
  79. package/lib/renderer/text-input.js +57 -0
  80. package/lib/style/computed-style.js +10 -4
  81. package/package.json +1 -1
  82. package/test/audio-connection-lifetime.test.js +46 -0
  83. package/test/canvas-paint.test.js +8 -0
  84. package/test/clipboard.test.js +19 -0
  85. package/test/computed-style.test.js +12 -0
  86. package/test/document-selection.test.js +27 -0
  87. package/test/double-click.test.js +85 -0
  88. package/test/fixtures/canvas-damage.js +40 -0
  89. package/test/fixtures/fake-renderer.js +1 -0
  90. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  91. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  92. package/test/fixtures/flea-helpers/bin/bat +3 -0
  93. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  94. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  95. package/test/fixtures/flea-helpers/bin/glow +3 -0
  96. package/test/fixtures/flea-helpers/bin/qs +3 -0
  97. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  98. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  99. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  100. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  101. package/test/fixtures/flea-helpers/check.mjs +36 -0
  102. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  103. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  104. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  105. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  106. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  107. package/test/fixtures/omastorm-helpers/Dockerfile +35 -0
  108. package/test/fixtures/omastorm-helpers/check.mjs +56 -0
  109. package/test/fixtures/omastorm-helpers/launch.mjs +24 -0
  110. package/test/fixtures/omastorm-helpers/prepare.mjs +17 -0
  111. package/test/fixtures/omastorm-helpers/runtime.mjs +32 -0
  112. package/test/fixtures/pointer-paint-batching.js +23 -0
  113. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  114. package/test/fixtures/qml/flea/LICENSE +21 -0
  115. package/test/fixtures/qml/flea/README.md +25 -0
  116. package/test/fixtures/qml/flea/Theme.qml +8 -0
  117. package/test/fixtures/qml/flea/demo.qml +24 -0
  118. package/test/fixtures/qml/flea/qmldir +3 -0
  119. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  120. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  121. package/test/fixtures/qml/flea-theme/README.md +70 -0
  122. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  123. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  124. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  125. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  126. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  127. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  128. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  129. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  130. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  131. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  132. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  133. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  134. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  135. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  136. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  137. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  138. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  139. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  140. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  141. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  142. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  143. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  144. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  145. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  146. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  147. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  148. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  173. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  174. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  175. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  176. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  177. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  178. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  179. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  180. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  181. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  182. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  183. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  184. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  185. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  186. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  187. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  188. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  189. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  190. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  191. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  192. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  193. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  194. package/test/fixtures/qml/hello.qml +26 -0
  195. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  196. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  197. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  198. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  199. package/test/fixtures/qml/imports/logic/base.js +2 -0
  200. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  201. package/test/fixtures/qml/imports/logic/math.js +3 -0
  202. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  203. package/test/fixtures/qml/imports/main.qml +21 -0
  204. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  205. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  206. package/test/fixtures/qml/multiply.qml +61 -0
  207. package/test/fixtures/qml-canvas-dom.js +90 -0
  208. package/test/fixtures/qml-fonts/main.qml +10 -0
  209. package/test/fixtures/qml-locale/check.mjs +6 -0
  210. package/test/fixtures/qml-locale/main.qml +8 -0
  211. package/test/fixtures/qml-media-audio.js +39 -0
  212. package/test/fixtures/qml-shader/grab.qml +14 -0
  213. package/test/fixtures/qml-shader/gradient.frag +5 -0
  214. package/test/fixtures/qml-shader/main.qml +14 -0
  215. package/test/fixtures/qml-shader/texture-grab.qml +25 -0
  216. package/test/fixtures/qml-shader/texture-size.frag +5 -0
  217. package/test/fixtures/qml-shader-check.mjs +24 -0
  218. package/test/fixtures/title-no-paint.js +20 -0
  219. package/test/helpers/flea-sandbox.js +48 -0
  220. package/test/helpers/quickshell-fixture.js +125 -0
  221. package/test/html-clipboard-wayland.test.js +47 -0
  222. package/test/index.test.js +13 -0
  223. package/test/manual-casty-without-qml.js +87 -0
  224. package/test/manual-double-click.html +21 -0
  225. package/test/manual-flea-application.js +49 -0
  226. package/test/manual-flea-binary.js +62 -0
  227. package/test/manual-flea-qml.js +9 -0
  228. package/test/manual-flea-settings.js +40 -0
  229. package/test/manual-flea-theme.js +40 -0
  230. package/test/manual-mouse-buttons.html +18 -0
  231. package/test/manual-omastorm-engine.mjs +19 -0
  232. package/test/manual-omastorm-shaders.mjs +28 -0
  233. package/test/manual-omastorm-software.mjs +25 -0
  234. package/test/manual-pointer-scroll.html +43 -0
  235. package/test/manual-qml-removal.js +46 -0
  236. package/test/manual-without-qml.js +45 -0
  237. package/test/pointer-drag.test.js +104 -0
  238. package/test/qml-canvas-dom.test.js +37 -0
  239. package/test/qml-canvas.test.js +21 -0
  240. package/test/qml-clipboard-bridge.test.js +25 -0
  241. package/test/qml-clipboard-wayland.test.js +40 -0
  242. package/test/qml-connections-timing.test.js +95 -0
  243. package/test/qml-controls-window.test.js +182 -0
  244. package/test/qml-dynamic.test.js +401 -0
  245. package/test/qml-flea-app.test.js +146 -0
  246. package/test/qml-flea-backend.test.js +78 -0
  247. package/test/qml-flea-columns-backend.test.js +38 -0
  248. package/test/qml-flea-compile.test.js +78 -0
  249. package/test/qml-flea-launcher.test.js +88 -0
  250. package/test/qml-flea-markdown-backend.test.js +31 -0
  251. package/test/qml-flea-media.test.js +28 -0
  252. package/test/qml-flea-navigation-backend.test.js +35 -0
  253. package/test/qml-flea-navigation-timing.test.js +147 -0
  254. package/test/qml-flea-preview-backend.test.js +53 -0
  255. package/test/qml-flea-previews.test.js +163 -0
  256. package/test/qml-flea-rows.test.js +101 -0
  257. package/test/qml-flea-textures.test.js +59 -0
  258. package/test/qml-flea-view-switch.test.js +33 -0
  259. package/test/qml-flea-wayland.test.js +68 -0
  260. package/test/qml-flea.test.js +34 -0
  261. package/test/qml-fonts.test.js +31 -0
  262. package/test/qml-grid-multiline.test.js +48 -0
  263. package/test/qml-host.test.js +238 -0
  264. package/test/qml-imports.test.js +102 -0
  265. package/test/qml-inherited-handlers.test.js +51 -0
  266. package/test/qml-input-selection.test.js +66 -0
  267. package/test/qml-interactions.test.js +309 -0
  268. package/test/qml-language.test.js +167 -0
  269. package/test/qml-launcher.test.js +54 -0
  270. package/test/qml-layout-text.test.js +205 -0
  271. package/test/qml-locale.test.js +45 -0
  272. package/test/qml-media.test.js +63 -0
  273. package/test/qml-nonvisual-visible.test.js +19 -0
  274. package/test/qml-persisted-delegates.test.js +45 -0
  275. package/test/qml-quickshell-controls.test.js +65 -0
  276. package/test/qml-quickshell-settings.test.js +62 -0
  277. package/test/qml-repeater.test.js +72 -0
  278. package/test/qml-shader-runtime.test.js +6 -0
  279. package/test/qml-shortcuts.test.js +39 -0
  280. package/test/qml-socket.test.js +60 -0
  281. package/test/qml-software-shader.test.js +55 -0
  282. package/test/qml-style-updates.test.js +124 -0
  283. package/test/qml-textures.test.js +66 -0
  284. package/test/qml-views.test.js +332 -0
  285. package/test/qml-window-update.test.js +36 -0
  286. package/test/qml-worker.test.js +69 -0
  287. package/test/qml.test.js +83 -0
  288. package/test/render-interval.test.js +33 -0
  289. package/test/renderer-host.test.js +11 -0
  290. package/test/text-input-pointer.test.js +31 -0
  291. package/test/text-input-scroll.test.js +29 -0
  292. package/test/text-input.test.js +47 -0
  293. package/test/title-no-paint.test.js +6 -0
  294. package/test/wayland-frontend.test.js +83 -0
  295. package/test/wayland-scroll.test.js +40 -0
  296. package/test/wayland-urlbar.test.js +61 -0
@@ -0,0 +1,188 @@
1
+ pragma Singleton
2
+ import Quickshell
3
+ import QtQuick
4
+
5
+ // Shared utility helpers used across plugins. Pure functions only — no
6
+ // state. Anything stateful belongs on Color, Style, or a service.
7
+ QtObject {
8
+ id: root
9
+
10
+ // Manual click forwarding must honor the same clipping as Qt's hit test.
11
+ // Collapsed drawers keep their children laid out beyond the visible edge.
12
+ function itemContainsPoint(target, source, x, y) {
13
+ if (!target || !target.contains(target.mapFromItem(source, x, y))) return false
14
+ for (var item = target; item; item = item.parent) {
15
+ if (!item.visible || item.opacity === 0 || !item.enabled) return false
16
+ if (item.clip && !item.contains(item.mapFromItem(source, x, y))) return false
17
+ }
18
+ return true
19
+ }
20
+
21
+ function clamp(value, min, max) {
22
+ var n = Number(value)
23
+ if (!isFinite(n)) return min
24
+ return Math.max(min, Math.min(max, n))
25
+ }
26
+
27
+ function clampAlpha(value) {
28
+ return clamp(value, 0, 1)
29
+ }
30
+
31
+ function wheelSteps(accumulator, delta) {
32
+ // Some mouse/compositor combinations scale a single notch well beyond
33
+ // Qt's conventional 120 units. Keep one event to one step while still
34
+ // accumulating the smaller deltas emitted by touchpads.
35
+ delta = Math.max(-120, Math.min(120, delta))
36
+ if (accumulator * delta < 0) accumulator = 0
37
+ var total = accumulator + delta
38
+ var steps = total < 0 ? Math.ceil(total / 120) : Math.floor(total / 120)
39
+ return { steps: steps, remainder: total - steps * 120 }
40
+ }
41
+
42
+ // Compose a base color with an opacity. Accepts a color object or a hex
43
+ // string; null/undefined yields transparent black at the requested alpha.
44
+ function alpha(c, opacity) {
45
+ var a = clampAlpha(opacity)
46
+ if (!c) return Qt.rgba(0, 0, 0, a)
47
+ if (typeof c === "string") c = Qt.color(c)
48
+ return Qt.rgba(c.r, c.g, c.b, a)
49
+ }
50
+
51
+ // file:// URL with each path segment percent-encoded so spaces and
52
+ // special chars in user paths don't break Image.source.
53
+ function fileUrl(path) {
54
+ if (!path) return ""
55
+ return "file://" + String(path).split("/").map(encodeURIComponent).join("/")
56
+ }
57
+
58
+ function isVideoPath(path) {
59
+ return /\.(mp4|m4v|mov|webm|mkv|avi)$/i.test(String(path || ""))
60
+ }
61
+
62
+ // Single-quote a string for bash. The replace handles embedded single
63
+ // quotes by closing, escaping, and re-opening the literal.
64
+ function shellQuote(value) {
65
+ return "'" + String(value || "").replace(/'/g, "'\\''") + "'"
66
+ }
67
+
68
+ function execDetached(command) {
69
+ Quickshell.execDetached(["bash", "-lc", command])
70
+ }
71
+
72
+ // Run an argv vector without a shell interpreting it: the constant `exec "$@"`
73
+ // means the args only ever land in positional parameters, which bash expands
74
+ // without re-tokenizing — so untrusted data ($(id), a filename) stays literal.
75
+ // The login shell (-l) keeps the PATH/session env GUI targets (omasnap, mpv,
76
+ // xdg-open) need. Prefer this over execDetached for anything built from input.
77
+ function execArgv(argv) {
78
+ Quickshell.execDetached(["bash", "-lc", 'exec "$@"', "bash"].concat(argv))
79
+ }
80
+
81
+ function isPlainObject(value) {
82
+ return value !== null && typeof value === "object" && !Array.isArray(value)
83
+ }
84
+
85
+ function canonicalWidgetId(id) {
86
+ return String(id || "")
87
+ }
88
+
89
+ // Best-effort base64 decode. Returns "" on parse failure rather than
90
+ // surfacing garbage downstream.
91
+ function decodeBase64(value) {
92
+ var s = String(value || "")
93
+ if (!s) return ""
94
+ try { return Qt.atob(s) } catch (e) { return "" }
95
+ }
96
+
97
+ function cloneJson(value) {
98
+ return JSON.parse(JSON.stringify(value === undefined ? null : value))
99
+ }
100
+
101
+ // Parse the last line of a custom-module / indicator process output as
102
+ // waybar-style JSON ({text, class, tooltip, ...}). Falls back to {text: raw}
103
+ // when the output isn't JSON, and {} for empty output.
104
+ function parseModuleJson(raw) {
105
+ var text = String(raw || "").trim()
106
+ if (!text) return {}
107
+ var lines = text.split("\n")
108
+ try {
109
+ return JSON.parse(lines[lines.length - 1])
110
+ } catch (e) {
111
+ return { text: text }
112
+ }
113
+ }
114
+
115
+ // Standard Qt text-editing keys shared by every searchable panel's filter:
116
+ // Backspace delete previous character
117
+ // Ctrl+Backspace delete previous word (Qt DeleteStartOfWord)
118
+ // Ctrl+U clear the whole field
119
+ // True only when the event would actually change the text, so an empty
120
+ // filter never swallows the key — panels keep their own empty-filter
121
+ // fallbacks (e.g. menu back-navigation) in later branches.
122
+ // The arrow a horizontal key press means along the reading direction: in a
123
+ // right-to-left layout, mirrored, Left moves forward and Right back. With
124
+ // vim set, h and l count as Left and Right too.
125
+ function readingKey(event, vim) {
126
+ var key = event.key
127
+ if (vim && (key === Qt.Key_H || event.text === "h")) key = Qt.Key_Left
128
+ else if (vim && (key === Qt.Key_L || event.text === "l")) key = Qt.Key_Right
129
+ if (I18n.rightToLeft) {
130
+ if (key === Qt.Key_Left) return Qt.Key_Right
131
+ if (key === Qt.Key_Right) return Qt.Key_Left
132
+ }
133
+ return key
134
+ }
135
+
136
+ function editsFilter(event, text) {
137
+ if (!text) return false
138
+ // Alt/Meta-modified sequences belong to other shortcuts — never edit here.
139
+ if (event.modifiers & (Qt.AltModifier | Qt.MetaModifier)) return false
140
+ if (event.key === Qt.Key_U) // Ctrl+U only (not Ctrl+Shift+U → Unicode input)
141
+ return event.modifiers === Qt.ControlModifier
142
+ return event.key === Qt.Key_Backspace // plain, Shift, or Ctrl Backspace
143
+ }
144
+
145
+ // New filter text after applying an edit key. Assumes editsFilter(event, text).
146
+ function editedFilter(event, text) {
147
+ if (event.key === Qt.Key_U) return "" // Ctrl+U: clear
148
+ if (event.modifiers & Qt.ControlModifier) // Ctrl+Backspace: word
149
+ return text.replace(/\s+$/, "").replace(/\S+$/, "")
150
+ return text.slice(0, -1) // Backspace: char
151
+ }
152
+
153
+ // Layout normalization shared by bar config consumers
154
+ // so the two never drift. Entries are deep-cloned to decouple from the
155
+ // input config; consumers can mutate without leaking back to shell.json.
156
+ function normalizeLayoutEntry(entry) {
157
+ if (typeof entry === "string") return { id: canonicalWidgetId(entry) }
158
+ if (isPlainObject(entry) && entry.id) {
159
+ var copy = cloneJson(entry)
160
+ copy.id = canonicalWidgetId(copy.id)
161
+ return copy
162
+ }
163
+ return null
164
+ }
165
+
166
+ function normalizeLayoutSection(list) {
167
+ if (!Array.isArray(list)) return []
168
+ var out = []
169
+ for (var i = 0; i < list.length; i++) {
170
+ var e = normalizeLayoutEntry(list[i])
171
+ if (e) out.push(e)
172
+ }
173
+ return out
174
+ }
175
+
176
+ // The bar's sections in the order they run along it, and tray, drawn
177
+ // inside the tray's drawer. The center box sits in the middle of the bar;
178
+ // centerBegin and centerEnd hang off its two edges.
179
+ readonly property var barSections: ["left", "centerBegin", "center", "centerEnd", "right", "tray"]
180
+
181
+ function normalizeLayout(layout) {
182
+ var src = isPlainObject(layout) ? layout : {}
183
+ var out = {}
184
+ for (var i = 0; i < barSections.length; i++)
185
+ out[barSections[i]] = normalizeLayoutSection(src[barSections[i]])
186
+ return out
187
+ }
188
+ }
@@ -0,0 +1,13 @@
1
+ module qs.Commons
2
+ singleton AudioNodes 1.0 AudioNodes.qml
3
+ singleton Border 1.0 Border.qml
4
+ singleton Color 1.0 Color.qml
5
+ singleton I18n 1.0 I18n.qml
6
+ singleton InputDevices 1.0 InputDevices.qml
7
+ singleton IpcRegistry 1.0 IpcRegistry.qml
8
+ singleton InputMethodState 1.0 InputMethodState.qml
9
+ singleton Style 1.0 Style.qml
10
+ singleton UntypedInput 1.0 UntypedInput.qml
11
+ singleton Util 1.0 Util.qml
12
+ ShellIpc 1.0 ShellIpc.qml
13
+ Translations 1.0 Translations.qml
@@ -0,0 +1,17 @@
1
+ import QtQuick
2
+ import Quickshell
3
+ import "./ui" as Flea
4
+ FloatingWindow {
5
+ id: demo
6
+ width: 800; height: 420
7
+ title: "Flea — settings segments"
8
+ color: Flea.Theme.color.background
9
+ property string choice: "List"
10
+ Column {
11
+ x: 24; y: 24; spacing: 24
12
+ Text {text:"Real Flea segmented choices";font.pixelSize:24;color:Flea.Theme.color.foreground}
13
+ Flea.SettingsSegment {options:["List","Grid","Columns"];value:demo.choice;onPicked:function(i){demo.choice=options[i]}}
14
+ Flea.SettingsSegment {options:["List","Grid","Columns"];glyphs:["list","grid","columns"];value:demo.choice;onPicked:function(i){demo.choice=options[i]}}
15
+ Text {id: status;text:"Selected: "+demo.choice;font.pixelSize:18;color:Flea.Theme.color.foreground}
16
+ }
17
+ }
@@ -0,0 +1,58 @@
1
+ import QtQuick
2
+ import QtQuick.Controls
3
+ import Quickshell
4
+ import "./ui" as Flea
5
+ import "./ui/js/TextSize.js" as TextSize
6
+ FloatingWindow {
7
+ id: demo
8
+ width: 800; height: 420
9
+ title: "Flea — text size settings | " + Flea.Theme.baseSize + " | " + demo.clicks
10
+ color: Flea.Theme.color.background
11
+ property bool editing: true
12
+ property int clicks: 0
13
+ Text {
14
+ x: 24; y: 24
15
+ text: "Flea text size"
16
+ font.pixelSize: 24
17
+ color: Flea.Theme.color.foreground
18
+ }
19
+ Flea.SettingsRuler {
20
+ id: ruler
21
+ x: 24; y: 85; width: 740
22
+ stops: TextSize.STOPS
23
+ index: TextSize.STOPS.indexOf(Flea.Theme.baseSize)
24
+ active: demo.editing
25
+ onPicked: function (size) {
26
+ demo.clicks++
27
+ // Change the real shared ViewState in memory; this demo never writes settings.
28
+ Flea.ViewState.state = {display: {textSize: {mode: size}}}
29
+ }
30
+ }
31
+ Text {
32
+ id: sample
33
+ x: 24; y: 165
34
+ text: "The quick brown fox — shared Theme"
35
+ color: Flea.Theme.color.foreground
36
+ font.family: Flea.Theme.font.family
37
+ font.pixelSize: Flea.Theme.font.body
38
+ }
39
+ Text {
40
+ id: status
41
+ x: 24; y: 220
42
+ text: "Size: " + Flea.Theme.baseSize + " / clicks: " + demo.clicks
43
+ color: Flea.Theme.color.muted
44
+ font.pixelSize: 16
45
+ }
46
+ Button {
47
+ id: toggle
48
+ x: 24; y: 280; width: 220; height: 44
49
+ text: demo.editing ? "Disable ruler" : "Enable ruler"
50
+ onClicked: demo.editing = !demo.editing
51
+ }
52
+ Button {
53
+ id: reset
54
+ x: 270; y: 280; width: 230; height: 44
55
+ text: "Follow Omarchy"
56
+ onClicked: Flea.ViewState.state = {display: {textSize: {mode: "system"}}}
57
+ }
58
+ }
@@ -0,0 +1,10 @@
1
+ import QtQuick
2
+ import Quickshell
3
+ FloatingWindow {
4
+ width: 400; height: 240
5
+ title: "Text metrics | " + empty.implicitWidth + ":" + empty.implicitHeight + ":" + empty.lineCount + ":" + single.lineCount + ":" + multi.lineCount + ":" + clipped.lineCount + ":" + clipped.truncated
6
+ Text {id:empty;text:""}
7
+ Text {id:single;y:40;text:"one"}
8
+ Text {id:multi;y:80;text:"one\ntwo"}
9
+ Text {id:clipped;y:140;text:"one\ntwo\nthree";maximumLineCount:2;elide:Text.ElideRight}
10
+ }
@@ -0,0 +1,150 @@
1
+ import QtQuick
2
+ import qs.Commons
3
+ import "js/Buttons.js" as Buttons
4
+
5
+ // The one control every dialog, card, picker button and strip action draws.
6
+ Item {
7
+ id: root
8
+
9
+ property string label: ""
10
+ property bool primary: false
11
+ // A destructive action rests as the error label in a muted frame, never primary.
12
+ property bool destructive: false
13
+ property real horizontalPadding: Theme.spacing.gap
14
+ // An unavailable action shows muted ink and its press does nothing.
15
+ property bool available: true
16
+ // The keyboard's own signal. Callers whose focus sits on a wrapper pass it down.
17
+ property bool focused: root.activeFocus
18
+ // True where the form owns the Tab order and the button only reports through tabbed.
19
+ property bool tabHandle: false
20
+ // A stop in its dialog's own Tab walk; while unavailable it is disabled, so it leaves the walk and keeps its look.
21
+ property bool tabStop: false
22
+ // A member of a set (the protocols): its frame and label are foreground while it is the current one and muted otherwise.
23
+ property bool setMember: false
24
+ property bool current: false
25
+ // Hosted in the chrome strip: a Theme.chromeHeight press area around a Theme.chromeControlHeight frame at caption size, and Tab reaches it.
26
+ property bool inStrip: false
27
+
28
+ // The pointer's own state, read by the native capture harness before it shoots a hover or a press.
29
+ readonly property bool hovered: hover.hovered
30
+ readonly property bool pressed: tap.pressed
31
+ // A release activates only inside the press area, so a held point outside draws the resting control.
32
+ readonly property bool pressShown: root.pressed && root.available && tap.point.position.x >= 0 && tap.point.position.x < root.width
33
+ && tap.point.position.y >= 0 && tap.point.position.y < root.height
34
+
35
+ signal activated()
36
+ // The form owns the order; a button only reports that Tab happened inside it.
37
+ signal tabbed(var from, bool back)
38
+
39
+ // Focus is the control's own frame: foreground, or accent for a set member where it has hue. Where a hairline would match the rest (a primary whose rest frame is the foreground, a set member without hue) the frame draws RING wide.
40
+ readonly property string focusFrame: Buttons.focusFrame(Theme.color.accentHasHue, root.setMember, root.primary && Qt.colorEqual(Theme.color.accentFrame, Theme.color.foreground))
41
+ readonly property bool focusShown: root.focused && root.available
42
+ // A secondary frame takes mutedFrame, ThemeRoles' border role, under a live label; a set member takes foreground while current and mutedFrame otherwise.
43
+ readonly property color frame: root.focusShown && root.focusFrame === "accent" ? Theme.color.accent
44
+ : root.focusShown ? Theme.color.foreground
45
+ : root.setMember ? (root.current ? Theme.color.foreground : Theme.color.mutedFrame)
46
+ : root.primary && root.available ? Theme.color.accentFrame : Theme.color.mutedFrame
47
+ readonly property color ink: !root.available || root.setMember && !root.current ? Theme.color.muted
48
+ : root.destructive ? Theme.color.error : Theme.color.foreground
49
+ // The frame and this wash say which action is being asked for; the label stays foreground.
50
+ readonly property color wash: root.primary && root.available ? Qt.alpha(Theme.color.accent, Buttons.WASH_PRIMARY) : "transparent"
51
+ // A border draws inside its rectangle, so the thick frame grows inward and nothing moves.
52
+ readonly property int frameWidth: root.focusShown && root.focusFrame === "thick" ? Buttons.RING : Theme.spacing.hairline
53
+
54
+ // A chrome strip's press area fills the strip and centres the control height above its rule.
55
+ readonly property real frameHeight: root.inStrip ? Theme.chromeControlHeight : root.height
56
+ readonly property real frameY: root.inStrip ? Math.round((root.height - Theme.spacing.hairline - root.frameHeight) / 2) : 0
57
+
58
+ implicitWidth: Math.max(Theme.hitMin, text.implicitWidth + 2 * root.horizontalPadding + 2 * Theme.spacing.hairline)
59
+ implicitHeight: root.inStrip ? Theme.chromeHeight : Theme.rowHeight - Theme.spacing.rowPaddingY
60
+ // A strip control or a tab stop stays in the Tab chain and drops out as a disabled item; flipping activeFocusOnTab under a focused control only warns.
61
+ activeFocusOnTab: root.inStrip || root.tabStop
62
+ enabled: root.available || !root.inStrip && !root.tabStop
63
+ opacity: root.available ? 1 : Buttons.DISABLED_OPACITY
64
+
65
+ Accessible.role: Accessible.Button
66
+ Accessible.name: root.label
67
+ Accessible.onPressAction: if (root.available) root.activated()
68
+
69
+ Item {
70
+ id: box
71
+ y: root.frameY
72
+ width: parent.width
73
+ height: root.frameHeight
74
+
75
+ // Only the frame and wash shrink: native text loses thin stems under a non-integer scale.
76
+ Item {
77
+ id: chrome
78
+ property int insetX: root.pressShown && !Theme.reducedMotion ? Math.round(box.width * (1 - Buttons.PRESS_SCALE) / 2) : 0
79
+ property int insetY: root.pressShown && !Theme.reducedMotion ? Math.round(box.height * (1 - Buttons.PRESS_SCALE) / 2) : 0
80
+ x: chrome.insetX
81
+ y: chrome.insetY
82
+ width: Math.round(box.width) - 2 * chrome.insetX
83
+ height: Math.round(box.height) - 2 * chrome.insetY
84
+ Behavior on insetX {
85
+ enabled: !Theme.reducedMotion
86
+ NumberAnimation { duration: Buttons.PRESS_MS; easing.type: Easing.OutCubic }
87
+ }
88
+ Behavior on insetY {
89
+ enabled: !Theme.reducedMotion
90
+ NumberAnimation { duration: Buttons.PRESS_MS; easing.type: Easing.OutCubic }
91
+ }
92
+ Rectangle {
93
+ objectName: "buttonFrame"
94
+ anchors.fill: parent
95
+ color: root.wash
96
+ border.width: root.frameWidth
97
+ border.color: root.frame
98
+ }
99
+ // Hover and press lay the control's own ink inside the frame at its width.
100
+ Rectangle {
101
+ objectName: "buttonWash"
102
+ anchors.fill: parent
103
+ anchors.margins: root.frameWidth
104
+ color: root.pressShown ? Qt.alpha(root.ink, Buttons.WASH_PRESS)
105
+ : !root.pressed && hover.hovered && root.available ? Qt.alpha(root.ink, Buttons.WASH_HOVER) : "transparent"
106
+ }
107
+ }
108
+
109
+ Text {
110
+ id: text
111
+ objectName: "buttonLabel"
112
+ anchors.centerIn: parent
113
+ text: root.label
114
+ color: root.ink
115
+ font.family: Theme.font.family
116
+ font.pixelSize: root.inStrip ? Theme.font.caption : Theme.font.body
117
+ textFormat: Text.PlainText
118
+ }
119
+ }
120
+
121
+ HoverHandler {
122
+ id: hover
123
+ cursorShape: root.available ? Qt.PointingHandCursor : Qt.ArrowCursor
124
+ }
125
+
126
+ // A held key's repeats never activate again, so a key held past a focus move cannot press the next button.
127
+ function keyActivate(event) { if (!root.available) event.accepted = false; else if (!event.isAutoRepeat) root.activated() }
128
+
129
+ // A focused button answers Enter exactly as a press does; a handled Tab is accepted here so Qt never moves focus past the form's own stepFocus order.
130
+ Keys.onReturnPressed: function(event) { root.keyActivate(event) }
131
+ Keys.onEnterPressed: function(event) { root.keyActivate(event) }
132
+ Keys.onSpacePressed: function(event) { root.keyActivate(event) }
133
+ Keys.onTabPressed: function(event) {
134
+ if (!root.tabHandle) { event.accepted = false; return }
135
+ root.tabbed(root, (event.modifiers & Qt.ShiftModifier) !== 0)
136
+ event.accepted = true
137
+ }
138
+ Keys.onBacktabPressed: function(event) {
139
+ if (!root.tabHandle) { event.accepted = false; return }
140
+ root.tabbed(root, true)
141
+ event.accepted = true
142
+ }
143
+
144
+ TapHandler {
145
+ id: tap
146
+ acceptedButtons: Qt.LeftButton
147
+ gesturePolicy: TapHandler.ReleaseWithinBounds
148
+ onTapped: if (root.available) root.activated()
149
+ }
150
+ }
@@ -0,0 +1,147 @@
1
+ import QtQuick
2
+ import qs.Commons
3
+ import "." as Flea
4
+
5
+ // One labelled field, as the canvas's Network board draws it: a small uppercase label above a
6
+ // bordered box that takes the accent while it has the caret.
7
+ Item {
8
+ id: root
9
+
10
+ property string label: ""
11
+ // An alias, not a plain property assigned from onTextChanged: writing to a property that carries
12
+ // a binding destroys that binding, so a later programmatic set (switching protocol resets the
13
+ // port) would silently never reach the field.
14
+ property alias text: field.text
15
+ property string placeholder: ""
16
+ // A password box hides what it holds, and the eye beside it reveals while held.
17
+ property bool secret: false
18
+ // The field that holds a refused answer frames itself in the error role; off everywhere else.
19
+ property bool error: false
20
+ // Bullets that stand for a refused answer the field no longer holds; the first key replaces them.
21
+ property int heldMask: 0
22
+
23
+ signal accepted()
24
+ // Keys changed the text, so a card can drop the sentence the error frame stands for.
25
+ signal edited()
26
+ // The form owns the order; a field only reports that Tab happened inside it.
27
+ signal tabbed(var from, bool back)
28
+
29
+ readonly property alias input: field
30
+ readonly property bool focused: field.activeFocus
31
+ // Read by the capture harness: whether the eye is held, and its centre in window coordinates.
32
+ readonly property bool revealed: reveal.pressed
33
+ function eyeCentre() {
34
+ var at = eye.mapToItem(null, eye.width / 2, eye.height / 2)
35
+ return Math.round(at.x) + " " + Math.round(at.y)
36
+ }
37
+
38
+ function takeFocus() {
39
+ field.forceActiveFocus()
40
+ }
41
+ readonly property real labelSpacing: 0.1
42
+
43
+ implicitHeight: caption.implicitHeight + Theme.spacing.hairline * 4 + box.height
44
+
45
+ Text {
46
+ id: caption
47
+ anchors.top: parent.top
48
+ anchors.left: parent.left
49
+ text: root.label.toUpperCase()
50
+ color: Theme.color.muted
51
+ font.family: Theme.font.family
52
+ font.pixelSize: Theme.font.caption
53
+ font.letterSpacing: Theme.font.caption * root.labelSpacing
54
+ textFormat: Text.PlainText
55
+ }
56
+
57
+ Rectangle {
58
+ id: box
59
+ anchors.top: caption.bottom
60
+ anchors.topMargin: Theme.spacing.hairline * 4
61
+ anchors.left: parent.left
62
+ anchors.right: parent.right
63
+ height: Theme.rowHeight - Theme.spacing.rowPaddingY
64
+ color: Theme.color.background
65
+ border.width: Theme.spacing.hairline
66
+ border.color: root.error ? Theme.color.error : (field.activeFocus ? Theme.color.accent : Theme.color.mutedFrame)
67
+
68
+ TextInput {
69
+ id: field
70
+ anchors.left: parent.left
71
+ anchors.right: eye.left
72
+ anchors.verticalCenter: parent.verticalCenter
73
+ anchors.leftMargin: Theme.spacing.gap
74
+ anchors.rightMargin: Theme.spacing.gap
75
+ color: Theme.color.foreground
76
+ selectionColor: Theme.color.accent
77
+ selectedTextColor: Theme.color.background
78
+ font.family: Theme.font.family
79
+ font.pixelSize: Theme.font.body
80
+ clip: true
81
+ leftPadding: heldMarks.visible ? heldMarks.width : 0
82
+ // Revealed only while the eye is held, so a shoulder never reads it from a stuck toggle.
83
+ echoMode: root.secret && !reveal.pressed ? TextInput.Password : TextInput.Normal
84
+ // The boards draw a masked value as U+2022 bullets, never Qt's larger default circle.
85
+ passwordCharacter: "\u2022"
86
+ onAccepted: root.accepted()
87
+ onTextEdited: { root.heldMask = 0; root.edited() }
88
+ Keys.onPressed: function(event) {
89
+ if ((event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) && field.text.length === 0 && root.heldMask > 0) { root.heldMask = 0; root.edited() }
90
+ }
91
+ Keys.onTabPressed: function(event) { root.tabbed(root, (event.modifiers & Qt.ShiftModifier) !== 0) }
92
+ Keys.onBacktabPressed: root.tabbed(root, true)
93
+ }
94
+
95
+ Text {
96
+ id: heldMarks
97
+ anchors.left: field.left
98
+ anchors.verticalCenter: field.verticalCenter
99
+ visible: root.heldMask > 0 && field.text.length === 0 && !reveal.pressed
100
+ text: field.passwordCharacter.repeat(Math.max(0, root.heldMask))
101
+ color: Theme.color.foreground
102
+ font: field.font
103
+ textFormat: Text.PlainText
104
+ }
105
+
106
+ Text {
107
+ anchors.left: field.left
108
+ anchors.verticalCenter: parent.verticalCenter
109
+ visible: field.text.length === 0 && !field.activeFocus && !heldMarks.visible
110
+ text: root.placeholder
111
+ color: Theme.color.muted
112
+ font.family: Theme.font.family
113
+ font.pixelSize: Theme.font.body
114
+ textFormat: Text.PlainText
115
+ elide: Text.ElideRight
116
+ }
117
+
118
+ Item {
119
+ id: eye
120
+ anchors.right: parent.right
121
+ anchors.rightMargin: Theme.spacing.gap
122
+ anchors.verticalCenter: parent.verticalCenter
123
+ width: root.secret ? Math.max(Theme.hitMin, Theme.font.bodySmall) : 0
124
+ height: root.secret ? Math.max(Theme.hitMin, Theme.font.bodySmall) : 0
125
+
126
+ Accessible.role: Accessible.Button
127
+ Accessible.name: "Show password"
128
+ Accessible.ignored: !root.secret
129
+
130
+ Flea.Glyph {
131
+ anchors.centerIn: parent
132
+ width: Theme.font.bodySmall
133
+ height: Theme.font.bodySmall
134
+ visible: root.secret
135
+ name: "eye"
136
+ color: reveal.pressed ? Theme.color.accent : Theme.color.mutedFrame
137
+ }
138
+
139
+ MouseArea {
140
+ id: reveal
141
+ anchors.fill: parent
142
+ enabled: root.secret
143
+ cursorShape: Qt.PointingHandCursor
144
+ }
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,11 @@
1
+ import QtQuick
2
+
3
+ // The word that closes a surface, in the corner every surface puts it in: Dialogs rule 7, and the
4
+ // spelling ui/SettingsPanel.qml already uses, so no two dialogs can name the way out differently.
5
+ Text {
6
+ text: "esc"
7
+ color: Theme.color.muted
8
+ font.family: Theme.font.family
9
+ font.pixelSize: Theme.font.caption
10
+ textFormat: Text.PlainText
11
+ }