Skip to content

Commit f21ea93

Browse files
committed
✨ Add resize() method and fix process exit delays
- Add resize(cols, rows, options) method to x11 driver for dynamic terminal sizing - Calculate actual char cell dimensions from window geometry for accurate pixel resizing - Use unref() on spawned Xvfb and xterm processes so Node.js can exit promptly - Make canvas dependency optional in package-lock.json
1 parent 56b2e8c commit f21ea93

3 files changed

Lines changed: 83 additions & 8 deletions

File tree

package-lock.json

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

src/drivers/x11.js

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,27 @@ async function createSession(options) {
127127
let tmpDir = join(tmpdir(), `tui-driver-${sessionId}`)
128128
await mkdir(tmpDir, { recursive: true })
129129

130-
// Make Xvfb large enough for any terminal size
131-
let xvfbWidth = 1920
132-
let xvfbHeight = 1080
130+
// Calculate character cell size based on font
131+
// Approximate: each character cell is ~9px wide, ~18px tall at 14pt font
132+
let charWidth = Math.ceil(font.size * 0.65)
133+
let charHeight = Math.ceil(font.size * 1.3)
134+
135+
// Track current dimensions (can be resized later)
136+
let currentCols = cols
137+
let currentRows = rows
138+
139+
// Calculate Xvfb size based on terminal dimensions
140+
// Add padding for window decorations and margins
141+
let xvfbWidth = Math.max(1920, cols * charWidth + 100)
142+
let xvfbHeight = Math.max(1080, rows * charHeight + 100)
133143

134144
// Start Xvfb
135145
let xvfb = spawn(
136146
'Xvfb',
137147
[`:${display}`, '-screen', '0', `${xvfbWidth}x${xvfbHeight}x24`, '-ac', '-nolisten', 'tcp'],
138-
{ stdio: ['ignore', 'pipe', 'pipe'], detached: true }
148+
{ stdio: 'ignore', detached: true }
139149
)
150+
xvfb.unref()
140151

141152
await sleep(500)
142153
await waitForX(display)
@@ -163,9 +174,10 @@ async function createSession(options) {
163174
LANG: 'en_US.UTF-8',
164175
LC_ALL: 'en_US.UTF-8'
165176
},
166-
stdio: ['ignore', 'pipe', 'pipe'],
177+
stdio: 'ignore',
167178
detached: true
168179
})
180+
xterm.unref()
169181

170182
let windowId = await waitForWindow(display)
171183

@@ -282,6 +294,53 @@ async function createSession(options) {
282294
throw new Error('waitForText is not supported by the x11 driver')
283295
},
284296

297+
async resize(newCols, newRows, options = {}) {
298+
if (closed) throw new Error('Session is closed')
299+
let { clear = true } = options
300+
301+
// Get current window geometry to calculate actual cell size
302+
let { stdout } = await execAsync(`DISPLAY=:${display} xdotool getwindowgeometry ${windowId}`)
303+
let match = stdout.match(/Geometry: (\d+)x(\d+)/)
304+
if (!match) throw new Error('Could not get window geometry')
305+
306+
let currentPixelWidth = parseInt(match[1], 10)
307+
let currentPixelHeight = parseInt(match[2], 10)
308+
309+
// Calculate actual character cell dimensions from current window
310+
let actualCharWidth = currentPixelWidth / currentCols
311+
let actualCharHeight = currentPixelHeight / currentRows
312+
313+
// Calculate new pixel dimensions
314+
let pixelWidth = Math.round(newCols * actualCharWidth)
315+
let pixelHeight = Math.round(newRows * actualCharHeight)
316+
317+
// Resize the Xvfb screen if needed
318+
let newXvfbWidth = Math.max(xvfbWidth, pixelWidth + 100)
319+
let newXvfbHeight = Math.max(xvfbHeight, pixelHeight + 100)
320+
321+
if (newXvfbWidth > xvfbWidth || newXvfbHeight > xvfbHeight) {
322+
// xrandr can resize the virtual screen
323+
await execAsync(`DISPLAY=:${display} xrandr --fb ${newXvfbWidth}x${newXvfbHeight}`).catch(() => {})
324+
xvfbWidth = newXvfbWidth
325+
xvfbHeight = newXvfbHeight
326+
}
327+
328+
// Resize the xterm window
329+
await execAsync(`DISPLAY=:${display} xdotool windowsize ${windowId} ${pixelWidth} ${pixelHeight}`)
330+
await sleep(200)
331+
332+
// Update stored dimensions
333+
currentCols = newCols
334+
currentRows = newRows
335+
336+
// Clear the terminal for a fresh start
337+
if (clear) {
338+
await this.type('clear')
339+
await this.press('Enter')
340+
await sleep(100)
341+
}
342+
},
343+
285344
async screenshot() {
286345
if (closed) throw new Error('Session is closed')
287346

src/terminal.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,22 @@ class TerminalSession {
7171
})
7272
}
7373

74+
/**
75+
* Resize the terminal
76+
* @param {number} cols - Number of columns
77+
* @param {number} rows - Number of rows
78+
* @param {object} [options]
79+
* @param {boolean} [options.clear=true] - Clear terminal after resize
80+
*/
81+
async resize(cols, rows, options = {}) {
82+
this.#assertOpen()
83+
if (this.#driverSession.resize) {
84+
await this.#driverSession.resize(cols, rows, options)
85+
} else {
86+
throw new Error('resize is not supported by this driver')
87+
}
88+
}
89+
7490
/**
7591
* Capture a screenshot
7692
* @param {object} [options]

0 commit comments

Comments
 (0)