Skip to content

Commit e769f80

Browse files
authored
Merge branch 'master' into master
2 parents fe9650c + 6651c4c commit e769f80

18 files changed

+386
-330
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Since xterm.js is typically implemented as a developer tool, generally only mode
9494

9595
### Node.js Support
9696

97-
We also publish [`xterm-headless`](https://www.npmjs.com/package/xterm-headless) which is a stripped down version of xterm.js that runs headless in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the [serialize addon](https://www.npmjs.com/package/@xterm/addon-serialize) so it can get all state restored upon reconnection.
97+
We also publish [`@xterm/headless`](https://www.npmjs.com/package/@xterm/headless) which is a stripped down version of xterm.js that runs headless in Node.js. An example use case for this is to keep track of a terminal's state where the process is running and using the [serialize addon](https://www.npmjs.com/package/@xterm/addon-serialize) so it can get all state restored upon reconnection.
9898

9999
## API
100100

@@ -230,6 +230,7 @@ Xterm.js is used in many world-class applications to provide great terminal expe
230230
- [**LabEx**](https://labex.io): Interactive learning platform with hands-on labs and xterm.js-based online terminals, focused on learn-by-doing approach.
231231
- [**EmuDevz**](https://afska.github.io/emudevz): A free coding game where players learn how to build an emulator from scratch.
232232
- [**SSH Connection Manager**](https://github.com/Amtrend/ssh-manager): Modern web-based SSH terminal and SFTP manager with AES-256 encryption, PWA support, and session management.
233+
- [**WooTTY**](https://github.com/icoretech/wootty): Flawless browser terminal for real operators.
233234
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)
234235

235236
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Please add any new contributions to the end of the list.

addons/addon-image/src/ImageAddon.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import type { ITerminalAddon, IDisposable } from '@xterm/xterm';
77
import type { ImageAddon as IImageApi } from '@xterm/addon-image';
8+
import { Emitter, type IEvent } from 'common/Event';
89
import { IIPHandler } from './IIPHandler';
910
import { ImageRenderer } from './ImageRenderer';
1011
import { ImageStorage, CELL_SIZE_DEFAULT } from './ImageStorage';
@@ -62,6 +63,8 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
6263
private _disposables: IDisposable[] = [];
6364
private _terminal: ITerminalExt | undefined;
6465
private _handlers: Map<String, IResetHandler> = new Map();
66+
private readonly _onImageAdded = new Emitter<void>();
67+
public readonly onImageAdded: IEvent<void> = this._onImageAdded.event;
6568

6669
constructor(opts?: Partial<IImageAddonOptions>) {
6770
this._opts = Object.assign({}, DEFAULT_OPTIONS, opts);
@@ -74,6 +77,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
7477
}
7578
this._disposables.length = 0;
7679
this._handlers.clear();
80+
this._onImageAdded.dispose();
7781
}
7882

7983
private _disposeLater(...args: IDisposable[]): void {
@@ -88,6 +92,7 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
8892
// internal data structures
8993
this._renderer = new ImageRenderer(terminal);
9094
this._storage = new ImageStorage(terminal, this._renderer, this._opts);
95+
this._storage.onImageAdded = () => this._onImageAdded.fire();
9196

9297
// enable size reports
9398
if (this._opts.enableSizeReports) {

addons/addon-image/src/ImageStorage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ export class ImageStorage implements IDisposable {
124124
private _pixelLimit: number = 2500000;
125125

126126
private _viewportMetrics: { cols: number, rows: number };
127+
public onImageAdded: (() => void) | undefined;
127128
public onImageDeleted: ((storageId: number) => void) | undefined;
128129

129130
constructor(
@@ -336,6 +337,7 @@ export class ImageStorage implements IDisposable {
336337

337338
// finally add the image
338339
this._images.set(imageId, imgSpec);
340+
this.onImageAdded?.();
339341
return imageId;
340342
}
341343

addons/addon-image/src/kitty/KittyGraphicsHandler.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,10 +752,10 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
752752
const b1 = src32[srcOffset++];
753753
const b2 = src32[srcOffset++];
754754
// Little-endian: pixel bytes are [R,G,B] → uint32 ABGR layout
755-
dst32[dstOffset++] = (b0 & 0x00FFFFFF) | 0xFF000000;
756-
dst32[dstOffset++] = ((b0 >>> 24) | (b1 << 8)) & 0x00FFFFFF | 0xFF000000;
757-
dst32[dstOffset++] = ((b1 >>> 16) | (b2 << 16)) & 0x00FFFFFF | 0xFF000000;
758-
dst32[dstOffset++] = (b2 >>> 8) | 0xFF000000;
755+
dst32[dstOffset++] = 0xFF000000 | b0;
756+
dst32[dstOffset++] = 0xFF000000 | (b0 >>> 24) | (b1 << 8);
757+
dst32[dstOffset++] = 0xFF000000 | (b1 >>> 16) | (b2 << 16);
758+
dst32[dstOffset++] = 0xFF000000 | (b2 >>> 8);
759759
}
760760

761761
// Handle remaining 1–3 pixels

addons/addon-image/test/ImageAddon.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,18 @@ test.describe('ImageAddon', () => {
196196
});
197197

198198
test.describe('image lifecycle & eviction', () => {
199+
test('onImageAdded fires for each image', async () => {
200+
await ctx.page.evaluate(`
201+
window._imageAddedCount = 0;
202+
window.imageAddon.onImageAdded(() => { window._imageAddedCount++; });
203+
`);
204+
await ctx.proxy.write(SIXEL_SEQ_0);
205+
await pollFor(ctx.page, 'window._imageAddedCount', 1);
206+
await ctx.proxy.write(SIXEL_SEQ_0);
207+
await pollFor(ctx.page, 'window._imageAddedCount', 2);
208+
await ctx.proxy.write(SIXEL_SEQ_0);
209+
await pollFor(ctx.page, 'window._imageAddedCount', 3);
210+
});
199211
test('delete image once scrolled off', async () => {
200212
await ctx.proxy.write(SIXEL_SEQ_0);
201213
pollFor(ctx.page, 'window.imageAddon._storage._images.size', 1);

addons/addon-image/test/KittyGraphics.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,19 @@ test.describe('Kitty Graphics Protocol', () => {
21592159
strictEqual(await ctx.page.evaluate(`window.imageAddon._storage._images.has(${oldStorageId})`), false);
21602160
});
21612161
});
2162+
2163+
test.describe('onImageAdded callback', () => {
2164+
test('onImageAdded fires for each kitty image', async () => {
2165+
await ctx.page.evaluate(`
2166+
window._imageAddedCount = 0;
2167+
window.imageAddon.onImageAdded(() => { window._imageAddedCount++; });
2168+
`);
2169+
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
2170+
await pollFor(ctx.page, 'window._imageAddedCount', 1);
2171+
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_RGB_3X1_BASE64}\x1b\\`);
2172+
await pollFor(ctx.page, 'window._imageAddedCount', 2);
2173+
});
2174+
});
21622175
});
21632176

21642177
/**

addons/addon-image/typings/addon-image.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @license MIT
44
*/
55

6-
import { Terminal, ITerminalAddon } from '@xterm/xterm';
6+
import { Terminal, ITerminalAddon, IEvent } from '@xterm/xterm';
77

88
declare module '@xterm/addon-image' {
99
export interface IImageAddonOptions {
@@ -116,6 +116,11 @@ declare module '@xterm/addon-image' {
116116
*/
117117
public showPlaceholder: boolean;
118118

119+
/**
120+
* Event fired whenever a new image is added to storage.
121+
*/
122+
public readonly onImageAdded: IEvent<void>;
123+
119124
/**
120125
* Get original image canvas at buffer position.
121126
*/

headless/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,30 @@
22

33
⚠ This package is experimental
44

5-
`xterm-headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted.
5+
`@xterm/headless` is a headless terminal that can be run in node.js. This is useful in combination with the frontend [`xterm`](https://www.npmjs.com/package/@xterm/xterm) for example to keep track of a terminal's state on a remote server where the process is hosted.
66

77
## Getting Started
88

99
First, you need to install the module, we ship exclusively through npm, so you need that installed and then add xterm.js as a dependency by running:
1010

1111
```sh
12-
npm install xterm-headless
12+
npm install @xterm/headless
1313
```
1414

15-
Then import as you would a regular node package. The recommended way to load `xterm-headless` is with TypeScript and the ES6 module syntax:
15+
Then import as you would a regular node package. The recommended way to load `@xterm/headless` is with TypeScript and the ES6 module syntax:
1616

1717
```javascript
1818
import { Terminal } from '@xterm/headless';
1919
```
2020

2121
## API
2222

23-
The full API for `xterm-headless` is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm-headless.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
23+
The full API for `@xterm/headless` is contained within the [TypeScript declaration file](https://github.com/xtermjs/xterm.js/blob/master/typings/xterm-headless.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
2424

2525
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
2626

2727
### Addons
2828

29-
Addons in `xterm-headless` work the [same as in `xterm`](https://github.com/xtermjs/xterm.js/blob/master/README.md#addons) with the one caveat being that the addon needs to be packaged for node.js and not use any DOM APIs.
29+
Addons in `@xterm/headless` work the [same as in `xterm`](https://github.com/xtermjs/xterm.js/blob/master/README.md#addons) with the one caveat being that the addon needs to be packaged for node.js and not use any DOM APIs.
3030

3131
Currently no official addons are packaged on npm.

package-lock.json

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

0 commit comments

Comments
 (0)