buninu 0.4.17 → 0.4.19

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.11",
3
+ "version": "1.1.14",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "jsgotty",
9
- "version": "1.1.11",
9
+ "version": "1.1.14",
10
10
  "dependencies": {
11
11
  "node-addon-api": "^7.1.1",
12
12
  "ws": "^8.18.3"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsgotty",
3
- "version": "1.1.12",
3
+ "version": "1.1.14",
4
4
  "description": "Share your terminal as a web application: A JavaScript rewrite of GoTTY",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -8,9 +8,13 @@
8
8
  },
9
9
  "type": "commonjs",
10
10
  "assets": [
11
+ "conpty",
11
12
  "static",
12
13
  "README.md",
13
- "README.en.md"
14
+ "README.en.md",
15
+ "LICENSE",
16
+ "NOTICE",
17
+ "LICENSES"
14
18
  ],
15
19
  "dependencies": {
16
20
  "node-addon-api": "^7.1.1",
@@ -0,0 +1,135 @@
1
+ // Windows PTY adapter for the out-of-band Microsoft Console package.
2
+ // Keep the same interface as BunPtyBackend in gotty.js.
3
+ const { dlopen, ptr } = require("bun:ffi");
4
+ const path = require("path");
5
+
6
+ const kernel = dlopen("kernel32.dll", {
7
+ CreatePipe: { args: ["ptr", "ptr", "ptr", "u32"], returns: "i32" },
8
+ InitializeProcThreadAttributeList: { args: ["ptr", "u32", "u32", "ptr"], returns: "i32" },
9
+ UpdateProcThreadAttribute: { args: ["ptr", "u32", "usize", "ptr", "usize", "ptr", "ptr"], returns: "i32" },
10
+ DeleteProcThreadAttributeList: { args: ["ptr"], returns: "void" },
11
+ CreateProcessW: { args: ["ptr", "ptr", "ptr", "ptr", "i32", "u32", "ptr", "ptr", "ptr", "ptr"], returns: "i32" },
12
+ PeekNamedPipe: { args: ["ptr", "ptr", "u32", "ptr", "ptr", "ptr"], returns: "i32" },
13
+ ReadFile: { args: ["ptr", "ptr", "u32", "ptr", "ptr"], returns: "i32" },
14
+ WriteFile: { args: ["ptr", "ptr", "u32", "ptr", "ptr"], returns: "i32" },
15
+ GetExitCodeProcess: { args: ["ptr", "ptr"], returns: "i32" },
16
+ TerminateProcess: { args: ["ptr", "u32"], returns: "i32" },
17
+ CloseHandle: { args: ["ptr"], returns: "i32" },
18
+ GetLastError: { args: [], returns: "u32" },
19
+ }).symbols;
20
+
21
+ const conptySymbols = {
22
+ ConptyCreatePseudoConsole: { args: ["u32", "ptr", "ptr", "u32", "ptr"], returns: "i32" },
23
+ ConptyResizePseudoConsole: { args: ["ptr", "u32"], returns: "i32" },
24
+ ConptyClosePseudoConsole: { args: ["ptr"], returns: "void" },
25
+ };
26
+ const wide = (value) => Buffer.from(`${value}\0`, "utf16le");
27
+ const handle = (buffer) => Number(buffer.readBigUInt64LE(0));
28
+ const size = (cols, rows) => ((Math.max(1, rows) & 0xffff) << 16) | (Math.max(1, cols) & 0xffff);
29
+ const winError = (operation) => new Error(`${operation} failed: ${kernel.GetLastError()}`);
30
+ // Windows CRT argv escaping: double backslashes before quotes and at the end.
31
+ function quoteArg(value) {
32
+ let result = '"', slashes = 0;
33
+ for (const char of String(value)) {
34
+ if (char === "\\") { slashes++; continue; }
35
+ if (char === '"') result += "\\".repeat(slashes * 2 + 1) + '"';
36
+ else result += "\\".repeat(slashes) + char;
37
+ slashes = 0;
38
+ }
39
+ return result + "\\".repeat(slashes * 2) + '"';
40
+ }
41
+
42
+ class WindowsConptyBackend {
43
+ constructor(options, dllPath) {
44
+ this.options = options;
45
+ this.dataHandlers = [];
46
+ this.exitHandlers = [];
47
+ this.closed = false;
48
+ this.conpty = dlopen(path.resolve(dllPath), conptySymbols).symbols;
49
+ const inputRead = Buffer.alloc(8), inputWrite = Buffer.alloc(8);
50
+ const outputRead = Buffer.alloc(8), outputWrite = Buffer.alloc(8);
51
+ if (!kernel.CreatePipe(inputRead, inputWrite, 0, 0)) throw winError("CreatePipe input");
52
+ if (!kernel.CreatePipe(outputRead, outputWrite, 0, 0)) throw winError("CreatePipe output");
53
+ this.input = handle(inputWrite);
54
+ this.output = handle(outputRead);
55
+ const pseudo = Buffer.alloc(8);
56
+ const hr = this.conpty.ConptyCreatePseudoConsole(
57
+ size(options.width || 80, options.height || 24), handle(inputRead), handle(outputWrite), 0, pseudo);
58
+ kernel.CloseHandle(handle(inputRead));
59
+ kernel.CloseHandle(handle(outputWrite));
60
+ if (hr !== 0) throw new Error(`ConptyCreatePseudoConsole failed: ${hr}`);
61
+ this.pseudo = handle(pseudo);
62
+
63
+ const listSize = Buffer.alloc(8);
64
+ kernel.InitializeProcThreadAttributeList(0, 1, 0, listSize);
65
+ const list = Buffer.alloc(Number(listSize.readBigUInt64LE(0)));
66
+ if (!kernel.InitializeProcThreadAttributeList(list, 1, 0, listSize)) throw winError("InitializeProcThreadAttributeList");
67
+ if (!kernel.UpdateProcThreadAttribute(list, 0, 0x00020016, this.pseudo, 8, 0, 0)) throw winError("UpdateProcThreadAttribute");
68
+ const startup = Buffer.alloc(112);
69
+ startup.writeUInt32LE(112, 0);
70
+ startup.writeUInt32LE(0x100, 60); // STARTF_USESTDHANDLES
71
+ startup.writeBigUInt64LE(BigInt(ptr(list)), 104);
72
+ const processInfo = Buffer.alloc(24);
73
+ const env = { ...process.env, TERM: "xterm-256color", ...(options.headerEnv || {}) };
74
+ const envBlock = wide(Object.entries(env).filter(([key]) => !key.startsWith("="))
75
+ .map(([key, value]) => `${key}=${value}`).join("\0") + "\0");
76
+ const executable = Bun.which(options.command) || options.command;
77
+ const command = wide([executable, ...options.argv].map(quoteArg).join(" "));
78
+ const ok = kernel.CreateProcessW(wide(executable), command, 0, 0, 0,
79
+ 0x00080000 | 0x00000400, envBlock, wide(process.cwd()), startup, processInfo);
80
+ kernel.DeleteProcThreadAttributeList(list);
81
+ if (!ok) throw winError("CreateProcessW");
82
+ this.process = handle(processInfo);
83
+ this.pid = processInfo.readUInt32LE(16);
84
+ kernel.CloseHandle(Number(processInfo.readBigUInt64LE(8)));
85
+ this.pollTimer = setInterval(() => this.poll(), 5);
86
+ }
87
+
88
+ onData(handler) { this.dataHandlers.push(handler); }
89
+ onExit(handler) { this.exitHandlers.push(handler); }
90
+ write(buffer) { this.writeBinary(buffer); }
91
+ writeBinary(buffer) {
92
+ const data = Buffer.from(buffer);
93
+ const written = Buffer.alloc(4);
94
+ for (let offset = 0; offset < data.length;) {
95
+ const chunk = data.subarray(offset);
96
+ if (!kernel.WriteFile(this.input, chunk, chunk.length, written, 0)) throw winError("WriteFile");
97
+ const count = written.readUInt32LE(0);
98
+ if (!count) throw new Error("WriteFile wrote no data");
99
+ offset += count;
100
+ }
101
+ }
102
+ resize(cols, rows) {
103
+ const hr = this.conpty.ConptyResizePseudoConsole(this.pseudo, size(cols, rows));
104
+ if (hr !== 0) throw new Error(`ConptyResizePseudoConsole failed: ${hr}`);
105
+ }
106
+ kill() { if (!this.closed) kernel.TerminateProcess(this.process, 1); }
107
+ close() { this.kill(); }
108
+
109
+ poll() {
110
+ if (this.closed) return;
111
+ const available = Buffer.alloc(4), readCount = Buffer.alloc(4);
112
+ if (kernel.PeekNamedPipe(this.output, 0, 0, 0, available, 0)) {
113
+ for (let bytes = available.readUInt32LE(0); bytes > 0; bytes = available.readUInt32LE(0)) {
114
+ const data = Buffer.alloc(Math.min(bytes, 65536));
115
+ if (!kernel.ReadFile(this.output, data, data.length, readCount, 0)) break;
116
+ const count = readCount.readUInt32LE(0);
117
+ if (!count) break;
118
+ for (const handler of this.dataHandlers) handler(data.subarray(0, count));
119
+ if (!kernel.PeekNamedPipe(this.output, 0, 0, 0, available, 0)) break;
120
+ }
121
+ }
122
+ const exitCode = Buffer.alloc(4);
123
+ if (kernel.GetExitCodeProcess(this.process, exitCode) && exitCode.readUInt32LE(0) !== 259) {
124
+ this.closed = true;
125
+ clearInterval(this.pollTimer);
126
+ this.conpty.ConptyClosePseudoConsole(this.pseudo);
127
+ kernel.CloseHandle(this.input);
128
+ kernel.CloseHandle(this.output);
129
+ kernel.CloseHandle(this.process);
130
+ for (const handler of this.exitHandlers) handler();
131
+ }
132
+ }
133
+ }
134
+
135
+ module.exports = WindowsConptyBackend;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu",
3
- "version": "0.4.17",
3
+ "version": "0.4.19",
4
4
  "description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
5
5
  "type": "module",
6
6
  "main": "./bin/init.js",