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,561 @@
1
+ pragma Singleton
2
+ import QtQuick
3
+ import qs.Commons as Commons
4
+ import Quickshell
5
+ import Quickshell.Hyprland
6
+ import Quickshell.Io
7
+
8
+ // Shared structural style tokens for the shell. Color is the palette
9
+ // singleton; Style holds everything else themes can influence — corner
10
+ // rounding, gap to screen edges, state affordances, spacing, typography
11
+ // scale, and bar dimensions.
12
+ //
13
+ // `cornerRadius` mirrors Hyprland's `decoration:rounding`. `gapsOut` is
14
+ // half of Hyprland's `general:gaps_out` — Hyprland's value works well as
15
+ // a window-to-window gap but feels too cavernous when used as the
16
+ // distance from a panel/notification to the screen edge, so the shell
17
+ // halves it. Themes and user Hyprland config own those values; the
18
+ // shell picks them up by re-running `hyprctl getoption` on startup and
19
+ // after theme IPC applies a theme.
20
+ //
21
+ // Typography, spacing, and bar size come from theme/shell.toml.
22
+ // `[font] base-size` is the rem root; every `Style.font.<token>` derives
23
+ // from it via the scale multipliers below unless the theme pins that
24
+ // specific token. `[spacing] scale` multiplies shared margins, gaps,
25
+ // padding, controls, and panel dimensions while preserving each component's
26
+ // proportions; by default it also tracks `base-size`. `[bar]
27
+ // size-horizontal` / `size-vertical` set the cross-axis dimension for
28
+ // top/bottom and left/right bars at the default 12px font size; by default
29
+ // those dimensions scale with `base-size` so larger fonts don't clip.
30
+ QtObject {
31
+ id: root
32
+
33
+ property int cornerRadius: 0
34
+ property int gapsOut: 5
35
+
36
+ // ---------------------------------------------------------- state tokens
37
+ //
38
+ // Shared interactive-state tokens for every reusable surface in the kit.
39
+ // The vocabulary:
40
+ // normal — idle control chrome
41
+ // hover-cursor — mouse hover OR panel keyboard cursor (`hasCursor`)
42
+ // selected — persistent chosen/current state
43
+ // focus — actual Qt activeFocus, defaulting to hover-cursor
44
+ //
45
+ // Each state has a color token plus fill/border alphas. Color tokens
46
+ // may be palette roles (`foreground`, `accent`, `urgent`, `background`)
47
+ // or hex colors. Set a state's border width to 0 to drop that border.
48
+ property var styleOverrides: ({})
49
+
50
+ function styleRawNum(key) {
51
+ var v = styleOverrides[key]
52
+ var n = Number(v)
53
+ return isFinite(n) ? n : null
54
+ }
55
+
56
+ function styleNum(key, fallback) {
57
+ var n = styleRawNum(key)
58
+ return n === null ? fallback : n
59
+ }
60
+
61
+ function styleAlpha(key, fallback) {
62
+ return Util.clampAlpha(styleNum(key, fallback))
63
+ }
64
+
65
+ function styleString(key, fallback) {
66
+ var v = styleOverrides[key]
67
+ if (typeof v !== "string") return fallback
68
+ v = v.replace(/^\s+|\s+$/g, "")
69
+ return v.length > 0 ? v : fallback
70
+ }
71
+
72
+ readonly property string normalColorToken: styleString("normal-color", "foreground")
73
+ readonly property string hoverColorToken: styleString("hover-cursor-color", "foreground")
74
+ readonly property string selectedColorToken: styleString("selected-color", "foreground")
75
+ readonly property string pressedColorToken: styleString("pressed-color", hoverColorToken)
76
+ readonly property string focusColorToken: styleString("focus-color", hoverColorToken)
77
+ readonly property string selectionColorToken: styleString("selection-color", "foreground")
78
+
79
+ readonly property int normalBorderWidth: Math.max(0, Math.round(styleNum("normal-border-width", 1)))
80
+ readonly property int hoverBorderWidth: Math.max(0, Math.round(styleNum("hover-cursor-border-width", normalBorderWidth)))
81
+ readonly property int selectedBorderWidth: Math.max(0, Math.round(styleNum("selected-border-width", 0)))
82
+ readonly property int focusBorderWidth: Math.max(0, Math.round(styleNum("focus-border-width", hoverBorderWidth)))
83
+
84
+ readonly property real normalFillAlpha: styleAlpha("normal-fill-alpha", 0.04)
85
+ readonly property real hoverFillAlpha: styleAlpha("hover-cursor-fill-alpha", 0.08)
86
+ readonly property real selectedFillAlpha: styleAlpha("selected-fill-alpha", 0.18)
87
+ readonly property real pressedFillAlpha: styleAlpha("pressed-fill-alpha", 0.22)
88
+ readonly property real focusFillAlpha: styleAlpha("focus-fill-alpha", hoverFillAlpha)
89
+ readonly property real selectionFillAlpha: styleAlpha("selection-fill-alpha", 0.35)
90
+
91
+ readonly property real normalBorderAlpha: styleAlpha("normal-border-alpha", 0.4)
92
+ readonly property real hoverBorderAlpha: styleAlpha("hover-cursor-border-alpha", 0.25)
93
+ readonly property real selectedBorderAlpha: styleAlpha("selected-border-alpha", 1.0)
94
+ readonly property real focusBorderAlpha: styleAlpha("focus-border-alpha", hoverBorderAlpha)
95
+
96
+ function colorFromHex(value, fallback) {
97
+ var s = String(value || "").replace(/^\s+|\s+$/g, "")
98
+ var shortHex = s.match(/^#([0-9A-Fa-f]{3})$/)
99
+ if (shortHex) {
100
+ var sh = shortHex[1]
101
+ return Qt.rgba(
102
+ parseInt(sh.charAt(0) + sh.charAt(0), 16) / 255,
103
+ parseInt(sh.charAt(1) + sh.charAt(1), 16) / 255,
104
+ parseInt(sh.charAt(2) + sh.charAt(2), 16) / 255,
105
+ 1)
106
+ }
107
+ var hex = s.match(/^#([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/)
108
+ if (!hex) return fallback
109
+ var h = hex[1]
110
+ return Qt.rgba(
111
+ parseInt(h.substr(0, 2), 16) / 255,
112
+ parseInt(h.substr(2, 2), 16) / 255,
113
+ parseInt(h.substr(4, 2), 16) / 255,
114
+ hex[2] ? parseInt(hex[2], 16) / 255 : 1)
115
+ }
116
+
117
+ function resolveStateColor(token, foreground, accent, urgent, fallback) {
118
+ var fb = fallback || foreground || Commons.Color.foreground
119
+ var s = String(token || "").replace(/^\s+|\s+$/g, "")
120
+ var role = s.toLowerCase()
121
+ if (role === "foreground" || role === "text") return foreground || Commons.Color.foreground
122
+ if (role === "accent") return accent || Commons.Color.accent
123
+ if (role === "urgent") return urgent || Commons.Color.urgent
124
+ if (role === "background") return Commons.Color.background
125
+ if (role === "transparent") return Qt.rgba(0, 0, 0, 0)
126
+ return colorFromHex(s, fb)
127
+ }
128
+
129
+ function normalStateColor(foreground, accent, urgent) {
130
+ return resolveStateColor(normalColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
131
+ }
132
+
133
+ function hoverStateColor(foreground, accent, urgent) {
134
+ return resolveStateColor(hoverColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
135
+ }
136
+
137
+ function selectedStateColor(foreground, accent, urgent) {
138
+ return resolveStateColor(selectedColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
139
+ }
140
+
141
+ function pressedStateColor(foreground, accent, urgent) {
142
+ return resolveStateColor(pressedColorToken, foreground, accent, urgent, hoverStateColor(foreground, accent, urgent))
143
+ }
144
+
145
+ function focusStateColor(foreground, accent, urgent) {
146
+ var role = String(focusColorToken || "").replace(/^\s+|\s+$/g, "").toLowerCase()
147
+ if (role === "hover" || role === "hover-cursor" || role === "inherit")
148
+ return hoverStateColor(foreground, accent, urgent)
149
+ return resolveStateColor(focusColorToken, foreground, accent, urgent, hoverStateColor(foreground, accent, urgent))
150
+ }
151
+
152
+ function selectionStateColor(foreground, accent, urgent) {
153
+ return resolveStateColor(selectionColorToken, foreground, accent, urgent, foreground || Commons.Color.foreground)
154
+ }
155
+
156
+ function normalFillFor(foreground, accent, urgent) { return Util.alpha(normalStateColor(foreground, accent, urgent), normalFillAlpha) }
157
+ function hoverFillFor(foreground, accent, urgent) { return Util.alpha(hoverStateColor(foreground, accent, urgent), hoverFillAlpha) }
158
+ function selectedFillFor(foreground, accent, urgent) { return Util.alpha(selectedStateColor(foreground, accent, urgent), selectedFillAlpha) }
159
+ function pressedFillFor(foreground, accent, urgent) { return Util.alpha(pressedStateColor(foreground, accent, urgent), pressedFillAlpha) }
160
+ function focusFillFor(foreground, accent, urgent) { return Util.alpha(focusStateColor(foreground, accent, urgent), focusFillAlpha) }
161
+ function selectionFillFor(foreground, accent, urgent) { return Util.alpha(selectionStateColor(foreground, accent, urgent), selectionFillAlpha) }
162
+
163
+ function normalBorderFor(foreground, accent, urgent) { return Util.alpha(normalStateColor(foreground, accent, urgent), normalBorderAlpha) }
164
+ function hoverBorderFor(foreground, accent, urgent) { return Util.alpha(hoverStateColor(foreground, accent, urgent), hoverBorderAlpha) }
165
+ function selectedBorderFor(foreground, accent, urgent) { return Util.alpha(selectedStateColor(foreground, accent, urgent), selectedBorderAlpha) }
166
+ function focusBorderFor(foreground, accent, urgent) { return Util.alpha(focusStateColor(foreground, accent, urgent), focusBorderAlpha) }
167
+
168
+ // Composite helpers for the focus > hover > normal priority chain used by
169
+ // every form control surface (TextField, NumberField, Dropdown, Toggle,
170
+ // etc.). Saves callers from re-writing the three-line ternary ladder for
171
+ // fill / border / border-width on every Rectangle background.
172
+ function controlFill(focused, hot, foreground, accent) {
173
+ if (focused) return focusFillFor(foreground, accent)
174
+ if (hot) return hoverFillFor(foreground, accent)
175
+ return normalFillFor(foreground, accent)
176
+ }
177
+
178
+ function controlBorder(focused, hot, foreground, accent) {
179
+ if (focused) return focusBorderFor(foreground, accent)
180
+ if (hot) return hoverBorderFor(foreground, accent)
181
+ return normalBorderFor(foreground, accent)
182
+ }
183
+
184
+ function controlBorderWidth(focused, hot) {
185
+ if (focused) return focusBorderWidth
186
+ if (hot) return hoverBorderWidth
187
+ return normalBorderWidth
188
+ }
189
+
190
+ // Convenience colors resolved against the foundational palette.
191
+ readonly property color normalFill: normalFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
192
+ readonly property color hoverFill: hoverFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
193
+ readonly property color selectedFill: selectedFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
194
+ readonly property color pressedFill: pressedFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
195
+ readonly property color focusFillColor: focusFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
196
+ readonly property color normalBorderColor: normalBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
197
+ readonly property color hoverBorderColor: hoverBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
198
+ readonly property color selectedBorderColor: selectedBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
199
+ readonly property color focusBorderColor: focusBorderFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
200
+ readonly property color selectedAccentFill: Util.alpha(Commons.Color.accent, selectedFillAlpha)
201
+ readonly property color selectionFill: selectionFillFor(Commons.Color.foreground, Commons.Color.accent, Commons.Color.urgent)
202
+
203
+ // ---------------------------------------------------------- spacing
204
+ //
205
+ // The spacing scale is the shell equivalent of rem for margins, gaps,
206
+ // and padding. Components keep their existing proportions by asking for
207
+ // the old pixel value through `Style.space(px)` (or `spaceReal(px)` for
208
+ // fractional geometry); themes can make the shell denser or roomier
209
+ // with `[spacing] scale`, or pin individual tokens.
210
+ property real spacingScale: 1.0
211
+ property bool spacingScaleWithFont: true
212
+ property var spacingOverrides: ({})
213
+ readonly property real effectiveSpacingScale: spacingScale * (spacingScaleWithFont ? fontScale : 1)
214
+
215
+ function spaceReal(px) {
216
+ var n = Number(px)
217
+ if (!isFinite(n) || n <= 0) return 0
218
+ return n * effectiveSpacingScale
219
+ }
220
+
221
+ function space(px) {
222
+ var n = spaceReal(px)
223
+ if (n <= 0) return 0
224
+ return Math.max(1, Math.round(n))
225
+ }
226
+
227
+ function spacingToken(key, fallback) {
228
+ var v = spacingOverrides[key]
229
+ var n = Number(v)
230
+ return (isFinite(n) && n >= 0) ? Math.round(n) : space(fallback)
231
+ }
232
+
233
+ readonly property QtObject spacing: QtObject {
234
+ readonly property real scale: root.effectiveSpacingScale
235
+
236
+ readonly property int hairline: root.space(1)
237
+ readonly property int xxs: root.spacingToken("xxs", 2)
238
+ readonly property int xs: root.spacingToken("xs", 3)
239
+ readonly property int sm: root.spacingToken("sm", 4)
240
+ readonly property int md: root.spacingToken("md", 6)
241
+ readonly property int lg: root.spacingToken("lg", 8)
242
+ readonly property int xl: root.spacingToken("xl", 10)
243
+ readonly property int xxl: root.spacingToken("xxl", 12)
244
+ readonly property int xxxl: root.spacingToken("xxxl", 14)
245
+ readonly property int huge: root.spacingToken("huge", 18)
246
+
247
+ readonly property int controlGap: root.spacingToken("control-gap", 8)
248
+ readonly property int controlPaddingX: root.spacingToken("control-padding-x", 10)
249
+ readonly property int controlPaddingY: root.spacingToken("control-padding-y", 6)
250
+ readonly property int inputPaddingY: root.spacingToken("input-padding-y", 7)
251
+ readonly property int controlHeight: root.spacingToken("control-height", 28)
252
+ readonly property int popupRowHeight: root.spacingToken("popup-row-height", 28)
253
+ readonly property int dropdownWidth: root.spacingToken("dropdown-width", 240)
254
+ readonly property int searchableDropdownWidth: root.spacingToken("searchable-dropdown-width", 260)
255
+ readonly property int numberFieldWidth: root.spacingToken("number-field-width", 120)
256
+ readonly property int searchablePopupMinHeight: root.spacingToken("searchable-popup-min-height", 220)
257
+ readonly property int rowGap: root.spacingToken("row-gap", 8)
258
+ readonly property int rowPaddingX: root.spacingToken("row-padding-x", 12)
259
+ readonly property int labelGap: root.spacingToken("label-gap", 4)
260
+ readonly property int panelGap: root.spacingToken("panel-gap", 14)
261
+ readonly property int panelPadding: root.spacingToken("panel-padding", 18)
262
+ readonly property int popupPadding: root.spacingToken("popup-padding", 14)
263
+ }
264
+
265
+ // ---------------------------------------------------------- typography
266
+ //
267
+ // `fontFamily` defaults to "monospace" so the bar and every qs.Ui
268
+ // component follows the fontconfig alias `omarchy-font-set` writes.
269
+ // Themes can override per-token via [font] in shell.toml, but the
270
+ // family stays system-wide.
271
+ property string fontFamily: "monospace"
272
+
273
+ // The concrete family `monospace` resolves to right now, e.g.
274
+ // "JetBrainsMono Nerd Font". Bind `font.family` to `fontFamily` (so the
275
+ // alias path keeps working when the user runs `omarchy font set`), but
276
+ // read `resolvedFontFamily` when you want to *display* what's drawing.
277
+ property string resolvedFontFamily: "monospace"
278
+
279
+ // The only sanity floor is 1px. Themes and users can make this as large
280
+ // as they like; if the shell gets ridiculous, that's their call.
281
+ property int fontBaseSize: 12
282
+
283
+ property var fontOverrides: ({})
284
+ property var barOverrides: ({})
285
+ property bool barScaleWithFont: true
286
+ readonly property real fontScale: Math.max(1 / 12, fontBaseSize / 12)
287
+
288
+ function fontPx(mult) {
289
+ return Math.max(1, Math.round(fontBaseSize * mult))
290
+ }
291
+
292
+ function fontToken(key, fallback) {
293
+ var v = fontOverrides[key]
294
+ var n = Number(v)
295
+ return (isFinite(n) && n > 0) ? Math.round(n) : fallback
296
+ }
297
+
298
+ function barToken(key, fallback) {
299
+ var v = barOverrides[key]
300
+ var n = Number(v)
301
+ var base = (isFinite(n) && n > 0) ? n : fallback
302
+ if (barScaleWithFont) base *= fontScale
303
+ return Math.max(1, Math.round(base))
304
+ }
305
+
306
+ function boolToken(value, fallback) {
307
+ if (value === undefined || value === null) return fallback
308
+ var s = String(value).replace(/^\s+|\s+$/g, "").toLowerCase()
309
+ if (s === "true" || s === "1" || s === "yes" || s === "on") return true
310
+ if (s === "false" || s === "0" || s === "no" || s === "off") return false
311
+ return fallback
312
+ }
313
+
314
+ // The menu, polkit, emojis, and clipboard surfaces honor an
315
+ // OMARCHY_MENU_FONT override for users who want a different family on the
316
+ // summoned popups than on the bar. Resolved once at startup; an empty env
317
+ // value falls back to the shared fontconfig alias.
318
+ readonly property string menuFontFamily: {
319
+ var override = Quickshell.env("OMARCHY_MENU_FONT")
320
+ return (override && override.length > 0) ? override : fontFamily
321
+ }
322
+
323
+ readonly property QtObject font: QtObject {
324
+ readonly property string family: root.fontFamily
325
+ readonly property string resolvedFamily: root.resolvedFontFamily
326
+ readonly property string menuFamily: root.menuFontFamily
327
+ readonly property int baseSize: root.fontBaseSize
328
+
329
+ readonly property int caption: root.fontToken("caption", root.fontPx(0.833)) // 10
330
+ readonly property int bodySmall: root.fontToken("body-small", root.fontPx(0.917)) // 11
331
+ readonly property int body: root.fontToken("body", root.fontPx(1.0)) // 12
332
+ readonly property int subtitle: root.fontToken("subtitle", root.fontPx(1.083)) // 13
333
+ readonly property int title: root.fontToken("title", root.fontPx(1.167)) // 14
334
+ readonly property int heading: root.fontToken("heading", root.fontPx(1.333)) // 16
335
+ readonly property int display: root.fontToken("display", root.fontPx(2.0)) // 24
336
+ readonly property int displayLarge: root.fontToken("display-large", root.fontPx(2.333)) // 28
337
+
338
+ readonly property int iconSmall: root.fontToken("icon-small", bodySmall)
339
+ readonly property int icon: root.fontToken("icon", title)
340
+ readonly property int iconLarge: root.fontToken("icon-large", root.fontPx(1.5)) // 18
341
+ }
342
+
343
+ readonly property QtObject bar: QtObject {
344
+ readonly property int sizeHorizontal: root.barToken("size-horizontal", 26)
345
+ readonly property int sizeVertical: root.barToken("size-vertical", 28)
346
+ // Calibrated notch cutout height in logical px, for panels whose real
347
+ // cutout differs from the derived default (see BarModel.notchHeight).
348
+ // 0 means derive it. Physical alignment, so it never scales with font.
349
+ readonly property int notchHeight: {
350
+ var v = Number(root.barOverrides["notch-height"])
351
+ return (isFinite(v) && v > 0) ? Math.round(v) : 0
352
+ }
353
+ readonly property int iconSlot: root.barToken("icon-slot", 27)
354
+ readonly property int iconCanvas: root.barToken("icon-canvas", 16)
355
+ readonly property int iconFont: root.barToken("icon-font", 13)
356
+ readonly property int iconSize: Math.min(iconCanvas, root.barToken("icon-size", 12))
357
+ readonly property int statusSlot: root.barToken("status-slot", 21)
358
+ }
359
+
360
+ // Off with Hyprland's own animations, as `omarchy toggle animations` turns
361
+ // them off for machines that render on the CPU. Every shell animation runs
362
+ // for Style.duration(ms), which is then 0, so nothing is drawn in between.
363
+ property bool reduceMotion: false
364
+
365
+ function duration(ms) {
366
+ return reduceMotion ? 0 : ms
367
+ }
368
+
369
+ function refresh() {
370
+ hyprctlProc.running = true
371
+ gapsOutProc.running = true
372
+ animationsProc.running = true
373
+ }
374
+
375
+ function scheduleRefresh() {
376
+ refreshTimer.restart()
377
+ }
378
+
379
+ function applyRoundingJson(raw) {
380
+ try {
381
+ var json = JSON.parse(raw || "{}")
382
+ var n = Number(json.int)
383
+ if (isFinite(n) && n >= 0) cornerRadius = n
384
+ } catch (e) {
385
+ // hyprctl missing / Hyprland not running — leave the previous value.
386
+ }
387
+ }
388
+
389
+ function applyGapsOutJson(raw) {
390
+ try {
391
+ var json = JSON.parse(raw || "{}")
392
+ var css = String(json.css || "")
393
+ var parts = css.match(/-?\d+(?:\.\d+)?/g) || []
394
+ var n = parts.length > 0 ? Number(parts[0]) : Number(json.int)
395
+ if (isFinite(n) && n >= 0) gapsOut = Math.max(0, Math.round(n / 2))
396
+ } catch (e) {
397
+ // hyprctl missing / Hyprland not running — leave the previous value.
398
+ }
399
+ }
400
+
401
+ // Pull typography, bar dimensions, state tokens, and spacing out of the
402
+ // shell.toml dict that Color already parsed. Called by Commons.Color.loadShell so
403
+ // a single parse pass feeds both singletons.
404
+ function applyShellValues(values) {
405
+ var fontOut = {}
406
+ var barOut = {}
407
+ var styleOut = {}
408
+ var spacingOut = {}
409
+ var nextBase = 12
410
+ var nextSpacingScale = 1.0
411
+ var nextSpacingScaleWithFont = true
412
+ var nextBarScaleWithFont = true
413
+ var v = values || {}
414
+ for (var fullKey in v) {
415
+ var dot = fullKey.indexOf(".")
416
+ if (dot < 0) continue
417
+ var section = fullKey.substr(0, dot)
418
+ var key = fullKey.substr(dot + 1)
419
+ var raw = v[fullKey]
420
+ if (section === "font") {
421
+ var ival = parseInt(raw, 10)
422
+ if (!isFinite(ival)) continue
423
+ if (key === "base-size") nextBase = ival
424
+ else fontOut[key] = ival
425
+ } else if (section === "bar") {
426
+ if (key === "scale-with-font") {
427
+ nextBarScaleWithFont = boolToken(raw, nextBarScaleWithFont)
428
+ } else if (key === "size-horizontal" || key === "size-vertical" || key === "notch-height"
429
+ || key === "icon-slot" || key === "icon-canvas" || key === "icon-font"
430
+ || key === "icon-size" || key === "status-slot") {
431
+ var b = parseInt(raw, 10)
432
+ if (isFinite(b)) barOut[key] = b
433
+ }
434
+ } else if (section === "spacing") {
435
+ if (key === "scale-with-font") {
436
+ nextSpacingScaleWithFont = boolToken(raw, nextSpacingScaleWithFont)
437
+ } else {
438
+ var fval = parseFloat(raw)
439
+ if (!isFinite(fval)) continue
440
+ if (key === "scale") nextSpacingScale = fval
441
+ else spacingOut[key] = fval
442
+ }
443
+ } else if (section === "controls" || section === "style") {
444
+ // Strings are passed through; styleRawNum/styleString coerce on read.
445
+ // [style] is the legacy name for [controls].
446
+ styleOut[key] = raw
447
+ }
448
+ }
449
+ // Keep only a 1px sanity floor. Per-token overrides aren't clamped
450
+ // either — a theme that wants display-large = 64 should be allowed to
451
+ // ship it.
452
+ if (!isFinite(nextBase) || nextBase < 1) nextBase = 1
453
+ if (!isFinite(nextSpacingScale) || nextSpacingScale < 0) nextSpacingScale = 1.0
454
+ spacingScale = nextSpacingScale
455
+ spacingScaleWithFont = nextSpacingScaleWithFont
456
+ fontBaseSize = nextBase
457
+ fontOverrides = fontOut
458
+ barOverrides = barOut
459
+ barScaleWithFont = nextBarScaleWithFont
460
+ spacingOverrides = spacingOut
461
+ styleOverrides = styleOut
462
+ }
463
+
464
+ property Process hyprctlProc: Process {
465
+ id: hyprctlProc
466
+ command: ["hyprctl", "-j", "getoption", "decoration:rounding"]
467
+ stdout: StdioCollector {
468
+ waitForEnd: true
469
+ onStreamFinished: root.applyRoundingJson(text)
470
+ }
471
+ }
472
+
473
+ property Process animationsProc: Process {
474
+ id: animationsProc
475
+ command: ["hyprctl", "-j", "getoption", "animations:enabled"]
476
+ stdout: StdioCollector {
477
+ waitForEnd: true
478
+ onStreamFinished: {
479
+ try {
480
+ root.reduceMotion = JSON.parse(text || "{}").bool === false
481
+ } catch (e) {
482
+ // hyprctl missing / Hyprland not running — leave the previous value.
483
+ }
484
+ }
485
+ }
486
+ }
487
+
488
+ // A reload is when the animations toggle, a theme or the user's own config
489
+ // lands, and Hyprland only announces it once the new values are in effect.
490
+ property Connections hyprlandEvents: Connections {
491
+ target: Hyprland
492
+ function onRawEvent(event) {
493
+ if (event && String(event.name) === "configreloaded") root.refresh()
494
+ }
495
+ }
496
+
497
+ property Process gapsOutProc: Process {
498
+ id: gapsOutProc
499
+ command: ["hyprctl", "-j", "getoption", "general:gaps_out"]
500
+ stdout: StdioCollector {
501
+ waitForEnd: true
502
+ onStreamFinished: root.applyGapsOutJson(text)
503
+ }
504
+ }
505
+
506
+ // Resolve the fontconfig alias to a concrete family name. `omarchy font
507
+ // set <name>` rewrites ~/.config/fontconfig/fonts.conf and restarts the
508
+ // shell, but rerun on file change anyway so manual edits propagate too.
509
+ function resolveFontFamily() {
510
+ fcMatchProc.running = true
511
+ }
512
+
513
+ property Process fcMatchProc: Process {
514
+ id: fcMatchProc
515
+ command: ["fc-match", "-f", "%{family[0]}", "monospace"]
516
+ stdout: StdioCollector {
517
+ waitForEnd: true
518
+ onStreamFinished: {
519
+ var name = String(text || "").trim()
520
+ if (name.length > 0) root.resolvedFontFamily = name
521
+ }
522
+ }
523
+ }
524
+
525
+ property FileView fontconfigFile: FileView {
526
+ path: Quickshell.env("HOME") + "/.config/fontconfig/fonts.conf"
527
+ watchChanges: true
528
+ printErrors: false
529
+ onFileChanged: root.resolveFontFamily()
530
+ onLoaded: root.resolveFontFamily()
531
+ onLoadFailed: root.resolveFontFamily()
532
+ }
533
+
534
+ // Re-poll Hyprland a beat after either input file changes. Hyprland's
535
+ // auto-reload runs asynchronously when its sourced .lua files change,
536
+ // so racing it with an immediate hyprctl gives the old value. 200ms is
537
+ // generous enough for Hyprland to settle without being user-visible.
538
+ property Timer refreshTimer: Timer {
539
+ id: refreshTimer
540
+ interval: 200
541
+ repeat: false
542
+ onTriggered: root.refresh()
543
+ }
544
+
545
+ // `omarchy toggle window-gaps` creates/removes this flag file. Hyprland
546
+ // reloads its config when sourced files change, then hyprctl reflects
547
+ // the new effective value.
548
+ property FileView windowNoGapsToggle: FileView {
549
+ path: Quickshell.env("HOME") + "/.local/state/omarchy/toggles/hypr/window-no-gaps.lua"
550
+ watchChanges: true
551
+ printErrors: false
552
+ onFileChanged: refreshTimer.restart()
553
+ onLoaded: refreshTimer.restart()
554
+ onLoadFailed: refreshTimer.restart()
555
+ }
556
+
557
+ Component.onCompleted: {
558
+ refresh()
559
+ resolveFontFamily()
560
+ }
561
+ }