buninu-browser 0.2.4 → 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.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +102 -2
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +57 -0
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +78 -13
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/paint/frame.js +35 -3
- package/lib/paint/raw-frames.js +47 -0
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/compositor-channel.js +103 -0
- package/lib/renderer/compositor.js +25 -33
- package/lib/renderer/flags.js +1 -1
- package/lib/renderer/host.js +128 -92
- package/lib/renderer/main.js +38 -4
- package/lib/renderer/page-realm.js +32 -1
- package/lib/renderer/page-renderer.js +250 -21
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +54 -0
- package/test/fixtures/browser-raw-demo.html +36 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/raw-capture-benchmark.json +76 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/raw-capture.test.js +152 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
- package/test/window-resize.test.js +36 -0
- package/tools/bench-raw-capture.js +32 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// SI, because a file's size on disk has no power-of-two basis; GLib's own rule and the whole GUI group.
|
|
4
|
+
var BYTES_PER_UNIT = 1000
|
|
5
|
+
var UNITS = ["B", "kB", "MB", "GB", "TB"]
|
|
6
|
+
// GNOME renders a narrow no-break space before the unit, and Flea's neighbours are GLib-formatted.
|
|
7
|
+
var UNIT_SPACE = " "
|
|
8
|
+
|
|
9
|
+
// Below one kilobyte a fraction is noise, so bytes print whole.
|
|
10
|
+
// Counts reach six figures on a real directory, so they are grouped the way the canvas draws them.
|
|
11
|
+
function count(n) {
|
|
12
|
+
var digits = String(n)
|
|
13
|
+
var out = ""
|
|
14
|
+
for (var i = 0; i < digits.length; i++) {
|
|
15
|
+
if (i > 0 && (digits.length - i) % 3 === 0)
|
|
16
|
+
out += ","
|
|
17
|
+
out += digits.charAt(i)
|
|
18
|
+
}
|
|
19
|
+
return out
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function size(bytes) {
|
|
23
|
+
if (bytes < BYTES_PER_UNIT) {
|
|
24
|
+
return bytes + UNIT_SPACE + UNITS[0]
|
|
25
|
+
}
|
|
26
|
+
var value = bytes
|
|
27
|
+
var unit = 0
|
|
28
|
+
while (value >= BYTES_PER_UNIT && unit < UNITS.length - 1) {
|
|
29
|
+
value = value / BYTES_PER_UNIT
|
|
30
|
+
unit += 1
|
|
31
|
+
}
|
|
32
|
+
return value.toFixed(1) + UNIT_SPACE + UNITS[unit]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Fills %1 to %9 in one pass: a value prints as given, a dollar sign or placeholder in it included; an unfilled one stays.
|
|
36
|
+
function fill(template) {
|
|
37
|
+
var values = arguments
|
|
38
|
+
return String(template).replace(/%([1-9])/g, function (token, at) { return at < values.length ? String(values[at]) : token })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function pad(n) {
|
|
42
|
+
return n < 10 ? "0" + n : "" + n
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// "2026-09-12 15:29", the one form every surface prints, in the machine's local wall clock.
|
|
46
|
+
function stamp(d) {
|
|
47
|
+
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate())
|
|
48
|
+
+ " " + pad(d.getHours()) + ":" + pad(d.getMinutes())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// One form, sortable and unambiguous, so no surface has to invent a relative word for a time.
|
|
52
|
+
function date(mtime) {
|
|
53
|
+
return stamp(new Date(mtime * 1000))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The send picker's column is SendPicker.html's 80 and not the window's 125, which holds about ten
|
|
57
|
+
// characters: the one place the full stamp does not fit. It drops the time and keeps the date, so it
|
|
58
|
+
// is still sortable and still unambiguous. Preview board, "One function, four surfaces".
|
|
59
|
+
function compactDate(mtime) {
|
|
60
|
+
var d = new Date(mtime * 1000)
|
|
61
|
+
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate())
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The low nine bits of st_mode, read three at a time.
|
|
65
|
+
var PERMISSION_BITS = 9
|
|
66
|
+
var TRIAD = "rwx"
|
|
67
|
+
|
|
68
|
+
function permissions(mode) {
|
|
69
|
+
var out = ""
|
|
70
|
+
for (var i = 0; i < PERMISSION_BITS; i++) {
|
|
71
|
+
var bit = 1 << (PERMISSION_BITS - 1 - i)
|
|
72
|
+
out += (mode & bit) ? TRIAD[i % TRIAD.length] : "-"
|
|
73
|
+
}
|
|
74
|
+
return out
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The file type lives in the top four bits of st_mode, as S_IFMT masks it.
|
|
78
|
+
var S_IFMT = 0o170000
|
|
79
|
+
var S_IFREG = 0o100000
|
|
80
|
+
var S_IFDIR = 0o040000
|
|
81
|
+
// The owner execute bit, the one Make executable adds.
|
|
82
|
+
var S_IXUSR = 0o100
|
|
83
|
+
var S_IFLNK = 0o120000
|
|
84
|
+
var ANY_EXECUTE_BIT = 0o111
|
|
85
|
+
|
|
86
|
+
function isSymlink(mode) {
|
|
87
|
+
return (mode & S_IFMT) === S_IFLNK
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isExecutable(mode) {
|
|
91
|
+
return (mode & ANY_EXECUTE_BIT) !== 0
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Shared by Row.qml's iconSource and PreviewMedia.qml's player source: encodeURI leaves # and ?
|
|
95
|
+
// literal, which Qt then reads as a URL fragment or query rather than path bytes.
|
|
96
|
+
function fileUri(path) {
|
|
97
|
+
return "file://" + encodeURI(path).replace(/#/g, "%23").replace(/\?/g, "%3F")
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// "3:05", or "1:03:05" once an hour is on the clock; mm/ss are always two digits, matching a media player's own clock rather than Format.date's prose.
|
|
101
|
+
function duration(ms) {
|
|
102
|
+
var totalSeconds = Math.max(0, Math.floor(ms / 1000))
|
|
103
|
+
var hours = Math.floor(totalSeconds / 3600)
|
|
104
|
+
var minutes = Math.floor((totalSeconds % 3600) / 60)
|
|
105
|
+
var seconds = totalSeconds % 60
|
|
106
|
+
if (hours > 0) {
|
|
107
|
+
return hours + ":" + pad(minutes) + ":" + pad(seconds)
|
|
108
|
+
}
|
|
109
|
+
return minutes + ":" + pad(seconds)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The scope reads as the user writes it, so the home prefix comes back as a tilde. Both the search
|
|
113
|
+
// strip and the window chrome draw a path through this, so the rule has one definition.
|
|
114
|
+
// Issue 95, nixfred: a bare prefix made /home/gmx into "~x", a sibling wearing home's name. The test
|
|
115
|
+
// is home itself or home and a separator, the one ui/js/Nav.js crumbs and Search.scopeRoot both make.
|
|
116
|
+
function tilde(path, home) {
|
|
117
|
+
var text = String(path)
|
|
118
|
+
if (home.length > 0 && (text === home || text.indexOf(home + "/") === 0)) {
|
|
119
|
+
return "~" + text.substring(home.length)
|
|
120
|
+
}
|
|
121
|
+
return text
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// A tab is named after the directory it is standing in, so the label is the path's last segment.
|
|
125
|
+
function leafPart(display) {
|
|
126
|
+
var text = String(display)
|
|
127
|
+
var cut = text.lastIndexOf("/")
|
|
128
|
+
if (cut < 0) {
|
|
129
|
+
return text
|
|
130
|
+
}
|
|
131
|
+
// "/" itself has no leaf, and its own separator is the whole label.
|
|
132
|
+
return cut === text.length - 1 ? text : text.substring(cut + 1)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// "44.1 kHz", the way the canvas writes an audio row's Rate; a zero is not a rate and reads empty.
|
|
136
|
+
function sampleRate(hz) {
|
|
137
|
+
var n = Number(hz)
|
|
138
|
+
if (!n || n <= 0) {
|
|
139
|
+
return ""
|
|
140
|
+
}
|
|
141
|
+
var khz = n / 1000
|
|
142
|
+
// A whole number of kilohertz reads without a decimal, so 48000 is "48 kHz" and not "48.0 kHz".
|
|
143
|
+
return (khz === Math.round(khz) ? khz : khz.toFixed(1)) + " kHz"
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Issue 67, jesedv: a yanked path is quoted unless a shell reads every character of it as itself.
|
|
147
|
+
function shellQuoted(path) {
|
|
148
|
+
var text = String(path)
|
|
149
|
+
if (/^[A-Za-z0-9_@%+=:,.\/-]+$/.test(text)) {
|
|
150
|
+
return text
|
|
151
|
+
}
|
|
152
|
+
return "'" + text.split("'").join("'\\''") + "'"
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// One window-level day boundary, so no Date is built and no timer ticks per row.
|
|
156
|
+
|
|
157
|
+
// Sample input: Date.now() on the board date, 2026-09-23 11:40 local.
|
|
158
|
+
var DAY_MS = 24 * 60 * 60 * 1000
|
|
159
|
+
// The midnight timer's minute step, so a suspend still lands the day within a minute of resume.
|
|
160
|
+
var MINUTE_MS = 60 * 1000
|
|
161
|
+
|
|
162
|
+
// Local midnight starting the day nowMs falls in, in ms since the epoch.
|
|
163
|
+
function dayStart(nowMs) {
|
|
164
|
+
var now = new Date(nowMs)
|
|
165
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// True when the stamp is on or after today's local midnight.
|
|
169
|
+
function isRecent(mtimeSec, todayStartMs) {
|
|
170
|
+
if (typeof mtimeSec !== "number" || !isFinite(mtimeSec))
|
|
171
|
+
return false
|
|
172
|
+
return mtimeSec * 1000 >= todayStartMs
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Built from components, so a daylight-saving night still lands on midnight.
|
|
176
|
+
function msUntilMidnight(nowMs) {
|
|
177
|
+
var now = new Date(nowMs)
|
|
178
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).getTime() - nowMs
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Elides in the middle so the extension stays visible; truncation costs a slice, never a relayout.
|
|
182
|
+
// Sample input: "screenshot-2026-08-30-final-review-for-gm-after-the-bench-v3.png", 49.
|
|
183
|
+
|
|
184
|
+
// The walk pairs surrogates by hand, so a cut never splits a code point.
|
|
185
|
+
function charsOf(text) {
|
|
186
|
+
var out = []
|
|
187
|
+
for (var i = 0; i < text.length; i++) {
|
|
188
|
+
var lead = text.charCodeAt(i)
|
|
189
|
+
if (lead >= 0xD800 && lead <= 0xDBFF && i + 1 < text.length) {
|
|
190
|
+
var trail = text.charCodeAt(i + 1)
|
|
191
|
+
if (trail >= 0xDC00 && trail <= 0xDFFF) {
|
|
192
|
+
out.push(text.substring(i, i + 2))
|
|
193
|
+
i += 1
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// A combining mark rides with the char before it, so an NFD accent never orphans.
|
|
198
|
+
if (lead >= 0x0300 && lead <= 0x036F && out.length > 0) {
|
|
199
|
+
out[out.length - 1] += text.charAt(i)
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
out.push(text.charAt(i))
|
|
203
|
+
}
|
|
204
|
+
return out
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Every fast unit sits below U+0300, so a fast name holds no surrogate, no combining mark and no wide glyph.
|
|
208
|
+
var FAST_LIMIT = 0x0300
|
|
209
|
+
|
|
210
|
+
// True when every UTF-16 unit is fast: each char paints one cell and slices never split a point.
|
|
211
|
+
function isFastText(text) {
|
|
212
|
+
for (var i = 0; i < text.length; i++)
|
|
213
|
+
if (text.charCodeAt(i) >= FAST_LIMIT) return false
|
|
214
|
+
return true
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function middleElide(name, maxCells) {
|
|
218
|
+
var text = String(name), max = Math.max(0, Math.floor(maxCells))
|
|
219
|
+
// A non-finite budget names no width, so the name goes through untouched.
|
|
220
|
+
if (!isFinite(max)) return text
|
|
221
|
+
// One scan serves the fit check and the store below, so a fitting fast name builds nothing.
|
|
222
|
+
var fast = isFastText(text)
|
|
223
|
+
// A fast name counts one cell a char, so a short one already fits untouched.
|
|
224
|
+
if (fast && text.length <= max) return text
|
|
225
|
+
var st = fast ? fastStoreOf(text) : storeOf(charsOf(text))
|
|
226
|
+
if (rangeCells(st, 0, st.n) <= max) return text
|
|
227
|
+
// The head takes the odd cell, so a 49-wide board keeps 24 and 24.
|
|
228
|
+
var h = spanFrom(st, 0, Math.ceil((max - 1) / 2)), t = tailSpan(st, Math.floor((max - 1) / 2))
|
|
229
|
+
return pieceOf(st, 0, h) + "…" + pieceOf(st, st.n - t, st.n)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Display cells: East Asian Wide and Fullwidth paint two columns, emoji paint two, all else one.
|
|
233
|
+
// Sample input: cellWidthOf("🎉") is 2, cellWidthOf("A") is 1.
|
|
234
|
+
var WIDE_RANGES = [[0x1100, 0x115F], [0x2E80, 0x303E], [0x3041, 0x33FF], [0x3400, 0x4DBF], [0x4E00, 0xA4CF], [0xAC00, 0xD7A3], [0xF900, 0xFAFF], [0xFE10, 0xFE19], [0xFE30, 0xFE4F], [0xFF00, 0xFF60], [0xFFE0, 0xFFE6], [0x20000, 0x3FFFD]]
|
|
235
|
+
var EMOJI_RANGES = [[0x2600, 0x26FF], [0x2700, 0x27BF], [0x2B00, 0x2BFF], [0x1F000, 0x1FAFF]]
|
|
236
|
+
|
|
237
|
+
// True when a code point paints two columns rather than one.
|
|
238
|
+
function isWideCode(cp) {
|
|
239
|
+
// Every wide range starts at U+1100, so a smaller code point never scans.
|
|
240
|
+
if (cp < 0x1100) return false
|
|
241
|
+
for (var i = 0; i < WIDE_RANGES.length; i++)
|
|
242
|
+
if (cp >= WIDE_RANGES[i][0] && cp <= WIDE_RANGES[i][1]) return true
|
|
243
|
+
for (var j = 0; j < EMOJI_RANGES.length; j++)
|
|
244
|
+
if (cp >= EMOJI_RANGES[j][0] && cp <= EMOJI_RANGES[j][1]) return true
|
|
245
|
+
return false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// The code point one charsOf element starts with, read without wrapping it in a string.
|
|
249
|
+
// Sample input: codeOf("🎉") is 0x1F389, codeOf("A") is 0x41.
|
|
250
|
+
function codeOf(ch) {
|
|
251
|
+
var s = String(ch), lead = s.charCodeAt(0)
|
|
252
|
+
if (lead >= 0xD800 && lead <= 0xDBFF && s.length > 1) {
|
|
253
|
+
var trail = s.charCodeAt(1)
|
|
254
|
+
if (trail >= 0xDC00 && trail <= 0xDFFF) return 0x10000 + ((lead - 0xD800) << 10) + (trail - 0xDC00)
|
|
255
|
+
}
|
|
256
|
+
return lead
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Cells one char paints: 2 for a wide code point, 1 for all else.
|
|
260
|
+
function cellWidthOf(ch) {
|
|
261
|
+
return isWideCode(codeOf(ch)) ? 2 : 1
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// One layout per call: widths plus prefix sums, so every range below is one subtraction.
|
|
265
|
+
// Sample input: storeOf(["a", "🎉"]) carries widths [1, 2].
|
|
266
|
+
function storeOf(chars) {
|
|
267
|
+
var widths = new Array(chars.length), prefix = new Array(chars.length + 1)
|
|
268
|
+
prefix[0] = 0
|
|
269
|
+
for (var i = 0; i < chars.length; i++) {
|
|
270
|
+
widths[i] = isWideCode(codeOf(chars[i])) ? 2 : 1
|
|
271
|
+
prefix[i + 1] = prefix[i] + widths[i]
|
|
272
|
+
}
|
|
273
|
+
return { seq: chars, widths: widths, prefix: prefix, n: chars.length }
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// The same layout over a fast string: no arrays, every range counts chars as cells.
|
|
277
|
+
function fastStoreOf(text) {
|
|
278
|
+
return { seq: text, widths: null, prefix: null, n: text.length }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Cells of elements [a..b).
|
|
282
|
+
function rangeCells(st, a, b) {
|
|
283
|
+
return st.widths === null ? b - a : st.prefix[b] - st.prefix[a]
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Elements from pos fitting in this many cells.
|
|
287
|
+
function spanFrom(st, pos, budget) {
|
|
288
|
+
if (st.widths === null) return Math.min(Math.max(budget, 0), st.n - pos)
|
|
289
|
+
var used = 0, i = pos
|
|
290
|
+
while (i < st.widths.length && used + st.widths[i] <= budget) { used += st.widths[i]; i += 1 }
|
|
291
|
+
return i - pos
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Trailing elements fitting in this many cells.
|
|
295
|
+
function tailSpan(st, budget) {
|
|
296
|
+
if (st.widths === null) return Math.min(Math.max(budget, 0), st.n)
|
|
297
|
+
var used = 0, i = st.widths.length
|
|
298
|
+
while (i > 0 && used + st.widths[i - 1] <= budget) { used += st.widths[i - 1]; i -= 1 }
|
|
299
|
+
return st.widths.length - i
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Text of elements [a..b): a slice for a fast string, a join for a char array.
|
|
303
|
+
function pieceOf(st, a, b) {
|
|
304
|
+
return st.widths === null ? st.seq.slice(a, b) : st.seq.slice(a, b).join("")
|
|
305
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Format.js" as Format
|
|
4
|
+
|
|
5
|
+
// The backend sends a freedesktop icon name per row; this is the only place Flea maps one to a mark.
|
|
6
|
+
// Counts re-derived 2026-08-31 from /usr/share/mime/generic-icons on this box, see AGENTS.md "Icons in the row":
|
|
7
|
+
// x-office-document 106, package-x-generic 89, application-x-executable 57, text-x-generic 46,
|
|
8
|
+
// image-x-generic 40, x-office-spreadsheet 38, text-x-script 36, font-x-generic 23, x-office-presentation 21,
|
|
9
|
+
// video-x-generic 15, text-html 11, then a tail of five or fewer each.
|
|
10
|
+
var GLYPHS = {
|
|
11
|
+
"folder": "folder",
|
|
12
|
+
"inode-directory": "folder",
|
|
13
|
+
"text-x-generic": "file-text",
|
|
14
|
+
"text-plain": "file-text",
|
|
15
|
+
// text-x-script, text-html and application-xml are 49 types a generic file mark would hide.
|
|
16
|
+
"text-x-script": "code",
|
|
17
|
+
"text-html": "code",
|
|
18
|
+
"application-xml": "code",
|
|
19
|
+
"image-x-generic": "image",
|
|
20
|
+
"video-x-generic": "film",
|
|
21
|
+
"audio-x-generic": "music",
|
|
22
|
+
"package-x-generic": "archive",
|
|
23
|
+
"font-x-generic": "type",
|
|
24
|
+
"application-x-executable": "terminal",
|
|
25
|
+
"application-x-generic": "file",
|
|
26
|
+
"x-office-document": "file-text",
|
|
27
|
+
"x-office-spreadsheet": "table",
|
|
28
|
+
"x-office-presentation": "presentation"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var FALLBACK = "file"
|
|
32
|
+
|
|
33
|
+
function glyphFor(iconName) {
|
|
34
|
+
var g = GLYPHS[String(iconName)]
|
|
35
|
+
return g ? g : FALLBACK
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// A row's mark. The backend resolves a symlink's icon to its target's, so a link to a directory
|
|
39
|
+
// arrives as "folder"; every board draws such a row with the link mark instead, so the mode decides
|
|
40
|
+
// here and the icon name only answers for what the row really is.
|
|
41
|
+
function glyphForRow(iconName, mode) {
|
|
42
|
+
if (Format.isSymlink(mode))
|
|
43
|
+
return "symlink"
|
|
44
|
+
return glyphFor(iconName)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The sidebar's own set, keyed on the favourite's label (Places.js leaf() or bookmark label),
|
|
48
|
+
// the operator's overnight ask; any bookmark outside this set draws the plain folder mark.
|
|
49
|
+
var SIDEBAR_GLYPHS = {
|
|
50
|
+
"Home": "house",
|
|
51
|
+
"Downloads": "download",
|
|
52
|
+
"Documents": "file-text",
|
|
53
|
+
"Pictures": "image",
|
|
54
|
+
"Videos": "film",
|
|
55
|
+
"Music": "music",
|
|
56
|
+
"Projects": "folder-git-2"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
var SIDEBAR_FALLBACK = "folder"
|
|
60
|
+
|
|
61
|
+
function sidebarGlyphFor(label) {
|
|
62
|
+
var g = SIDEBAR_GLYPHS[String(label)]
|
|
63
|
+
return g ? g : SIDEBAR_FALLBACK
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Lucide-static 1.38.0 geometry, ISC licence, https://github.com/lucide-icons/lucide, one "d"
|
|
67
|
+
// per mark on its native 24 unit grid, recut to the Omarchy edge: baked rounded corners became
|
|
68
|
+
// hard corners, real curves stayed; see AGENTS.md "Lucide path data", "The Omarchy cut".
|
|
69
|
+
var PATHS = {
|
|
70
|
+
"info": "M12 11v6 M12 7h.01 M22 12a10 10 0 1 1-20 0a10 10 0 1 1 20 0",
|
|
71
|
+
"shelf": "M21 21H3V3h18v14H7V7h10v6h-6",
|
|
72
|
+
"pin": "M9 3h6v6l3 3v2H6v-2l3-3z M12 14v7",
|
|
73
|
+
"star": "M12 2l2.9 6.6 7.1.6-5.4 4.7 1.6 7L12 17.3l-6.2 3.6 1.6-7L2 9.2l7.1-.6z",
|
|
74
|
+
"globe": "M2 12h20 M12 2a15 15 0 0 1 0 20 M12 2a15 15 0 0 0 0 20 M22 12a10 10 0 1 1-20 0a10 10 0 1 1 20 0",
|
|
75
|
+
|
|
76
|
+
"file": "M4 22V2h10l6 6v14H4z M14 2v6h6",
|
|
77
|
+
"folder": "M2 20V3h6l2 3h12v14H2z",
|
|
78
|
+
"folders-first": "M2 12V4h6l2 2h12v6H2z M2 17h20 M2 21h20",
|
|
79
|
+
"file-text": "M4 22V2h10l6 6v14H4z M14 2v6h6 M8 9h2 M8 13h8 M8 17h8",
|
|
80
|
+
// LanguageMarks' Markdown mark, Flea's cut of dcurtis/markdown-mark (CC0): the frame, the M and the down arrow in one stroke.
|
|
81
|
+
"markdown": "M2 5h20v14H2z M5.5 15V9l3 3.5 3-3.5v6 M17 9v6 M14.5 12.5 17 15l2.5-2.5",
|
|
82
|
+
"html": "M4 2h16l-1.5 17L12 22l-6.5-3z M15.5 6.5h-7V11h7v5L12 17.5 8.5 16",
|
|
83
|
+
"code": "M16 18l6-6-6-6 M8 6l-6 6 6 6",
|
|
84
|
+
"image": "M3 3h18v18H3z M7 9A2 2 0 1 0 11 9A2 2 0 1 0 7 9Z M21 15l-3-3L6 21",
|
|
85
|
+
"film": "M3 3h18v18H3z M7 3v18 M3 7.5h4 M3 12h18 M3 16.5h4 M17 3v18 M17 7.5h4 M17 16.5h4",
|
|
86
|
+
// Note heads squared instead of lucide's circles: the set's brand tell.
|
|
87
|
+
"music": "M9 18V5l12-2v13 M3 15h6v6H3z M15 13h6v6h-6z",
|
|
88
|
+
"archive": "M2 3h20v5H2z M4 8v13h16V8 M10 12h4",
|
|
89
|
+
"type": "M12 4v16 M4 7V4h16v3 M9 20h6",
|
|
90
|
+
"terminal": "M12 19h8 M4 17l6-6-6-6",
|
|
91
|
+
"table": "M3 3h18v18H3z M12 3v18 M3 9h18 M3 15h18",
|
|
92
|
+
"presentation": "M2 3h20 M3 3v13h18V3 M7 21l5-5 5 5",
|
|
93
|
+
"house": "M3 21V10l9-7 9 7v11h-6v-8H9v8H3z",
|
|
94
|
+
"download": "M12 15V3 M7 10l5 5 5-5 M3 15v6h18v-6",
|
|
95
|
+
"folder-git-2": "M18 19a5 5 0 0 1-5-5v8 M9 20H2V3h6l2 2h12v5 M11 12A2 2 0 1 0 15 12A2 2 0 1 0 11 12Z M18 19A2 2 0 1 0 22 19A2 2 0 1 0 18 19Z",
|
|
96
|
+
// The two near-zero-length lines are lucide's own technique for the rack unit's LED dots; SquareCap draws them as square dots, matching the cut.
|
|
97
|
+
"server": "M2 2h20v8H2z M2 14h20v8H2z M6 6L6.01 6 M6 18L6.01 18",
|
|
98
|
+
// The Network group's add mark, replacing a Text "+" the operator read as a Christian cross.
|
|
99
|
+
"plus": "M5 12h14 M12 5v14",
|
|
100
|
+
// The picker rail's Recent mark, lucide rotate-ccw, the one SendPicker.html itself draws; the
|
|
101
|
+
// circle is a real curve and stays, and the arrow head is already square on lucide's own grid.
|
|
102
|
+
"history": "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8 M3 3v5h5",
|
|
103
|
+
// Lucide's hard-drive with its four baked 2 unit corner arcs cut square, which lands the body on
|
|
104
|
+
// (6,4) (18,4) (22,12) (22,20) (2,20) (2,12); the divider and the two LED dots are lucide's own.
|
|
105
|
+
"drive": "M6 4h12l4 8v8H2v-8z M2 12h20 M6 16L6.01 16 M10 16L10.01 16",
|
|
106
|
+
// The DEVICES rail's phone mark, lucide's smartphone with its rx=2 body rect cut square the way
|
|
107
|
+
// drive's arcs were; the home-button dot is lucide's own near-zero line, drawn as server's are.
|
|
108
|
+
"smartphone": "M5 2h14v20H5z M12 18L12.01 18",
|
|
109
|
+
// Its GPhoto2 sibling, per the PhoneMark board: lucide's camera with the four a2 2 body arcs cut
|
|
110
|
+
// to (2,7) (22,7) (22,20) (2,20), the pentaprism kept, and the lens a genuine circle, cut rule 4.
|
|
111
|
+
"camera": "M14.5 4h-5L7 7H2v13h20V7h-5z M15 13a3 3 0 1 1-6 0a3 3 0 1 1 6 0",
|
|
112
|
+
// The rail menu's release mark, the shelf's "for: unmount": lucide's triangle corner arcs
|
|
113
|
+
// extend to (12, 2.09) (22.32, 13) (1.68, 13) and snap to grid, its rx=1 bar rect squares off.
|
|
114
|
+
"eject": "M12 2 22 13H2z M3 17h18v4H3z",
|
|
115
|
+
// Task 22's transport strip, recut sharp like the rest of the set.
|
|
116
|
+
"play": "M6 4l14 8-14 8z",
|
|
117
|
+
"pause": "M14 3h5v18h-5z M5 3h5v18H5z",
|
|
118
|
+
// The search strip's mark; the lens is a real curve, so it stays a circle under the cut.
|
|
119
|
+
"search": "M16.7 16.7 21 21 M3 11A8 8 0 1 0 19 11A8 8 0 1 0 3 11Z",
|
|
120
|
+
// The filter strip's mark, the funnel the icon spec already shelves and the Main board draws.
|
|
121
|
+
"filter": "M3 4h18l-7 8v7l-4 2v-9z",
|
|
122
|
+
// The context menu's own seven marks, added with the operations surface that draws them.
|
|
123
|
+
// The canvas's own open folder, which keeps the closed folder's back panel byte for byte.
|
|
124
|
+
"folder-open": "M2 20V3h6l2 3h12v3 M22 11l-2.5 9H2l2.5-9z",
|
|
125
|
+
// OpenWith.html draws this window for the Open with row, its tail row and an app the theme cannot name.
|
|
126
|
+
"app-window": "M3 4h18v16H3z M3 9h18 M6 6.5h.01 M9 6.5h.01",
|
|
127
|
+
"undo": "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8 M3 3v5h5",
|
|
128
|
+
"rename": "M3 21l1-4L17 4l3 3L7 20l-4 1z M14 7l3 3",
|
|
129
|
+
"file-plus": "M6 22V2h8l4 4v16H6z M14 2v4h4 M12 11v6 M9 14h6",
|
|
130
|
+
// lucide's own folder-plus is the folder body byte for byte plus these two strokes, so the recut
|
|
131
|
+
// body is reused verbatim and only the plus is new; the specimen sheet's 5 unit plus is not it.
|
|
132
|
+
"folder-plus": "M2 20V3h6l2 3h12v14H2z M12 10v6 M9 13h6",
|
|
133
|
+
"trash": "M3 6h18 M8 6V3h8v3 M6 6l1.2 15h9.6L18 6 M10 10v7 M14 10v7",
|
|
134
|
+
// A diamond eye with a square pupil: lucide's own eye is two arcs meeting at points, which the cut squares off.
|
|
135
|
+
"eye": "M12 5 22 12 12 19 2 12z M10 10h4v4h-4z",
|
|
136
|
+
"eye-off": "M12 5 22 12 12 19 2 12z M10 10h4v4h-4z M4 4l16 16",
|
|
137
|
+
// Replaces the "▸" Text the menu used to draw, so it stops mixing a font dingbat into a path language.
|
|
138
|
+
"chevron-right": "M9 18l6-6-6-6",
|
|
139
|
+
"chevron-down": "M6 9l6 6 6-6",
|
|
140
|
+
"chevron-left": "M15 18l-6-6 6-6",
|
|
141
|
+
// The window chrome's own five marks, added with the bar that draws them.
|
|
142
|
+
"arrow-left": "M19 12H5 M12 19l-7-7 7-7",
|
|
143
|
+
"arrow-up": "M12 19V5 M5 12l7-7 7 7",
|
|
144
|
+
// The bullets are lucide's own zero-length-line technique, which SquareCap draws as square dots.
|
|
145
|
+
"list": "M8 6h13 M8 12h13 M8 18h13 M3 6L3.01 6 M3 12L3.01 12 M3 18L3.01 18",
|
|
146
|
+
"columns": "M3 3h18v18H3z M9 3v18 M15 3v18",
|
|
147
|
+
"preview": "M3 3h18v18H3z M15 3v18",
|
|
148
|
+
"dual": "M3 4h8v16H3z M13 4h8v16h-8z",
|
|
149
|
+
"grid": "M3 3h8v8H3z M13 3h8v8h-8z M3 13h8v8H3z M13 13h8v8h-8z",
|
|
150
|
+
// The preview column's own two, taken from the canvas's icon table rather than recut from lucide.
|
|
151
|
+
"symlink": "M7 17L17 7 M8 7h9v9",
|
|
152
|
+
// The dot is the zero-length-line technique again, which SquareCap draws square.
|
|
153
|
+
"alert": "M12 2 23 21H1z M12 10v4 M12 18L12.01 18",
|
|
154
|
+
// The Locked pane state draws this beside it: the shackle is a real curve so it stays an arc, and the keyhole is the same dot technique.
|
|
155
|
+
"lock": "M4 11h16v10H4z M8 11V7a4 4 0 0 1 8 0v4 M12 15h.01",
|
|
156
|
+
// The compress submenu's With-password row: a bow ring, a shaft and two teeth; the bow is a true circle under the cut.
|
|
157
|
+
"key": "M3 7a4 4 0 1 0 8 0a4 4 0 1 0-8 0 M10.5 10.5l8 8 M16 16l2.5-2.5 M18.5 18.5l1.5-1.5",
|
|
158
|
+
// The operations rows the archive and convert subsystems draw, from the spec's own glyph shelf.
|
|
159
|
+
"archive-out": "M2 3h20v5H2z M4 8v13h16V8 M12 18v-6 M9 15l3-3 3 3",
|
|
160
|
+
// The Move to Dropbox row, which the canvas draws with the network mark.
|
|
161
|
+
"network": "M9 2h6v6H9z M2 16h6v6H2z M16 16h6v6h-6z M12 8v4 M5 16v-4h14v4",
|
|
162
|
+
// The convert popup's checkbox mark.
|
|
163
|
+
"check": "M4 12l6 6L20 6",
|
|
164
|
+
// The Tags row's mark: a square-cornered price tag with its hole as a SquareCap dot.
|
|
165
|
+
"tag": "M3 3h7l11 11-7 7L3 10z M7.5 7.5L7.51 7.5",
|
|
166
|
+
// The PDF viewer's own three, recut sharp like the rest of the set; "maximize" is lucide's
|
|
167
|
+
// name for the mark the canvas calls expand.
|
|
168
|
+
"minus": "M5 12h14",
|
|
169
|
+
"maximize": "M8 3H3v5 M16 3h5v5 M8 21H3v-5 M16 21h5v-5",
|
|
170
|
+
"x": "M6 6l12 12 M18 6 6 18",
|
|
171
|
+
"sliders": "M4 21v-7 M4 10V3 M12 21v-9 M12 8V3 M20 21v-5 M20 12V3 M2 14h4 M10 8h4 M18 16h4",
|
|
172
|
+
// Grid zoom's own mark, the SettingsGrammar board's arrows, lucide's move-horizontal recut sharp.
|
|
173
|
+
"move-horizontal": "M4 12h16 M8 8l-4 4 4 4 M16 8l4 4-4 4",
|
|
174
|
+
// The background menu's Sort by row, Menus.html's own geometry: three rules shortening to the
|
|
175
|
+
// right of a bare down arrow. IconSets.html names this mark when it caps the corner radius.
|
|
176
|
+
"sort": "M11 5h10 M11 9h7 M11 13h4 M7 5v14 M7 19l-3-3 M7 19l3-3",
|
|
177
|
+
// MediaMute rule 2: lucide's speaker polygon is already hard-cornered, and the two waves are
|
|
178
|
+
// genuine arcs that stay arcs under cut rule 4; the cross is two lines.
|
|
179
|
+
"volume": "M11 5L6 9H2v6h4l5 4z M15.5 8.5a5 5 0 0 1 0 7 M19 5a10 10 0 0 1 0 14",
|
|
180
|
+
"volume-x": "M11 5L6 9H2v6h4l5 4z M22 9l-6 6 M16 9l6 6",
|
|
181
|
+
// The three basic clipboard rows the context menu grew with its visibility settings. Lucide's
|
|
182
|
+
// scissors and clipboard both carry arcs; these are the cut's own square handles and square tray.
|
|
183
|
+
"scissors": "M5 3l14 14 M19 3L5 17 M2 17h5v5H2z M17 17h5v5h-5z",
|
|
184
|
+
"copy": "M9 8h12v13H9z M4 16V3h13",
|
|
185
|
+
"clipboard": "M9 2h6v4H9z M6 4H3v18h18V4h-3 M8 12h8 M8 16h5",
|
|
186
|
+
// The Keys section's rail mark: the key caps are the same zero-length-line dots the list mark uses.
|
|
187
|
+
"keyboard": "M2 6h20v12H2z M6 10L6.01 10 M10 10L10.01 10 M14 10L14.01 10 M18 10L18.01 10 M8 14h8",
|
|
188
|
+
// Invert selection's mark, lucide's contrast, its outer ring kept a true curve.
|
|
189
|
+
"contrast": "M12 2a10 10 0 1 0 0 20a10 10 0 1 0 0-20 M12 18a6 6 0 0 0 0-12v12z",
|
|
190
|
+
// Settings > Click and refresh's Open items with mark: the arrow head and its tail, as Icons040 cuts it.
|
|
191
|
+
"pointer": "M4 4l16 6.5-7 2-2 7z M13 13l6 6"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// The sub-paths a mark fills as well as strokes, by name; ui/Glyph.qml draws them in the mark's own ink.
|
|
195
|
+
var FILLED = {
|
|
196
|
+
// MenuAdditions040: Invert selection's right half disc is solid, lucide's contrast.
|
|
197
|
+
"contrast": "M12 18a6 6 0 0 0 0-12v12z"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function filledPathFor(name) {
|
|
201
|
+
return FILLED[name] || ""
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function pathFor(name) {
|
|
205
|
+
return PATHS[name] || PATHS[FALLBACK]
|
|
206
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
// Delete carries DEL through libxkbcommon; retain SheetQuery's single-character semantics.
|
|
4
|
+
function isPrintable(text) {
|
|
5
|
+
var s = String(text || "")
|
|
6
|
+
return s.length === 1 && s >= " " && s !== "\u007f"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Issue 287's paste chords, matched on the four real modifiers the way ui/js/Keymap.js matches keys.
|
|
10
|
+
function isPasteKey(event) {
|
|
11
|
+
var mods = event.modifiers & (Qt.ControlModifier | Qt.ShiftModifier | Qt.AltModifier | Qt.MetaModifier)
|
|
12
|
+
return (event.key === Qt.Key_V && mods === Qt.ControlModifier)
|
|
13
|
+
|| (event.key === Qt.Key_Insert && mods === Qt.ShiftModifier)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// The clipboard's first line, keeping printable characters and whole surrogate pairs only.
|
|
17
|
+
function cleanPaste(text) {
|
|
18
|
+
var line = String(text || "").split(/\r\n|[\r\n]/)[0]
|
|
19
|
+
var out = ""
|
|
20
|
+
for (var i = 0; i < line.length; i++) {
|
|
21
|
+
var code = line.charCodeAt(i)
|
|
22
|
+
var low = i + 1 < line.length ? line.charCodeAt(i + 1) : 0
|
|
23
|
+
if (code >= 0xd800 && code <= 0xdbff && low >= 0xdc00 && low <= 0xdfff) {
|
|
24
|
+
out += line.substr(i, 2)
|
|
25
|
+
i++
|
|
26
|
+
} else if ((code < 0xd800 || code > 0xdfff) && isPrintable(line.charAt(i))) {
|
|
27
|
+
out += line.charAt(i)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Hands a query line's paste to its own typing route; the chord is consumed even when nothing is left to type.
|
|
34
|
+
function pasteInto(host, typeText) {
|
|
35
|
+
var pasted = cleanPaste(host.clipboardText())
|
|
36
|
+
if (pasted.length > 0)
|
|
37
|
+
typeText(pasted)
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
.pragma library
|
|
2
|
+
|
|
3
|
+
.import "Keymap.js" as Keymap
|
|
4
|
+
.import "Format.js" as Format
|
|
5
|
+
|
|
6
|
+
// A key the way the running preset binds it, spelled for a sentence; the sentences stay in their own modules with a "%1" for the key.
|
|
7
|
+
|
|
8
|
+
// The names keys.toml gives a key that a person would write as a mark.
|
|
9
|
+
var MARKS = { plus: "+", minus: "-", equal: "=", underscore: "_", greater: ">", less: "<", slash: "/", period: ".", comma: "," }
|
|
10
|
+
var MODIFIERS = { ctrl: "Ctrl", shift: "Shift", alt: "Alt", super: "Super" }
|
|
11
|
+
|
|
12
|
+
// Sample input: "ctrl-shift-n" answers "Ctrl+Shift+N"; "delete" answers "Delete"; "ctrl-shift-plus" answers "Ctrl+Shift++".
|
|
13
|
+
function spelled(keys) {
|
|
14
|
+
var parts = String(keys).split("-")
|
|
15
|
+
var out = []
|
|
16
|
+
for (var i = 0; i < parts.length; i++) {
|
|
17
|
+
var part = parts[i]
|
|
18
|
+
if (i < parts.length - 1 && MODIFIERS[part] !== undefined) { out.push(MODIFIERS[part]); continue }
|
|
19
|
+
if (MARKS[part] !== undefined) out.push(MARKS[part])
|
|
20
|
+
else if (part.length === 1) out.push(out.length > 0 ? part.toUpperCase() : part)
|
|
21
|
+
else out.push(part.charAt(0).toUpperCase() + part.substring(1))
|
|
22
|
+
}
|
|
23
|
+
return out.join("+")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The row the running preset binds to an action in the listing, the first of the given modifier kind when one is asked for.
|
|
27
|
+
function rowFor(action, mods) {
|
|
28
|
+
var rows = Keymap.bindingRows(Keymap.preset, "gui", Keymap.actionGroup(action))
|
|
29
|
+
for (var i = 0; i < rows.length; i++) {
|
|
30
|
+
if (rows[i].action === action && (mods === undefined || rows[i].mods === mods)) return rows[i]
|
|
31
|
+
}
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The key to press for an action: the plain key the menus draw, else the first chord. "" when the preset binds none.
|
|
36
|
+
function keyFor(action) {
|
|
37
|
+
var plain = Keymap.hintFor(action)
|
|
38
|
+
if (plain.length > 0) return plain
|
|
39
|
+
var row = rowFor(action)
|
|
40
|
+
return row === null ? "" : spelled(row.keys)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The key of one modifier kind ("none" for a key pressed alone), "" when the preset binds none.
|
|
44
|
+
function keyOf(action, mods) {
|
|
45
|
+
var row = rowFor(action, mods)
|
|
46
|
+
return row === null ? "" : spelled(row.keys)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A whole sentence with "%1" for the key bound to an action, "" when the preset binds none so no hint names a missing key.
|
|
50
|
+
function hinted(template, action) {
|
|
51
|
+
var key = keyFor(action)
|
|
52
|
+
return key.length > 0 ? Format.fill(template, key) : ""
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The same with "%1", "%2" and on for the keys bound to several actions, "" when the preset leaves any one of them unbound.
|
|
56
|
+
function hintedAll(template, actions) {
|
|
57
|
+
var keys = [template]
|
|
58
|
+
for (var i = 0; i < actions.length; i++) {
|
|
59
|
+
var key = keyFor(actions[i])
|
|
60
|
+
if (key.length === 0) return ""
|
|
61
|
+
keys.push(key)
|
|
62
|
+
}
|
|
63
|
+
return Format.fill.apply(null, keys)
|
|
64
|
+
}
|