buninu-browser 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +19 -4
- 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 +37 -2
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +47 -8
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- 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/host.js +31 -6
- package/lib/renderer/main.js +19 -6
- package/lib/renderer/page-realm.js +16 -1
- package/lib/renderer/page-renderer.js +230 -13
- 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 +19 -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/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/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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { computeElementStyle } from '../style/computed-style.js';
|
|
2
|
+
export const inputPresentation = new WeakMap();
|
|
3
|
+
export const inputOwner = new WeakMap();
|
|
4
|
+
|
|
5
|
+
const spans = new WeakMap();
|
|
6
|
+
const carets = new WeakMap();
|
|
7
|
+
export const inputCaretNode = element => carets.get(element);
|
|
8
|
+
|
|
9
|
+
// Form controls share native selection and caret presentation across frontends.
|
|
10
|
+
export function textInputSelection(element, text, style, make) {
|
|
11
|
+
const options=inputPresentation.get(element)||{};
|
|
12
|
+
const focused=element.ownerDocument.activeElement===element;
|
|
13
|
+
const value=String(element.value??'');
|
|
14
|
+
const position=at=>element.type==='password'?[...value.slice(0,at)].length:at;
|
|
15
|
+
const start=position(element.selectionStart??0),end=position(element.selectionEnd??0);
|
|
16
|
+
const raw=(focused||options.persistent)&&end>start ? {start,end,background:options.background||'#3874f2',color:options.selectedColor||'white'}:null;
|
|
17
|
+
const caretAt = (at, selected=false) => {
|
|
18
|
+
let span=carets.get(element);
|
|
19
|
+
if(!span){span=element.ownerDocument.createElement('span');carets.set(element,span);inputOwner.set(span,element);}
|
|
20
|
+
const height=options.height||parseFloat(style.fontSize)||16;
|
|
21
|
+
const color=options.color||style.color;
|
|
22
|
+
const on=!selected&&options.on!==false&&options.visible!==false;
|
|
23
|
+
span.style.cssText=`display:inline-block;width:0;height:${height}px;border-left:1px solid ${on?color:'transparent'};margin-right:-1px;vertical-align:text-bottom;pointer-events:none`;
|
|
24
|
+
return make('caret','inline-block',{tagName:'SPAN',style:computeElementStyle(span,style),children:Object.freeze([])},span);
|
|
25
|
+
};
|
|
26
|
+
if (!raw) {
|
|
27
|
+
if(!focused||element.disabled||options.visible===false)return null;
|
|
28
|
+
const node=(part,text)=>make(part,'text',{text,style,valueStart:part==='after'?start:0,pseudo:'value',children:Object.freeze([])},element);
|
|
29
|
+
return [node('before',text.slice(0,start)),caretAt(start),node('after',text.slice(start))];
|
|
30
|
+
}
|
|
31
|
+
const { background, color } = raw;
|
|
32
|
+
const from = Math.max(0, Math.min(text.length, start)),
|
|
33
|
+
to = Math.max(from, Math.min(text.length, end));
|
|
34
|
+
if (from === to) return null;
|
|
35
|
+
const textNode = (part, text, textStyle = style, domNode = element) => make(part, 'text', {
|
|
36
|
+
text, style: textStyle, valueStart:part==='selected'?from:part==='after'?to:0, pseudo: 'value', children: Object.freeze([]),
|
|
37
|
+
}, domNode);
|
|
38
|
+
let span = spans.get(element);
|
|
39
|
+
if (!span) { span = element.ownerDocument.createElement('span'); spans.set(element, span); inputOwner.set(span,element); }
|
|
40
|
+
span.style.pointerEvents = 'none';
|
|
41
|
+
span.style.backgroundColor = background;
|
|
42
|
+
span.style.color = color;
|
|
43
|
+
const selectedStyle = computeElementStyle(span, style);
|
|
44
|
+
const children = [];
|
|
45
|
+
if (from) children.push(textNode('before', text.slice(0, from)));
|
|
46
|
+
if(focused&&element.selectionDirection==='backward')children.push(caretAt(from,true));
|
|
47
|
+
children.push(make('selection', 'inline', {
|
|
48
|
+
tagName: 'SPAN', style: selectedStyle,
|
|
49
|
+
children: Object.freeze([textNode('selected', text.slice(from, to), selectedStyle, span)]),
|
|
50
|
+
}, span));
|
|
51
|
+
if(focused&&element.selectionDirection!=='backward')children.push(caretAt(to,true));
|
|
52
|
+
if (to < text.length) children.push(textNode('after', text.slice(to)));
|
|
53
|
+
return children;
|
|
54
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Shared editing defaults for HTML and QML's real input/textarea elements.
|
|
2
|
+
export function isTextInput(element) {
|
|
3
|
+
return !!element && !element.disabled && (element.tagName==='TEXTAREA' ||
|
|
4
|
+
element.tagName==='INPUT' && ['text','search','url','email','tel','password'].includes(element.type));
|
|
5
|
+
}
|
|
6
|
+
export const inputRange = element => ({text:String(element.value),start:element.selectionStart??0,end:element.selectionEnd??0});
|
|
7
|
+
export function selectInput(element,start,end=start) {
|
|
8
|
+
element.setSelectionRange(Math.min(start,end),Math.max(start,end),end<start?'backward':'forward');
|
|
9
|
+
}
|
|
10
|
+
const columns=new WeakMap();
|
|
11
|
+
export function moveInputSelection(element,event) {
|
|
12
|
+
if(!isTextInput(element)||event.altKey)return false;
|
|
13
|
+
const {text,start,end}=inputRange(element),ctrl=event.ctrlKey||event.metaKey,key=event.key;
|
|
14
|
+
if(ctrl&&key.toLowerCase()==='a'){selectInput(element,0,text.length);return true;}
|
|
15
|
+
if(!['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Home','End'].includes(key))return false;
|
|
16
|
+
let at=element.selectionDirection==='backward'?start:end;
|
|
17
|
+
const anchor=at===start?end:start,multi=element.tagName==='TEXTAREA';
|
|
18
|
+
const lineStart=text.lastIndexOf('\n',at-1)+1;
|
|
19
|
+
if(key==='ArrowUp'||key==='ArrowDown') {
|
|
20
|
+
if(!multi)return false;
|
|
21
|
+
const previous=columns.get(element),column=previous?.at===at?previous.column:at-lineStart;
|
|
22
|
+
if(key==='ArrowUp') {
|
|
23
|
+
const stop=lineStart-1;at=stop<0?0:Math.min(text.lastIndexOf('\n',stop-1)+1+column,stop);
|
|
24
|
+
} else {
|
|
25
|
+
const next=text.indexOf('\n',at),stop=next<0?-1:text.indexOf('\n',next+1);
|
|
26
|
+
at=next<0?text.length:Math.min(next+1+column,stop<0?text.length:stop);
|
|
27
|
+
}
|
|
28
|
+
columns.set(element,{at,column});
|
|
29
|
+
} else {
|
|
30
|
+
columns.delete(element);
|
|
31
|
+
if(key==='Home')at=multi&&!ctrl?lineStart:0;
|
|
32
|
+
else if(key==='End'){const stop=text.indexOf('\n',at);at=multi&&!ctrl&&stop>=0?stop:text.length;}
|
|
33
|
+
else if(!event.shiftKey&&start!==end)at=key==='ArrowLeft'?start:end;
|
|
34
|
+
else if(key==='ArrowLeft') {
|
|
35
|
+
at-=[...text.slice(0,at)].pop()?.length??0;
|
|
36
|
+
if(ctrl){while(at>0&&/\s/u.test(text[at]))at--;while(at>0&&/[\p{L}\p{N}_]/u.test(text[at-1]))at--;}
|
|
37
|
+
} else {
|
|
38
|
+
at+=text.codePointAt(at)>0xffff?2:at<text.length?1:0;
|
|
39
|
+
if(ctrl){while(at<text.length&&/[\p{L}\p{N}_]/u.test(text[at]))at++;while(at<text.length&&/\s/u.test(text[at]))at++;}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
selectInput(element,event.shiftKey?anchor:at,at);return true;
|
|
43
|
+
}
|
|
44
|
+
export async function inputClipboard(element,operation,{read,write,replace}) {
|
|
45
|
+
if(!isTextInput(element)||operation!=='copy'&&element.readOnly)return;
|
|
46
|
+
const range=inputRange(element);
|
|
47
|
+
if(operation==='copy'||operation==='cut') {
|
|
48
|
+
if(element.type==='password'||range.start===range.end)return;
|
|
49
|
+
await write(range.text.slice(range.start,range.end));
|
|
50
|
+
if(operation==='copy')return;
|
|
51
|
+
}
|
|
52
|
+
let text=operation==='paste'?await read():'';
|
|
53
|
+
if(String(element.value)!==range.text||element.selectionStart!==range.start||element.selectionEnd!==range.end||element.readOnly||!element.isConnected)return;
|
|
54
|
+
if(element.tagName!=='TEXTAREA')text=text.replace(/[\r\n]+/g,'');
|
|
55
|
+
if(element.maxLength>=0)text=text.slice(0,Math.max(0,element.maxLength-(range.text.length-range.end+range.start)));
|
|
56
|
+
replace(text,operation==='paste'?'insertFromPaste':'deleteByCut');
|
|
57
|
+
}
|
|
@@ -284,8 +284,8 @@ const CONTROL_FONT = "font-size: 13.333px; font-family: sans-serif";
|
|
|
284
284
|
const FIELD = `${CONTROL_FONT}; border: 1px solid #767676; background-color: white`;
|
|
285
285
|
const BUTTON_LIKE = `${CONTROL_FONT}; border: 1px solid #767676; border-radius: 2px; padding: 1px 6px; background-color: #efefef; text-align: center`;
|
|
286
286
|
const UA_CONTROL_DECLARATIONS = Object.freeze({
|
|
287
|
-
textarea: parseDeclarations(`${FIELD}; font-family: monospace; padding: 2px; white-space: pre-wrap; overflow-wrap: break-word`),
|
|
288
|
-
text: parseDeclarations(`${FIELD}; padding: 1px 2px; white-space: pre`),
|
|
287
|
+
textarea: parseDeclarations(`${FIELD}; font-family: monospace; padding: 2px; white-space: pre-wrap; overflow-wrap: break-word; overflow: auto`),
|
|
288
|
+
text: parseDeclarations(`${FIELD}; padding: 1px 2px; white-space: pre; overflow: hidden`),
|
|
289
289
|
checkbox: parseDeclarations("width: 13px; height: 13px; margin: 3px 3px 3px 4px; border: 1px solid #767676; border-radius: 2px; background-color: white; box-sizing: border-box"),
|
|
290
290
|
radio: parseDeclarations("width: 13px; height: 13px; margin: 3px 3px 0 5px; border: 1px solid #767676; border-radius: 50%; background-color: white; box-sizing: border-box"),
|
|
291
291
|
button: parseDeclarations(`${BUTTON_LIKE}; white-space: pre`),
|
package/package.json
CHANGED
package/test/clipboard.test.js
CHANGED
|
@@ -33,3 +33,22 @@ test('a real button click writes through controller; page-only calls cannot over
|
|
|
33
33
|
expect(await host.call('evaluate','window.copyStatus')).toBe('copied');
|
|
34
34
|
}finally{host.close();}
|
|
35
35
|
},20000);
|
|
36
|
+
|
|
37
|
+
test('Windows also uses Bun.which-resolved xclip before its platform fallback',async()=>{
|
|
38
|
+
const seen=[];
|
|
39
|
+
await writeClipboard('Windows 中文',{platform:'win32',which:name=>{seen.push(name);return name==='xclip'?'C:/bin/xclip.exe':null;},spawn:(args,opts)=>{expect(args).toEqual(['C:/bin/xclip.exe','-selection','clipboard']);expect(opts.stdin.toString()).toBe('Windows 中文');return {exited:Promise.resolve(0),kill(){}};}});
|
|
40
|
+
expect(seen).toEqual(['xclip']);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('clipboard read is one-shot, preserves empty/text values, and prefers xclip on Windows too',async()=>{
|
|
44
|
+
const {readClipboard}=await import('../lib/clipboard.js');
|
|
45
|
+
const calls=[];let value='中文\n';
|
|
46
|
+
const options={platform:'win32',which:name=>name==='xclip'?'/bin/xclip':null,spawn:(args)=>{
|
|
47
|
+
calls.push(args);return {stdout:new Response(value).body,exited:Promise.resolve(0),kill(){}};
|
|
48
|
+
}};
|
|
49
|
+
expect(await readClipboard(options)).toBe('中文\n');
|
|
50
|
+
expect(calls).toEqual([['/bin/xclip','-selection','clipboard','-o']]);
|
|
51
|
+
value='';expect(await readClipboard(options)).toBe('');
|
|
52
|
+
await Bun.sleep(30);expect(calls).toHaveLength(2);
|
|
53
|
+
await expect(readClipboard({...options,which:()=>null})).rejects.toThrow('No working');
|
|
54
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Build context: buninu-browser repository root. Compatible with js-udocker.
|
|
2
|
+
FROM oven/bun:1.4.4
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
4
|
+
ENV LC_ALL=C.UTF-8
|
|
5
|
+
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
|
|
6
|
+
RUN set -eu; apt-get update; apt-get install -y --no-install-recommends ca-certificates curl zstd shared-mime-info util-linux libgcc-s1; apt-get clean
|
|
7
|
+
RUN set -eu; case "$(uname -m)" in aarch64|arm64) arch=aarch64 ;; x86_64) arch=x86_64 ;; *) exit 1 ;; esac; curl -fL --retry 3 "https://pkgs.omarchy.org/stable/$arch/flea-0.3.12-1-$arch.pkg.tar.zst" -o /tmp/flea.pkg.tar.zst; tar --zstd -xf /tmp/flea.pkg.tar.zst -C / usr; /usr/bin/flea --version
|
|
8
|
+
RUN set -eu; mkdir -p /usr/share/omarchy; curl -fL --retry 3 https://codeload.github.com/basecamp/omarchy/tar.gz/3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d -o /tmp/omarchy.tar.gz; tar -xzf /tmp/omarchy.tar.gz --strip-components=1 -C /usr/share/omarchy omarchy-3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d/shell/Commons omarchy-3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d/shell/Ui omarchy-3c7027db2a4fa9fa6a49d7af7f87c27ac48e677d/LICENSE; ln -sfn shell /usr/share/omarchy/qs
|
|
9
|
+
COPY buninu-browser.js /opt/buninu-browser/buninu-browser.js
|
|
10
|
+
COPY frontends /opt/buninu-browser/frontends
|
|
11
|
+
COPY usr /opt/buninu-browser/usr
|
|
12
|
+
COPY lib /opt/buninu-browser/lib
|
|
13
|
+
COPY vendor /opt/buninu-browser/vendor
|
|
14
|
+
COPY LICENSES /opt/buninu-browser/LICENSES
|
|
15
|
+
COPY LICENSE /opt/buninu-browser/LICENSE
|
|
16
|
+
COPY THIRD_PARTY_NOTICES.md /opt/buninu-browser/THIRD_PARTY_NOTICES.md
|
|
17
|
+
COPY package.json /opt/buninu-browser/package.json
|
|
18
|
+
COPY test/fixtures/flea-helpers /usr/local/lib/flea-helpers
|
|
19
|
+
COPY test/fixtures/flea-thumbnail-stub /opt/flea-thumbnail-stub
|
|
20
|
+
COPY test/fixtures/tone.mp3 /root/flea-demo/tone.mp3
|
|
21
|
+
COPY test/fixtures/long-video.mp4 /root/flea-demo/long-video.mp4
|
|
22
|
+
WORKDIR /usr/local/lib/flea-helpers
|
|
23
|
+
RUN set -eu; bun i --backend=copyfile jsmdcui@0.19.2; chmod +x /usr/local/lib/flea-helpers/bin/*; for tool in qs bat glow bwrap; do ln -sfn /usr/local/lib/flea-helpers/bin/$tool /usr/local/bin/$tool; done; install -Dm755 /opt/flea-thumbnail-stub/flea-thumbnail-stub /usr/local/bin/flea-thumbnail-stub; install -Dm644 /opt/flea-thumbnail-stub/flea-placeholder.thumbnailer /usr/share/thumbnailers/flea-placeholder.thumbnailer
|
|
24
|
+
RUN set -eu; install -Dm644 source-mime.xml /usr/share/mime/packages/buninu-source.xml; update-mime-database /usr/share/mime
|
|
25
|
+
RUN set -eu; printf 'const answer = 42;\n' > /root/flea-demo/hello.js; bun /usr/local/lib/flea-helpers/check.mjs
|
|
26
|
+
ENV HOME=/root
|
|
27
|
+
ENV XDG_RUNTIME_DIR=/tmp
|
|
28
|
+
ENV WAYLAND_DISPLAY=wayland-buninu
|
|
29
|
+
ENV XDG_CONFIG_HOME=/root/.config
|
|
30
|
+
ENV XDG_DATA_HOME=/root/.local/share
|
|
31
|
+
ENV XDG_STATE_HOME=/root/.local/state
|
|
32
|
+
ENV XDG_CACHE_HOME=/root/.cache
|
|
33
|
+
ENV QSG_RHI_BACKEND=opengl
|
|
34
|
+
WORKDIR /root
|
|
35
|
+
ENTRYPOINT []
|
|
36
|
+
CMD ["/usr/bin/flea", "--gui", "/root/flea-demo"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/local/bin/bun
|
|
2
|
+
// Experimental Flea bat argv adapter, using the unchanged bundled glow highlighter.
|
|
3
|
+
import {RuntimeRegistry} from './node_modules/jsmdcui/src/runtime/registry.js';
|
|
4
|
+
import {Colorscheme} from './node_modules/jsmdcui/src/config/colorscheme.js';
|
|
5
|
+
import {SyntaxDefinition,detectSyntax,loadSyntaxDefinitions} from './node_modules/jsmdcui/src/highlight/parser.js';
|
|
6
|
+
import {Highlighter} from './node_modules/jsmdcui/src/highlight/highlighter.js';
|
|
7
|
+
import {styleToAnsi} from './node_modules/jsmdcui/src/display/ansi-style.js';
|
|
8
|
+
import {fileURLToPath} from 'node:url';
|
|
9
|
+
import {adaptSyntaxRules} from './syntax-compat.mjs';
|
|
10
|
+
let name='',lang='',file=null,first=1,last=Infinity;
|
|
11
|
+
for(const arg of Bun.argv.slice(2)){
|
|
12
|
+
if(['--no-config','--no-custom-assets','--theme=ansi','--color=always','--style=plain','--paging=never','--wrap=never'].includes(arg))continue;
|
|
13
|
+
if(arg.startsWith('--file-name='))name=arg.slice(12);
|
|
14
|
+
else if(arg.startsWith('--language='))lang=arg.slice(11);
|
|
15
|
+
else if(arg.startsWith('--line-range=')){const m=/^--line-range=(\d+):(\d+)$/.exec(arg);if(!m)throw Error('Unsupported line range');first=+m[1];last=+m[2];}
|
|
16
|
+
else if(arg.startsWith('-')&&arg!=='-')throw Error('Unsupported bat option: '+arg);
|
|
17
|
+
else if(file!==null)throw Error('Only one input is supported');else file=arg;
|
|
18
|
+
}
|
|
19
|
+
const text=await (file&&file!=='-'?Bun.file(file):Bun.stdin).text();
|
|
20
|
+
if(Buffer.byteLength(text)>1048576)throw Error('Preview exceeds 1 MiB');
|
|
21
|
+
const runtime=new RuntimeRegistry({repoRoot:fileURLToPath(new URL('./node_modules/jsmdcui',import.meta.url)),configDir:'/tmp'});
|
|
22
|
+
await runtime.init({user:false});
|
|
23
|
+
const scheme=await new Colorscheme(runtime).load('simple'),defs=await loadSyntaxDefinitions(runtime);
|
|
24
|
+
adaptSyntaxRules(defs,SyntaxDefinition);
|
|
25
|
+
const lines=text.replace(/\r\n?/g,'\n').split('\n');if(lines.at(-1)==='')lines.pop();
|
|
26
|
+
const aliases={js:'javascript',jsx:'javascript',mjs:'javascript',cjs:'javascript',node:'javascript',nodejs:'javascript',ts:'typescript',tsx:'typescript',mts:'typescript',cts:'typescript',rs:'rust',py:'python',python3:'python',sh:'shell',bash:'shell',shellscript:'shell',md:'markdown',yml:'yaml',jsonc:'json',json5:'json',jsonl:'json',ndjson:'json',cpp:'c++',cc:'c++',cxx:'c++',hpp:'c++',cs:'csharp','c#':'csharp',rb:'ruby',golang:'go',kt:'kotlin',kts:'kotlin',ps1:'powershell',pwsh:'powershell',bat:'batch',cmd:'batch',docker:'dockerfile',make:'makefile',cmakelists:'cmake',tf:'terraform',proto:'proto',protobuf:'proto',tex:'tex',latex:'tex',diff:'patch',dotenv:'ini'};
|
|
27
|
+
const byType=new Map(defs.map(d=>[d.filetype.toLowerCase(),d]));
|
|
28
|
+
const lookup=key=>byType.get(aliases[key.toLowerCase()]||key.toLowerCase());
|
|
29
|
+
const path=name||file||'',ext=path.split('.').at(-1).toLowerCase();
|
|
30
|
+
const def=lang?lookup(lang):lookup(({jsx:'jsx',mts:'ts',cts:'ts',jsonc:'json',json5:'json',jsonl:'json',ndjson:'json',env:'ini'})[ext]||'')||detectSyntax(defs,{path,firstLine:lines[0]||'',lines:lines.slice(0,50)});
|
|
31
|
+
if(lang&&!def)process.exit(1);
|
|
32
|
+
const highlighter=def?new Highlighter(def,defs):null;let state=null;
|
|
33
|
+
for(let i=0;i<Math.min(lines.length,last);i++){
|
|
34
|
+
const line=lines[i],result=highlighter?.highlightLine(line,state);if(result)state=result.state;
|
|
35
|
+
if(i+1<first)continue;
|
|
36
|
+
if(!result){process.stdout.write(line+'\n');continue;}
|
|
37
|
+
const changes=[...result.changes.entries()];let output='';
|
|
38
|
+
for(let j=0;j<changes.length-1;j++){
|
|
39
|
+
const [from,group]=changes[j],to=changes[j+1][0];
|
|
40
|
+
output+=styleToAnsi(scheme.get(group)).replace(/38;5;(\d+)/g,(_,n)=>String(+n<8?30+(+n):90+(+n)-8))+line.slice(from,to);
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(output+'\x1b[0m\n');
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Experiment only: no namespace/filesystem isolation; outer prlimit remains.
|
|
3
|
+
envargs=()
|
|
4
|
+
while (($#)); do
|
|
5
|
+
case "$1" in
|
|
6
|
+
--unshare-*|--die-with-parent|--new-session|--clearenv) shift ;; --setenv) envargs+=("$2=$3"); shift 3 ;; --ro-bind|--bind|--symlink) shift 3 ;; --proc|--dev|--tmpfs|--dir) shift 2 ;;
|
|
7
|
+
--json-status-fd) fd=$2; [[ $fd =~ ^[0-9]+$ ]] || exit 2; eval "exec ${fd}>&-"; shift 2 ;; --) shift; break ;; --*) echo "Unsupported experimental bwrap option: $1" >&2; exit 2 ;; *) break ;;
|
|
8
|
+
esac
|
|
9
|
+
done
|
|
10
|
+
exec env "${envargs[@]}" "$@"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Diagnostic only: retains real bwrap; currently fails under bunproot.
|
|
3
|
+
args=()
|
|
4
|
+
for arg in "$@"; do
|
|
5
|
+
case "$arg" in
|
|
6
|
+
--unshare-all) args+=(--unshare-user --unshare-ipc --unshare-pid --unshare-uts --unshare-cgroup-try) ;; --unshare-net) ;; *) args+=("$arg") ;;
|
|
7
|
+
esac
|
|
8
|
+
done
|
|
9
|
+
exec /usr/bin/bwrap-real "${args[@]}"
|
|
@@ -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});}
|