buninu-browser 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +40 -4
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +152 -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 +130 -0
- package/frontends/wayland/input.js +75 -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 +162 -0
- package/index.js +28 -1
- package/lib/audio/shared-connection.js +31 -0
- package/lib/cdp-server.js +15 -2
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +37 -2
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +54 -12
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +6 -1
- package/lib/qml/README.md +1427 -0
- package/lib/qml/builtins.js +771 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/canvas.js +76 -0
- package/lib/qml/document.js +1199 -0
- package/lib/qml/fonts.js +69 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +352 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +521 -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 +182 -0
- package/lib/qml/runtime.js +3603 -0
- 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 +251 -0
- package/lib/qml/shortcuts.js +65 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +126 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/window-constraints.js +16 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +16 -1
- package/lib/renderer/canvas-2d.js +29 -0
- package/lib/renderer/document-selection.js +19 -0
- package/lib/renderer/host.js +45 -17
- package/lib/renderer/main.js +21 -6
- package/lib/renderer/page-realm.js +20 -10
- package/lib/renderer/page-renderer.js +497 -42
- package/lib/renderer/pointer-move.js +5 -3
- package/lib/renderer/render-interval.js +9 -0
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +10 -4
- package/package.json +1 -1
- package/test/audio-connection-lifetime.test.js +46 -0
- package/test/canvas-paint.test.js +8 -0
- package/test/clipboard.test.js +19 -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/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/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/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-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-media-audio.js +39 -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/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-double-click.html +21 -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-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/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/pointer-drag.test.js +104 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-canvas.test.js +21 -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 +95 -0
- package/test/qml-controls-window.test.js +182 -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-fonts.test.js +31 -0
- package/test/qml-grid-multiline.test.js +48 -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-locale.test.js +45 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-nonvisual-visible.test.js +19 -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-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-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-window-update.test.js +36 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/render-interval.test.js +33 -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/title-no-paint.test.js +6 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-scroll.test.js +40 -0
- package/test/wayland-urlbar.test.js +61 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Exercise the official backend, including bat path validation, sandbox and SGR parsing.
|
|
2
|
+
import cases from './highlight-cases.json' with {type:'json'};
|
|
3
|
+
import {mkdir,realpath} from 'node:fs/promises';
|
|
4
|
+
const base='/root/flea-demo/code';
|
|
5
|
+
await mkdir(base,{recursive:true});
|
|
6
|
+
if(!(await realpath('/usr/local/bin/bat')).startsWith('/usr/'))throw Error('Flea requires bat realpath under /usr');
|
|
7
|
+
const p=Bun.spawn(['/usr/bin/flea','--backend'],{stdin:'pipe',stdout:'pipe',stderr:'pipe'});
|
|
8
|
+
const errors=new Response(p.stderr).text();
|
|
9
|
+
let pending,token=0;
|
|
10
|
+
const reader=(async()=>{
|
|
11
|
+
let buffer='';const decoder=new TextDecoder();
|
|
12
|
+
for await(const chunk of p.stdout){
|
|
13
|
+
buffer+=decoder.decode(chunk,{stream:true});let i;
|
|
14
|
+
while((i=buffer.indexOf('\n'))>=0){
|
|
15
|
+
const line=buffer.slice(0,i);buffer=buffer.slice(i+1);
|
|
16
|
+
const msg=JSON.parse(line);if(msg.t==='code'&&msg.token===token)pending?.(msg);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
async function ask(params){
|
|
21
|
+
token++;
|
|
22
|
+
let timer;
|
|
23
|
+
try{return await new Promise((resolve,reject)=>{
|
|
24
|
+
pending=resolve;timer=setTimeout(()=>reject(Error('Timed out: '+JSON.stringify(params))),15000);
|
|
25
|
+
p.stdin.write(JSON.stringify({c:'code',token,slot:'verify',first:1,count:400,...params})+'\n');
|
|
26
|
+
});}finally{clearTimeout(timer);pending=null;}
|
|
27
|
+
}
|
|
28
|
+
let checks=0;
|
|
29
|
+
try{
|
|
30
|
+
for(const sample of cases){
|
|
31
|
+
const path=base+'/'+sample.name;await Bun.write(path,sample.text);
|
|
32
|
+
for(const query of [{path},{text:sample.text,name:sample.name,lang:sample.lang}]){
|
|
33
|
+
const result=await ask(query);
|
|
34
|
+
const colored=result.lines?.some(line=>line.some((v,i)=>i%2===0&&v>0));
|
|
35
|
+
if(result.state!=='styled'||!colored)throw Error(`${sample.name} (${query.path?'file':sample.lang}): ${JSON.stringify(result)}`);
|
|
36
|
+
const text=result.lines.map(line=>line.filter((_,i)=>i%2===1).join('')).join('\n');
|
|
37
|
+
if(text!==sample.text.trimEnd())throw Error('Text changed: '+sample.name);
|
|
38
|
+
checks++;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const plain=await ask({text:'plain text\n',name:'sample.unknown',lang:'not-a-real-language'});
|
|
42
|
+
if(plain.state!=='plain')throw Error('Unknown language must fall back to plain text');
|
|
43
|
+
console.log(`PASS: official Flea colored ${cases.length} formats, ${checks} file/language requests; unknown language falls back to plain`);
|
|
44
|
+
}finally{
|
|
45
|
+
p.stdin.write('{"c":"quit"}\n');p.stdin.end();await p.exited;await reader;
|
|
46
|
+
const stderr=await errors;if(stderr)console.error(stderr);
|
|
47
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Real Flea media component, decoder and host PulseAudio connection.
|
|
2
|
+
import {createBrowser} from '/opt/buninu-browser/lib/headless-shell.js';
|
|
3
|
+
import {PulseClient} from '/opt/buninu-browser/lib/audio/pulse-client.js';
|
|
4
|
+
import {mkdtemp,rm} from 'node:fs/promises';
|
|
5
|
+
const dir=await mkdtemp('/tmp/flea-media-check-');
|
|
6
|
+
Object.assign(process.env,{HOME:dir,XDG_CONFIG_HOME:dir+'/config',XDG_CACHE_HOME:dir+'/cache',QML_IMPORT_PATH:'/usr/share/omarchy',FLEA_BIN:'/usr/bin/flea',FLEA_PATH:'/root/flea-demo'});
|
|
7
|
+
let b;
|
|
8
|
+
try{
|
|
9
|
+
const pulse=await Promise.race([PulseClient.connect(),Bun.sleep(5000).then(()=>{throw Error('Pulse connection timeout')})]);
|
|
10
|
+
console.log('Pulse connected');pulse.close();
|
|
11
|
+
b=await createBrowser({spareRenderer:false});
|
|
12
|
+
await b.context.addInitScript(`globalThis.players=[];globalThis.panes=[];let rt;Object.defineProperty(globalThis,'__qmlRuntime',{get:()=>rt,set:r=>{rt=r;const d=r.describe;r.describe=(v,t,...a)=>{if(t.includes('Pane'))panes.push(v);if(t.includes('MediaPlayer'))players.push(v);return d(v,t,...a)}}});`);
|
|
13
|
+
await b.context.navigate('file:///usr/share/flea/ui/boot/shell.qml');
|
|
14
|
+
await Bun.sleep(1500);
|
|
15
|
+
await b.context.press('2',{code:'Digit2',modifiers:2});
|
|
16
|
+
await Bun.sleep(300);
|
|
17
|
+
const point=await b.context.evaluate(`(()=>{const e=[...document.querySelectorAll('[data-qml-type=Text]')].find(e=>e.textContent==='tone.mp3'&&e.getBoundingClientRect().width>0);const r=e.getBoundingClientRect();return [r.x+r.width/2,r.y+r.height/2]})()`);
|
|
18
|
+
await b.context.click(...point);await Bun.sleep(500);
|
|
19
|
+
console.log('before',await b.context.evaluate(`panes.map(p=>({mode:p.viewMode,path:p.previewColumnItem?.path,visible:p.previewColumnItem?.visible,allowed:p.previewColumnItem?.playerAllowed}))`));
|
|
20
|
+
await b.context.evaluate(`panes[0].previewColumnItem.mediaStripItem().toggled()`);
|
|
21
|
+
|
|
22
|
+
for(let i=0;i<8;i++){
|
|
23
|
+
await Bun.sleep(400);
|
|
24
|
+
console.log('column',await b.context.evaluate(`panes.map(p=>({wants:p.previewColumnItem?.wantsPlayback,loaded:p.previewColumnItem?.playerLoaded(),playing:p.previewColumnItem?.mediaPlaying(),position:p.previewColumnItem?.mediaPosition()}))`));
|
|
25
|
+
console.log(await b.context.evaluate(`players.map(p=>({source:p.source,error:p.error,message:p.errorString,position:p.position,duration:p.duration,state:p.playbackState,volume:p.audioOutput?.volume,muted:p.audioOutput?.muted}))`));
|
|
26
|
+
}
|
|
27
|
+
}finally{b?.close();await rm(dir,{recursive:true,force:true});}
|
|
28
|
+
|
|
29
|
+
process.exit(0);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Actual Flea UI + backend, isolated settings, without changing the user's Land.
|
|
2
|
+
import {mkdtemp,rm} from 'node:fs/promises';
|
|
3
|
+
import {createBrowser} from '/opt/buninu-browser/lib/headless-shell.js';
|
|
4
|
+
const dir=await mkdtemp('/tmp/flea-color-ui-');
|
|
5
|
+
Object.assign(process.env,{HOME:dir,XDG_CONFIG_HOME:dir+'/config',XDG_CACHE_HOME:dir+'/cache',XDG_DATA_HOME:dir+'/data',XDG_STATE_HOME:dir+'/state',QML_IMPORT_PATH:'/usr/share/omarchy',FLEA_BIN:'/usr/bin/flea',FLEA_PATH:'/root/flea-demo'});
|
|
6
|
+
let browser;
|
|
7
|
+
try{
|
|
8
|
+
browser=await createBrowser({spareRenderer:false,rendererTimeout:30000});
|
|
9
|
+
await browser.context.resize(900,600);
|
|
10
|
+
await browser.context.addInitScript(`globalThis.codeReaders=[];let rt;Object.defineProperty(globalThis,'__qmlRuntime',{get:()=>rt,set:r=>{rt=r;const d=r.describe;r.describe=(v,t,...a)=>{if(t.includes('CodeLines'))codeReaders.push(v);return d(v,t,...a)}}});`);
|
|
11
|
+
await browser.context.navigate('file:///usr/share/flea/ui/boot/shell.qml');
|
|
12
|
+
async function waitFor(expression){for(let i=0;i<200;i++){if(await browser.context.evaluate(expression))return;await Bun.sleep(50)}throw Error('UI timeout: '+expression);}
|
|
13
|
+
await waitFor(`document.body.innerText.includes('hello.js')`);
|
|
14
|
+
for(const [name,text] of [['sample.jsonl','{"answer":42}\n'],['sample.html','<html><body><h1>Hello</h1></body></html>\n']])await Bun.write('/root/flea-demo/'+name,text);
|
|
15
|
+
for(const name of ['hello.js','sample.jsonl','sample.html']){
|
|
16
|
+
await waitFor(`document.body.innerText.includes(${JSON.stringify(name)})`);
|
|
17
|
+
const point=await browser.context.evaluate(`(()=>{const e=[...document.querySelectorAll('[data-qml-type=Text]')].find(e=>e.textContent===${JSON.stringify(name)}&&e.getBoundingClientRect().width>0);const r=e.getBoundingClientRect();return [r.x+r.width/2,r.y+r.height/2]})()`);
|
|
18
|
+
await browser.context.click(...point);
|
|
19
|
+
await browser.context.press(' ',{code:'Space'});
|
|
20
|
+
if(name.endsWith('.html')){await Bun.sleep(800);await browser.context.press('r',{code:'KeyR'});}
|
|
21
|
+
if(name.endsWith('.html')) await waitFor(`Array.from(document.querySelectorAll('[data-qml-type=Text]')).some(e=>e.textContent.includes('<html>')&&Array.from(e.querySelectorAll('span')).some(s=>s.style.color))`);
|
|
22
|
+
else await waitFor(`codeReaders.some(v=>v.active&&v.path.endsWith(${JSON.stringify(name)})&&v.answer==='styled'&&Object.values(v.held).some(l=>l.some((r,i)=>i%2===0&&r>0)))`);
|
|
23
|
+
await Bun.write('/tmp/flea-js-preview.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));
|
|
24
|
+
console.log('PASS: official Flea UI preview styled: '+name);
|
|
25
|
+
await browser.context.press('Escape',{code:'Escape'});
|
|
26
|
+
}
|
|
27
|
+
}catch(error){await Bun.write('/tmp/flea-html-preview.png',Buffer.from(await browser.context.screenshot({format:'png'}),'base64'));console.error(await browser.context.evaluate(`({text:document.body.innerText,html:Array.from(document.querySelectorAll('[data-qml-type=Text]')).filter(e=>e.textContent.includes('<html>')).map(e=>e.innerHTML),readers:codeReaders.map(v=>({active:v.active,path:v.path,answer:v.answer}))})`));throw error;}finally{browser?.close();await rm(dir,{recursive:true,force:true});}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Build smoke test: actual tools, real Flea backend, no compositor required.
|
|
2
|
+
import {mkdtemp,rm} from 'node:fs/promises';
|
|
3
|
+
const assert=(ok,message)=>{if(!ok)throw Error(message);};
|
|
4
|
+
async function run(args,input='',env={}){
|
|
5
|
+
const p=Bun.spawn(args,{env:{...process.env,PATH:'/usr/local/bin:/usr/bin:/bin',...env},stdin:'pipe',stdout:'pipe',stderr:'pipe'});
|
|
6
|
+
p.stdin.write(input);
|
|
7
|
+
p.stdin.end();
|
|
8
|
+
const [out,err,code]=await Promise.all([new Response(p.stdout).text(),new Response(p.stderr).text(),p.exited]);
|
|
9
|
+
assert(code===0,`${args.join(' ')} exited ${code}: ${err}`);return out;
|
|
10
|
+
}
|
|
11
|
+
assert(Bun.version==='1.4.4',`Expected Bun 1.4.4, got ${Bun.version}`);
|
|
12
|
+
assert(await Bun.file('/usr/share/mime/globs2').exists(),'Missing MIME database');
|
|
13
|
+
assert(await Bun.file('/usr/share/omarchy/qs/Commons/Style.qml').exists(),'Missing Omarchy Commons');
|
|
14
|
+
const ansi=await run(['bat','--color=always','--style=plain','--paging=never','--file-name=sample.js','-'],'const answer = 42;\n');
|
|
15
|
+
assert(ansi.includes('\x1b[')&&Bun.stripANSI(ansi).includes('const answer = 42;'),'bat adapter did not highlight input');
|
|
16
|
+
await run([process.execPath,new URL('./check-highlighting.mjs',import.meta.url).pathname]).then(text=>process.stdout.write(text));
|
|
17
|
+
const cache=await mkdtemp('/tmp/flea-build-check-');
|
|
18
|
+
try{
|
|
19
|
+
const out=await run(['/usr/bin/flea','--backend'],[
|
|
20
|
+
{c:'list',path:'/root/flea-demo',first:20},{c:'thumb',rows:[0,1,2]},{c:'quit'}
|
|
21
|
+
].map(x=>JSON.stringify(x)+'\n').join(''),{XDG_CACHE_HOME:cache});
|
|
22
|
+
const replies=out.trim().split('\n').map(x=>JSON.parse(x));
|
|
23
|
+
const thumb=replies.find(x=>x.t==='thumbed'&&x.file);
|
|
24
|
+
assert(thumb,'Flea did not accept placeholder thumbnail: '+out);
|
|
25
|
+
assert((await Bun.file(thumb.file).bytes()).subarray(1,4).toString()==='80,78,71','Invalid PNG');
|
|
26
|
+
const {createBrowser}=await import('/opt/buninu-browser/lib/headless-shell.js');
|
|
27
|
+
const page=await createBrowser({spareRenderer:false});
|
|
28
|
+
try {
|
|
29
|
+
const qml=cache+'/check.qml';
|
|
30
|
+
await Bun.write(qml,'import QtQuick\nRectangle {width:320;height:120;color:"white";Text {text:"Flea QML ready";color:"black"}}');
|
|
31
|
+
await page.context.navigate(Bun.pathToFileURL(qml).href);
|
|
32
|
+
assert((await page.context.evaluate('document.body.innerText')).includes('Flea QML ready'),'QML runtime did not render text');
|
|
33
|
+
assert((await page.context.screenshot({format:'png'})).length>100,'QML screenshot is empty');
|
|
34
|
+
} finally {page.close();}
|
|
35
|
+
console.log('PASS: Bun 1.4.4, MIME, Omarchy imports, jsmdcui ANSI and official Flea thumbnail backend, QML rendering');
|
|
36
|
+
}finally{await rm(cache,{recursive:true,force:true});}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "sample.js",
|
|
4
|
+
"lang": "js",
|
|
5
|
+
"text": "const answer = 42; // comment\n"
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"name": "sample.mjs",
|
|
9
|
+
"lang": "JavaScript",
|
|
10
|
+
"text": "export const answer = 42;\n"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "sample.cjs",
|
|
14
|
+
"lang": "node",
|
|
15
|
+
"text": "const answer = require(\"fs\");\n"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"name": "sample.jsx",
|
|
19
|
+
"lang": "jsx",
|
|
20
|
+
"text": "const answer = 42;\n"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "sample.ts",
|
|
24
|
+
"lang": "ts",
|
|
25
|
+
"text": "const answer: number = 42;\n"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "sample.tsx",
|
|
29
|
+
"lang": "tsx",
|
|
30
|
+
"text": "const answer: number = 42;\n"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "sample.mts",
|
|
34
|
+
"lang": "typescript",
|
|
35
|
+
"text": "export const answer: number = 42;\n"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "sample.py",
|
|
39
|
+
"lang": "python3",
|
|
40
|
+
"text": "def answer():\n return 42 # comment\n"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "sample.sh",
|
|
44
|
+
"lang": "bash",
|
|
45
|
+
"text": "#!/bin/sh\necho \"$HOME\" # comment\n"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"name": "sample.zsh",
|
|
49
|
+
"lang": "zsh",
|
|
50
|
+
"text": "#!/bin/zsh\necho \"$HOME\"\n"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "sample.fish",
|
|
54
|
+
"lang": "fish",
|
|
55
|
+
"text": "echo \"$HOME\"\n"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "sample.json",
|
|
59
|
+
"lang": "json",
|
|
60
|
+
"text": "{\"answer\":42,\"ok\":true}\n"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "sample.jsonc",
|
|
64
|
+
"lang": "jsonc",
|
|
65
|
+
"text": "{\"answer\":42} // comment\n"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "sample.json5",
|
|
69
|
+
"lang": "json5",
|
|
70
|
+
"text": "{\"answer\":42}\n"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "sample.jsonl",
|
|
74
|
+
"lang": "jsonl",
|
|
75
|
+
"text": "{\"answer\":42}\n"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"name": "sample.yaml",
|
|
79
|
+
"lang": "yml",
|
|
80
|
+
"text": "answer: 42 # comment\n"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "sample.toml",
|
|
84
|
+
"lang": "toml",
|
|
85
|
+
"text": "answer = 42 # comment\n"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"name": "sample.ini",
|
|
89
|
+
"lang": "ini",
|
|
90
|
+
"text": "[section]\nanswer = 42\n"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": ".env",
|
|
94
|
+
"lang": "dotenv",
|
|
95
|
+
"text": "ANSWER=\"hello\" # comment\n"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "sample.html",
|
|
99
|
+
"lang": "HTML",
|
|
100
|
+
"text": "<div class=\"hello\">world</div>\n"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "sample.xml",
|
|
104
|
+
"lang": "xml",
|
|
105
|
+
"text": "<hello answer=\"42\"/>\n"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "sample.css",
|
|
109
|
+
"lang": "css",
|
|
110
|
+
"text": "body { color: red; margin: 42px; }\n"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "sample.scss",
|
|
114
|
+
"lang": "css",
|
|
115
|
+
"text": "body { color: red; }\n"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "sample.sql",
|
|
119
|
+
"lang": "sql",
|
|
120
|
+
"text": "SELECT * FROM users WHERE id = 42;\n"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "sample.c",
|
|
124
|
+
"lang": "c",
|
|
125
|
+
"text": "int main(void) { return 42; }\n"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "sample.cpp",
|
|
129
|
+
"lang": "cpp",
|
|
130
|
+
"text": "int main() { return 42; }\n"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"name": "sample.cs",
|
|
134
|
+
"lang": "c#",
|
|
135
|
+
"text": "public class Hello { int answer = 42; }\n"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "sample.go",
|
|
139
|
+
"lang": "golang",
|
|
140
|
+
"text": "package main\nfunc main() { println(42) }\n"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "sample.rs",
|
|
144
|
+
"lang": "rs",
|
|
145
|
+
"text": "fn main() { let answer = 42; }\n"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"name": "sample.java",
|
|
149
|
+
"lang": "java",
|
|
150
|
+
"text": "public class Hello { int answer = 42; }\n"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"name": "sample.kt",
|
|
154
|
+
"lang": "kt",
|
|
155
|
+
"text": "val answer = 42\n"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"name": "sample.rb",
|
|
159
|
+
"lang": "rb",
|
|
160
|
+
"text": "def answer\n return 42\nend\n"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"name": "sample.php",
|
|
164
|
+
"lang": "php",
|
|
165
|
+
"text": "<?php echo \"hello\"; ?>\n"
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
"name": "sample.lua",
|
|
169
|
+
"lang": "lua",
|
|
170
|
+
"text": "local answer = 42 -- comment\n"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "sample.pl",
|
|
174
|
+
"lang": "perl",
|
|
175
|
+
"text": "my $answer = 42; # comment\n"
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"name": "sample.ps1",
|
|
179
|
+
"lang": "pwsh",
|
|
180
|
+
"text": "$answer = \"hello\" # comment\n"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"name": "sample.bat",
|
|
184
|
+
"lang": "cmd",
|
|
185
|
+
"text": "@echo off\nset ANSWER=42\nREM comment\n"
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
"name": "Dockerfile",
|
|
189
|
+
"lang": "docker",
|
|
190
|
+
"text": "FROM debian:13\nRUN echo hello\n"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "Makefile",
|
|
194
|
+
"lang": "make",
|
|
195
|
+
"text": "all:\n\techo \"hello\" # comment\n"
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "CMakeLists.txt",
|
|
199
|
+
"lang": "cmake",
|
|
200
|
+
"text": "set(ANSWER 42) # comment\n"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "sample.md",
|
|
204
|
+
"lang": "md",
|
|
205
|
+
"text": "# Heading\n**bold** and `code`\n"
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "sample.diff",
|
|
209
|
+
"lang": "diff",
|
|
210
|
+
"text": "--- a/file\n+++ b/file\n@@ -1 +1 @@\n-old\n+new\n"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"name": "sample.tf",
|
|
214
|
+
"lang": "tf",
|
|
215
|
+
"text": "variable \"answer\" { default = 42 }\n"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"name": "sample.vue",
|
|
219
|
+
"lang": "vue",
|
|
220
|
+
"text": "<template><div class=\"hello\">Hello</div></template>\n"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "sample.svelte",
|
|
224
|
+
"lang": "svelte",
|
|
225
|
+
"text": "<script>const answer = 42;</script>\n"
|
|
226
|
+
}
|
|
227
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
|
3
|
+
<!-- Low priority: retain the distribution's more specific MIME definitions. -->
|
|
4
|
+
<mime-type type="text/x-buninu-source">
|
|
5
|
+
<comment>Source code</comment>
|
|
6
|
+
<sub-class-of type="text/plain"/>
|
|
7
|
+
<generic-icon name="text-x-script"/>
|
|
8
|
+
<glob pattern="*.jsonl" weight="10"/>
|
|
9
|
+
<glob pattern="*.ndjson" weight="10"/>
|
|
10
|
+
<glob pattern="*.jsonc" weight="10"/>
|
|
11
|
+
<glob pattern="*.json5" weight="10"/>
|
|
12
|
+
<glob pattern="*.jsx" weight="10"/>
|
|
13
|
+
<glob pattern="Dockerfile" weight="10"/>
|
|
14
|
+
<glob pattern="*.cjs" weight="10"/>
|
|
15
|
+
<glob pattern="*.ini" weight="10"/>
|
|
16
|
+
<glob pattern="*.mts" weight="10"/>
|
|
17
|
+
<glob pattern="*.cts" weight="10"/>
|
|
18
|
+
<glob pattern="*.vue" weight="10"/>
|
|
19
|
+
<glob pattern="*.svelte" weight="10"/>
|
|
20
|
+
</mime-type>
|
|
21
|
+
<!-- TSX is ambiguous: prefer TypeScript JSX over Debian's Tiled tileset rule (50). -->
|
|
22
|
+
<mime-type type="text/x-buninu-tsx">
|
|
23
|
+
<comment>TypeScript JSX source</comment>
|
|
24
|
+
<sub-class-of type="text/plain"/>
|
|
25
|
+
<generic-icon name="text-x-script"/>
|
|
26
|
+
<glob pattern="*.tsx" weight="60"/>
|
|
27
|
+
</mime-type>
|
|
28
|
+
</mime-info>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// jsmdcui 0.19.2's Micro rules contain Go regex flags/boundaries unsupported by JS.
|
|
2
|
+
// Adapt those rule objects locally; do not modify the installed npm package.
|
|
3
|
+
export function adaptSyntaxRules(definitions, SyntaxDefinition) {
|
|
4
|
+
function compile(pattern, original) {
|
|
5
|
+
if(typeof pattern!=='string'||!(/\(\?[ims]+\)|\\[<>]/.test(pattern)))return original;
|
|
6
|
+
const flags=new Set();
|
|
7
|
+
const source=pattern.replace(/\(\?([ims]+)\)/g,(_,letters)=>{for(const flag of letters)flags.add(flag);return '';})
|
|
8
|
+
.replaceAll('\\<','\\b(?=\\w)').replaceAll('\\>','(?<=\\w)\\b');
|
|
9
|
+
const regex=new SyntaxDefinition({}, {rules:[{compat:source}]}).rules[0]?.regex;
|
|
10
|
+
if(!regex)return original;
|
|
11
|
+
try{return new RegExp(regex.source,[...new Set([...regex.flags,...flags])].join(''));}catch{return original;}
|
|
12
|
+
}
|
|
13
|
+
function visit(rules) {
|
|
14
|
+
for(const rule of rules){
|
|
15
|
+
if(rule.type==='pattern')rule.regex=compile(rule.source,rule.regex);
|
|
16
|
+
if(rule.type==='region'){
|
|
17
|
+
for(const key of ['start','end','skip'])rule[key]=compile(rule.source?.[key],rule[key]);
|
|
18
|
+
visit(rule.rules);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for(const definition of definitions)visit(definition.rules);
|
|
23
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Build context: buninu-browser root. Single-stage, js-udocker-compatible syntax.
|
|
2
|
+
FROM oven/bun:1.4.4
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
+
ENV HOME=/root
|
|
5
|
+
ENV LC_ALL=C.UTF-8
|
|
6
|
+
RUN set -eu; if [ -f /usr/bin/perl ] && [ ! -e /usr/bin/perl5.40.1 ]; then cp /usr/bin/perl /usr/bin/perl5.40.1; fi
|
|
7
|
+
RUN set -eu; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl libgcc-s1; apt-get clean
|
|
8
|
+
RUN set -eu; mkdir -p /opt/omastorm; curl -fL --retry 3 https://codeload.github.com/wesleygrimes/omastorm/tar.gz/492e97841146bd9d6d9f7a3064e5e1ae9840cc68 -o /tmp/omastorm.tar.gz; tar -xzf /tmp/omastorm.tar.gz --strip-components=1 -C /opt/omastorm; rm /tmp/omastorm.tar.gz
|
|
9
|
+
RUN set -eu; cd /opt/omastorm; XDG_DATA_HOME=/usr/local/share bash scripts/fetch-engine.sh; ln -sfn /usr/local/share/omastorm/bin/omastorm-engine /usr/local/bin/omastorm-engine
|
|
10
|
+
RUN set -eu; mkdir -p /usr/local/share/fonts/omastorm; curl -fL --retry 3 https://raw.githubusercontent.com/ryanoasis/nerd-fonts/v3.4.0/patched-fonts/JetBrainsMono/Ligatures/Regular/JetBrainsMonoNerdFontMono-Regular.ttf -o /usr/local/share/fonts/omastorm/JetBrainsMonoNerdFontMono-Regular.ttf; curl -fL --retry 3 https://raw.githubusercontent.com/ryanoasis/nerd-fonts/v3.4.0/patched-fonts/JetBrainsMono/Ligatures/Regular/OFL.txt -o /usr/local/share/fonts/omastorm/OFL.txt
|
|
11
|
+
COPY index.js /opt/buninu-browser/index.js
|
|
12
|
+
COPY buninu-browser.js /opt/buninu-browser/buninu-browser.js
|
|
13
|
+
COPY frontends /opt/buninu-browser/frontends
|
|
14
|
+
COPY usr /opt/buninu-browser/usr
|
|
15
|
+
COPY lib /opt/buninu-browser/lib
|
|
16
|
+
COPY vendor /opt/buninu-browser/vendor
|
|
17
|
+
COPY LICENSES /opt/buninu-browser/LICENSES
|
|
18
|
+
COPY LICENSE /opt/buninu-browser/LICENSE
|
|
19
|
+
COPY THIRD_PARTY_NOTICES.md /opt/buninu-browser/THIRD_PARTY_NOTICES.md
|
|
20
|
+
COPY package.json /opt/buninu-browser/package.json
|
|
21
|
+
COPY test/fixtures/omastorm-helpers /usr/local/lib/omastorm-helpers
|
|
22
|
+
ENV OMASTORM_ROOT=/opt/omastorm
|
|
23
|
+
ENV OMASTORM_ENGINE_BINARY=/usr/local/bin/omastorm-engine
|
|
24
|
+
ENV QT_QPA_FONTDIR=/usr/local/share/fonts/omastorm
|
|
25
|
+
ENV XDG_RUNTIME_DIR=/tmp
|
|
26
|
+
ENV WAYLAND_DISPLAY=/tmp/wayland-buninu
|
|
27
|
+
ENV XDG_CONFIG_HOME=/root/.config
|
|
28
|
+
ENV XDG_DATA_HOME=/root/.local/share
|
|
29
|
+
ENV XDG_STATE_HOME=/root/.local/state
|
|
30
|
+
ENV XDG_CACHE_HOME=/root/.cache
|
|
31
|
+
RUN bun /usr/local/lib/omastorm-helpers/prepare.mjs /opt/omastorm
|
|
32
|
+
RUN bun /usr/local/lib/omastorm-helpers/check.mjs
|
|
33
|
+
WORKDIR /root
|
|
34
|
+
ENTRYPOINT []
|
|
35
|
+
CMD ["bun", "/usr/local/lib/omastorm-helpers/launch.mjs"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Build-time smoke test: no Wayland socket, GPU, Rust compiler or native Qt.
|
|
2
|
+
import {strict as assert} from 'node:assert';
|
|
3
|
+
import {mkdtemp,rm} from 'node:fs/promises';
|
|
4
|
+
import {pathToFileURL} from 'node:url';
|
|
5
|
+
import {startEngine} from './runtime.mjs';
|
|
6
|
+
const root=process.env.OMASTORM_ROOT||'/opt/omastorm';
|
|
7
|
+
const browser=process.env.BUNINU_BROWSER_ROOT||'/opt/buninu-browser';
|
|
8
|
+
assert.equal(Bun.version,'1.4.4','Expected the pinned oven/bun:1.4.4 runtime');
|
|
9
|
+
for(const name of ['TextureCheck.qml','RadarMap.qml']) {
|
|
10
|
+
const source=await Bun.file(root+'/ui/'+name).text();
|
|
11
|
+
assert(!source.includes('GraphicsInfo.api === GraphicsInfo.Software'),'Software policy was not prepared');
|
|
12
|
+
assert(await Bun.file(root+'/ui/'+name+'.upstream').exists(),'Missing upstream backup');
|
|
13
|
+
}
|
|
14
|
+
const {loadGraphics}=await import(pathToFileURL(browser+'/lib/renderer/page-renderer.js').href);
|
|
15
|
+
const {fragmentSource}=await import(pathToFileURL(browser+'/lib/qml/shader/qsb.js').href);
|
|
16
|
+
const {compileFragment}=await import(pathToFileURL(browser+'/lib/qml/shader/fragment.js').href);
|
|
17
|
+
const {CanvasKit}=await loadGraphics();
|
|
18
|
+
for(const name of ['radar','tile','grid','texture-check']) {
|
|
19
|
+
const compiled=compileFragment(CanvasKit,fragmentSource(await Bun.file(root+'/ui/shaders/'+name+'.frag.qsb').bytes()));
|
|
20
|
+
compiled.effect.delete();
|
|
21
|
+
}
|
|
22
|
+
const font=CanvasKit.Typeface.MakeTypefaceFromData(await Bun.file(process.env.QT_QPA_FONTDIR+'/JetBrainsMonoNerdFontMono-Regular.ttf').bytes());
|
|
23
|
+
assert(font,'Font cannot be decoded');
|
|
24
|
+
assert([...font.getGlyphIDs('')].every(id=>id!==0),'Nerd icons missing');font.delete();
|
|
25
|
+
const help=Bun.spawn([process.env.OMASTORM_ENGINE_BINARY||'/usr/local/bin/omastorm-engine','--help'],{stdout:'pipe',stderr:'pipe'});
|
|
26
|
+
const [helpOut,helpErr,helpCode]=await Promise.all([new Response(help.stdout).text(),new Response(help.stderr).text(),help.exited]);
|
|
27
|
+
assert(helpCode===1 && (helpOut+helpErr).includes('Usage: omastorm-engine'),'Official engine could not execute');
|
|
28
|
+
// During js-udocker build the unbound ROOT/tmp path may exceed AF_UNIX's
|
|
29
|
+
// native path limit. Exercise the socket after binding host TMPDIR to /tmp.
|
|
30
|
+
if(process.argv.includes('--engine')) {
|
|
31
|
+
const engine=await startEngine();
|
|
32
|
+
try {
|
|
33
|
+
await new Promise((resolve,reject)=>{
|
|
34
|
+
let connection,buffer='';
|
|
35
|
+
const timer=setTimeout(()=>{connection?.end();reject(Error('No engine hello'));},10000);
|
|
36
|
+
Bun.connect({unix:engine.socket,socket:{
|
|
37
|
+
open(s){connection=s;},
|
|
38
|
+
data(s,data){buffer+=Buffer.from(data).toString();const i=buffer.indexOf('\n');if(i<0)return;
|
|
39
|
+
try{const hello=JSON.parse(buffer.slice(0,i));assert.equal(hello.type,'hello');assert(hello.sites?.length>0);clearTimeout(timer);s.end();resolve();}
|
|
40
|
+
catch(e){clearTimeout(timer);s.end();reject(e);}},
|
|
41
|
+
error(s,e){clearTimeout(timer);reject(e);},
|
|
42
|
+
}}).catch(e=>{clearTimeout(timer);reject(e);});
|
|
43
|
+
});
|
|
44
|
+
}finally{await engine.close();}
|
|
45
|
+
}
|
|
46
|
+
const scratch=await mkdtemp('/tmp/omastorm-build-check-');
|
|
47
|
+
const {createBrowser}=await import(pathToFileURL(browser+'/lib/headless-shell.js').href);
|
|
48
|
+
const page=await createBrowser({spareRenderer:false});
|
|
49
|
+
try {
|
|
50
|
+
const qml=scratch+'/main.qml';
|
|
51
|
+
await Bun.write(qml,'import QtQuick\nRectangle {width:320;height:120;color:"white";Text {text:"Omastorm ";font.family:"monospace";color:"black"}}');
|
|
52
|
+
await page.context.navigate(pathToFileURL(qml).href);
|
|
53
|
+
assert((await page.context.evaluate('document.body.innerText')).includes('Omastorm'),'QML did not render');
|
|
54
|
+
assert((await page.context.screenshot({format:'png'})).length>100,'Empty screenshot');
|
|
55
|
+
}finally{page.close();await rm(scratch,{recursive:true,force:true});}
|
|
56
|
+
console.log('PASS: Bun 1.4.4, official engine executable, four software shaders, Nerd Font icons and QML screenshot'+(process.argv.includes('--engine')?', engine hello/site list':''));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {pathToFileURL} from 'node:url';
|
|
2
|
+
import {stat} from 'node:fs/promises';
|
|
3
|
+
import {startEngine,waylandPath} from './runtime.mjs';
|
|
4
|
+
const socket=waylandPath();
|
|
5
|
+
if(!await stat(socket).then(s=>s.isSocket(),()=>false))throw Error('Start Land and bind its socket to '+socket);
|
|
6
|
+
const root=process.env.OMASTORM_ROOT||'/opt/omastorm';
|
|
7
|
+
const browser=process.env.BUNINU_BROWSER_ROOT||'/opt/buninu-browser';
|
|
8
|
+
let engine,app,stopping=false;
|
|
9
|
+
const stop=()=>{stopping=true;app?.close();};
|
|
10
|
+
for(const signal of ['SIGINT','SIGTERM','SIGHUP'])process.on(signal,stop);
|
|
11
|
+
try {
|
|
12
|
+
engine=await startEngine();
|
|
13
|
+
// Set before importing the renderer/QML host, which snapshots environment.
|
|
14
|
+
Object.assign(process.env,engine.env,{WAYLAND_DISPLAY:socket,OMASTORM_ROOT:root});
|
|
15
|
+
const {launch}=await import(pathToFileURL(browser+'/frontends/wayland/index.js').href);
|
|
16
|
+
if(!stopping){
|
|
17
|
+
app=await launch({url:pathToFileURL(root+'/ui/shell.qml').href,socket,width:960,height:680,chrome:false});
|
|
18
|
+
if(stopping)app.close();
|
|
19
|
+
await Promise.race([app.done,engine.child.exited.then(code=>{throw Error('Omastorm engine stopped: '+code);})]);
|
|
20
|
+
}
|
|
21
|
+
}finally{
|
|
22
|
+
app?.close();await engine?.close();
|
|
23
|
+
for(const signal of ['SIGINT','SIGTERM','SIGHUP'])process.off(signal,stop);
|
|
24
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Explicit app experiment, never a browser/renderer special case.
|
|
2
|
+
import {readFile,writeFile,copyFile} from 'node:fs/promises';
|
|
3
|
+
import {resolve} from 'node:path';
|
|
4
|
+
export async function prepare(root) {
|
|
5
|
+
const replacements=[
|
|
6
|
+
['TextureCheck.qml', / if \(GraphicsInfo\.api === GraphicsInfo\.Software\) \{\n fail\("Radar requires GPU rendering \(OpenGL\/Vulkan\)\."\);\n return;\n \}\n/],
|
|
7
|
+
['RadarMap.qml', / Component\.onCompleted: if \(GraphicsInfo\.api === GraphicsInfo\.Software\) map\.graphicsError = "Radar requires GPU rendering \(OpenGL\/Vulkan\)\."\n/],
|
|
8
|
+
];
|
|
9
|
+
// Validate both before changing either. Keep originals for inspection.
|
|
10
|
+
const sources=await Promise.all(replacements.map(async([name,pattern])=>{
|
|
11
|
+
const path=resolve(root,'ui',name),source=await readFile(path,'utf8');
|
|
12
|
+
if(!pattern.test(source))throw Error('Upstream software policy changed; review '+name);
|
|
13
|
+
return {path,source,pattern};
|
|
14
|
+
}));
|
|
15
|
+
for(const {path,source,pattern} of sources){await copyFile(path,path+'.upstream');await writeFile(path,source.replace(pattern,''));}
|
|
16
|
+
}
|
|
17
|
+
if(import.meta.main)await prepare(resolve(process.argv[2]||'/opt/omastorm'));
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {mkdtemp,rm,stat} from 'node:fs/promises';
|
|
2
|
+
import {resolve,isAbsolute} from 'node:path';
|
|
3
|
+
export async function startEngine() {
|
|
4
|
+
const runtime=await mkdtemp('/tmp/omastorm-buninu-');
|
|
5
|
+
const engine=process.env.OMASTORM_ENGINE_BINARY||'/usr/local/bin/omastorm-engine';
|
|
6
|
+
const env={...process.env,XDG_RUNTIME_DIR:runtime,OMASTORM_ENGINE_BINARY:engine};
|
|
7
|
+
let child;
|
|
8
|
+
const close=async()=>{
|
|
9
|
+
if(child && child.exitCode===null){
|
|
10
|
+
child.kill('SIGTERM');
|
|
11
|
+
const timer=setTimeout(()=>{if(child.exitCode===null)child.kill('SIGKILL');},3000);
|
|
12
|
+
try{await child.exited;}finally{clearTimeout(timer);}
|
|
13
|
+
}
|
|
14
|
+
await rm(runtime,{recursive:true,force:true});
|
|
15
|
+
};
|
|
16
|
+
try {
|
|
17
|
+
child=Bun.spawn([engine,'serve'],{env,stdin:'ignore',stdout:'inherit',stderr:'inherit'});
|
|
18
|
+
const socket=runtime+'/omastorm/engine.sock';
|
|
19
|
+
const deadline=Date.now()+15000;
|
|
20
|
+
while(true){
|
|
21
|
+
if(child.exitCode!==null)throw Error('Omastorm engine exited: '+child.exitCode);
|
|
22
|
+
if(await stat(socket).then(s=>s.isSocket(),()=>false))break;
|
|
23
|
+
if(Date.now()>deadline)throw Error('Omastorm engine socket did not become ready');
|
|
24
|
+
await Bun.sleep(50);
|
|
25
|
+
}
|
|
26
|
+
return {env,socket,child,close};
|
|
27
|
+
}catch(e){await close();throw e;}
|
|
28
|
+
}
|
|
29
|
+
export function waylandPath(env=process.env){
|
|
30
|
+
const path=env.WAYLAND_DISPLAY||'/tmp/wayland-buninu';
|
|
31
|
+
return isAbsolute(path)?path:resolve(env.XDG_RUNTIME_DIR||'/tmp',path);
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {strict as assert} from 'node:assert';
|
|
2
|
+
import {createPageRenderer} from '../../lib/renderer/page-renderer.js';
|
|
3
|
+
let frames=0;
|
|
4
|
+
const r=await createPageRenderer({onFrame:()=>frames++});
|
|
5
|
+
try {
|
|
6
|
+
r.resize(400,300,1);
|
|
7
|
+
r.setScripting('host');
|
|
8
|
+
await r.loadDocument({url:'https://paint.test/',contentType:'text/html',source:`
|
|
9
|
+
<body style="margin:0"><canvas width="400" height="300"></canvas><script>
|
|
10
|
+
globalThis.moves=0;
|
|
11
|
+
const c=document.querySelector('canvas'),ctx=c.getContext('2d');
|
|
12
|
+
c.onmousemove=e=>{moves++;ctx.fillRect(e.clientX,e.clientY,2,2)};
|
|
13
|
+
</script>`});
|
|
14
|
+
await r.whenLoaded();
|
|
15
|
+
await Bun.sleep(100);
|
|
16
|
+
frames=0;
|
|
17
|
+
for(let i=0;i<50;i++)r.move(20+i,30);
|
|
18
|
+
assert.equal(frames,0,'input must not synchronously rasterize each motion');
|
|
19
|
+
await Bun.sleep(100);
|
|
20
|
+
assert.ok(frames>0 && frames<50,'queued canvas changes must reach the compositor');
|
|
21
|
+
assert.equal(await r.evaluate('moves'),50,'all drawing points must be delivered');
|
|
22
|
+
assert.equal(await r.evaluate("document.querySelector('canvas').getContext('2d').getImageData(69,30,1,1).data[3]"),255);
|
|
23
|
+
} finally {r.dispose()}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
|
|
3
|
+
// The word that closes a surface, in the corner every surface puts it in: Dialogs rule 7, and the
|
|
4
|
+
// spelling ui/SettingsPanel.qml already uses, so no two dialogs can name the way out differently.
|
|
5
|
+
Text {
|
|
6
|
+
text: "esc"
|
|
7
|
+
color: Theme.color.muted
|
|
8
|
+
font.family: Theme.font.family
|
|
9
|
+
font.pixelSize: Theme.font.caption
|
|
10
|
+
textFormat: Text.PlainText
|
|
11
|
+
}
|