retroemu 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +372 -0
- package/bin/cli.js +199 -0
- package/cores/atari800_libretro.js +2 -0
- package/cores/atari800_libretro.wasm +0 -0
- package/cores/beetle_pce_fast_libretro.js +2 -0
- package/cores/beetle_pce_fast_libretro.wasm +0 -0
- package/cores/fceumm_libretro.js +2 -0
- package/cores/fceumm_libretro.wasm +0 -0
- package/cores/fmsx_libretro.js +2 -0
- package/cores/fmsx_libretro.wasm +0 -0
- package/cores/fuse_libretro.js +2 -0
- package/cores/fuse_libretro.wasm +0 -0
- package/cores/gambatte_libretro.js +2 -0
- package/cores/gambatte_libretro.wasm +0 -0
- package/cores/gearcoleco_libretro.js +2 -0
- package/cores/gearcoleco_libretro.wasm +0 -0
- package/cores/genesis_plus_gx_libretro.js +2 -0
- package/cores/genesis_plus_gx_libretro.wasm +0 -0
- package/cores/handy_libretro.js +2 -0
- package/cores/handy_libretro.wasm +0 -0
- package/cores/mednafen_ngp_libretro.js +2 -0
- package/cores/mednafen_ngp_libretro.wasm +0 -0
- package/cores/mednafen_wswan_libretro.js +2 -0
- package/cores/mednafen_wswan_libretro.wasm +0 -0
- package/cores/mgba_libretro.js +2 -0
- package/cores/mgba_libretro.wasm +0 -0
- package/cores/pcsx_rearmed_libretro.js +2 -0
- package/cores/pcsx_rearmed_libretro.wasm +0 -0
- package/cores/prosystem_libretro.js +2 -0
- package/cores/prosystem_libretro.wasm +0 -0
- package/cores/snes9x2010_libretro.js +2 -0
- package/cores/snes9x2010_libretro.wasm +0 -0
- package/cores/snes9x_libretro.js +2 -0
- package/cores/snes9x_libretro.wasm +0 -0
- package/cores/stella2014_libretro.js +2 -0
- package/cores/stella2014_libretro.wasm +0 -0
- package/cores/vecx_libretro.js +2 -0
- package/cores/vecx_libretro.wasm +0 -0
- package/index.js +8 -0
- package/package.json +52 -0
- package/src/audio/AudioBridge.js +61 -0
- package/src/constants/libretro.js +97 -0
- package/src/core/CoreLoader.js +42 -0
- package/src/core/LibretroHost.js +542 -0
- package/src/core/RomLoader.js +96 -0
- package/src/core/SaveManager.js +115 -0
- package/src/core/SystemDetector.js +122 -0
- package/src/input/InputManager.js +222 -0
- package/src/input/InputMap.js +47 -0
- package/src/video/VideoOutput.js +207 -0
- package/src/video/videoWorker.js +164 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Worker } from 'worker_threads';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import {
|
|
5
|
+
RETRO_PIXEL_FORMAT_0RGB1555,
|
|
6
|
+
RETRO_PIXEL_FORMAT_XRGB8888,
|
|
7
|
+
RETRO_PIXEL_FORMAT_RGB565,
|
|
8
|
+
} from '../constants/libretro.js';
|
|
9
|
+
|
|
10
|
+
// Pre-computed lookup tables for RGB565 → RGB8 conversion
|
|
11
|
+
const RGB5_TO_8 = new Uint8Array(32);
|
|
12
|
+
const RGB6_TO_8 = new Uint8Array(64);
|
|
13
|
+
for (let i = 0; i < 32; i++) RGB5_TO_8[i] = (i * 255 / 31 + 0.5) | 0;
|
|
14
|
+
for (let i = 0; i < 64; i++) RGB6_TO_8[i] = (i * 255 / 63 + 0.5) | 0;
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
export class VideoOutput {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.worker = null;
|
|
22
|
+
this.workerReady = false;
|
|
23
|
+
this.frameCount = 0;
|
|
24
|
+
this.renderEveryN = 2; // Render every Nth frame to terminal
|
|
25
|
+
this.rgbaBuffer = null;
|
|
26
|
+
this.pendingFrame = false;
|
|
27
|
+
this.displayAspectRatio = 4 / 3; // Default to 4:3, can be set by core
|
|
28
|
+
this.contrast = 1.0; // 1.0 = no change, >1 = more contrast
|
|
29
|
+
this.renderMode = 'detailed'; // 'detailed' or 'fast' (half blocks)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async init() {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
this.worker = new Worker(join(__dirname, 'videoWorker.js'));
|
|
35
|
+
|
|
36
|
+
this.worker.on('message', (msg) => {
|
|
37
|
+
if (msg.type === 'ready') {
|
|
38
|
+
this.workerReady = true;
|
|
39
|
+
resolve();
|
|
40
|
+
} else if (msg.type === 'frame') {
|
|
41
|
+
this.pendingFrame = false;
|
|
42
|
+
process.stdout.write(`\x1b[H${msg.ansi}`);
|
|
43
|
+
} else if (msg.type === 'error') {
|
|
44
|
+
if (!this.workerReady) {
|
|
45
|
+
reject(new Error(msg.message));
|
|
46
|
+
} else {
|
|
47
|
+
console.error('Video worker error:', msg.message);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
this.worker.on('error', (err) => {
|
|
53
|
+
if (!this.workerReady) {
|
|
54
|
+
reject(err);
|
|
55
|
+
} else {
|
|
56
|
+
console.error('Video worker error:', err.message);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setFrameSkip(n) {
|
|
63
|
+
this.renderEveryN = Math.max(1, n | 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setAspectRatio(ratio) {
|
|
67
|
+
this.displayAspectRatio = ratio > 0 ? ratio : 4 / 3;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
setContrast(value) {
|
|
71
|
+
this.contrast = Math.max(0.5, Math.min(3.0, value));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setRenderMode(mode) {
|
|
75
|
+
const validModes = ['fast', 'ascii', 'braille', 'braille-dither'];
|
|
76
|
+
if (validModes.includes(mode)) {
|
|
77
|
+
this.renderMode = mode;
|
|
78
|
+
} else {
|
|
79
|
+
this.renderMode = 'detailed';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
onFrame(wasmModule, dataPtr, width, height, pitch, pixelFormat) {
|
|
84
|
+
this.frameCount++;
|
|
85
|
+
|
|
86
|
+
if (this.frameCount % this.renderEveryN !== 0) return;
|
|
87
|
+
if (this.pendingFrame || !this.workerReady) return;
|
|
88
|
+
|
|
89
|
+
// Convert to RGBA on main thread (known working)
|
|
90
|
+
const rgbaData = this._convertToRGBA(wasmModule, dataPtr, width, height, pitch, pixelFormat);
|
|
91
|
+
|
|
92
|
+
const termCols = process.stdout.columns || 80;
|
|
93
|
+
const termRows = (process.stdout.rows || 24) - 4;
|
|
94
|
+
|
|
95
|
+
// Calculate dimensions that preserve display aspect ratio (4:3 for most retro consoles)
|
|
96
|
+
// Terminal chars are ~2:1 (height:width), so multiply width by 2
|
|
97
|
+
const sourceAspect = this.displayAspectRatio;
|
|
98
|
+
const termCharAspect = 2.0;
|
|
99
|
+
|
|
100
|
+
let usedCols, usedRows;
|
|
101
|
+
const rowsNeededForWidth = termCols / (sourceAspect * termCharAspect);
|
|
102
|
+
|
|
103
|
+
if (rowsNeededForWidth <= termRows) {
|
|
104
|
+
// Width-constrained: use full width, calculate height
|
|
105
|
+
usedCols = termCols;
|
|
106
|
+
usedRows = Math.floor(rowsNeededForWidth);
|
|
107
|
+
} else {
|
|
108
|
+
// Height-constrained: use full height, calculate width
|
|
109
|
+
usedRows = termRows;
|
|
110
|
+
usedCols = Math.floor(termRows * sourceAspect * termCharAspect);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
this.pendingFrame = true;
|
|
114
|
+
this.worker.postMessage({
|
|
115
|
+
type: 'render',
|
|
116
|
+
rgbaData: rgbaData.buffer,
|
|
117
|
+
width,
|
|
118
|
+
height,
|
|
119
|
+
termCols: usedCols,
|
|
120
|
+
termRows: usedRows,
|
|
121
|
+
contrast: this.contrast,
|
|
122
|
+
renderMode: this.renderMode
|
|
123
|
+
}, [rgbaData.buffer]);
|
|
124
|
+
|
|
125
|
+
this.rgbaBuffer = null; // Need new buffer since we transferred
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_convertToRGBA(wasmModule, dataPtr, width, height, pitch, pixelFormat) {
|
|
129
|
+
const totalPixels = width * height;
|
|
130
|
+
|
|
131
|
+
if (!this.rgbaBuffer || this.rgbaBuffer.length !== totalPixels * 4) {
|
|
132
|
+
this.rgbaBuffer = new Uint8ClampedArray(totalPixels * 4);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const rgba = this.rgbaBuffer;
|
|
136
|
+
|
|
137
|
+
switch (pixelFormat) {
|
|
138
|
+
case RETRO_PIXEL_FORMAT_XRGB8888:
|
|
139
|
+
this._convertXRGB8888(wasmModule, dataPtr, width, height, pitch, rgba);
|
|
140
|
+
break;
|
|
141
|
+
case RETRO_PIXEL_FORMAT_RGB565:
|
|
142
|
+
this._convertRGB565(wasmModule, dataPtr, width, height, pitch, rgba);
|
|
143
|
+
break;
|
|
144
|
+
case RETRO_PIXEL_FORMAT_0RGB1555:
|
|
145
|
+
this._convert0RGB1555(wasmModule, dataPtr, width, height, pitch, rgba);
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return rgba;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_convertXRGB8888(mod, dataPtr, width, height, pitch, rgba) {
|
|
153
|
+
for (let y = 0; y < height; y++) {
|
|
154
|
+
const srcRowByteOffset = dataPtr + y * pitch;
|
|
155
|
+
const dstRowOffset = y * width * 4;
|
|
156
|
+
|
|
157
|
+
for (let x = 0; x < width; x++) {
|
|
158
|
+
const pixel = mod.HEAPU32[(srcRowByteOffset >> 2) + x];
|
|
159
|
+
const dst = dstRowOffset + x * 4;
|
|
160
|
+
rgba[dst] = (pixel >> 16) & 0xFF;
|
|
161
|
+
rgba[dst + 1] = (pixel >> 8) & 0xFF;
|
|
162
|
+
rgba[dst + 2] = pixel & 0xFF;
|
|
163
|
+
rgba[dst + 3] = 255;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
_convertRGB565(mod, dataPtr, width, height, pitch, rgba) {
|
|
169
|
+
for (let y = 0; y < height; y++) {
|
|
170
|
+
const srcRowByteOffset = dataPtr + y * pitch;
|
|
171
|
+
const dstRowOffset = y * width * 4;
|
|
172
|
+
|
|
173
|
+
for (let x = 0; x < width; x++) {
|
|
174
|
+
const pixel = mod.HEAPU16[(srcRowByteOffset >> 1) + x];
|
|
175
|
+
const dst = dstRowOffset + x * 4;
|
|
176
|
+
rgba[dst] = RGB5_TO_8[(pixel >> 11) & 0x1F];
|
|
177
|
+
rgba[dst + 1] = RGB6_TO_8[(pixel >> 5) & 0x3F];
|
|
178
|
+
rgba[dst + 2] = RGB5_TO_8[pixel & 0x1F];
|
|
179
|
+
rgba[dst + 3] = 255;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
_convert0RGB1555(mod, dataPtr, width, height, pitch, rgba) {
|
|
185
|
+
for (let y = 0; y < height; y++) {
|
|
186
|
+
const srcRowByteOffset = dataPtr + y * pitch;
|
|
187
|
+
const dstRowOffset = y * width * 4;
|
|
188
|
+
|
|
189
|
+
for (let x = 0; x < width; x++) {
|
|
190
|
+
const pixel = mod.HEAPU16[(srcRowByteOffset >> 1) + x];
|
|
191
|
+
const dst = dstRowOffset + x * 4;
|
|
192
|
+
rgba[dst] = RGB5_TO_8[(pixel >> 10) & 0x1F];
|
|
193
|
+
rgba[dst + 1] = RGB5_TO_8[(pixel >> 5) & 0x1F];
|
|
194
|
+
rgba[dst + 2] = RGB5_TO_8[pixel & 0x1F];
|
|
195
|
+
rgba[dst + 3] = 255;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
destroy() {
|
|
201
|
+
if (this.worker) {
|
|
202
|
+
this.worker.terminate();
|
|
203
|
+
this.worker = null;
|
|
204
|
+
}
|
|
205
|
+
this.workerReady = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { parentPort } from 'worker_threads';
|
|
2
|
+
|
|
3
|
+
// Chafa constants
|
|
4
|
+
const CHAFA_CANVAS_MODE_TRUECOLOR = 0;
|
|
5
|
+
const CHAFA_SYMBOL_TAG_SPACE = 0x1;
|
|
6
|
+
const CHAFA_SYMBOL_TAG_BLOCK = 0x8;
|
|
7
|
+
const CHAFA_SYMBOL_TAG_BORDER = 0x10;
|
|
8
|
+
const CHAFA_SYMBOL_TAG_VHALF = 0x200; // Vertical half blocks (▀▄) - fastest mode
|
|
9
|
+
const CHAFA_SYMBOL_TAG_BRAILLE = 0x800; // Braille characters
|
|
10
|
+
const CHAFA_SYMBOL_TAG_ASCII = 0x4000; // ASCII characters
|
|
11
|
+
|
|
12
|
+
const CHAFA_CANVAS_MODE_FGBG = 5; // 2-color mode (foreground/background)
|
|
13
|
+
const CHAFA_DITHER_MODE_NONE = 0;
|
|
14
|
+
const CHAFA_DITHER_MODE_DIFFUSION = 2; // Floyd-Steinberg dithering
|
|
15
|
+
|
|
16
|
+
let chafa = null;
|
|
17
|
+
let canvasConfig = 0;
|
|
18
|
+
let symbolMap = 0;
|
|
19
|
+
let canvas = 0;
|
|
20
|
+
let lastWidth = 0;
|
|
21
|
+
let lastHeight = 0;
|
|
22
|
+
let lastRenderMode = 'detailed';
|
|
23
|
+
let currentCanvasMode = CHAFA_CANVAS_MODE_TRUECOLOR;
|
|
24
|
+
let currentDitherMode = CHAFA_DITHER_MODE_NONE;
|
|
25
|
+
|
|
26
|
+
function applyContrast(rgbaData, contrast) {
|
|
27
|
+
if (contrast === 1.0) return rgbaData;
|
|
28
|
+
|
|
29
|
+
const result = new Uint8ClampedArray(rgbaData.length);
|
|
30
|
+
for (let i = 0; i < rgbaData.length; i += 4) {
|
|
31
|
+
// Apply contrast to RGB, leave alpha unchanged
|
|
32
|
+
result[i] = Math.min(255, Math.max(0, ((rgbaData[i] - 128) * contrast) + 128));
|
|
33
|
+
result[i + 1] = Math.min(255, Math.max(0, ((rgbaData[i + 1] - 128) * contrast) + 128));
|
|
34
|
+
result[i + 2] = Math.min(255, Math.max(0, ((rgbaData[i + 2] - 128) * contrast) + 128));
|
|
35
|
+
result[i + 3] = rgbaData[i + 3];
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function createSymbolMap(mode) {
|
|
41
|
+
if (symbolMap) {
|
|
42
|
+
chafa._chafa_symbol_map_unref(symbolMap);
|
|
43
|
+
}
|
|
44
|
+
symbolMap = chafa._chafa_symbol_map_new();
|
|
45
|
+
|
|
46
|
+
// Reset to defaults
|
|
47
|
+
currentCanvasMode = CHAFA_CANVAS_MODE_TRUECOLOR;
|
|
48
|
+
currentDitherMode = CHAFA_DITHER_MODE_NONE;
|
|
49
|
+
|
|
50
|
+
if (mode === 'fast') {
|
|
51
|
+
// Use only vertical half blocks - fastest possible rendering
|
|
52
|
+
chafa._chafa_symbol_map_add_by_tags(symbolMap, CHAFA_SYMBOL_TAG_SPACE | CHAFA_SYMBOL_TAG_VHALF);
|
|
53
|
+
} else if (mode === 'ascii') {
|
|
54
|
+
// ASCII characters only
|
|
55
|
+
chafa._chafa_symbol_map_add_by_tags(symbolMap, CHAFA_SYMBOL_TAG_SPACE | CHAFA_SYMBOL_TAG_ASCII);
|
|
56
|
+
} else if (mode === 'braille') {
|
|
57
|
+
// Braille characters, black and white
|
|
58
|
+
chafa._chafa_symbol_map_add_by_tags(symbolMap, CHAFA_SYMBOL_TAG_BRAILLE);
|
|
59
|
+
currentCanvasMode = CHAFA_CANVAS_MODE_FGBG;
|
|
60
|
+
} else if (mode === 'braille-dither') {
|
|
61
|
+
// Braille characters with dithering for better grayscale
|
|
62
|
+
chafa._chafa_symbol_map_add_by_tags(symbolMap, CHAFA_SYMBOL_TAG_BRAILLE);
|
|
63
|
+
currentCanvasMode = CHAFA_CANVAS_MODE_FGBG;
|
|
64
|
+
currentDitherMode = CHAFA_DITHER_MODE_DIFFUSION;
|
|
65
|
+
} else {
|
|
66
|
+
// Detailed mode - block symbols for better quality
|
|
67
|
+
chafa._chafa_symbol_map_add_by_tags(symbolMap, CHAFA_SYMBOL_TAG_SPACE | CHAFA_SYMBOL_TAG_BLOCK | CHAFA_SYMBOL_TAG_BORDER);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function initChafa() {
|
|
72
|
+
const chafaModule = await import('chafa-wasm');
|
|
73
|
+
chafa = chafaModule.default || chafaModule;
|
|
74
|
+
if (typeof chafa === 'function') {
|
|
75
|
+
chafa = await chafa();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
createSymbolMap('detailed');
|
|
79
|
+
|
|
80
|
+
parentPort.postMessage({ type: 'ready' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderFrame(rgbaData, width, height, termCols, termRows, contrast, renderMode = 'detailed') {
|
|
84
|
+
if (!chafa) return null;
|
|
85
|
+
|
|
86
|
+
// Apply contrast boost if needed
|
|
87
|
+
if (contrast && contrast !== 1.0) {
|
|
88
|
+
rgbaData = applyContrast(rgbaData, contrast);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Recreate symbol map if render mode changed
|
|
92
|
+
if (lastRenderMode !== renderMode) {
|
|
93
|
+
createSymbolMap(renderMode);
|
|
94
|
+
lastRenderMode = renderMode;
|
|
95
|
+
// Force canvas config recreation
|
|
96
|
+
lastWidth = 0;
|
|
97
|
+
lastHeight = 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Recreate canvas config if terminal size changed
|
|
101
|
+
if (lastWidth !== termCols || lastHeight !== termRows) {
|
|
102
|
+
if (canvasConfig) {
|
|
103
|
+
chafa._chafa_canvas_config_unref(canvasConfig);
|
|
104
|
+
}
|
|
105
|
+
if (canvas) {
|
|
106
|
+
chafa._chafa_canvas_unref(canvas);
|
|
107
|
+
canvas = 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
canvasConfig = chafa._chafa_canvas_config_new();
|
|
111
|
+
chafa._chafa_canvas_config_set_geometry(canvasConfig, termCols, termRows);
|
|
112
|
+
chafa._chafa_canvas_config_set_canvas_mode(canvasConfig, currentCanvasMode);
|
|
113
|
+
chafa._chafa_canvas_config_set_symbol_map(canvasConfig, symbolMap);
|
|
114
|
+
if (currentDitherMode !== CHAFA_DITHER_MODE_NONE) {
|
|
115
|
+
chafa._chafa_canvas_config_set_dither_mode(canvasConfig, currentDitherMode);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
lastWidth = termCols;
|
|
119
|
+
lastHeight = termRows;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!canvas) {
|
|
123
|
+
canvas = chafa._chafa_canvas_new(canvasConfig);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Allocate and copy RGBA data to chafa's heap
|
|
127
|
+
const dataPtr = chafa._malloc(rgbaData.length);
|
|
128
|
+
chafa.HEAPU8.set(rgbaData, dataPtr);
|
|
129
|
+
|
|
130
|
+
// Draw pixels
|
|
131
|
+
chafa._chafa_canvas_set_contents_rgba8(canvas, dataPtr, width, height, width * 4);
|
|
132
|
+
chafa._free(dataPtr);
|
|
133
|
+
|
|
134
|
+
// Build ANSI output
|
|
135
|
+
const gsPtr = chafa._chafa_canvas_build_ansi(canvas);
|
|
136
|
+
if (!gsPtr) return null;
|
|
137
|
+
|
|
138
|
+
const strPtr = chafa._g_string_free_and_steal(gsPtr);
|
|
139
|
+
const ansi = chafa.UTF8ToString(strPtr);
|
|
140
|
+
chafa._free(strPtr);
|
|
141
|
+
|
|
142
|
+
return ansi;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
parentPort.on('message', (msg) => {
|
|
146
|
+
if (msg.type === 'render') {
|
|
147
|
+
const ansi = renderFrame(
|
|
148
|
+
new Uint8ClampedArray(msg.rgbaData),
|
|
149
|
+
msg.width,
|
|
150
|
+
msg.height,
|
|
151
|
+
msg.termCols,
|
|
152
|
+
msg.termRows,
|
|
153
|
+
msg.contrast || 1.0,
|
|
154
|
+
msg.renderMode || 'detailed'
|
|
155
|
+
);
|
|
156
|
+
if (ansi) {
|
|
157
|
+
parentPort.postMessage({ type: 'frame', ansi });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
initChafa().catch(err => {
|
|
163
|
+
parentPort.postMessage({ type: 'error', message: err.message });
|
|
164
|
+
});
|