Skip to content

Commit 0d6a690

Browse files
committed
Merge nm-kitty-meow (PR coder/ghostty-web#162): Upgrade to Ghostty 1.3
Brings in NimbleMarkets/ghostty-web@nm-kitty-meow: bump ghostty submodule to upstream main, drop now-upstreamed WASM patches, rewire WASM bindings onto upstream's C ABI, add kitty graphics support (PNG decode + placement compositing), event-driven render scheduler, and the U+10EEEE Unicode placeholder protocol. Conflict resolution: - lib/renderer.ts (dirty-rows union): include both cursorRows.has(y) from main and this.kittyDamagedRows.has(y) from the PR. - lib/renderer.ts (block-element rendering): keep main's drawBoxOrBlock path. main's box-drawing system (U+2500..U+259F) is a superset of the PR's renderBlockElement (U+2580..U+259F) so the PR's narrower fast path is dropped to avoid the wider drawBoxOrBlock not running for blocks. renderBlockElement remains defined but unused; a follow-up can prune. - lib/renderer.ts (kitty composite gate): replace the PR's anyLinesRendered flag (lost in main's collect-then-render refactor) with linesToRender.length > 0. - lib/terminal.ts (handleFontChange/resize): keep main's removal of the canvas.width/style writes (renderer.resize now owns canvas sizing) while adopting the PR's updateWasmPixelSize() calls for kitty graphics size sync. - patches/ghostty-wasm-api.patch: take the PR version (rewritten to match ghostty 1.3 upstream).
2 parents 9068aa5 + b6ef353 commit 0d6a690

21 files changed

Lines changed: 3428 additions & 2119 deletions

.github/actions/setup-zig/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ runs:
99
using: 'composite'
1010
steps:
1111
- name: Cache Zig
12-
uses: actions/cache@v4
12+
uses: actions/cache@v5
1313
id: cache-zig
1414
with:
1515
path: |

.github/workflows/ci.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ jobs:
1111
name: fmt
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v4
14+
- uses: actions/checkout@v6
1515

16-
- uses: oven-sh/setup-bun@v1
16+
- uses: oven-sh/setup-bun@v2
1717
with:
1818
bun-version: latest
1919

@@ -27,9 +27,9 @@ jobs:
2727
name: lint
2828
runs-on: ubuntu-latest
2929
steps:
30-
- uses: actions/checkout@v4
30+
- uses: actions/checkout@v6
3131

32-
- uses: oven-sh/setup-bun@v1
32+
- uses: oven-sh/setup-bun@v2
3333
with:
3434
bun-version: latest
3535

@@ -43,9 +43,9 @@ jobs:
4343
name: type check
4444
runs-on: ubuntu-latest
4545
steps:
46-
- uses: actions/checkout@v4
46+
- uses: actions/checkout@v6
4747

48-
- uses: oven-sh/setup-bun@v1
48+
- uses: oven-sh/setup-bun@v2
4949
with:
5050
bun-version: latest
5151

@@ -59,11 +59,11 @@ jobs:
5959
name: test
6060
runs-on: ubuntu-latest
6161
steps:
62-
- uses: actions/checkout@v4
62+
- uses: actions/checkout@v6
6363
with:
6464
submodules: recursive
6565

66-
- uses: oven-sh/setup-bun@v1
66+
- uses: oven-sh/setup-bun@v2
6767
with:
6868
bun-version: latest
6969

@@ -84,11 +84,11 @@ jobs:
8484
name: build
8585
runs-on: ubuntu-latest
8686
steps:
87-
- uses: actions/checkout@v4
87+
- uses: actions/checkout@v6
8888
with:
8989
submodules: recursive
9090

91-
- uses: oven-sh/setup-bun@v1
91+
- uses: oven-sh/setup-bun@v2
9292
with:
9393
bun-version: latest
9494

@@ -103,8 +103,8 @@ jobs:
103103
run: |
104104
SIZE=$(stat -c%s ghostty-vt.wasm)
105105
echo "WASM size: $SIZE bytes"
106-
if [ "$SIZE" -gt 524288 ]; then
107-
echo "❌ Error: WASM exceeds 512 KB limit"
106+
if [ "$SIZE" -gt 786432 ]; then
107+
echo "❌ Error: WASM exceeds 768 KB limit"
108108
exit 1
109109
fi
110110

.github/workflows/publish.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ jobs:
2323
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'push' }}
2424
steps:
2525
- name: Checkout code
26-
uses: actions/checkout@v4
26+
uses: actions/checkout@v6
2727
with:
2828
ref: ${{ github.ref }}
2929
fetch-depth: 0 # Required for git describe to find tags
3030
submodules: recursive
3131

3232
- name: Setup Bun
33-
uses: oven-sh/setup-bun@v1
33+
uses: oven-sh/setup-bun@v2
3434
with:
3535
bun-version: latest
3636

@@ -61,9 +61,9 @@ jobs:
6161
run: bun run build
6262

6363
- name: Setup Node.js for npm
64-
uses: actions/setup-node@v4
64+
uses: actions/setup-node@v6
6565
with:
66-
node-version: '20'
66+
node-version: '24'
6767
registry-url: 'https://registry.npmjs.org'
6868

6969
# Ensure npm 11.5.1 or later for trusted publishing
@@ -159,15 +159,15 @@ jobs:
159159
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'push' }}
160160
steps:
161161
- name: Checkout code
162-
uses: actions/checkout@v4
162+
uses: actions/checkout@v6
163163
with:
164164
ref: ${{ github.ref }}
165165
fetch-depth: 0
166166

167167
- name: Setup Node.js for npm
168-
uses: actions/setup-node@v4
168+
uses: actions/setup-node@v6
169169
with:
170-
node-version: '20'
170+
node-version: '24'
171171
registry-url: 'https://registry.npmjs.org'
172172

173173
- run: npm install -g npm@latest

bun.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/bin/demo.js

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@ import { homedir } from 'os';
1313
import path from 'path';
1414
import { fileURLToPath } from 'url';
1515

16-
// Node-pty for cross-platform PTY support
17-
import pty from '@lydell/node-pty';
16+
// Node-pty for cross-platform PTY support. The 1.2.0-beta.x line adds a
17+
// `pixelSize` argument to resize(), which sets ws_xpixel / ws_ypixel in
18+
// the slave PTY's winsize struct so kitty kittens (icat etc.) can detect
19+
// graphics support via TIOCGWINSZ instead of falling back to terminal
20+
// queries. Lydell's fork is based on 1.1.0-beta14 (pre-pixelSize), so we
21+
// use upstream's beta directly.
22+
import pty from 'node-pty';
1823
// WebSocket server
1924
import { WebSocketServer } from 'ws';
2025

@@ -243,12 +248,32 @@ const HTML_TEMPLATE = `<!doctype html>
243248
const wsUrl = protocol + '//' + window.location.host + '/ws?cols=' + term.cols + '&rows=' + term.rows;
244249
let ws;
245250
251+
// Read total canvas pixel dims (CSS pixels). The server stuffs these
252+
// into ws_xpixel / ws_ypixel via node-pty's resize(cols, rows, pixelSize)
253+
// so kittens like icat see non-zero TIOCGWINSZ pixel fields.
254+
function getPixelSize() {
255+
const canvas = container.querySelector('canvas');
256+
return canvas
257+
? { xpixel: canvas.clientWidth, ypixel: canvas.clientHeight }
258+
: { xpixel: 0, ypixel: 0 };
259+
}
260+
246261
function connect() {
247262
setStatus('connecting', 'Connecting...');
248263
ws = new WebSocket(wsUrl);
249264
250265
ws.onopen = () => {
251266
setStatus('connected', 'Connected');
267+
// Push initial pixel dims so TIOCGWINSZ-gated tools see them
268+
// before the first resize event.
269+
const px = getPixelSize();
270+
ws.send(JSON.stringify({
271+
type: 'resize',
272+
cols: term.cols,
273+
rows: term.rows,
274+
xpixel: px.xpixel,
275+
ypixel: px.ypixel,
276+
}));
252277
};
253278
254279
ws.onmessage = (event) => {
@@ -278,7 +303,14 @@ const HTML_TEMPLATE = `<!doctype html>
278303
// Handle resize - notify PTY when terminal dimensions change
279304
term.onResize(({ cols, rows }) => {
280305
if (ws && ws.readyState === WebSocket.OPEN) {
281-
ws.send(JSON.stringify({ type: 'resize', cols, rows }));
306+
const px = getPixelSize();
307+
ws.send(JSON.stringify({
308+
type: 'resize',
309+
cols,
310+
rows,
311+
xpixel: px.xpixel,
312+
ypixel: px.ypixel,
313+
}));
282314
}
283315
});
284316
@@ -463,7 +495,18 @@ wss.on('connection', (ws, req) => {
463495
try {
464496
const msg = JSON.parse(message);
465497
if (msg.type === 'resize') {
466-
ptyProcess.resize(msg.cols, msg.rows);
498+
// node-pty 1.2.0+ accepts a third pixelSize arg that sets
499+
// ws_xpixel / ws_ypixel in the PTY winsize struct. Without it,
500+
// kitty kittens (icat, etc.) read zeros via TIOCGWINSZ and
501+
// refuse to render images.
502+
if (msg.xpixel > 0 && msg.ypixel > 0) {
503+
ptyProcess.resize(msg.cols, msg.rows, {
504+
width: msg.xpixel,
505+
height: msg.ypixel,
506+
});
507+
} else {
508+
ptyProcess.resize(msg.cols, msg.rows);
509+
}
467510
return;
468511
}
469512
} catch (e) {

demo/bun.lock

Lines changed: 4 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/index.html

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,20 @@
138138
let ws;
139139
let fitAddon;
140140

141+
// Read total canvas pixel dimensions (CSS pixels). Used so the
142+
// server can stuff ws_xpixel / ws_ypixel into the PTY winsize via
143+
// node-pty's resize(cols, rows, pixelSize) — kitty kittens (icat
144+
// etc.) read those fields from their stdin and bail "doesn't
145+
// support reporting screen sizes in pixels" if they're zero.
146+
// Module-scope so both initTerminal and connectWebSocket can call it.
147+
function getPixelSize() {
148+
const container = document.getElementById('terminal-container');
149+
const canvas = container?.querySelector('canvas');
150+
return canvas
151+
? { xpixel: canvas.clientWidth, ypixel: canvas.clientHeight }
152+
: { xpixel: 0, ypixel: 0 };
153+
}
154+
141155
async function initTerminal() {
142156
// Initialize WASM
143157
await init();
@@ -168,8 +182,16 @@
168182
// Handle terminal resize
169183
term.onResize((size) => {
170184
if (ws && ws.readyState === WebSocket.OPEN) {
171-
// Send resize as control sequence (server expects this format)
172-
ws.send(JSON.stringify({ type: 'resize', cols: size.cols, rows: size.rows }));
185+
const px = getPixelSize();
186+
ws.send(
187+
JSON.stringify({
188+
type: 'resize',
189+
cols: size.cols,
190+
rows: size.rows,
191+
xpixel: px.xpixel,
192+
ypixel: px.ypixel,
193+
})
194+
);
173195
}
174196
});
175197

@@ -200,6 +222,19 @@
200222
ws.onopen = () => {
201223
console.log('WebSocket connected');
202224
updateConnectionStatus(true);
225+
// Push initial pixel dims into the PTY winsize so tools that
226+
// gate on TIOCGWINSZ (e.g. kitten icat) can detect graphics
227+
// support without falling back to terminal queries.
228+
const px = getPixelSize();
229+
ws.send(
230+
JSON.stringify({
231+
type: 'resize',
232+
cols: term.cols,
233+
rows: term.rows,
234+
xpixel: px.xpixel,
235+
ypixel: px.ypixel,
236+
})
237+
);
203238
};
204239

205240
ws.onmessage = (event) => {

demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"dev": "node bin/demo.js --dev"
1212
},
1313
"dependencies": {
14-
"@lydell/node-pty": "^1.0.1",
14+
"node-pty": "1.2.0-beta.12",
1515
"ghostty-web": "latest",
1616
"ws": "^8.18.0"
1717
},

ghostty

Submodule ghostty updated 4883 files

0 commit comments

Comments
 (0)