diff --git a/.eslintrc.json b/.eslintrc.json index 682e42de46..5e49b021bb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -18,6 +18,8 @@ "addons/addon-attach/test/tsconfig.json", "addons/addon-canvas/src/tsconfig.json", "addons/addon-canvas/test/tsconfig.json", + "addons/addon-clipboard/src/tsconfig.json", + "addons/addon-clipboard/test/tsconfig.json", "addons/addon-fit/src/tsconfig.json", "addons/addon-fit/test/tsconfig.json", "addons/addon-image/src/tsconfig.json", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ffaf853f63..c893d8a7dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: ./addons/addon-attach/out-test/* \ ./addons/addon-canvas/out/* \ ./addons/addon-canvas/out-test/* \ + ./addons/addon-clipboard/out/* \ + ./addons/addon-clipboard/out-test/* \ ./addons/addon-fit/out/* \ ./addons/addon-fit/out-test/* \ ./addons/addon-image/out/* \ diff --git a/README.md b/README.md index 5f4a786ad4..d9392de355 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ The xterm.js team maintains the following addons, but anyone can build them: - [`@xterm/addon-attach`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-attach): Attaches to a server running a process via a websocket - [`@xterm/addon-canvas`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-canvas): Renders xterm.js using a `canvas` element's 2d context +- [`@xterm/addon-clipboard`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard): Access the browser's clipboard - [`@xterm/addon-fit`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-fit): Fits the terminal to the containing element - [`@xterm/addon-image`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-image): Adds image support - [`@xterm/addon-search`](https://github.com/xtermjs/xterm.js/tree/master/addons/addon-search): Adds search functionality diff --git a/addons/addon-clipboard/.gitignore b/addons/addon-clipboard/.gitignore new file mode 100644 index 0000000000..3063f07d55 --- /dev/null +++ b/addons/addon-clipboard/.gitignore @@ -0,0 +1,2 @@ +lib +node_modules diff --git a/addons/addon-clipboard/.npmignore b/addons/addon-clipboard/.npmignore new file mode 100644 index 0000000000..b203232aff --- /dev/null +++ b/addons/addon-clipboard/.npmignore @@ -0,0 +1,29 @@ +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/addon-clipboard/LICENSE b/addons/addon-clipboard/LICENSE new file mode 100644 index 0000000000..b6c38b1547 --- /dev/null +++ b/addons/addon-clipboard/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/addons/addon-clipboard/README.md b/addons/addon-clipboard/README.md new file mode 100644 index 0000000000..7fef805ca4 --- /dev/null +++ b/addons/addon-clipboard/README.md @@ -0,0 +1,53 @@ +## @xterm/addon-clipboard + +An addon for [xterm.js](https://github.com/xtermjs/xterm.js) that enables +accessing the system clipboard. This addon requires xterm.js v4+. + +### Install + +```bash +npm install --save @xterm/addon-clipboard +``` + +### Usage + +```ts +import { Terminal } from 'xterm'; +import { ClipboardAddon } from '@xterm/addon-clipboard'; + +const terminal = new Terminal(); +const clipboardAddon = new ClipboardAddon(); +terminal.loadAddon(clipboardAddon); +``` + +To use a custom clipboard provider + +```ts +import { Terminal } from '@xterm/xterm'; +import { ClipboardAddon, IClipboardProvider, ClipboardSelectionType } from '@xterm/addon-clipboard'; + +function b64Encode(data: string): string { + // Base64 encode impl +} + +function b64Decode(data: string): string { + // Base64 decode impl +} + +class MyCustomClipboardProvider implements IClipboardProvider { + private _data: string + public readText(selection: ClipboardSelectionType): Promise { + return Promise.resolve(b64Encode(this._data)); + } + public writeText(selection: ClipboardSelectionType, data: string): Promise { + this._data = b64Decode(data); + return Promise.resolve(); + } +} + +const terminal = new Terminal(); +const clipboardAddon = new ClipboardAddon(new MyCustomClipboardProvider()); +terminal.loadAddon(clipboardAddon); +``` + +See the full [API](https://github.com/xtermjs/xterm.js/blob/master/addons/addon-clipboard/typings/addon-clipboard.d.ts) for more advanced usage. diff --git a/addons/addon-clipboard/package.json b/addons/addon-clipboard/package.json new file mode 100644 index 0000000000..af433f9368 --- /dev/null +++ b/addons/addon-clipboard/package.json @@ -0,0 +1,29 @@ +{ + "name": "@xterm/addon-clipboard", + "version": "0.1.0", + "author": { + "name": "The xterm.js authors", + "url": "https://xtermjs.org/" + }, + "main": "lib/addon-clipboard.js", + "types": "typings/addon-clipboard.d.ts", + "repository": "https://github.com/xtermjs/xterm.js/tree/master/addons/addon-clipboard", + "license": "MIT", + "keywords": [ + "terminal", + "xterm", + "xterm.js" + ], + "scripts": { + "build": "../../node_modules/.bin/tsc -p .", + "prepackage": "npm run build", + "package": "../../node_modules/.bin/webpack", + "prepublishOnly": "npm run package" + }, + "peerDependencies": { + "@xterm/xterm": "^5.4.0" + }, + "dependencies": { + "js-base64": "^3.7.5" + } +} diff --git a/addons/addon-clipboard/src/ClipboardAddon.ts b/addons/addon-clipboard/src/ClipboardAddon.ts new file mode 100644 index 0000000000..58425924f5 --- /dev/null +++ b/addons/addon-clipboard/src/ClipboardAddon.ts @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import type { IDisposable, ITerminalAddon, Terminal } from '@xterm/xterm'; +import { type IClipboardProvider, ClipboardSelectionType, type IBase64 } from '@xterm/addon-clipboard'; +import { Base64 as JSBase64 } from 'js-base64'; + +export class ClipboardAddon implements ITerminalAddon { + private _terminal?: Terminal; + private _disposable?: IDisposable; + + constructor( + private _base64: IBase64 = new Base64(), + private _provider: IClipboardProvider = new BrowserClipboardProvider() + ) {} + + public activate(terminal: Terminal): void { + this._terminal = terminal; + this._disposable = terminal.parser.registerOscHandler(52, data => this._setOrReportClipboard(data)); + } + + public dispose(): void { + return this._disposable?.dispose(); + } + + private _readText(sel: ClipboardSelectionType, data: string): void { + const b64 = this._base64.encodeText(data); + this._terminal?.input(`\x1b]52;${sel};${b64}\x07`, false); + } + + private _setOrReportClipboard(data: string): boolean | Promise { + const args = data.split(';'); + if (args.length < 2) { + return true; + } + + const pc = args[0] as ClipboardSelectionType; + const pd = args[1]; + if (pd === '?') { + const text = this._provider.readText(pc); + + // Report clipboard + if (text instanceof Promise) { + return text.then((data) => { + this._readText(pc, data); + return true; + }); + } + + this._readText(pc, text); + return true; + } + + // Clear clipboard if text is not a base64 encoded string. + let text = ''; + try { + text = this._base64.decodeText(pd); + } catch {} + + + const result = this._provider.writeText(pc, text); + if (result instanceof Promise) { + return result.then(() => true); + } + + return true; + } +} + +export class BrowserClipboardProvider implements IClipboardProvider { + public async readText(selection: ClipboardSelectionType): Promise { + if (selection !== 'c') { + return Promise.resolve(''); + } + return navigator.clipboard.readText(); + } + + public async writeText(selection: ClipboardSelectionType, text: string): Promise { + if (selection !== 'c') { + return Promise.resolve(); + } + return navigator.clipboard.writeText(text); + } +} + +export class Base64 implements IBase64 { + public encodeText(data: string): string { + return JSBase64.encode(data); + } + public decodeText(data: string): string { + const text = JSBase64.decode(data); + if (!JSBase64.isValid(data) || JSBase64.encode(text) !== data) { + return ''; + } + return text; + } +} diff --git a/addons/addon-clipboard/src/tsconfig.json b/addons/addon-clipboard/src/tsconfig.json new file mode 100644 index 0000000000..b610728084 --- /dev/null +++ b/addons/addon-clipboard/src/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2021", + "lib": [ + "dom", + "es2015" + ], + "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, + "strict": true, + "types": [ + "../../../node_modules/@types/mocha" + ], + "paths": { + "browser/*": [ + "../../../src/browser/*" + ], + "@xterm/addon-clipboard": [ + "../typings/addon-clipboard.d.ts" + ] + } + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { + "path": "../../../src/browser" + } + ] +} diff --git a/addons/addon-clipboard/test/ClipboardAddon.api.ts b/addons/addon-clipboard/test/ClipboardAddon.api.ts new file mode 100644 index 0000000000..4ef768e8e7 --- /dev/null +++ b/addons/addon-clipboard/test/ClipboardAddon.api.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { openTerminal, launchBrowser, writeSync, getBrowserType } from '../../../out-test/api/TestUtils'; +import { Browser, BrowserContext, Page } from '@playwright/test'; +import { beforeEach } from 'mocha'; + +const APP = 'http://127.0.0.1:3001/test'; + +let browser: Browser; +let context: BrowserContext; +let page: Page; +const width = 800; +const height = 600; + +describe('ClipboardAddon', () => { + before(async function (): Promise { + browser = await launchBrowser({ + // Enable clipboard access in firefox, mainly for readText + firefoxUserPrefs: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'dom.events.testing.asyncClipboard': true, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'dom.events.asyncClipboard.readText': true + } + }); + context = await browser.newContext(); + if (getBrowserType().name() !== 'webkit') { + // Enable clipboard access in chromium without user gesture + context.grantPermissions(['clipboard-read', 'clipboard-write']); + } + page = await context.newPage(); + await page.setViewportSize({ width, height }); + await page.goto(APP); + await openTerminal(page); + await page.evaluate(` + window.clipboardAddon = new ClipboardAddon(); + window.term.loadAddon(window.clipboardAddon); + `); + }); + + after(() => { + browser.close(); + }); + + beforeEach(async () => { + await page.evaluate(`window.term.reset()`); + }); + + const testDataEncoded = 'aGVsbG8gd29ybGQ='; + const testDataDecoded = 'hello world'; + + describe('write data', async function (): Promise { + it('simple string', async () => { + await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`); + assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), testDataDecoded); + }); + it('invalid base64 string', async () => { + await writeSync(page, `\x1b]52;c;${testDataEncoded}invalid\x07`); + assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), ''); + }); + it('empty string', async () => { + await writeSync(page, `\x1b]52;c;${testDataEncoded}\x07`); + await writeSync(page, `\x1b]52;c;\x07`); + assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), ''); + }); + }); + + describe('read data', async function (): Promise { + it('simple string', async () => { + await page.evaluate(` + window.data = []; + window.term.onData(e => data.push(e)); + `); + await page.evaluate(() => window.navigator.clipboard.writeText('hello world')); + await writeSync(page, `\x1b]52;c;?\x07`); + assert.deepEqual(await page.evaluate('window.data'), [`\x1b]52;c;${testDataEncoded}\x07`]); + }); + it('clear clipboard', async () => { + await writeSync(page, `\x1b]52;c;!\x07`); + await writeSync(page, `\x1b]52;c;?\x07`); + assert.deepEqual(await page.evaluate(() => window.navigator.clipboard.readText()), ''); + }); + }); +}); diff --git a/addons/addon-clipboard/test/tsconfig.json b/addons/addon-clipboard/test/tsconfig.json new file mode 100644 index 0000000000..67ad42b720 --- /dev/null +++ b/addons/addon-clipboard/test/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2021", + "lib": [ + "es2015" + ], + "rootDir": ".", + "outDir": "../out-test", + "sourceMap": true, + "removeComments": true, + "strict": true, + "types": [ + "../../../node_modules/@types/mocha", + "../../../node_modules/@types/node", + "../../../out-test/api/TestUtils" + ] + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ] +} diff --git a/addons/addon-clipboard/tsconfig.json b/addons/addon-clipboard/tsconfig.json new file mode 100644 index 0000000000..2d820dd1a6 --- /dev/null +++ b/addons/addon-clipboard/tsconfig.json @@ -0,0 +1,8 @@ +{ + "files": [], + "include": [], + "references": [ + { "path": "./src" }, + { "path": "./test" } + ] +} diff --git a/addons/addon-clipboard/typings/addon-clipboard.d.ts b/addons/addon-clipboard/typings/addon-clipboard.d.ts new file mode 100644 index 0000000000..f37748fadf --- /dev/null +++ b/addons/addon-clipboard/typings/addon-clipboard.d.ts @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon } from '@xterm/xterm'; + +declare module '@xterm/addon-clipboard' { + /** + * An xterm.js addon that enables accessing the system clipboard from + * xterm.js. + */ + export class ClipboardAddon implements ITerminalAddon { + /** + * Creates a new clipboard addon. + */ + constructor(provider?: IClipboardProvider); + + /** + * Activates the addon + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: Terminal): void; + + /** + * Disposes the addon. + */ + public dispose(): void + } + + /** + * Clipboard selection type. This is used to specify which selection buffer to + * read or write to. + * - SYSTEM `c`: The system clipboard. + * - PRIMARY `p`: The primary clipboard. This is provided for compatibility + * with Linux X11. + */ + export const enum ClipboardSelectionType { + SYSTEM = 'c', + PRIMARY = 'p', + } + + export interface IBase64 { + /** + * Converts a utf-8 string to a base64 string. + * @param data The utf-8 string to convert to base64 string. + */ + encodeText(data: string): string; + + /** + * Converts a base64 string to a utf-8 string. + * @param data The base64 string to convert to utf-8 string. + * @throws An error if the input is not valid base64. + */ + decodeText(data: string): string; + } + + /** + * A default Base64 encoding and decoding type. + **/ + export class Base64 implements IBase64 { + /** + * Converts a utf-8 string to a base64 string. + * @param data The utf-8 string to convert to base64 string. + */ + public encodeText(data: string): string; + + /** + * Converts a base64 string to a utf-8 string. + * @param data The base64 string to convert to utf-8 string. + * @throws An error if the input is not valid base64. + */ + public decodeText(data: string): string; + } + + export interface IClipboardProvider { + /** + * Gets the clipboard content. + * @param selection The clipboard selection to read. + * @returns A promise that resolves with clipboard selection data. + */ + readText(selection: ClipboardSelectionType): string | Promise; + + /** + * Sets the clipboard content. + * @param selection The clipboard selection to set. + * @param data The clipboard text to write. + */ + writeText(selection: ClipboardSelectionType, text: string): void | Promise; + } + + /** + * The clipboard provider interface that enables xterm.js to access the system clipboard. + */ + export class BrowserClipboardProvider implements IClipboardProvider{ + /** + * Reads text from the clipboard. + * @param selection The selection type to read from. + * @returns A promise that resolves with the text from the clipboard. + */ + public readText(selection: ClipboardSelectionType): Promise; + + /** + * Writes text to the clipboard. + * @param selection The selection type to write to. + * @param data The text to write to the clipboard. + * @returns A promise that resolves when the text has been written to the clipboard. + */ + public writeText(selection: ClipboardSelectionType, data: string): Promise; + } +} diff --git a/addons/addon-clipboard/webpack.config.js b/addons/addon-clipboard/webpack.config.js new file mode 100644 index 0000000000..c00191abfb --- /dev/null +++ b/addons/addon-clipboard/webpack.config.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2023 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const path = require('path'); + +const addonName = 'ClipboardAddon'; +const mainFile = 'addon-clipboard.js'; + +module.exports = { + entry: `./out/${addonName}.js`, + devtool: 'source-map', + module: { + rules: [ + { + test: /\.js$/, + use: ["source-map-loader"], + enforce: "pre", + exclude: /node_modules/ + } + ] + }, + output: { + filename: mainFile, + path: path.resolve('./lib'), + library: addonName, + libraryTarget: 'umd' + }, + mode: 'production' +}; diff --git a/addons/addon-clipboard/yarn.lock b/addons/addon-clipboard/yarn.lock new file mode 100644 index 0000000000..01d54e3673 --- /dev/null +++ b/addons/addon-clipboard/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +js-base64@^3.7.5: + version "3.7.7" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" + integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== diff --git a/bin/publish.js b/bin/publish.js index ad9455345a..4b945d7569 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -29,6 +29,7 @@ if (changedFiles.some(e => e.search(/^addons\//) === -1)) { const addonPackageDirs = [ path.resolve(__dirname, '../addons/addon-attach'), path.resolve(__dirname, '../addons/addon-canvas'), + path.resolve(__dirname, '../addons/addon-clipboard'), path.resolve(__dirname, '../addons/addon-fit'), path.resolve(__dirname, '../addons/addon-image'), path.resolve(__dirname, '../addons/addon-ligatures'), diff --git a/demo/client.ts b/demo/client.ts index a3e58051c8..78f8396dde 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -12,6 +12,7 @@ import { Terminal } from '../out/browser/public/Terminal'; import { AttachAddon } from '../addons/addon-attach/out/AttachAddon'; import { CanvasAddon } from '../addons/addon-canvas/out/CanvasAddon'; +import { ClipboardAddon } from '../addons/addon-clipboard/out/ClipboardAddon'; import { FitAddon } from '../addons/addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/addon-search/out/SearchAddon'; import { SerializeAddon } from '../addons/addon-serialize/out/SerializeAddon'; @@ -32,6 +33,7 @@ if ('WebAssembly' in window) { // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; // import { AttachAddon } from '@xterm/addon-attach'; +// import { ClipboardAddon } from '@xterm/addon-clipboard'; // import { FitAddon } from '@xterm/addon-fit'; // import { ImageAddon } from '@xterm/addon-image'; // import { SearchAddon, ISearchOptions } from '@xterm/addon-search'; @@ -51,6 +53,7 @@ export interface IWindowWithTerminal extends Window { Terminal?: typeof TerminalType; // eslint-disable-line @typescript-eslint/naming-convention AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention CanvasAddon?: typeof CanvasAddon; // eslint-disable-line @typescript-eslint/naming-convention + ClipboardAddon?: typeof ClipboardAddon; // eslint-disable-line @typescript-eslint/naming-convention FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention ImageAddon?: typeof ImageAddonType; // eslint-disable-line @typescript-eslint/naming-convention SearchAddon?: typeof SearchAddon; // eslint-disable-line @typescript-eslint/naming-convention @@ -70,7 +73,7 @@ let socket; let pid; let autoResize: boolean = true; -type AddonType = 'attach' | 'canvas' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures'; +type AddonType = 'attach' | 'canvas' | 'clipboard' | 'fit' | 'image' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures'; interface IDemoAddon { name: T; @@ -78,35 +81,38 @@ interface IDemoAddon { ctor: ( T extends 'attach' ? typeof AttachAddon : T extends 'canvas' ? typeof CanvasAddon : - T extends 'fit' ? typeof FitAddon : - T extends 'image' ? typeof ImageAddonType : - T extends 'search' ? typeof SearchAddon : - T extends 'serialize' ? typeof SerializeAddon : - T extends 'webLinks' ? typeof WebLinksAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : - T extends 'ligatures' ? typeof LigaturesAddon : + T extends 'clipboard' ? typeof ClipboardAddon : + T extends 'fit' ? typeof FitAddon : + T extends 'image' ? typeof ImageAddonType : + T extends 'search' ? typeof SearchAddon : + T extends 'serialize' ? typeof SerializeAddon : + T extends 'webLinks' ? typeof WebLinksAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : + T extends 'ligatures' ? typeof LigaturesAddon : typeof WebglAddon ); instance?: ( T extends 'attach' ? AttachAddon : T extends 'canvas' ? CanvasAddon : - T extends 'fit' ? FitAddon : - T extends 'image' ? ImageAddonType : - T extends 'search' ? SearchAddon : - T extends 'serialize' ? SerializeAddon : - T extends 'webLinks' ? WebLinksAddon : - T extends 'webgl' ? WebglAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : - T extends 'ligatures' ? typeof LigaturesAddon : - never + T extends 'clipboard' ? ClipboardAddon : + T extends 'fit' ? FitAddon : + T extends 'image' ? ImageAddonType : + T extends 'search' ? SearchAddon : + T extends 'serialize' ? SerializeAddon : + T extends 'webLinks' ? WebLinksAddon : + T extends 'webgl' ? WebglAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon : + T extends 'ligatures' ? typeof LigaturesAddon : + never ); } const addons: { [T in AddonType]: IDemoAddon } = { attach: { name: 'attach', ctor: AttachAddon, canChange: false }, canvas: { name: 'canvas', ctor: CanvasAddon, canChange: true }, + clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true }, fit: { name: 'fit', ctor: FitAddon, canChange: false }, image: { name: 'image', ctor: ImageAddon, canChange: true }, search: { name: 'search', ctor: SearchAddon, canChange: true }, @@ -179,6 +185,7 @@ const disposeRecreateButtonHandler: () => void = () => { socket = null; addons.attach.instance = undefined; addons.canvas.instance = undefined; + addons.clipboard.instance = undefined; addons.fit.instance = undefined; addons.image.instance = undefined; addons.search.instance = undefined; @@ -228,6 +235,7 @@ if (document.location.pathname === '/test') { window.Terminal = Terminal; window.AttachAddon = AttachAddon; window.CanvasAddon = CanvasAddon; + window.ClipboardAddon = ClipboardAddon; window.FitAddon = FitAddon; window.ImageAddon = ImageAddon; window.SearchAddon = SearchAddon; @@ -288,6 +296,7 @@ function createTerminal(): void { addons.fit.instance = new FitAddon(); addons.image.instance = new ImageAddon(); addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon(); + addons.clipboard.instance = new ClipboardAddon(); try { // try to start with webgl renderer (might throw on older safari/webkit) addons.webgl.instance = new WebglAddon(); } catch (e) { @@ -300,6 +309,7 @@ function createTerminal(): void { typedTerm.loadAddon(addons.serialize.instance); typedTerm.loadAddon(addons.unicodeGraphemes.instance); typedTerm.loadAddon(addons.webLinks.instance); + typedTerm.loadAddon(addons.clipboard.instance); window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { diff --git a/demo/tsconfig.json b/demo/tsconfig.json index f0aebb1f3b..4114f6d6dc 100644 --- a/demo/tsconfig.json +++ b/demo/tsconfig.json @@ -7,6 +7,7 @@ "baseUrl": ".", "paths": { "addon-attach": ["../addons/addon-attach"], + "addon-clipboard": ["../addons/addon-clipboard"], "addon-fit": ["../addons/addon-fit"], "addon-image": ["../addons/addon-image"], "addon-search": ["../addons/addon-search"], diff --git a/test/api/TestUtils.ts b/test/api/TestUtils.ts index 9ebf67b6a1..cee59cceb1 100644 --- a/test/api/TestUtils.ts +++ b/test/api/TestUtils.ts @@ -74,9 +74,10 @@ export function getBrowserType(): playwright.BrowserType { +export function launchBrowser(opts?: playwright.LaunchOptions): Promise { const browserType = getBrowserType(); - const options: Record = { + const options: playwright.LaunchOptions = { + ...opts, headless: process.argv.includes('--headless') }; diff --git a/test/playwright/TestUtils.ts b/test/playwright/TestUtils.ts index 1427578c11..79408d4180 100644 --- a/test/playwright/TestUtils.ts +++ b/test/playwright/TestUtils.ts @@ -492,9 +492,10 @@ export function getBrowserType(): playwright.BrowserType { +export function launchBrowser(opts?: playwright.LaunchOptions): Promise { const browserType = getBrowserType(); - const options: Record = { + const options: playwright.LaunchOptions = { + ...opts, headless: process.argv.includes('--headless') }; diff --git a/tsconfig.all.json b/tsconfig.all.json index e2028479d5..d40761f337 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -9,6 +9,7 @@ { "path": "./test/playwright" }, { "path": "./addons/addon-attach" }, { "path": "./addons/addon-canvas" }, + { "path": "./addons/addon-clipboard" }, { "path": "./addons/addon-fit" }, { "path": "./addons/addon-image" }, { "path": "./addons/addon-ligatures" },