buninu-browser 0.4.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.
- package/README.md +22 -1
- package/frontends/wayland/README.md +50 -21
- package/frontends/wayland/index.js +14 -0
- package/frontends/wayland/input.js +24 -5
- package/frontends/wayland/urlbar.js +9 -5
- package/lib/audio/shared-connection.js +31 -0
- package/lib/cdp-server.js +15 -2
- package/lib/headless-shell.js +7 -4
- package/lib/paint/display-list.js +2 -1
- package/lib/qml/README.md +456 -0
- package/lib/qml/builtins.js +76 -5
- package/lib/qml/canvas.js +76 -0
- package/lib/qml/document.js +47 -24
- package/lib/qml/fonts.js +69 -0
- package/lib/qml/host.js +28 -0
- package/lib/qml/loader.js +7 -3
- package/lib/qml/models.js +1 -1
- package/lib/qml/reactive.js +26 -3
- package/lib/qml/runtime.js +263 -41
- package/lib/qml/shader/fragment.js +83 -0
- package/lib/qml/shader/qsb.js +27 -0
- package/lib/qml/shader/renderer.js +39 -0
- package/lib/qml/shader/runtime.js +61 -0
- package/lib/qml/shapes.js +9 -1
- package/lib/qml/shortcuts.js +65 -0
- package/lib/qml/textures.js +4 -1
- package/lib/qml/window-constraints.js +16 -0
- package/lib/renderer/bindings.js +5 -1
- package/lib/renderer/canvas-2d.js +16 -0
- package/lib/renderer/document-selection.js +19 -0
- package/lib/renderer/host.js +14 -11
- package/lib/renderer/main.js +2 -0
- package/lib/renderer/page-realm.js +4 -9
- package/lib/renderer/page-renderer.js +273 -35
- package/lib/renderer/pointer-move.js +5 -3
- package/lib/renderer/render-interval.js +9 -0
- package/lib/style/computed-style.js +8 -2
- package/package.json +1 -1
- package/test/audio-connection-lifetime.test.js +46 -0
- package/test/canvas-paint.test.js +8 -0
- package/test/computed-style.test.js +12 -0
- package/test/document-selection.test.js +27 -0
- package/test/double-click.test.js +85 -0
- package/test/fixtures/canvas-damage.js +40 -0
- package/test/fixtures/fake-renderer.js +1 -0
- package/test/fixtures/omastorm-helpers/Dockerfile +35 -0
- package/test/fixtures/omastorm-helpers/check.mjs +56 -0
- package/test/fixtures/omastorm-helpers/launch.mjs +24 -0
- package/test/fixtures/omastorm-helpers/prepare.mjs +17 -0
- package/test/fixtures/omastorm-helpers/runtime.mjs +32 -0
- package/test/fixtures/pointer-paint-batching.js +23 -0
- package/test/fixtures/qml-fonts/main.qml +10 -0
- package/test/fixtures/qml-locale/check.mjs +6 -0
- package/test/fixtures/qml-locale/main.qml +8 -0
- package/test/fixtures/qml-shader/grab.qml +14 -0
- package/test/fixtures/qml-shader/gradient.frag +5 -0
- package/test/fixtures/qml-shader/main.qml +14 -0
- package/test/fixtures/qml-shader/texture-grab.qml +25 -0
- package/test/fixtures/qml-shader/texture-size.frag +5 -0
- package/test/fixtures/qml-shader-check.mjs +24 -0
- package/test/fixtures/title-no-paint.js +20 -0
- package/test/manual-double-click.html +21 -0
- package/test/manual-mouse-buttons.html +18 -0
- package/test/manual-omastorm-engine.mjs +19 -0
- package/test/manual-omastorm-shaders.mjs +28 -0
- package/test/manual-omastorm-software.mjs +25 -0
- package/test/manual-pointer-scroll.html +43 -0
- package/test/pointer-drag.test.js +104 -0
- package/test/qml-canvas.test.js +21 -0
- package/test/qml-connections-timing.test.js +22 -0
- package/test/qml-controls-window.test.js +182 -0
- package/test/qml-fonts.test.js +31 -0
- package/test/qml-grid-multiline.test.js +48 -0
- package/test/qml-locale.test.js +45 -0
- package/test/qml-nonvisual-visible.test.js +19 -0
- package/test/qml-shader-runtime.test.js +6 -0
- package/test/qml-shortcuts.test.js +39 -0
- package/test/qml-socket.test.js +60 -0
- package/test/qml-software-shader.test.js +55 -0
- package/test/qml-window-update.test.js +36 -0
- package/test/render-interval.test.js +33 -0
- package/test/title-no-paint.test.js +6 -0
- package/test/wayland-scroll.test.js +40 -0
- package/test/wayland-urlbar.test.js +23 -0
package/lib/qml/README.md
CHANGED
|
@@ -969,3 +969,459 @@ remain in Flea's thumbnail cache until invalidated or removed.
|
|
|
969
969
|
媒體資訊。官方 0.3.12 後端已實測接受並寫入縮圖快取,沿用實驗容器的
|
|
970
970
|
bwrap 繞過路徑,無須修改 Flea QML。刪除註冊檔可停用,但既有佔位縮圖
|
|
971
971
|
仍保留在快取,直到失效或被移除。
|
|
972
|
+
|
|
973
|
+
## Omastorm container
|
|
974
|
+
|
|
975
|
+
### Android / Termux setup
|
|
976
|
+
|
|
977
|
+
Termux's terminal does not support Kitty graphics. Start Land inside jsgotty's
|
|
978
|
+
browser terminal instead; keep container build commands in a separate native
|
|
979
|
+
Termux shell. These steps are self-contained and do not require the Flea setup.
|
|
980
|
+
|
|
981
|
+
1. **Install Bun and start jsgotty in native Termux**:
|
|
982
|
+
|
|
983
|
+
```sh
|
|
984
|
+
npm i -g bun
|
|
985
|
+
bun --version
|
|
986
|
+
bunx jsgotty -w -r --session bash
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
Open the complete URL printed by jsgotty in your phone's browser or WebView.
|
|
990
|
+
`-w` permits terminal input, `-r` generates that random URL, and `--session`
|
|
991
|
+
keeps the shell available for reconnection. This server-side `-r` is different
|
|
992
|
+
from the CLI client's `--client -r PID`.
|
|
993
|
+
|
|
994
|
+
For on-screen Super, Alt and Ctrl keys, you can first build a minapk app:
|
|
995
|
+
|
|
996
|
+
```sh
|
|
997
|
+
npx @drxiaozhi/minapk
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
Install and open the generated APK. Inside the app, press **Volume Up**,
|
|
1001
|
+
choose **Go to URL**, and enter the complete jsgotty URL. Use the app's
|
|
1002
|
+
modifier keys for shortcuts; **long-press Alt to activate Super**.
|
|
1003
|
+
|
|
1004
|
+
2. **Start Land inside that browser terminal** and leave it running:
|
|
1005
|
+
|
|
1006
|
+
```sh
|
|
1007
|
+
env XDG_RUNTIME_DIR="$TMPDIR" bunx buninu-land
|
|
1008
|
+
```
|
|
1009
|
+
|
|
1010
|
+
The first launch may download Land and Casty. Its display stays in the browser
|
|
1011
|
+
terminal; the default socket is `$TMPDIR/wayland-buninu`. Use that default
|
|
1012
|
+
socket for the commands below. An existing Land instance can cause a numbered
|
|
1013
|
+
name such as `wayland-buninu1`; check the address shown by Land.
|
|
1014
|
+
|
|
1015
|
+
3. **Open another native Termux shell and install js-udocker**. Do not run these
|
|
1016
|
+
commands in Land's address bar or inside a Debian proot. Skip package
|
|
1017
|
+
installation if these tools are already installed:
|
|
1018
|
+
|
|
1019
|
+
```sh
|
|
1020
|
+
pkg install git nodejs proot curl tar
|
|
1021
|
+
cd "$HOME"
|
|
1022
|
+
git clone https://github.com/jjtseng93/js-udocker.git
|
|
1023
|
+
cd js-udocker
|
|
1024
|
+
sh termux_install.sh
|
|
1025
|
+
hash -r
|
|
1026
|
+
command -v udocker
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
The installer points `udocker` at this checkout and uses Node.js and proot.
|
|
1030
|
+
Keep the checkout in place. If js-udocker is already installed, reuse it.
|
|
1031
|
+
|
|
1032
|
+
4. **Get buninu-browser as the build context**, still in the second shell.
|
|
1033
|
+
Reuse your checkout if it already contains this Dockerfile:
|
|
1034
|
+
|
|
1035
|
+
```sh
|
|
1036
|
+
cd "$HOME"
|
|
1037
|
+
git clone https://github.com/jjtseng93/buninu-browser.git
|
|
1038
|
+
cd buninu-browser
|
|
1039
|
+
test -f test/fixtures/omastorm-helpers/Dockerfile
|
|
1040
|
+
test -S "$TMPDIR/wayland-buninu"
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
No `bun install` is needed in the checkout. A socket file alone does not prove
|
|
1044
|
+
Land is still running: `ECONNREFUSED` means you must restart Land.
|
|
1045
|
+
|
|
1046
|
+
5. **Build and run from the repository root**:
|
|
1047
|
+
|
|
1048
|
+
```sh
|
|
1049
|
+
udocker build -t omastorm-qml -y -f test/fixtures/omastorm-helpers/Dockerfile .
|
|
1050
|
+
udocker run -b "$TMPDIR:/tmp" omastorm-qml
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
Return to the browser terminal to use Omastorm. The bind exposes the host's
|
|
1054
|
+
Land socket at `/tmp/wayland-buninu`; no extra startup arguments or font
|
|
1055
|
+
environment variables are needed. Do not start another Land inside the
|
|
1056
|
+
container. If deliberately using a numbered socket, pass
|
|
1057
|
+
`-e WAYLAND_DISPLAY=/tmp/wayland-buninu1` before `omastorm-qml` in the run
|
|
1058
|
+
command instead.
|
|
1059
|
+
|
|
1060
|
+
### Linux Docker
|
|
1061
|
+
|
|
1062
|
+
Start `bunx buninu-land` on the host in a terminal supporting Kitty graphics.
|
|
1063
|
+
Then build from the buninu-browser repository root in another shell:
|
|
1064
|
+
|
|
1065
|
+
```sh
|
|
1066
|
+
docker build -t omastorm-qml -f test/fixtures/omastorm-helpers/Dockerfile .
|
|
1067
|
+
docker run --rm -v "${XDG_RUNTIME_DIR:-/tmp}:/tmp" omastorm-qml
|
|
1068
|
+
```
|
|
1069
|
+
|
|
1070
|
+
The mounted directory must contain the running Land socket named
|
|
1071
|
+
`wayland-buninu`, and the container user must be able to connect to it.
|
|
1072
|
+
Docker's `--rm` removes container settings after exit; arrange persistent
|
|
1073
|
+
storage separately if needed.
|
|
1074
|
+
|
|
1075
|
+
### Image contents and validation
|
|
1076
|
+
|
|
1077
|
+
The following explains the image and its checks; no additional manual
|
|
1078
|
+
installation is needed after the steps above.
|
|
1079
|
+
|
|
1080
|
+
- [Dockerfile](../../test/fixtures/omastorm-helpers/Dockerfile) uses `oven/bun:1.4.4`,
|
|
1081
|
+
single-stage COPY/RUN, one `ENV KEY=value` per line and JSON CMD, matching the
|
|
1082
|
+
Flea recipe and js-udocker parser.
|
|
1083
|
+
- It downloads Omastorm revision `492e97841146bd9d6d9f7a3064e5e1ae9840cc68`
|
|
1084
|
+
(manifest 0.1.23) and the official engine 0.1.15 via upstream's pinned release
|
|
1085
|
+
installer and checksums, for aarch64 or x86_64. Rust compilation is not needed.
|
|
1086
|
+
- It downloads JetBrainsMono Nerd Font Mono Regular from Nerd Fonts v3.4.0,
|
|
1087
|
+
including its OFL license, and sets `QT_QPA_FONTDIR` inside the image.
|
|
1088
|
+
- Browser code and its existing WASM/fonts are copied from the build context;
|
|
1089
|
+
no npm dependencies are installed into your checkout. Rebuild after changing
|
|
1090
|
+
browser code. Native Qt, Quickshell, Mesa and GPU device access are unnecessary.
|
|
1091
|
+
- [prepare.mjs](../../test/fixtures/omastorm-helpers/prepare.mjs) removes only the
|
|
1092
|
+
two explicit Software-backend refusal checks in this container's Omastorm UI.
|
|
1093
|
+
It saves `.upstream` originals and refuses unexpected source changes. Actual
|
|
1094
|
+
shader compilation and texture verification remain enabled; this is the same
|
|
1095
|
+
experimental software path as the manual Omastorm demonstration.
|
|
1096
|
+
- [launch.mjs](../../test/fixtures/omastorm-helpers/launch.mjs) starts an owned
|
|
1097
|
+
engine in a unique temporary runtime directory, then opens the QML frontend.
|
|
1098
|
+
It keeps the Land socket at its original absolute path, so sharing `/tmp`
|
|
1099
|
+
does not reuse or stop another Omastorm engine. Closing the window or stopping
|
|
1100
|
+
the launcher closes its engine and removes its temporary textures/socket.
|
|
1101
|
+
- [check.mjs](../../test/fixtures/omastorm-helpers/check.mjs) runs during build:
|
|
1102
|
+
verifies Bun version, the official engine executable, all four software
|
|
1103
|
+
shaders, Nerd Font glyph coverage and a QML screenshot. It needs no Land or
|
|
1104
|
+
weather downloads. Require the final `PASS:` message; js-udocker versions
|
|
1105
|
+
which continue after failed RUN steps may still report overall success.
|
|
1106
|
+
- After binding `/tmp`, verify the real engine Unix-socket hello/site list with
|
|
1107
|
+
`udocker run -b "$TMPDIR:/tmp" omastorm-qml bun /usr/local/lib/omastorm-helpers/check.mjs --engine`.
|
|
1108
|
+
This is separate because js-udocker's unbound build ROOT/tmp path can exceed
|
|
1109
|
+
the Unix socket path-length limit. The normal launcher also waits for its
|
|
1110
|
+
engine socket before opening QML.
|
|
1111
|
+
- Live radar/map data still needs network access and a selected station. A fresh
|
|
1112
|
+
profile may ask for a location; use the application's picker. Build checks do
|
|
1113
|
+
not promise available live weather, full native-QML parity or GPU performance.
|
|
1114
|
+
|
|
1115
|
+
## Omastorm compatibility experiments
|
|
1116
|
+
|
|
1117
|
+
The first compatibility stage provides local `Quickshell.Io.Socket` with Unix
|
|
1118
|
+
socket connections, UTF-8 `SplitParser` messages, writes, disconnect/reconnect,
|
|
1119
|
+
and document-owned cleanup. `connected` reads the actual connection state;
|
|
1120
|
+
assigning it requests a connection/disconnection. Writes are queued immediately;
|
|
1121
|
+
`flush()` is accepted without adding a second buffer. This does not expose host
|
|
1122
|
+
sockets to ordinary HTML or remote QML.
|
|
1123
|
+
|
|
1124
|
+
QML `Canvas` reuses the browser's HTML canvas and 2D context. The adapter supplies
|
|
1125
|
+
paint scheduling, dimensions, QML color conversion, reset, and cancellation when
|
|
1126
|
+
disposed. It currently supports `requestPaint`, `markDirty` (full repaint),
|
|
1127
|
+
`contextType: "2d"`, and animation callbacks. QML image-loading extensions,
|
|
1128
|
+
canvas tiling and alternate render strategies remain unsupported. Software
|
|
1129
|
+
`ShaderEffect` and image-loading support are described in the later experiment
|
|
1130
|
+
sections. `QtQuick.Window` imports resolve existing Window attached behavior;
|
|
1131
|
+
this does not implement every type from that module.
|
|
1132
|
+
|
|
1133
|
+
To probe an existing official Omastorm engine using the unmodified `Engine.qml`:
|
|
1134
|
+
|
|
1135
|
+
```sh
|
|
1136
|
+
env XDG_RUNTIME_DIR=/tmp bun test/manual-omastorm-engine.mjs /path/to/omastorm
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
The probe does not download or start the engine. This stage has received the
|
|
1140
|
+
real engine's site list and polar-frame state, and rendered `RadarMark.qml`.
|
|
1141
|
+
That initial milestone has since progressed to the experimental software radar
|
|
1142
|
+
window described above; it does not imply full native Quickshell compatibility.
|
|
1143
|
+
|
|
1144
|
+
### Omastorm 相容性實驗
|
|
1145
|
+
|
|
1146
|
+
第一階段加入本地 `Quickshell.Io.Socket`:Unix socket 連線、UTF-8
|
|
1147
|
+
`SplitParser` 訊息、寫入、斷線重連,以及頁面關閉時清理。`connected` 讀取實際
|
|
1148
|
+
連線狀態,賦值則要求連線/斷線;寫入立即排入傳輸,`flush()` 不另建緩衝。
|
|
1149
|
+
一般 HTML 與遠端 QML 不會因此取得 host socket 能力。
|
|
1150
|
+
|
|
1151
|
+
QML `Canvas` 重用瀏覽器的 HTML canvas/2D context,只轉接重繪排程、尺寸、
|
|
1152
|
+
QML 顏色、reset 與銷毀清理。目前支援 `requestPaint`、`markDirty`(整張重繪)、
|
|
1153
|
+
`contextType: "2d"` 與動畫回呼;QML 圖片載入擴充、畫布分塊、其他渲染策略與
|
|
1154
|
+
自訂 `ShaderEffect` 已有實驗性軟體子集,詳見後段說明。`QtQuick.Window` import 可使用既有 Window
|
|
1155
|
+
附加行為,不代表該模組所有類型皆已實作。
|
|
1156
|
+
|
|
1157
|
+
上方命令可使用未修改的 `Engine.qml` 連到已啟動的官方後端,不會下載或啟動後端。
|
|
1158
|
+
已實測收到站點清單與 polar frame 狀態,並渲染原版 `RadarMark.qml`;完整 shell
|
|
1159
|
+
已由此初期成果推進到後段的軟體雷達實驗,詳見下方 ShaderEffect 與 Omastorm 軟體版應用說明;仍不代表完整相容。
|
|
1160
|
+
|
|
1161
|
+
### Keyboard shortcuts and object delegates
|
|
1162
|
+
|
|
1163
|
+
`Shortcut` supports string `sequence`/`sequences`, portable key names,
|
|
1164
|
+
Ctrl/Alt/Shift/Meta modifiers, up to four key strokes (one-second prefix timeout),
|
|
1165
|
+
enabled/autoRepeat, and activated/activatedAmbiguously signals. It consumes
|
|
1166
|
+
matching DOM keydown events before ordinary key handlers. Window scope uses the
|
|
1167
|
+
existing QML window/focus state; ApplicationShortcut applies across the document's
|
|
1168
|
+
windows. Native text currently uses portable text. Numeric StandardKey bindings,
|
|
1169
|
+
platform-localized key names and Keys.shortcutOverride are not implemented.
|
|
1170
|
+
|
|
1171
|
+
`Instantiator` reuses Repeater's scoped factories for synchronous array/count
|
|
1172
|
+
models. Objects are parented to the Instantiator; active, count, object/objectAt,
|
|
1173
|
+
objectAdded/objectRemoved, model replacement and disposal are supported. Dynamic
|
|
1174
|
+
delegate replacement and richer model adapters remain unsupported; asynchronous
|
|
1175
|
+
creation is rejected explicitly. As with Repeater, a model replacement currently
|
|
1176
|
+
recreates all delegates rather than preserving identities by row diff.
|
|
1177
|
+
|
|
1178
|
+
### 快捷鍵與物件委派
|
|
1179
|
+
|
|
1180
|
+
`Shortcut` 支援字串 sequence/sequences、可攜式鍵名、Ctrl/Alt/Shift/Meta、
|
|
1181
|
+
最多四段按鍵(前綴等待一秒)、enabled/autoRepeat,以及 activated/
|
|
1182
|
+
activatedAmbiguously。透過 DOM keydown 攔截匹配按鍵,視窗範圍沿用既有 QML 焦點
|
|
1183
|
+
狀態。nativeText 暫用 portableText;數字 StandardKey、平台翻譯鍵名與
|
|
1184
|
+
Keys.shortcutOverride 尚未支援。
|
|
1185
|
+
|
|
1186
|
+
`Instantiator` 重用 Repeater 的作用域工廠,同步建立陣列/數量模型的物件。
|
|
1187
|
+
支援正確父物件、active、count、object/objectAt、增加/移除訊號、模型替換與
|
|
1188
|
+
銷毀清理。動態 delegate 替換及其他模型轉接尚未支援,非同步建立會明確報錯。
|
|
1189
|
+
目前模型替換會重建所有委派,尚未以逐列差異保留物件身分。
|
|
1190
|
+
|
|
1191
|
+
`PathMultiline` now emits independent SVG subpaths; the existing canvas ShapePath
|
|
1192
|
+
fallback also accepts the same geometry. `GridLayout` supports sequential
|
|
1193
|
+
row-major placement with columns/rows, spacing, preferred/minimum sizes,
|
|
1194
|
+
fill/alignment and implicit dimensions. Explicit cell coordinates, spans,
|
|
1195
|
+
right-to-left flow and uniform-cell options are not implemented. Nested layouts
|
|
1196
|
+
retain intrinsic preferred dimensions instead of feeding allocated widths back
|
|
1197
|
+
into the parent. The original Omastorm KeysSheet has been screenshot-checked with
|
|
1198
|
+
both columns visible; this does not validate the shader-dependent radar window.
|
|
1199
|
+
|
|
1200
|
+
`PathMultiline` 已接到 SVG 子路徑,既有 canvas ShapePath 路徑也接受同一份幾何。
|
|
1201
|
+
`GridLayout` 支援逐列排列、欄列數、間距、偏好/最小尺寸、填滿/對齊與自然尺寸;
|
|
1202
|
+
指定格位、跨格、由右至左及等寬格選項尚未支援。巢狀 Layout 不再把父層分配的
|
|
1203
|
+
寬度反灌成自然寬度。已截圖確認原版 KeysSheet 雙欄正常,但不代表雷達視窗已可用。
|
|
1204
|
+
|
|
1205
|
+
### Experimental software ShaderEffect
|
|
1206
|
+
|
|
1207
|
+
`shader/fragment.js` experimentally translates a Qt-style GLSL fragment subset
|
|
1208
|
+
to SkSL and compiles it with the existing CanvasKit RuntimeEffect. CPU raster
|
|
1209
|
+
surfaces execute it without EGL, Vulkan, WebGL, new native libraries or a second
|
|
1210
|
+
production CanvasKit instance. The caller supplies existing child shaders and
|
|
1211
|
+
uniforms; the drawing path does not encode images or read pixels back into JS.
|
|
1212
|
+
Only tests read pixels to verify results.
|
|
1213
|
+
|
|
1214
|
+
```sh
|
|
1215
|
+
bun test test/qml-software-shader.test.js
|
|
1216
|
+
bun test/manual-omastorm-shaders.mjs /path/to/omastorm
|
|
1217
|
+
```
|
|
1218
|
+
|
|
1219
|
+
The offline manual probe compiles all four upstream `.frag.qsb` packages and checks
|
|
1220
|
+
known pixels for tile and texture-check. Radar/grid compilation is **not** a
|
|
1221
|
+
visual correctness or performance result. The current translator supports one
|
|
1222
|
+
vec4 output, qt_TexCoord0, simple uniform blocks, scalar/vector/matrix uniforms,
|
|
1223
|
+
2D child sampling and level-zero textureSize. It is not a complete GLSL compiler;
|
|
1224
|
+
preprocessing, custom vertex shaders and additional interfaces are unsupported.
|
|
1225
|
+
Native shader compilation errors remain errors, never successful placeholders.
|
|
1226
|
+
|
|
1227
|
+
Local QML ShaderEffect now accepts raw GLSL or QSB v9 desktop GLSL records.
|
|
1228
|
+
Numeric/vector/color uniforms update reactively; Image and ShaderEffectSource
|
|
1229
|
+
samplers reuse renderer images, and output goes directly into an HTML canvas.
|
|
1230
|
+
Resources are disposed on item destruction or page teardown. No PNG encoding,
|
|
1231
|
+
base64 or JS pixel readback is introduced in the rendering path.
|
|
1232
|
+
|
|
1233
|
+
Run the self-contained clickable gradient with:
|
|
1234
|
+
|
|
1235
|
+
```sh
|
|
1236
|
+
bun lib/qml/qswayland -p test/fixtures/qml-shader/main.qml
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
An original Omastorm tile QSB and cached map texture have rendered on CPU.
|
|
1240
|
+
A patched application copy now displays a polar RadarMap; large mosaic
|
|
1241
|
+
TextureCheck and complete UI compatibility are not yet verified. Custom
|
|
1242
|
+
vertex shaders, mesh/culling changes, full GLSL and other QSB versions remain
|
|
1243
|
+
unsupported. GPU acceleration remains optional future work.
|
|
1244
|
+
|
|
1245
|
+
### 實驗性純軟體 ShaderEffect
|
|
1246
|
+
|
|
1247
|
+
`shader/fragment.js` 實驗性地把 Qt 風格 GLSL fragment 子集轉成 SkSL,重用現有
|
|
1248
|
+
CanvasKit RuntimeEffect,在 CPU raster surface 執行,不需要 EGL、Vulkan、
|
|
1249
|
+
WebGL 或新的原生依賴。材質由同一個 CanvasKit 的 child shader 提供,繪製本身
|
|
1250
|
+
不做圖片編碼或像素讀回;測試才讀回像素驗證。
|
|
1251
|
+
|
|
1252
|
+
上方離線實驗已編譯原版四份 `.frag.qsb`,並驗證 tile/texture-check 的已知像素。
|
|
1253
|
+
radar 已在完整介面顯示極座標回波;grid 仍只有編譯驗證,尚未確認大型拼接圖。轉譯器僅支援
|
|
1254
|
+
單一 vec4 輸出、qt_TexCoord0、簡單 uniform block、數值/向量/矩陣 uniform、
|
|
1255
|
+
二維材質取樣及第零層 textureSize,不是完整 GLSL 編譯器。
|
|
1256
|
+
|
|
1257
|
+
本地 QML 已接上 QSB v9 的桌面 GLSL、數值/向量/色彩 uniform、Image 與
|
|
1258
|
+
ShaderEffectSource 材質,以及重繪與資源釋放。輸出直接寫入既有 HTML canvas,
|
|
1259
|
+
沒有增加 PNG、base64 或 JS 像素讀回。上方命令可開啟點擊換色的獨立示範。
|
|
1260
|
+
已用原版 Omastorm 地圖與雷達 QSB 確認 CPU 繪圖;大型拼接圖的
|
|
1261
|
+
TextureCheck 與全部 UI 行為尚未驗證。自訂 vertex shader、mesh/culling、完整 GLSL 與其他
|
|
1262
|
+
QSB 版本仍未支援。純軟體是必要路徑,GPU 僅作未來可選加速。
|
|
1263
|
+
|
|
1264
|
+
### Item capture and Canvas image reads
|
|
1265
|
+
|
|
1266
|
+
Local QML items support `grabToImage(callback, size)` using the existing DOM
|
|
1267
|
+
snapshot and HTML canvas path. The result exposes a PNG data URL; encoding is
|
|
1268
|
+
performed only for an explicit grab, never for each ShaderEffect frame.
|
|
1269
|
+
Canvas supports `loadImage`, `unloadImage`, `isImageLoaded`, `isImageLoading`,
|
|
1270
|
+
`isImageError`, `imageLoaded`, and the `createImageData(url)` image-read overload.
|
|
1271
|
+
Pending captures are cancelled on item destruction. `saveToFile` is not yet
|
|
1272
|
+
implemented. A sampler-size shader → 16×16 grab → Canvas image read integration
|
|
1273
|
+
test verifies green pixels for the actual 2×3 source texture, without a GPU.
|
|
1274
|
+
|
|
1275
|
+
The original Omastorm application still explicitly rejects `GraphicsInfo.Software`;
|
|
1276
|
+
software shader support alone cannot bypass that application policy. The explicit
|
|
1277
|
+
test-copy procedure below removes those policy checks without faking a backend.
|
|
1278
|
+
|
|
1279
|
+
### Item 擷取與 Canvas 圖片讀取
|
|
1280
|
+
|
|
1281
|
+
本地 QML Item 的 `grabToImage(callback, size)` 重用既有 DOM snapshot 與 HTML
|
|
1282
|
+
canvas,回傳 PNG data URL。只有明確擷取才編碼,不增加每幀 ShaderEffect 的成本。
|
|
1283
|
+
Canvas 已支援載入、卸載、載入狀態查詢、`imageLoaded` 及
|
|
1284
|
+
`createImageData(url)` 讀取圖片。Item 銷毀會取消待執行擷取;`saveToFile` 尚未實作。
|
|
1285
|
+
整合測試已驗證「材質尺寸 shader → 16×16 擷取 → Canvas 讀取」取得正確綠色像素,
|
|
1286
|
+
確認原始 2×3 材質尺寸,完全不需要 GPU。
|
|
1287
|
+
|
|
1288
|
+
Omastorm 原版仍明確拒絕 `GraphicsInfo.Software`,這是應用自己的限制,不能只靠
|
|
1289
|
+
軟體 shader 支援解除。下方明確修改測試副本的做法會移除這兩處政策限制,
|
|
1290
|
+
沒有假報繪圖後端。
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
### Omastorm software application experiment
|
|
1294
|
+
|
|
1295
|
+
With the official engine already running and Land available through
|
|
1296
|
+
`WAYLAND_DISPLAY` / `XDG_RUNTIME_DIR`:
|
|
1297
|
+
|
|
1298
|
+
```sh
|
|
1299
|
+
bun test/manual-omastorm-software.mjs ../omastorm
|
|
1300
|
+
```
|
|
1301
|
+
|
|
1302
|
+
The script prints a new temporary directory, copies only the UI, and removes
|
|
1303
|
+
the two explicit `GraphicsInfo.Software` rejection branches. It leaves the
|
|
1304
|
+
checkout, native shaders, texture-size verification and minimum window size
|
|
1305
|
+
unchanged. `--prepare-only` creates the copy without opening a window.
|
|
1306
|
+
|
|
1307
|
+
The browser now supplies Button content/background/padding/focus handling,
|
|
1308
|
+
attached Window changes, ListModel required delegate roles, locale/date helpers,
|
|
1309
|
+
and size/vector defaults. `FloatingWindow.minimumSize` and `maximumSize` affect
|
|
1310
|
+
QML geometry and are sent as xdg_toplevel size hints by the Wayland frontend.
|
|
1311
|
+
Only one frontend toplevel is currently represented; independent native windows
|
|
1312
|
+
and all Qt Control styling semantics are not claimed. Rectangle antialiasing
|
|
1313
|
+
uses the existing CSS/CanvasKit raster policy (disabling it is not implemented).
|
|
1314
|
+
|
|
1315
|
+
Nested Loader startup no longer loses pending outer change handlers. Imperative
|
|
1316
|
+
`createObject` initialization no longer adds child-state dependencies to its
|
|
1317
|
+
calling binding, which previously could recreate sockets repeatedly.
|
|
1318
|
+
|
|
1319
|
+
The full UI has displayed cached KMUX polar radar echoes, map tiles and place
|
|
1320
|
+
labels using software rendering. Missing icon glyphs, rendering cost, custom
|
|
1321
|
+
cursor visuals, mosaic grids and the full set of interactions still need work.
|
|
1322
|
+
This is an experiment, not complete Omastorm compatibility.
|
|
1323
|
+
|
|
1324
|
+
### Omastorm 軟體版應用實驗
|
|
1325
|
+
|
|
1326
|
+
先啟動官方引擎及 Land,設定 `WAYLAND_DISPLAY`/`XDG_RUNTIME_DIR`,再執行上方
|
|
1327
|
+
命令。腳本會印出新的暫存目錄,只複製 UI、移除兩處 Software 拒絕判斷,不修改
|
|
1328
|
+
checkout、shader、材質尺寸驗證或最小視窗尺寸。`--prepare-only` 只建立副本。
|
|
1329
|
+
|
|
1330
|
+
已補 Button 的內容、背景、padding 與焦點,Window 附加通知、ListModel 必填角色、
|
|
1331
|
+
地區/日期格式及 size/向量初值。`minimumSize`/`maximumSize` 除了約束 QML
|
|
1332
|
+
幾何,也會傳送 Wayland 尺寸提示。目前 frontend 仍只有單一原生 toplevel,
|
|
1333
|
+
不是完整多視窗或全部 Qt Controls 樣式相容。Rectangle 抗鋸齒沿用現有 renderer,
|
|
1334
|
+
尚未實作關閉抗鋸齒。
|
|
1335
|
+
|
|
1336
|
+
已修正巢狀 Loader 啟動遺失外層變更通知,以及 `createObject` 建立過程污染呼叫方
|
|
1337
|
+
binding 依賴、造成 Socket 重建的問題。完整介面已用軟體顯示 KMUX 極座標雷達
|
|
1338
|
+
回波、地圖與地名;圖示缺字、繪製成本、自訂游標視覺、拼接圖及全部互動仍待驗證。
|
|
1339
|
+
|
|
1340
|
+
### Local fallback fonts / 本機備用字型
|
|
1341
|
+
|
|
1342
|
+
Local QML documents in host mode accept `QT_QPA_FONTDIR`. It names **one
|
|
1343
|
+
folder**, as in Qt; colon/semicolon-separated folder lists are not supported.
|
|
1344
|
+
This implementation additionally searches subfolders and follows symlinks with
|
|
1345
|
+
cycle/duplicate protection. Put fonts from several sources under one folder
|
|
1346
|
+
(or link their folders into it).
|
|
1347
|
+
|
|
1348
|
+
```sh
|
|
1349
|
+
env QT_QPA_FONTDIR=/path/to/fonts \
|
|
1350
|
+
bun index.js --quickshell-wayland -p /path/to/shell.qml
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
Supported files are `.ttf` and `.otf`. Fonts are decoded once on the first local
|
|
1354
|
+
QML navigation per renderer process, then reused; there is no per-frame scan or
|
|
1355
|
+
automatic download. Existing fonts remain first, and the discovered fonts supply
|
|
1356
|
+
missing glyphs to both Text and Canvas. Regular faces are ordered before other
|
|
1357
|
+
faces. Explicit family names are also recognized, but complete Qt font style
|
|
1358
|
+
matching is not implemented. Private-use icon codepoints require the appropriate
|
|
1359
|
+
icon font (for example Nerd Font); another font may use the same codepoint for a
|
|
1360
|
+
different symbol.
|
|
1361
|
+
|
|
1362
|
+
The directory is read from the environment at process startup. Restart after
|
|
1363
|
+
changing its contents. No variable means no extra font scan. Ordinary HTML pages
|
|
1364
|
+
retain their existing fonts, including after navigation away from QML. Invalid
|
|
1365
|
+
fonts are reported and skipped. Loading is bounded to 32 MiB per file, 128 MiB
|
|
1366
|
+
total, 128 decoded faces, 4096 visited paths and 16 directory levels. Font
|
|
1367
|
+
collections (`.ttc`), Fontconfig rules and automatic system discovery are not
|
|
1368
|
+
implemented here.
|
|
1369
|
+
|
|
1370
|
+
本機 QML 的 host 模式支援 `QT_QPA_FONTDIR`,與 Qt 一樣指定**單一目錄**,
|
|
1371
|
+
不接受冒號或分號分隔的多目錄清單。我們另外支援遞迴子目錄及符號連結,會避免
|
|
1372
|
+
循環和重複;多個字型來源可以放在同一目錄下,或把它們的目錄連結進去。
|
|
1373
|
+
|
|
1374
|
+
支援 `.ttf`、`.otf`,每個 renderer 程序首次載入本機 QML 時解碼一次並重用,
|
|
1375
|
+
不會每幀掃描或自動下載。原有字型優先,缺字才使用找到的字型,Text 與 Canvas
|
|
1376
|
+
共用。Regular 字型排在其他樣式之前,也支援直接指定找到的 family 名稱,但尚未
|
|
1377
|
+
完整實作 Qt 的字重/樣式匹配。私用區圖示需使用正確的圖示字型(例如 Nerd Font);
|
|
1378
|
+
另一套字型在同一碼位可能是不同圖案。
|
|
1379
|
+
|
|
1380
|
+
環境變數在程序啟動時讀取,修改目錄內容後請重啟。未設定就不額外掃描;一般 HTML
|
|
1381
|
+
維持原本字型,即使從 QML 導航回 HTML 也一樣。損壞字型會回報並跳過。上限為每檔
|
|
1382
|
+
32 MiB、合計 128 MiB、128 個解碼字型、4096 個路徑與 16 層目錄。此功能尚不支援
|
|
1383
|
+
`.ttc` 字型集合、Fontconfig 規則或自動搜尋系統字型。
|
|
1384
|
+
|
|
1385
|
+
Manual Text/Canvas icon check / 手動檢查文字及 Canvas 圖示:
|
|
1386
|
+
|
|
1387
|
+
```sh
|
|
1388
|
+
env QT_QPA_FONTDIR=/path/to/nerd-fonts \
|
|
1389
|
+
bun index.js --quickshell-wayland -p test/fixtures/qml-fonts/main.qml
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
### System locale / 系統地區設定
|
|
1393
|
+
|
|
1394
|
+
Local host-mode QML reads `LC_ALL`, then the relevant category (`LC_TIME`,
|
|
1395
|
+
`LC_MEASUREMENT`, or `LC_MESSAGES`), then `LANG`. Empty values are ignored.
|
|
1396
|
+
`C`, `C.UTF-8` and `POSIX` use the Qt C locale: metric units, day-first short
|
|
1397
|
+
dates and 24-hour time. Explicit `Qt.locale("en_US")` overrides the system
|
|
1398
|
+
categories; Unix names such as `en_US.UTF-8` are accepted. Unsupported locale
|
|
1399
|
+
names fall back to C. The environment is captured at process startup.
|
|
1400
|
+
Non-C format data and timezone labels still use JavaScript Intl, so exact
|
|
1401
|
+
punctuation or timezone abbreviations may differ from Qt's locale database.
|
|
1402
|
+
This does not change ordinary web-page Intl behavior.
|
|
1403
|
+
|
|
1404
|
+
本機 host 模式的 QML 依序採用 `LC_ALL`、對應分類(`LC_TIME`、
|
|
1405
|
+
`LC_MEASUREMENT` 或 `LC_MESSAGES`)、`LANG`,空值跳過。
|
|
1406
|
+
`C`、`C.UTF-8` 和 `POSIX` 採 Qt 的 C locale:公制、日優先的短日期與
|
|
1407
|
+
24 小時制。明確指定 `Qt.locale("en_US")` 時不受系統分類設定影響,也接受
|
|
1408
|
+
`en_US.UTF-8` 形式;不支援的名稱退回 C。環境設定在程序啟動時保存。
|
|
1409
|
+
非 C 的格式資料及時區名稱仍使用 JavaScript Intl,因此標點或時區縮寫可能與
|
|
1410
|
+
Qt 資料庫不同。一般網頁的 Intl 行為不受影響。
|
|
1411
|
+
|
|
1412
|
+
### Pointer actions and synchronous notifications / 滑鼠操作與同步通知
|
|
1413
|
+
|
|
1414
|
+
QML MouseArea and TapHandler now receive accepted right/middle clicks through
|
|
1415
|
+
both CDP/Casty and the Wayland frontend; double clicks also reach their handlers.
|
|
1416
|
+
Property `Changed.connect(...)` notifications run synchronously after binding
|
|
1417
|
+
invalidation, allowing callers to read mirrored state immediately. Ordinary
|
|
1418
|
+
rendering effects remain batched. This fixes Flea's Duplicate validation reading
|
|
1419
|
+
an obsolete zero selection count after its menu closes; it does not bypass the
|
|
1420
|
+
application's permission or selection checks. `Window.window.update()` requests
|
|
1421
|
+
a frame through the existing renderer; it does not create a separate render loop.
|
|
1422
|
+
|
|
1423
|
+
QML MouseArea/TapHandler 可從 CDP/Casty 與 Wayland 收到允許的右/中鍵,
|
|
1424
|
+
雙擊也會傳給對應 handler。屬性的 `Changed.connect(...)` 在 binding 失效後同步
|
|
1425
|
+
通知,讓呼叫端立刻讀到同步後的狀態;一般畫面更新仍批次處理。這修復 Flea
|
|
1426
|
+
關閉選單後 Duplicate 驗證讀到舊的零選取數量,沒有跳過權限或選取檢查。
|
|
1427
|
+
`Window.window.update()` 透過既有 renderer 要求重繪,不另開繪圖迴圈。
|
package/lib/qml/builtins.js
CHANGED
|
@@ -27,7 +27,7 @@ export const nonvisualTypes = new Set([
|
|
|
27
27
|
'Component',
|
|
28
28
|
'Scale',
|
|
29
29
|
'Translate',
|
|
30
|
-
'Repeater',
|
|
30
|
+
'Shortcut', 'Instantiator', 'Repeater',
|
|
31
31
|
'TapHandler',
|
|
32
32
|
'HoverHandler',
|
|
33
33
|
'QtObject',
|
|
@@ -35,6 +35,7 @@ export const nonvisualTypes = new Set([
|
|
|
35
35
|
'Timer',
|
|
36
36
|
'Connections',
|
|
37
37
|
'FileView',
|
|
38
|
+
'Socket',
|
|
38
39
|
'Process',
|
|
39
40
|
'StdioCollector',
|
|
40
41
|
'SplitParser',
|
|
@@ -42,6 +43,8 @@ export const nonvisualTypes = new Set([
|
|
|
42
43
|
'FontMetrics',
|
|
43
44
|
]);
|
|
44
45
|
export const typeProperties = {
|
|
46
|
+
FloatingWindow:['minimumSize','maximumSize'],
|
|
47
|
+
Button:['padding','contentItem','background','focusPolicy','hovered','pressed','down'],
|
|
45
48
|
MediaPlayer:['source','autoPlay','audioOutput','videoOutput','position','duration','playbackState','playing','mediaStatus','error','errorString','hasAudio','hasVideo','seekable','bufferProgress','loops','play','pause','stop'],
|
|
46
49
|
AudioOutput:['muted','volume'],
|
|
47
50
|
VideoOutput:['fillMode','sourceRect','contentRect'],
|
|
@@ -262,7 +265,7 @@ export const typeProperties = {
|
|
|
262
265
|
'font',
|
|
263
266
|
'color',
|
|
264
267
|
'cursorPosition',
|
|
265
|
-
'cursorVisible',
|
|
268
|
+
'cursorVisible','cursorDelegate',
|
|
266
269
|
'selectionStart',
|
|
267
270
|
'selectionEnd',
|
|
268
271
|
'selectedText',
|
|
@@ -294,7 +297,7 @@ export const typeProperties = {
|
|
|
294
297
|
'font',
|
|
295
298
|
'color',
|
|
296
299
|
'cursorPosition',
|
|
297
|
-
'cursorVisible',
|
|
300
|
+
'cursorVisible','cursorDelegate',
|
|
298
301
|
'selectionStart',
|
|
299
302
|
'selectionEnd',
|
|
300
303
|
'selectedText',
|
|
@@ -360,7 +363,9 @@ export const typeProperties = {
|
|
|
360
363
|
Scale: ['xScale', 'yScale', 'origin'],
|
|
361
364
|
Translate: ['x', 'y'],
|
|
362
365
|
Shape: ['preferredRendererType', 'transform'],
|
|
363
|
-
|
|
366
|
+
Canvas: ['loadImage','unloadImage','isImageLoaded','isImageLoading','isImageError','imageLoaded','available','contextType','context','canvasSize','getContext','requestPaint','markDirty','paint','painted','requestAnimationFrame','cancelRequestAnimationFrame'],
|
|
367
|
+
ShaderEffect: ['fragmentShader','vertexShader','status','log','blending','cullMode'],
|
|
368
|
+
ShaderEffectSource: ['mipmap','sourceItem', 'sourceRect', 'textureSize', 'hideSource', 'live', 'scheduleUpdate', 'scheduledUpdateCompleted'],
|
|
364
369
|
ShapePath: [
|
|
365
370
|
'fillItem',
|
|
366
371
|
'fillGradient',
|
|
@@ -379,8 +384,12 @@ export const typeProperties = {
|
|
|
379
384
|
Flow: ['flow', 'spacing', 'layoutDirection'],
|
|
380
385
|
RowLayout: ['spacing', 'layoutDirection', 'uniformCellSizes'],
|
|
381
386
|
ColumnLayout: ['spacing', 'uniformCellSizes'],
|
|
387
|
+
GridLayout: ['columns','rows','rowSpacing','columnSpacing'],
|
|
388
|
+
PathMultiline: ['paths'],
|
|
382
389
|
Grid: ['columns', 'rows', 'spacing', 'rowSpacing', 'columnSpacing'],
|
|
383
390
|
|
|
391
|
+
Shortcut: ['sequence','sequences','enabled','autoRepeat','context','portableText','nativeText','activated','activatedAmbiguously'],
|
|
392
|
+
Instantiator: ['model','delegate','count','active','asynchronous','object','objectAt','objectAdded','objectRemoved'],
|
|
384
393
|
Repeater: ['model', 'delegate', 'count', 'itemAt'],
|
|
385
394
|
MouseArea: [
|
|
386
395
|
'acceptedButtons',
|
|
@@ -432,6 +441,7 @@ export const typeProperties = {
|
|
|
432
441
|
'fileChanged',
|
|
433
442
|
'loadFailed',
|
|
434
443
|
],
|
|
444
|
+
Socket: ['path', 'connected', 'parser', 'write', 'flush', 'error'],
|
|
435
445
|
Process: [
|
|
436
446
|
'running',
|
|
437
447
|
'command',
|
|
@@ -477,7 +487,12 @@ for (const type of ['ListView', 'GridView'])
|
|
|
477
487
|
typeProperties[type].push(...typeProperties.Flickable);
|
|
478
488
|
|
|
479
489
|
// Serialized into the page together with the reactive runtime.
|
|
480
|
-
export function createQt() {
|
|
490
|
+
export function createQt(localeEnvironment = null) {
|
|
491
|
+
// Only local QML has the host capability. Never expose process.env to a
|
|
492
|
+
// sandboxed page; accept an explicit snapshot for deterministic tests.
|
|
493
|
+
const localeEnv = localeEnvironment ?? Object.fromEntries(
|
|
494
|
+
['LC_ALL','LC_MESSAGES','LC_TIME','LC_MEASUREMENT','LANG'].map(key =>
|
|
495
|
+
[key, globalThis.__qmlHost?.env(key) || '']));
|
|
481
496
|
const bindingValues = new WeakMap();
|
|
482
497
|
const clamp = (x) => Math.max(0, Math.min(1, Number(x)));
|
|
483
498
|
function rgba(r, g, b, a = 1) {
|
|
@@ -607,7 +622,60 @@ export function createQt() {
|
|
|
607
622
|
for (let i = 0; i < 26; i++) keys[String.fromCharCode(65 + i)] = 65 + i;
|
|
608
623
|
for (let i = 0; i < 10; i++) keys[String(i)] = 48 + i;
|
|
609
624
|
for (let i = 1; i <= 35; i++) keys['F' + i] = 0x01000030 + i - 1;
|
|
625
|
+
function localeTag(name) {
|
|
626
|
+
const raw = String(name || 'C');
|
|
627
|
+
const base = raw.split('.')[0].split('@')[0];
|
|
628
|
+
if (/^(C|POSIX)$/i.test(base)) return 'C';
|
|
629
|
+
try {
|
|
630
|
+
const tag = Intl.getCanonicalLocales(base.replaceAll('_','-'))[0];
|
|
631
|
+
return Intl.DateTimeFormat.supportedLocalesOf([tag]).length ? tag : 'C';
|
|
632
|
+
} catch { return 'C'; }
|
|
633
|
+
}
|
|
634
|
+
function systemLocale(category) {
|
|
635
|
+
return localeTag(localeEnv.LC_ALL || localeEnv[category] || localeEnv.LANG || 'C');
|
|
636
|
+
}
|
|
637
|
+
function locale(name) {
|
|
638
|
+
const explicit = name !== undefined && name !== '';
|
|
639
|
+
const actual = explicit ? localeTag(name) : systemLocale('LC_MESSAGES');
|
|
640
|
+
const timeLocale = explicit ? actual : systemLocale('LC_TIME');
|
|
641
|
+
const measurement = explicit ? actual : systemLocale('LC_MEASUREMENT');
|
|
642
|
+
const region = measurement === 'C' ? '' : new Intl.Locale(measurement).maximize().region;
|
|
643
|
+
const pattern = (time, short) => {
|
|
644
|
+
if (timeLocale === 'C') return time ? (short ? 'HH:mm' : 'HH:mm:ss t')
|
|
645
|
+
: (short ? 'd MM yyyy' : 'dddd, d MMMM yyyy');
|
|
646
|
+
const formatter = new Intl.DateTimeFormat(timeLocale, time
|
|
647
|
+
? {hour:'numeric',minute:'2-digit',...(short?{}:{second:'2-digit'})}
|
|
648
|
+
: {dateStyle:short?'short':'full'});
|
|
649
|
+
const twelve = ['h11','h12'].includes(formatter.resolvedOptions().hourCycle);
|
|
650
|
+
return formatter.formatToParts(new Date(2024,10,23,17,45,36)).map(p => {
|
|
651
|
+
switch(p.type) {
|
|
652
|
+
case 'year': return p.value.length === 2 ? 'yy' : 'yyyy';
|
|
653
|
+
case 'month': return /^\d+$/.test(p.value) ? (p.value.length === 2 ? 'MM' : 'M') : 'MMMM';
|
|
654
|
+
case 'day': return p.value.length === 2 ? 'dd' : 'd';
|
|
655
|
+
case 'weekday': return 'dddd';
|
|
656
|
+
case 'hour': return twelve ? 'h' : 'HH';
|
|
657
|
+
case 'minute': return 'mm';
|
|
658
|
+
case 'second': return 'ss';
|
|
659
|
+
case 'dayPeriod': return 'AP';
|
|
660
|
+
default: return /[A-Za-z']/.test(p.value) ? "'"+p.value.replaceAll("'","''")+"'" : p.value;
|
|
661
|
+
}
|
|
662
|
+
}).join('');
|
|
663
|
+
};
|
|
664
|
+
return {name:actual.replaceAll('-','_'),measurementSystem:region==='US'?1:region==='GB'?2:0,
|
|
665
|
+
timeFormat:format=>pattern(true,format===1),dateFormat:format=>pattern(false,format===1)};
|
|
666
|
+
}
|
|
667
|
+
function formatDateTime(input,format='ddd MMM d yyyy HH:mm:ss') {
|
|
668
|
+
const d=input instanceof Date?input:new Date(input);if(!Number.isFinite(d.getTime()))return '';
|
|
669
|
+
const pad=(n,width=2)=>String(n).padStart(width,'0'),hour=/AP|ap/.test(format)?d.getHours()%12||12:d.getHours();
|
|
670
|
+
const localTag=systemLocale('LC_TIME'), intlTag=localTag==='C'?'en-US':localTag;
|
|
671
|
+
const names=(options)=>new Intl.DateTimeFormat(intlTag,options).format(d);
|
|
672
|
+
const values={yyyy:d.getFullYear(),yy:pad(d.getFullYear()%100),MMMM:names({month:'long'}),MMM:names({month:'short'}),MM:pad(d.getMonth()+1),M:d.getMonth()+1,dddd:names({weekday:'long'}),ddd:names({weekday:'short'}),dd:pad(d.getDate()),d:d.getDate(),HH:pad(d.getHours()),H:d.getHours(),hh:pad(hour),h:hour,mm:pad(d.getMinutes()),m:d.getMinutes(),ss:pad(d.getSeconds()),s:d.getSeconds(),zzz:pad(d.getMilliseconds(),3),z:d.getMilliseconds(),AP:d.getHours()<12?'AM':'PM',ap:d.getHours()<12?'am':'pm',t:new Intl.DateTimeFormat(undefined,{timeZoneName:'short'}).formatToParts(d).find(p=>p.type==='timeZoneName')?.value||''};
|
|
673
|
+
return String(format).replace(/'((?:[^']|'')*)'|yyyy|MMMM|dddd|zzz|MMM|ddd|yy|MM|dd|HH|hh|mm|ss|AP|ap|[MdHhmszt]/g,(token,literal)=>literal!==undefined?literal.replaceAll("''", "'"):String(values[token]));
|
|
674
|
+
}
|
|
610
675
|
return {
|
|
676
|
+
locale,formatDateTime,formatTime:(date,format="HH:mm:ss")=>formatDateTime(date,format),formatDate:(date,format="ddd MMM d yyyy")=>formatDateTime(date,format),
|
|
677
|
+
NoFocus:0, TabFocus:1, ClickFocus:2, StrongFocus:3,
|
|
678
|
+
WindowShortcut: 1, ApplicationShortcut: 2,
|
|
611
679
|
...Object.fromEntries(
|
|
612
680
|
Object.entries(keys).map(([name, value]) => ['Key_' + name, value]),
|
|
613
681
|
),
|
|
@@ -634,6 +702,9 @@ export function createQt() {
|
|
|
634
702
|
y = color(b);
|
|
635
703
|
return ['r', 'g', 'b', 'a'].every((k) => Math.abs(x[k] - y[k]) < 1 / 255);
|
|
636
704
|
},
|
|
705
|
+
vector2d: (x,y)=>({x:Number(x),y:Number(y)}),
|
|
706
|
+
vector3d: (x,y,z)=>({x:Number(x),y:Number(y),z:Number(z)}),
|
|
707
|
+
vector4d: (x,y,z,w)=>({x:Number(x),y:Number(y),z:Number(z),w:Number(w)}),
|
|
637
708
|
point: (x, y) => ({ x, y }),
|
|
638
709
|
size: (width, height) => ({ width, height }),
|
|
639
710
|
rect: (x, y, width, height) => ({ x, y, width, height }),
|