diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 000000000..36b59c61e Binary files /dev/null and b/assets/icon.png differ diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 000000000..d8fa7ea67 Binary files /dev/null and b/assets/logo.png differ diff --git a/flake.nix b/flake.nix index cc624004a..5915e754f 100644 --- a/flake.nix +++ b/flake.nix @@ -6,15 +6,22 @@ flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachSystem flake-utils.lib.allSystems (system: + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachSystem flake-utils.lib.allSystems ( + system: let pkgs = import nixpkgs { inherit system; }; hashesFile = builtins.fromJSON (builtins.readFile ./hashes.json); lib = pkgs.lib; - in rec { + in + rec { packages.default = pkgs.buildNpmPackage { pname = "spacebar-server-ts"; name = "spacebar-server-ts"; @@ -41,27 +48,35 @@ npm prune --omit dev --no-save $npmInstallFlags "''${npmInstallFlagsArray[@]}" $npmFlags "''${npmFlagsArray[@]}" find node_modules -maxdepth 1 -type d -empty -delete - mkdir -p $out/node_modules/ - cp -r node_modules/* $out/node_modules/ - cp -r dist/ $out/node_modules/@spacebar + + #mkdir -p $out/node_modules/ + #cp -r node_modules/* $out/node_modules/ + #cp -r dist/ $out/node_modules/@spacebar + #for i in dist/**/start.js + #do + # makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/node_modules/@spacebar`dirname ''${i/dist/}`/start.js + #done + #set +x + #substituteInPlace package.json --replace 'dist/' 'node_modules/@spacebar/' + #find $out/node_modules/@spacebar/ -type f -name "*.js" | while read srcFile; do + # echo Patching imports in ''${srcFile/$out\/node_modules\/@spacebar//}... + # substituteInPlace $srcFile --replace 'require("./' 'require(__dirname + "/' + # substituteInPlace $srcFile --replace 'require("../' 'require(__dirname + "/../' + # substituteInPlace $srcFile --replace ', "assets"' ', "..", "assets"' + # #substituteInPlace $srcFile --replace 'require("@spacebar/' 'require(" + #done + #set -x + #cp -r assets/ $out/ + #cp package.json $out/ + #rm -v $out/assets/openapi.json + ##rm -v $out/assets/schemas.json + + mkdir -p $out + cp -r assets dist node_modules package.json $out/ for i in dist/**/start.js do - makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/node_modules/@spacebar`dirname ''${i/dist/}`/start.js + makeWrapper ${pkgs.nodejs-slim}/bin/node $out/bin/start-`dirname ''${i/dist\//}` --prefix NODE_PATH : $out/node_modules --add-flags $out/$i done - set +x - substituteInPlace package.json --replace 'dist/' 'node_modules/@spacebar/' - find $out/node_modules/@spacebar/ -type f -name "*.js" | while read srcFile; do - echo Patching imports in ''${srcFile/$out\/node_modules\/@spacebar//}... - substituteInPlace $srcFile --replace 'require("./' 'require(__dirname + "/' - substituteInPlace $srcFile --replace 'require("../' 'require(__dirname + "/../' - substituteInPlace $srcFile --replace ', "assets"' ', "..", "assets"' - #substituteInPlace $srcFile --replace 'require("@spacebar/' 'require(" - done - set -x - cp -r assets/ $out/ - cp package.json $out/ - rm -v $out/assets/openapi.json - #rm -v $out/assets/schemas.json #debug utils: #cp $out/node_modules/@spacebar/ $out/build_output -r diff --git a/hashes.json b/hashes.json index 613da98a5..ae3c54d8d 100644 --- a/hashes.json +++ b/hashes.json @@ -1,3 +1,3 @@ { - "npmDepsHash": "sha256-KuKvhybhYGxC6oiVUxyjdEQD4xalXZ3OxT4KJ49ROAw=" + "npmDepsHash": "sha256-zs6ViAc3w1g4/VTsrNajKL65gKFHbzvBUIxlDbEO690=" } diff --git a/package-lock.json b/package-lock.json index cdfb564dc..fa335f156 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@aws-sdk/client-s3": "^3.629.0", "@sentry/integrations": "^7.66.0", "@sentry/node": "^7.66.0", + "@types/ref": "*", "ajv": "^8.6.2", "ajv-formats": "2.1.1", "amqplib": "^0.10.3", @@ -83,14 +84,19 @@ "typescript": "^4.9.5" }, "optionalDependencies": { + "@types/ref": "^0.0.32", + "@types/ref-struct": "^0.0.33", "@yukikaze-bot/erlpack": "^1.0.1", "erlpack": "^0.1.4", + "ioctl": "^2.0.2", "jimp": "^0.22.12", "mysql": "^2.18.1", + "node-ioctl": "*", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport", "nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport", "pg": "^8.11.3", + "ref-struct": "^1.1.0", "sqlite3": "^5.1.6" } }, @@ -3167,6 +3173,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ref": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/ref/-/ref-0.0.32.tgz", + "integrity": "sha512-5q2nxslQF7GnoVzCYPsHD1B8pU020l6zy2rNw5Dzd01plmnRDj0b6thsXUOtbMjVEMAQ5uCgoajJP71f3FWiyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ref-struct": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/ref-struct/-/ref-struct-0.0.33.tgz", + "integrity": "sha512-xLed3yIeCQaxegdXG/ZzE8PychgrfjgyWpn5TnkOqeuiIWAhMUzQPssnV8+0q1xGvbMAjd5Atfb7TWyq2oC/4Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/ref": "*" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", @@ -6509,6 +6535,18 @@ "license": "ISC", "optional": true }, + "node_modules/ioctl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ioctl/-/ioctl-2.0.2.tgz", + "integrity": "sha512-GPEiU99bJb3Z50JDRujQ9t+q4JaRvc1UrD7F5/kHDVWlRA3L4TMwqbMw/XIu/BzqccFP8OfsK+JIXmlAvJDs1g==", + "hasInstallScript": true, + "license": "ISC", + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.14.0" + } + }, "node_modules/ip": { "version": "1.1.9", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", @@ -9042,6 +9080,64 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/ref": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ref/-/ref-1.3.5.tgz", + "integrity": "sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "1", + "debug": "2", + "nan": "2" + } + }, + "node_modules/ref-struct": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ref-struct/-/ref-struct-1.1.0.tgz", + "integrity": "sha512-h2OSdAUycdqiwFBp2wB3XEFheWA/U+n/JYnhEi3584JqnCCDXIA3qDIhHH3klIHMNZwrJW+VagpxPGeSf6777Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "2", + "ref": "1" + } + }, + "node_modules/ref-struct/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ref-struct/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, + "node_modules/ref/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/ref/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT", + "optional": true + }, "node_modules/reflect-metadata": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", diff --git a/package.json b/package.json index 42e729f51..67fbe6c67 100644 --- a/package.json +++ b/package.json @@ -117,14 +117,19 @@ "@spacebar/util": "dist/util" }, "optionalDependencies": { + "@types/ref": "^0.0.32", + "@types/ref-struct": "^0.0.33", "@yukikaze-bot/erlpack": "^1.0.1", "erlpack": "^0.1.4", + "ioctl": "^2.0.2", "jimp": "^0.22.12", "mysql": "^2.18.1", + "node-ioctl": "*", "nodemailer-mailgun-transport": "^2.1.5", "nodemailer-mailjet-transport": "github:n0script22/nodemailer-mailjet-transport", "nodemailer-sendgrid-transport": "github:Maria-Golomb/nodemailer-sendgrid-transport", "pg": "^8.11.3", + "ref-struct": "^1.1.0", "sqlite3": "^5.1.6" } } diff --git a/src/api/start.ts b/src/api/start.ts index 4a3f858eb..ff447f483 100644 --- a/src/api/start.ts +++ b/src/api/start.ts @@ -35,7 +35,7 @@ try { } if (cluster.isPrimary && process.env.NODE_ENV == "production") { - console.log(`Primary ${process.pid} is running`); + console.log(`Primary PID: ${process.pid}`); // Fork workers. for (let i = 0; i < cores; i++) { @@ -43,7 +43,7 @@ if (cluster.isPrimary && process.env.NODE_ENV == "production") { } cluster.on("exit", (worker) => { - console.log(`worker ${worker.process.pid} died, restart worker`); + console.log(`Worker ${worker.process.pid} died, restarting worker`); cluster.fork(); }); } else { diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts index d281120d1..df8e28ce4 100644 --- a/src/bundle/Server.ts +++ b/src/bundle/Server.ts @@ -58,7 +58,7 @@ async function main() { Sentry.errorHandler(app); - console.log(`[Server] ${green(`listening on port ${bold(port)}`)}`); + console.log(`[Server] ${green(`Listening on port ${bold(port)}`)}`); } main().catch(console.error); diff --git a/src/bundle/start.ts b/src/bundle/start.ts index fe177cbc7..33465abc3 100644 --- a/src/bundle/start.ts +++ b/src/bundle/start.ts @@ -18,6 +18,7 @@ // process.env.MONGOMS_DEBUG = "true"; import moduleAlias from "module-alias"; + moduleAlias(__dirname + "../../../package.json"); import "reflect-metadata"; @@ -26,8 +27,10 @@ import os from "os"; import { red, bold, yellow, cyan } from "picocolors"; import { initStats } from "./stats"; import { config } from "dotenv"; + config(); import { execSync } from "child_process"; +import { centerString, Logo } from "@spacebar/util"; const cores = process.env.THREADS ? parseInt(process.env.THREADS) : 1; @@ -41,23 +44,19 @@ function getCommitOrFail() { if (cluster.isPrimary) { const commit = getCommitOrFail(); - + Logo.printLogo(); console.log( bold(` -███████╗██████╗ █████╗ ██████╗███████╗██████╗ █████╗ ██████╗ -██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔══██╗ -███████╗██████╔╝███████║██║ █████╗ ██████╔╝███████║██████╔╝ -╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ ██╔══██╗██╔══██║██╔══██╗ -███████║██║ ██║ ██║╚██████╗███████╗██████╔╝██║ ██║██║ ██║ -╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ - - spacebar-server | ${yellow( - `Pre-release (${ - commit !== null - ? commit.slice(0, 7) - : "Unknown (Git cannot be found)" - })`, - )} +${centerString( + `spacebar-server | ${yellow( + `Pre-release (${ + commit !== null + ? commit.slice(0, 7) + : "Unknown (Git cannot be found)" + })`, + )}`, + 64, +)} Commit Hash: ${ commit !== null @@ -74,7 +73,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) initStats(); - console.log(`[Process] starting with ${cores} threads`); + console.log(`[Process] Starting with ${cores} threads`); if (cores === 1) { require("./Server"); @@ -87,7 +86,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) const delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000; setTimeout(() => { cluster.fork(); - console.log(`[Process] worker ${cyan(i)} started.`); + console.log(`[Process] Worker ${cyan(i)} started.`); }, delay); } @@ -102,7 +101,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) cluster.on("exit", (worker) => { console.log( `[Worker] ${red( - `died with PID: ${worker.process.pid} , restarting ...`, + `PID ${worker.process.pid} died, restarting ...`, )}`, ); cluster.fork(); diff --git a/src/bundle/stats.ts b/src/bundle/stats.ts index b690eb752..92e46027b 100644 --- a/src/bundle/stats.ts +++ b/src/bundle/stats.ts @@ -18,18 +18,44 @@ import os from "os"; import osu from "node-os-utils"; +import { readFileSync } from "node:fs"; import { red } from "picocolors"; export function initStats() { - console.log(`[Path] running in ${__dirname}`); + console.log(`[Path] Running in ${process.cwd()}`); + console.log(`[Path] Running from ${__dirname}`); try { - console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`); + console.log(`[CPU] ${osu.cpu.model()} (x${osu.cpu.count()})`); } catch { - console.log("[CPU] Failed to get cpu model!"); + console.log("[CPU] Failed to get CPU model!"); } - console.log(`[System] ${os.platform()} ${os.arch()}`); - console.log(`[Process] running with PID: ${process.pid}`); + console.log(`[System] ${os.platform()} ${os.release()} ${os.arch()}`); + if (os.platform() == "linux") { + try { + const osReleaseLines = readFileSync( + "/etc/os-release", + "utf8", + ).split("\n"); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + const osRelease: any = {}; + for (const line of osReleaseLines) { + if (!line) continue; + const [key, value] = line.match(/(.*?)="?([^"]*)"?/)!.slice(1); + osRelease[key] = value; + } + console.log( + `[System]\x1b[${osRelease.ANSI_COLOR}m ${osRelease.NAME ?? "Unknown"} ${osRelease.VERSION ?? "Unknown"} (${osRelease.BUILD_ID ?? "No build ID"})\x1b[0m`, + ); + } catch (e) { + console.log( + "[System] Unknown Linux distribution (missing /etc/os-release)", + ); + console.log(e); + } + } + console.log(`[Process] Running with PID: ${process.pid}`); if (process.getuid && process.getuid() === 0) { console.warn( red( diff --git a/src/util/util/KittyLogo.ts b/src/util/util/KittyLogo.ts new file mode 100644 index 000000000..6e74007f4 --- /dev/null +++ b/src/util/util/KittyLogo.ts @@ -0,0 +1,141 @@ +import { readFileSync } from "node:fs"; +import fs from "fs"; + +var util = require("util"); + +// noinspection ES6ConvertRequireIntoImport +// const ioctl = require("ioctl"); +// import ref from "ref"; +// import ArrayType from "ref-array"; +// import StructType from "ref-struct"; +// import os from "os"; + +// const winsize = StructType({ +// ws_row : ref.types.ushort, +// ws_col : ref.types.ushort, +// ws_xpixel : ref.types.ushort, +// ws_ypixel : ref.types.ushort +// }); + +// const originalConsoleLog = console.log; +// console.error = +// console.log = +// console.debug = +// function (message: object, ...optionalParams: object[]) { +// KittyLogo.printWithIcon(message + " " + optionalParams.join(" ")); +// }; + +export class KittyLogo { + public static printLogo(): void { + const data = readFileSync(__dirname + "/../../../assets/logo.png", { + encoding: "base64", + }); + KittyLogo.writeImage({ + base64Data: data, + width: 70, + addNewline: true, + }); + } + + public static printWithIcon(text?: string): void { + if (text) { + const lines = text.split("\n"); + for (const line of lines) { + this.writeIcon(); + process.stdout.write(" " + line + "\n"); + } + } + } + + private static writeIcon(): void { + const data = readFileSync(__dirname + "/../../../assets/icon.png", { + encoding: "base64", + }); + KittyLogo.writeImage({ + base64Data: data, + width: 2, + addNewline: false, + }); + } + + private static checkSupport(cb: void): boolean { + process.stdin.setEncoding("utf8"); + process.stdin.setRawMode(true); + let resp = ""; + process.stdin.once("data", function (key) { + console.log(util.inspect(key)); + process.stdin.setRawMode(false); + process.stdin.pause(); + resp = key.toString(); + }); + process.stdout.write( + "\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c", + ); + + while(resp == "") { + console.log("waiting"); + Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000); + } + + return false; + } + + // private static sleep(ms: number): void { + // Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); + // } + + private static writeImage(request: KittyImageMetadata): void { + if (this.checkSupport()) return; + let pngData = request.base64Data; + + // Ga=T,q=2,o=z,s=1022,v=181,X=5; + const chunkSize = 1024; + + //#region Header + let header = `\x1b_G`; // enable graphics + header += "a=T"; // action = transmit & display + header += ",q=2"; // suppress response + header += ",f=100"; // image format = png + header += ",t=d"; // transmission = direct + header += ",x=0"; // current x position + header += ",y=0"; // current y position + if (request.width) header += `,c=${request.width}`; // width (columns) + if (request.height) header += `,r=${request.height}`; // height (rows) + if (request.widthPixels) header += `,w=${request.widthPixels}`; // width (pixels) + if (request.heightPixels) header += `,h=${request.heightPixels}`; // height (pixels) + //#endregion + + while (pngData.length > 0) { + const dataSize = Math.min(pngData.length, chunkSize); + + process.stdout.write( + header + `,m=${dataSize == chunkSize ? 1 : 0};`, + ); + process.stdout.write(pngData.slice(0, chunkSize)); + pngData = pngData.slice(chunkSize); + process.stdout.write("\x1b\\"); + } + + if (request.addNewline) process.stdout.write("\n"); + } +} + +export class KittyImageMetadata { + public base64Data: string; + public width?: number; + public height?: number; + public widthPixels?: number; + public heightPixels?: number; + public addNewline?: boolean; +} + +KittyLogo.printLogo(); + +// +// for (let i = 0; i < 10; i++) { +// KittyLogo.printLogo(); +// } +// for (let i = 0; i < 10; i++) { +// +// console.log(" ".repeat(i)+"meow"); +// } diff --git a/src/util/util/Logo.ts b/src/util/util/Logo.ts new file mode 100644 index 000000000..20a3253ad --- /dev/null +++ b/src/util/util/Logo.ts @@ -0,0 +1,55 @@ +import { execSync } from "child_process"; +import findOnPath from "./os-utils/OsUtils"; +// noinspection ES6ConvertRequireIntoImport +import terminfo from "./os-utils/TermInfo/TermInfo"; +import { KittyLogo } from "./KittyLogo"; + + +export class Logo { + public static printLogo() { + KittyLogo.printLogo(); + // const chafaPath = findOnPath("chafa"); + // console.log("Chafa path: " + chafaPath); + // const info = terminfo.parse({debug: true}); + // process.exit(0); + return; + // console.log(info); + // if (chafaPath) + // return execSync( + // "chafa Spacebar__Logo-Blue.png -s 70", + // { + // env: process.env, + // encoding: "utf-8", + // stdio: "inherit" + // } + // ); + // else console.log(Logo.logoVersions["1"] as string); + } + + private static getConsoleColors(): number { + return 1; + if (!process.env.TERM) return 1; + else { + switch (process.env.TERM) { + case "": + break; + + default: + break; + } + } + return 1; + } + private static logoVersions: any = { + "1": ` + ███████╗██████╗ █████╗ ██████╗███████╗██████╗ █████╗ ██████╗ + ██╔════╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗██╔══██╗██╔══██╗ + ███████╗██████╔╝███████║██║ █████╗ ██████╔╝███████║██████╔╝ + ╚════██║██╔═══╝ ██╔══██║██║ ██╔══╝ ██╔══██╗██╔══██║██╔══██╗ + ███████║██║ ██║ ██║╚██████╗███████╗██████╔╝██║ ██║██║ ██║ + ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝`, + "2": `` + }; +} + +Logo.printLogo(); \ No newline at end of file diff --git a/src/util/util/String.ts b/src/util/util/String.ts index 1000ebe9b..7b2dc1677 100644 --- a/src/util/util/String.ts +++ b/src/util/util/String.ts @@ -22,3 +22,9 @@ export function trimSpecial(str?: string): string { if (!str) return ""; return str.replace(SPECIAL_CHAR, "").trim(); } + +export function centerString(str: string, len: number): string { + const pad = len - str.length; + const padLeft = Math.floor(pad / 2) + str.length; + return str.padStart(padLeft).padEnd(len); +} \ No newline at end of file diff --git a/src/util/util/index.ts b/src/util/util/index.ts index 10e09b5c3..7bd0f631f 100644 --- a/src/util/util/index.ts +++ b/src/util/util/index.ts @@ -43,3 +43,4 @@ export * from "./TraverseDirectory"; export * from "./WebAuthn"; export * from "./Gifs"; export * from "./Application"; +export * from "./Logo"; diff --git a/src/util/util/os-utils/OsUtils.ts b/src/util/util/os-utils/OsUtils.ts new file mode 100644 index 000000000..babc99a1a --- /dev/null +++ b/src/util/util/os-utils/OsUtils.ts @@ -0,0 +1,21 @@ +import { existsSync, statSync } from "fs"; +import os from "os"; + +export default function findOnPath(binary: string): string | null { + const paths = + (os.platform() == "win32" + ? process.env.PATH?.split(";") + : process.env.PATH?.split(":")) || []; + + if (os.platform() == "win32") { + binary += ".exe"; + } + + for (const path of paths) { + if (existsSync(`${path}/${binary}`)) { + const stat = statSync(`${path}/${binary}`); + if (stat.isFile() && stat.mode & 0o111) return `${path}/${binary}`; + } + } + return null; +} diff --git a/src/util/util/os-utils/TermInfo/Constants.ts b/src/util/util/os-utils/TermInfo/Constants.ts new file mode 100644 index 000000000..34f6e4a66 --- /dev/null +++ b/src/util/util/os-utils/TermInfo/Constants.ts @@ -0,0 +1,964 @@ +"use strict"; + +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +/* + * the variable names and capnames are from the terminfo(5) man page + */ + +export const ALL_VARS: Record = { + // ============== + // == BOOLEANS == + // ============== + AUTO_LEFT_MARGIN: "bw", + AUTO_RIGHT_MARGIN: "am", + BACK_COLOR_ERASE: "bce", + CAN_CHANGE: "ccc", + CEOL_STANDOUT_GLITCH: "xhp", + COL_ADDR_GLITCH: "xhpa", + CPI_CHANGES_RES: "cpix", + CR_CANCELS_MICRO_MODE: "crxm", + DEST_TABS_MAGIC_SMSO: "xt", + EAT_NEWLINE_GLITCH: "xenl", + ERASE_OVERSTRIKE: "eo", + GENERIC_TYPE: "gn", + HARD_COPY: "hc", + HARD_CURSOR: "chts", + HAS_META_KEY: "km", + HAS_PRINT_WHEEL: "daisy", + HAS_STATUS_LINE: "hs", + HUE_LIGHTNESS_SATURATION: "hls", + INSERT_NULL_GLITCH: "in", + LPI_CHANGES_RES: "lpix", + MEMORY_ABOVE: "da", + MEMORY_BELOW: "db", + MOVE_INSERT_MODE: "mir", + MOVE_STANDOUT_MODE: "msgr", + NEEDS_XON_XOFF: "nxon", + NO_ESC_CTLC: "xsb", + NO_PAD_CHAR: "npc", + NON_DEST_SCROLL_REGION: "ndscr", + NON_REV_RMCUP: "nrrmc", + OVER_STRIKE: "os", + PRTR_SILENT: "mc5i", + ROW_ADDR_GLITCH: "xvpa", + SEMI_AUTO_RIGHT_MARGIN: "sam", + STATUS_LINE_ESC_OK: "eslok", + TILDE_GLITCH: "hz", + TRANSPARENT_UNDERLINE: "ul", + XON_XOFF: "xon", + // ============= + // == NUMBERS == + // ============= + COLUMNS: "cols", + INIT_TABS: "it", + LABEL_HEIGHT: "lh", + LABEL_WIDTH: "lw", + LINES: "lines", + LINES_OF_MEMORY: "lm", + MAGIC_COOKIE_GLITCH: "xmc", + MAX_ATTRIBUTES: "ma", + MAX_COLORS: "colors", + MAX_PAIRS: "pairs", + MAXIMUM_WINDOWS: "wnum", + NO_COLOR_VIDEO: "ncv", + NUM_LABELS: "nlab", + PADDING_BAUD_RATE: "pb", + VIRTUAL_TERMINAL: "vt", + WIDTH_STATUS_LINE: "wsl", + BIT_IMAGE_ENTWINING: "bitwin", + BIT_IMAGE_TYPE: "bitype", + BUFFER_CAPACITY: "bufsz", + BUTTONS: "btns", + DOT_HORZ_SPACING: "spinh", + DOT_VERT_SPACING: "spinv", + MAX_MICRO_ADDRESS: "maddr", + MAX_MICRO_JUMP: "mjump", + MICRO_COL_SIZE: "mcs", + MICRO_LINE_SIZE: "mls", + NUMBER_OF_PINS: "npins", + OUTPUT_RES_CHAR: "orc", + OUTPUT_RES_HORZ_INCH: "orhi", + OUTPUT_RES_LINE: "orl", + OUTPUT_RES_VERT_INCH: "orvi", + PRINT_RATE: "cps", + WIDE_CHAR_SIZE: "widcs", + // ============= + // == STRINGS == + // ============= + ACS_CHARS: "acsc", + BACK_TAB: "cbt", + BELL: "bel", + CARRIAGE_RETURN: "cr", + CHANGE_CHAR_PITCH: "cpi", + CHANGE_LINE_PITCH: "lpi", + CHANGE_RES_HORZ: "chr", + CHANGE_RES_VERT: "cvr", + CHANGE_SCROLL_REGION: "csr", + CHAR_PADDING: "rmp", + CLEAR_ALL_TABS: "tbc", + CLEAR_MARGINS: "mgc", + CLEAR_SCREEN: "clear", + CLR_BOL: "el1", + CLR_EOL: "el", + CLR_EOS: "ed", + COLUMN_ADDRESS: "hpa", + COMMAND_CHARACTER: "cmdch", + CREATE_WINDOW: "cwin", + CURSOR_ADDRESS: "cup", + CURSOR_DOWN: "cud1", + CURSOR_HOME: "home", + CURSOR_INVISIBLE: "civis", + CURSOR_LEFT: "cub1", + CURSOR_MEM_ADDRESS: "mrcup", + CURSOR_NORMAL: "cnorm", + CURSOR_RIGHT: "cuf1", + CURSOR_TO_LL: "ll", + CURSOR_UP: "cuu1", + CURSOR_VISIBLE: "cvvis", + DEFINE_CHAR: "defc", + DELETE_CHARACTER: "dch1", + DELETE_LINE: "dl1", + DIAL_PHONE: "dial", + DIS_STATUS_LINE: "dsl", + DISPLAY_CLOCK: "dclk", + DOWN_HALF_LINE: "hd", + ENA_ACS: "enacs", + ENTER_ALT_CHARSET_MODE: "smacs", + ENTER_AM_MODE: "smam", + ENTER_BLINK_MODE: "blink", + ENTER_BOLD_MODE: "bold", + ENTER_CA_MODE: "smcup", + ENTER_DELETE_MODE: "smdc", + ENTER_DIM_MODE: "dim", + ENTER_DOUBLEWIDE_MODE: "swidm", + ENTER_DRAFT_QUALITY: "sdrfq", + ENTER_INSERT_MODE: "smir", + ENTER_ITALICS_MODE: "sitm", + ENTER_LEFTWARD_MODE: "slm", + ENTER_MICRO_MODE: "smicm", + ENTER_NEAR_LETTER_QUALITY: "snlq", + ENTER_NORMAL_QUALITY: "snrmq", + ENTER_PROTECTED_MODE: "prot", + ENTER_REVERSE_MODE: "rev", + ENTER_SECURE_MODE: "invis", + ENTER_SHADOW_MODE: "sshm", + ENTER_STANDOUT_MODE: "smso", + ENTER_SUBSCRIPT_MODE: "ssubm", + ENTER_SUPERSCRIPT_MODE: "ssupm", + ENTER_UNDERLINE_MODE: "smul", + ENTER_UPWARD_MODE: "sum", + ENTER_XON_MODE: "smxon", + ERASE_CHARS: "ech", + EXIT_ALT_CHARSET_MODE: "rmacs", + EXIT_AM_MODE: "rmam", + EXIT_ATTRIBUTE_MODE: "sgr0", + EXIT_CA_MODE: "rmcup", + EXIT_DELETE_MODE: "rmdc", + EXIT_DOUBLEWIDE_MODE: "rwidm", + EXIT_INSERT_MODE: "rmir", + EXIT_ITALICS_MODE: "ritm", + EXIT_LEFTWARD_MODE: "rlm", + EXIT_MICRO_MODE: "rmicm", + EXIT_SHADOW_MODE: "rshm", + EXIT_STANDOUT_MODE: "rmso", + EXIT_SUBSCRIPT_MODE: "rsubm", + EXIT_SUPERSCRIPT_MODE: "rsupm", + EXIT_UNDERLINE_MODE: "rmul", + EXIT_UPWARD_MODE: "rum", + EXIT_XON_MODE: "rmxon", + FIXED_PAUSE: "pause", + FLASH_HOOK: "hook", + FLASH_SCREEN: "flash", + FORM_FEED: "ff", + FROM_STATUS_LINE: "fsl", + GOTO_WINDOW: "wingo", + HANGUP: "hup", + INIT_1STRING: "is1", + INIT_2STRING: "is2", + INIT_3STRING: "is3", + INIT_FILE: "if", + INIT_PROG: "iprog", + INITIALIZE_COLOR: "initc", + INITIALIZE_PAIR: "initp", + INSERT_CHARACTER: "ich1", + INSERT_LINE: "il1", + INSERT_PADDING: "ip", + KEY_A1: "ka1", + KEY_A3: "ka3", + KEY_B2: "kb2", + KEY_BACKSPACE: "kbs", + KEY_BEG: "kbeg", + KEY_BTAB: "kcbt", + KEY_C1: "kc1", + KEY_C3: "kc3", + KEY_CANCEL: "kcan", + KEY_CATAB: "ktbc", + KEY_CLEAR: "kclr", + KEY_CLOSE: "kclo", + KEY_COMMAND: "kcmd", + KEY_COPY: "kcpy", + KEY_CREATE: "kcrt", + KEY_CTAB: "kctab", + KEY_DC: "kdch1", + KEY_DL: "kdl1", + KEY_DOWN: "kcud1", + KEY_EIC: "krmir", + KEY_END: "kend", + KEY_ENTER: "kent", + KEY_EOL: "kel", + KEY_EOS: "ked", + KEY_EXIT: "kext", + KEY_F0: "kf0", + KEY_F1: "kf1", + KEY_F10: "kf10", + KEY_F11: "kf11", + KEY_F12: "kf12", + KEY_F13: "kf13", + KEY_F14: "kf14", + KEY_F15: "kf15", + KEY_F16: "kf16", + KEY_F17: "kf17", + KEY_F18: "kf18", + KEY_F19: "kf19", + KEY_F2: "kf2", + KEY_F20: "kf20", + KEY_F21: "kf21", + KEY_F22: "kf22", + KEY_F23: "kf23", + KEY_F24: "kf24", + KEY_F25: "kf25", + KEY_F26: "kf26", + KEY_F27: "kf27", + KEY_F28: "kf28", + KEY_F29: "kf29", + KEY_F3: "kf3", + KEY_F30: "kf30", + KEY_F31: "kf31", + KEY_F32: "kf32", + KEY_F33: "kf33", + KEY_F34: "kf34", + KEY_F35: "kf35", + KEY_F36: "kf36", + KEY_F37: "kf37", + KEY_F38: "kf38", + KEY_F39: "kf39", + KEY_F4: "kf4", + KEY_F40: "kf40", + KEY_F41: "kf41", + KEY_F42: "kf42", + KEY_F43: "kf43", + KEY_F44: "kf44", + KEY_F45: "kf45", + KEY_F46: "kf46", + KEY_F47: "kf47", + KEY_F48: "kf48", + KEY_F49: "kf49", + KEY_F5: "kf5", + KEY_F50: "kf50", + KEY_F51: "kf51", + KEY_F52: "kf52", + KEY_F53: "kf53", + KEY_F54: "kf54", + KEY_F55: "kf55", + KEY_F56: "kf56", + KEY_F57: "kf57", + KEY_F58: "kf58", + KEY_F59: "kf59", + KEY_F6: "kf6", + KEY_F60: "kf60", + KEY_F61: "kf61", + KEY_F62: "kf62", + KEY_F63: "kf63", + KEY_F7: "kf7", + KEY_F8: "kf8", + KEY_F9: "kf9", + KEY_FIND: "kfnd", + KEY_HELP: "khlp", + KEY_HOME: "khome", + KEY_IC: "kich1", + KEY_IL: "kil1", + KEY_LEFT: "kcub1", + KEY_LL: "kll", + KEY_MARK: "kmrk", + KEY_MESSAGE: "kmsg", + KEY_MOVE: "kmov", + KEY_NEXT: "knxt", + KEY_NPAGE: "knp", + KEY_OPEN: "kopn", + KEY_OPTIONS: "kopt", + KEY_PPAGE: "kpp", + KEY_PREVIOUS: "kprv", + KEY_PRINT: "kprt", + KEY_REDO: "krdo", + KEY_REFERENCE: "kref", + KEY_REFRESH: "krfr", + KEY_REPLACE: "krpl", + KEY_RESTART: "krst", + KEY_RESUME: "kres", + KEY_RIGHT: "kcuf1", + KEY_SAVE: "ksav", + KEY_SBEG: "kBEG", + KEY_SCANCEL: "kCAN", + KEY_SCOMMAND: "kCMD", + KEY_SCOPY: "kCPY", + KEY_SCREATE: "kCRT", + KEY_SDC: "kDC", + KEY_SDL: "kDL", + KEY_SELECT: "kslt", + KEY_SEND: "kEND", + KEY_SEOL: "kEOL", + KEY_SEXIT: "kEXT", + KEY_SF: "kind", + KEY_SFIND: "kFND", + KEY_SHELP: "kHLP", + KEY_SHOME: "kHOM", + KEY_SIC: "kIC", + KEY_SLEFT: "kLFT", + KEY_SMESSAGE: "kMSG", + KEY_SMOVE: "kMOV", + KEY_SNEXT: "kNXT", + KEY_SOPTIONS: "kOPT", + KEY_SPREVIOUS: "kPRV", + KEY_SPRINT: "kPRT", + KEY_SR: "kri", + KEY_SREDO: "kRDO", + KEY_SREPLACE: "kRPL", + KEY_SRIGHT: "kRIT", + KEY_SRSUME: "kRES", + KEY_SSAVE: "kSAV", + KEY_SSUSPEND: "kSPD", + KEY_STAB: "khts", + KEY_SUNDO: "kUND", + KEY_SUSPEND: "kspd", + KEY_UNDO: "kund", + KEY_UP: "kcuu1", + KEYPAD_LOCAL: "rmkx", + KEYPAD_XMIT: "smkx", + LAB_F0: "lf0", + LAB_F1: "lf1", + LAB_F10: "lf10", + LAB_F2: "lf2", + LAB_F3: "lf3", + LAB_F4: "lf4", + LAB_F5: "lf5", + LAB_F6: "lf6", + LAB_F7: "lf7", + LAB_F8: "lf8", + LAB_F9: "lf9", + LABEL_FORMAT: "fln", + LABEL_OFF: "rmln", + LABEL_ON: "smln", + META_OFF: "rmm", + META_ON: "smm", + MICRO_COLUMN_ADDRESS: "mhpa", + MICRO_DOWN: "mcud1", + MICRO_LEFT: "mcub1", + MICRO_RIGHT: "mcuf1", + MICRO_ROW_ADDRESS: "mvpa", + MICRO_UP: "mcuu1", + NEWLINE: "nel", + ORDER_OF_PINS: "porder", + ORIG_COLORS: "oc", + ORIG_PAIR: "op", + PAD_CHAR: "pad", + PARM_DCH: "dch", + PARM_DELETE_LINE: "dl", + PARM_DOWN_CURSOR: "cud", + PARM_DOWN_MICRO: "mcud", + PARM_ICH: "ich", + PARM_INDEX: "indn", + PARM_INSERT_LINE: "il", + PARM_LEFT_CURSOR: "cub", + PARM_LEFT_MICRO: "mcub", + PARM_RIGHT_CURSOR: "cuf", + PARM_RIGHT_MICRO: "mcuf", + PARM_RINDEX: "rin", + PARM_UP_CURSOR: "cuu", + PARM_UP_MICRO: "mcuu", + PKEY_KEY: "pfkey", + PKEY_LOCAL: "pfloc", + PKEY_XMIT: "pfx", + PLAB_NORM: "pln", + PRINT_SCREEN: "mc0", + PRTR_NON: "mc5p", + PRTR_OFF: "mc4", + PRTR_ON: "mc5", + PULSE: "pulse", + QUICK_DIAL: "qdial", + REMOVE_CLOCK: "rmclk", + REPEAT_CHAR: "rep", + REQ_FOR_INPUT: "rfi", + RESET_1STRING: "rs1", + RESET_2STRING: "rs2", + RESET_3STRING: "rs3", + RESET_FILE: "rf", + RESTORE_CURSOR: "rc", + ROW_ADDRESS: "vpa", + SAVE_CURSOR: "sc", + SCROLL_FORWARD: "ind", + SCROLL_REVERSE: "ri", + SELECT_CHAR_SET: "scs", + SET_ATTRIBUTES: "sgr", + SET_BACKGROUND: "setb", + SET_BOTTOM_MARGIN: "smgb", + SET_BOTTOM_MARGIN_PARM: "smgbp", + SET_CLOCK: "sclk", + SET_COLOR_PAIR: "scp", + SET_FOREGROUND: "setf", + SET_LEFT_MARGIN: "smgl", + SET_LEFT_MARGIN_PARM: "smglp", + SET_RIGHT_MARGIN: "smgr", + SET_RIGHT_MARGIN_PARM: "smgrp", + SET_TAB: "hts", + SET_TOP_MARGIN: "smgt", + SET_TOP_MARGIN_PARM: "smgtp", + SET_WINDOW: "wind", + START_BIT_IMAGE: "sbim", + START_CHAR_SET_DEF: "scsd", + STOP_BIT_IMAGE: "rbim", + STOP_CHAR_SET_DEF: "rcsd", + SUBSCRIPT_CHARACTERS: "subcs", + SUPERSCRIPT_CHARACTERS: "supcs", + TAB: "ht", + THESE_CAUSE_CR: "docr", + TO_STATUS_LINE: "tsl", + TONE: "tone", + UNDERLINE_CHAR: "uc", + UP_HALF_LINE: "hu", + USER0: "u0", + USER1: "u1", + USER2: "u2", + USER3: "u3", + USER4: "u4", + USER5: "u5", + USER6: "u6", + USER7: "u7", + USER8: "u8", + USER9: "u9", + WAIT_TONE: "wait", + XOFF_CHARACTER: "xoffc", + XON_CHARACTER: "xonc", + ZERO_MOTION: "zerom", + ALT_SCANCODE_ESC: "scesa", + BIT_IMAGE_CARRIAGE_RETURN: "bicr", + BIT_IMAGE_NEWLINE: "binel", + BIT_IMAGE_REPEAT: "birep", + CHAR_SET_NAMES: "csnm", + CODE_SET_INIT: "csin", + COLOR_NAMES: "colornm", + DEFINE_BIT_IMAGE_REGION: "defbi", + DEVICE_TYPE: "devt", + DISPLAY_PC_CHAR: "dispc", + END_BIT_IMAGE_REGION: "endbi", + ENTER_PC_CHARSET_MODE: "smpch", + ENTER_SCANCODE_MODE: "smsc", + EXIT_PC_CHARSET_MODE: "rmpch", + EXIT_SCANCODE_MODE: "rmsc", + GET_MOUSE: "getm", + KEY_MOUSE: "kmous", + MOUSE_INFO: "minfo", + PC_TERM_OPTIONS: "pctrm", + PKEY_PLAB: "pfxl", + REQ_MOUSE_POS: "reqmp", + SCANCODE_ESCAPE: "scesc", + SET0_DES_SEQ: "s0ds", + SET1_DES_SEQ: "s1ds", + SET2_DES_SEQ: "s2ds", + SET3_DES_SEQ: "s3ds", + SET_A_BACKGROUND: "setab", + SET_A_FOREGROUND: "setaf", + SET_COLOR_BAND: "setcolor", + SET_LR_MARGIN: "smglr", + SET_PAGE_LENGTH: "slines", + SET_TB_MARGIN: "smgtb", + ENTER_HORIZONTAL_HL_MODE: "ehhlm", + ENTER_LEFT_HL_MODE: "elhlm", + ENTER_LOW_HL_MODE: "elohlm", + ENTER_RIGHT_HL_MODE: "erhlm", + ENTER_TOP_HL_MODE: "ethlm", + ENTER_VERTICAL_HL_MODE: "evhlm", + SET_A_ATTRIBUTES: "sgr1", + SET_PGLEN_INCH: "slength", +}; + +/* + * the order of the variables are from the header file + */ + +export const VARORDER: Record<"booleans" | "numbers" | "strings", string[]> = { + booleans: [ + "AUTO_LEFT_MARGIN", + "AUTO_RIGHT_MARGIN", + "NO_ESC_CTLC", + "CEOL_STANDOUT_GLITCH", + "EAT_NEWLINE_GLITCH", + "ERASE_OVERSTRIKE", + "GENERIC_TYPE", + "HARD_COPY", + "HAS_META_KEY", + "HAS_STATUS_LINE", + "INSERT_NULL_GLITCH", + "MEMORY_ABOVE", + "MEMORY_BELOW", + "MOVE_INSERT_MODE", + "MOVE_STANDOUT_MODE", + "OVER_STRIKE", + "STATUS_LINE_ESC_OK", + "DEST_TABS_MAGIC_SMSO", + "TILDE_GLITCH", + "TRANSPARENT_UNDERLINE", + "XON_XOFF", + "NEEDS_XON_XOFF", + "PRTR_SILENT", + "HARD_CURSOR", + "NON_REV_RMCUP", + "NO_PAD_CHAR", + "NON_DEST_SCROLL_REGION", + "CAN_CHANGE", + "BACK_COLOR_ERASE", + "HUE_LIGHTNESS_SATURATION", + "COL_ADDR_GLITCH", + "CR_CANCELS_MICRO_MODE", + "HAS_PRINT_WHEEL", + "ROW_ADDR_GLITCH", + "SEMI_AUTO_RIGHT_MARGIN", + "CPI_CHANGES_RES", + "LPI_CHANGES_RES", + ], + numbers: [ + "COLUMNS", + "INIT_TABS", + "LINES", + "LINES_OF_MEMORY", + "MAGIC_COOKIE_GLITCH", + "PADDING_BAUD_RATE", + "VIRTUAL_TERMINAL", + "WIDTH_STATUS_LINE", + "NUM_LABELS", + "LABEL_HEIGHT", + "LABEL_WIDTH", + "MAX_ATTRIBUTES", + "MAXIMUM_WINDOWS", + "MAX_COLORS", + "MAX_PAIRS", + "NO_COLOR_VIDEO", + "BUFFER_CAPACITY", + "DOT_VERT_SPACING", + "DOT_HORZ_SPACING", + "MAX_MICRO_ADDRESS", + "MAX_MICRO_JUMP", + "MICRO_COL_SIZE", + "MICRO_LINE_SIZE", + "NUMBER_OF_PINS", + "OUTPUT_RES_CHAR", + "OUTPUT_RES_LINE", + "OUTPUT_RES_HORZ_INCH", + "OUTPUT_RES_VERT_INCH", + "PRINT_RATE", + "WIDE_CHAR_SIZE", + "BUTTONS", + "BIT_IMAGE_ENTWINING", + "BIT_IMAGE_TYPE", + ], + strings: [ + "BACK_TAB", + "BELL", + "CARRIAGE_RETURN", + "CHANGE_SCROLL_REGION", + "CLEAR_ALL_TABS", + "CLEAR_SCREEN", + "CLR_EOL", + "CLR_EOS", + "COLUMN_ADDRESS", + "COMMAND_CHARACTER", + "CURSOR_ADDRESS", + "CURSOR_DOWN", + "CURSOR_HOME", + "CURSOR_INVISIBLE", + "CURSOR_LEFT", + "CURSOR_MEM_ADDRESS", + "CURSOR_NORMAL", + "CURSOR_RIGHT", + "CURSOR_TO_LL", + "CURSOR_UP", + "CURSOR_VISIBLE", + "DELETE_CHARACTER", + "DELETE_LINE", + "DIS_STATUS_LINE", + "DOWN_HALF_LINE", + "ENTER_ALT_CHARSET_MODE", + "ENTER_BLINK_MODE", + "ENTER_BOLD_MODE", + "ENTER_CA_MODE", + "ENTER_DELETE_MODE", + "ENTER_DIM_MODE", + "ENTER_INSERT_MODE", + "ENTER_SECURE_MODE", + "ENTER_PROTECTED_MODE", + "ENTER_REVERSE_MODE", + "ENTER_STANDOUT_MODE", + "ENTER_UNDERLINE_MODE", + "ERASE_CHARS", + "EXIT_ALT_CHARSET_MODE", + "EXIT_ATTRIBUTE_MODE", + "EXIT_CA_MODE", + "EXIT_DELETE_MODE", + "EXIT_INSERT_MODE", + "EXIT_STANDOUT_MODE", + "EXIT_UNDERLINE_MODE", + "FLASH_SCREEN", + "FORM_FEED", + "FROM_STATUS_LINE", + "INIT_1STRING", + "INIT_2STRING", + "INIT_3STRING", + "INIT_FILE", + "INSERT_CHARACTER", + "INSERT_LINE", + "INSERT_PADDING", + "KEY_BACKSPACE", + "KEY_CATAB", + "KEY_CLEAR", + "KEY_CTAB", + "KEY_DC", + "KEY_DL", + "KEY_DOWN", + "KEY_EIC", + "KEY_EOL", + "KEY_EOS", + "KEY_F0", + "KEY_F1", + "KEY_F10", + "KEY_F2", + "KEY_F3", + "KEY_F4", + "KEY_F5", + "KEY_F6", + "KEY_F7", + "KEY_F8", + "KEY_F9", + "KEY_HOME", + "KEY_IC", + "KEY_IL", + "KEY_LEFT", + "KEY_LL", + "KEY_NPAGE", + "KEY_PPAGE", + "KEY_RIGHT", + "KEY_SF", + "KEY_SR", + "KEY_STAB", + "KEY_UP", + "KEYPAD_LOCAL", + "KEYPAD_XMIT", + "LAB_F0", + "LAB_F1", + "LAB_F10", + "LAB_F2", + "LAB_F3", + "LAB_F4", + "LAB_F5", + "LAB_F6", + "LAB_F7", + "LAB_F8", + "LAB_F9", + "META_OFF", + "META_ON", + "NEWLINE", + "PAD_CHAR", + "PARM_DCH", + "PARM_DELETE_LINE", + "PARM_DOWN_CURSOR", + "PARM_ICH", + "PARM_INDEX", + "PARM_INSERT_LINE", + "PARM_LEFT_CURSOR", + "PARM_RIGHT_CURSOR", + "PARM_RINDEX", + "PARM_UP_CURSOR", + "PKEY_KEY", + "PKEY_LOCAL", + "PKEY_XMIT", + "PRINT_SCREEN", + "PRTR_OFF", + "PRTR_ON", + "REPEAT_CHAR", + "RESET_1STRING", + "RESET_2STRING", + "RESET_3STRING", + "RESET_FILE", + "RESTORE_CURSOR", + "ROW_ADDRESS", + "SAVE_CURSOR", + "SCROLL_FORWARD", + "SCROLL_REVERSE", + "SET_ATTRIBUTES", + "SET_TAB", + "SET_WINDOW", + "TAB", + "TO_STATUS_LINE", + "UNDERLINE_CHAR", + "UP_HALF_LINE", + "INIT_PROG", + "KEY_A1", + "KEY_A3", + "KEY_B2", + "KEY_C1", + "KEY_C3", + "PRTR_NON", + "CHAR_PADDING", + "ACS_CHARS", + "PLAB_NORM", + "KEY_BTAB", + "ENTER_XON_MODE", + "EXIT_XON_MODE", + "ENTER_AM_MODE", + "EXIT_AM_MODE", + "XON_CHARACTER", + "XOFF_CHARACTER", + "ENA_ACS", + "LABEL_ON", + "LABEL_OFF", + "KEY_BEG", + "KEY_CANCEL", + "KEY_CLOSE", + "KEY_COMMAND", + "KEY_COPY", + "KEY_CREATE", + "KEY_END", + "KEY_ENTER", + "KEY_EXIT", + "KEY_FIND", + "KEY_HELP", + "KEY_MARK", + "KEY_MESSAGE", + "KEY_MOVE", + "KEY_NEXT", + "KEY_OPEN", + "KEY_OPTIONS", + "KEY_PREVIOUS", + "KEY_PRINT", + "KEY_REDO", + "KEY_REFERENCE", + "KEY_REFRESH", + "KEY_REPLACE", + "KEY_RESTART", + "KEY_RESUME", + "KEY_SAVE", + "KEY_SUSPEND", + "KEY_UNDO", + "KEY_SBEG", + "KEY_SCANCEL", + "KEY_SCOMMAND", + "KEY_SCOPY", + "KEY_SCREATE", + "KEY_SDC", + "KEY_SDL", + "KEY_SELECT", + "KEY_SEND", + "KEY_SEOL", + "KEY_SEXIT", + "KEY_SFIND", + "KEY_SHELP", + "KEY_SHOME", + "KEY_SIC", + "KEY_SLEFT", + "KEY_SMESSAGE", + "KEY_SMOVE", + "KEY_SNEXT", + "KEY_SOPTIONS", + "KEY_SPREVIOUS", + "KEY_SPRINT", + "KEY_SREDO", + "KEY_SREPLACE", + "KEY_SRIGHT", + "KEY_SRSUME", + "KEY_SSAVE", + "KEY_SSUSPEND", + "KEY_SUNDO", + "REQ_FOR_INPUT", + "KEY_F11", + "KEY_F12", + "KEY_F13", + "KEY_F14", + "KEY_F15", + "KEY_F16", + "KEY_F17", + "KEY_F18", + "KEY_F19", + "KEY_F20", + "KEY_F21", + "KEY_F22", + "KEY_F23", + "KEY_F24", + "KEY_F25", + "KEY_F26", + "KEY_F27", + "KEY_F28", + "KEY_F29", + "KEY_F30", + "KEY_F31", + "KEY_F32", + "KEY_F33", + "KEY_F34", + "KEY_F35", + "KEY_F36", + "KEY_F37", + "KEY_F38", + "KEY_F39", + "KEY_F40", + "KEY_F41", + "KEY_F42", + "KEY_F43", + "KEY_F44", + "KEY_F45", + "KEY_F46", + "KEY_F47", + "KEY_F48", + "KEY_F49", + "KEY_F50", + "KEY_F51", + "KEY_F52", + "KEY_F53", + "KEY_F54", + "KEY_F55", + "KEY_F56", + "KEY_F57", + "KEY_F58", + "KEY_F59", + "KEY_F60", + "KEY_F61", + "KEY_F62", + "KEY_F63", + "CLR_BOL", + "CLEAR_MARGINS", + "SET_LEFT_MARGIN", + "SET_RIGHT_MARGIN", + "LABEL_FORMAT", + "SET_CLOCK", + "DISPLAY_CLOCK", + "REMOVE_CLOCK", + "CREATE_WINDOW", + "GOTO_WINDOW", + "HANGUP", + "DIAL_PHONE", + "QUICK_DIAL", + "TONE", + "PULSE", + "FLASH_HOOK", + "FIXED_PAUSE", + "WAIT_TONE", + "USER0", + "USER1", + "USER2", + "USER3", + "USER4", + "USER5", + "USER6", + "USER7", + "USER8", + "USER9", + "ORIG_PAIR", + "ORIG_COLORS", + "INITIALIZE_COLOR", + "INITIALIZE_PAIR", + "SET_COLOR_PAIR", + "SET_FOREGROUND", + "SET_BACKGROUND", + "CHANGE_CHAR_PITCH", + "CHANGE_LINE_PITCH", + "CHANGE_RES_HORZ", + "CHANGE_RES_VERT", + "DEFINE_CHAR", + "ENTER_DOUBLEWIDE_MODE", + "ENTER_DRAFT_QUALITY", + "ENTER_ITALICS_MODE", + "ENTER_LEFTWARD_MODE", + "ENTER_MICRO_MODE", + "ENTER_NEAR_LETTER_QUALITY", + "ENTER_NORMAL_QUALITY", + "ENTER_SHADOW_MODE", + "ENTER_SUBSCRIPT_MODE", + "ENTER_SUPERSCRIPT_MODE", + "ENTER_UPWARD_MODE", + "EXIT_DOUBLEWIDE_MODE", + "EXIT_ITALICS_MODE", + "EXIT_LEFTWARD_MODE", + "EXIT_MICRO_MODE", + "EXIT_SHADOW_MODE", + "EXIT_SUBSCRIPT_MODE", + "EXIT_SUPERSCRIPT_MODE", + "EXIT_UPWARD_MODE", + "MICRO_COLUMN_ADDRESS", + "MICRO_DOWN", + "MICRO_LEFT", + "MICRO_RIGHT", + "MICRO_ROW_ADDRESS", + "MICRO_UP", + "ORDER_OF_PINS", + "PARM_DOWN_MICRO", + "PARM_LEFT_MICRO", + "PARM_RIGHT_MICRO", + "PARM_UP_MICRO", + "SELECT_CHAR_SET", + "SET_BOTTOM_MARGIN", + "SET_BOTTOM_MARGIN_PARM", + "SET_LEFT_MARGIN_PARM", + "SET_RIGHT_MARGIN_PARM", + "SET_TOP_MARGIN", + "SET_TOP_MARGIN_PARM", + "START_BIT_IMAGE", + "START_CHAR_SET_DEF", + "STOP_BIT_IMAGE", + "STOP_CHAR_SET_DEF", + "SUBSCRIPT_CHARACTERS", + "SUPERSCRIPT_CHARACTERS", + "THESE_CAUSE_CR", + "ZERO_MOTION", + "CHAR_SET_NAMES", + "KEY_MOUSE", + "MOUSE_INFO", + "REQ_MOUSE_POS", + "GET_MOUSE", + "SET_A_FOREGROUND", + "SET_A_BACKGROUND", + "PKEY_PLAB", + "DEVICE_TYPE", + "CODE_SET_INIT", + "SET0_DES_SEQ", + "SET1_DES_SEQ", + "SET2_DES_SEQ", + "SET3_DES_SEQ", + "SET_LR_MARGIN", + "SET_TB_MARGIN", + "BIT_IMAGE_REPEAT", + "BIT_IMAGE_NEWLINE", + "BIT_IMAGE_CARRIAGE_RETURN", + "COLOR_NAMES", + "DEFINE_BIT_IMAGE_REGION", + "END_BIT_IMAGE_REGION", + "SET_COLOR_BAND", + "SET_PAGE_LENGTH", + "DISPLAY_PC_CHAR", + "ENTER_PC_CHARSET_MODE", + "EXIT_PC_CHARSET_MODE", + "ENTER_SCANCODE_MODE", + "EXIT_SCANCODE_MODE", + "PC_TERM_OPTIONS", + "SCANCODE_ESCAPE", + "ALT_SCANCODE_ESC", + "ENTER_HORIZONTAL_HL_MODE", + "ENTER_LEFT_HL_MODE", + "ENTER_LOW_HL_MODE", + "ENTER_RIGHT_HL_MODE", + "ENTER_TOP_HL_MODE", + "ENTER_VERTICAL_HL_MODE", + "SET_A_ATTRIBUTES", + "SET_PGLEN_INCH", + ], +}; diff --git a/src/util/util/os-utils/TermInfo/TermInfo.ts b/src/util/util/os-utils/TermInfo/TermInfo.ts new file mode 100644 index 000000000..bc669960d --- /dev/null +++ b/src/util/util/os-utils/TermInfo/TermInfo.ts @@ -0,0 +1,50 @@ +// SOURCE: https://github.com/eistaa/parse-terminfo +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +import { openTerminfoBuffer } from "./openTerminfoBuffer"; +import { parseTerminfo, TermInfo } from "./parseTerminfo"; + +export class ParseOptions { + term?: string; + directories?: string[]; + debug: boolean = false; +} + +export function parse(opts?: ParseOptions): TermInfo { + let term; + + if (process.platform === "win32") + throw new Error("no terminfo for windows..."); + + // get term + if (opts?.term) { + term = String(opts.term); + } else { + if (process.env.TERM && process.env.TERM !== "") { + term = process.env.TERM; + } else { + throw new Error( + "No terminal specified (`opts.term`) and TERM is undefined", + ); + } + } + + if(opts?.debug) console.log("Parsing terminfo for", term); + + const bufferData = openTerminfoBuffer(term, opts); + const capabilities = parseTerminfo(bufferData.buffer, term, opts); + capabilities.path = bufferData.path; + + if(opts?.debug) console.log("Parsed terminfo for", term, ":", capabilities); + + return capabilities; +} + +export default { + VARIABLES: require("./Constants").ALL_VARS, + parse +}; diff --git a/src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts b/src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts new file mode 100644 index 000000000..6e71510cb --- /dev/null +++ b/src/util/util/os-utils/TermInfo/openTerminfoBuffer.ts @@ -0,0 +1,104 @@ +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +import fs from "fs"; +import path from "path"; +import { ParseOptions } from "./TermInfo"; + +const DEFAULT_DB_DIRECTORIES = [ + "/etc/terminfo", + "/lib/terminfo", + "/usr/share/terminfo", +]; + +function isDirectory(directory: string) { + try { + return fs.statSync(path.normalize(directory.trim())).isDirectory(); + } catch (err) { + return false; + } +} + +function constructDBDirectories(dirs?: string[] | string) { + /* + * the ordering comes from manpage 'terminfo(5)' + */ + + const directories: string[] = []; + + // argument can be array or string + if (dirs) { + if (Array.isArray(dirs)) { + dirs.filter(isDirectory).forEach((dir) => directories.push(dir)); + } else { + if (isDirectory(dirs)) directories.push(dirs); + } + } + + // TERMINFO may exist + if (process.env.TERMINFO && isDirectory(process.env.TERMINFO)) + directories.push(process.env.TERMINFO); + + // there may be a local terminfo directory + if ( + process.env.HOME && + isDirectory(path.normalize(path.join(process.env.HOME, ".terminfo"))) + ) + directories.push(path.join(process.env.HOME, ".terminfo")); + + // TERMINFO_DIRS can contain a :-separated list of directories + if (process.env.TERMINFO_DIRS) { + const terminfoDirectories = process.env.TERMINFO_DIRS.split(":"); + terminfoDirectories + .filter(isDirectory) + .forEach((dir) => directories.push(dir)); + } + + // default to hardcoded directories + DEFAULT_DB_DIRECTORIES.filter(isDirectory).forEach((dir) => + directories.push(dir), + ); + + return directories; +} + +export function openTerminfoBuffer( + term: string, + opts: ParseOptions | undefined, +) { + // determine directories + const directories = constructDBDirectories(opts?.directories); + + if (opts?.debug) console.log("Directories:", directories); + + let filepath; + + if (directories.length === 0) + throw new Error("No terminfo database directories exist"); + + // use first valid directory + for (let i = 0; i < directories.length; i++) { + try { + filepath = path.join(directories[i], term.charAt(0), term); + if (fs.statSync(filepath).isFile()) { + if (opts?.debug) + console.log("Found terminfo data at", filepath); + break; + } + } catch (err) { + filepath = undefined; + } + } + + if (filepath === undefined) + throw new Error("Found no terminfo database for " + term); + + // read to buffer + return { + path: filepath, + buffer: fs.readFileSync(filepath), + }; +} diff --git a/src/util/util/os-utils/TermInfo/parseTerminfo.ts b/src/util/util/os-utils/TermInfo/parseTerminfo.ts new file mode 100644 index 000000000..71a4b933b --- /dev/null +++ b/src/util/util/os-utils/TermInfo/parseTerminfo.ts @@ -0,0 +1,159 @@ +"use strict"; + +/* ========================================================================= + * Copyright (c) 2016 Eivind Storm Aarnæs + * Licensed under the MIT license + * (see https://github.com/eistaa/parse-terminfo/blob/master/LICENSE) + * ========================================================================= */ + +import { ALL_VARS, VARORDER } from "./Constants"; + +/* + * based of on the format description in the term(5) manual page. + */ + +export class TermInfo { + description?: string; + term?: string[]; + capabilities: { + booleans: Record; + numbers: Record; + strings: Record; + }; + path: string; + integerSize: number; +} + +export function parseTerminfo( + buffer: Buffer, + term: string, + opts?: { debug: boolean }, +): TermInfo { + let offset = 0; + + function readInt() { + const result = buffer.readInt16LE(offset); + console.log("Read int @", offset, ":", result); + offset += 2; + return result; + } + + /// @type {{ description: string | undefined; term: string[] | undefined; capabilities: { booleans: {}; numbers: {}; strings: {}; }; }} + const result: TermInfo = { + capabilities: { + booleans: {}, + numbers: {}, + strings: {}, + }, + path: "", + description: undefined, + term: undefined, + integerSize: 0, + }; + + // check the magic number + const magic = readInt(); + + // if (magic !== 0x011a) + // throw new Error("invalid magic number in buffer for " + term + ": " + magic.toString(16)); + switch (magic) { + case 0x011a: + result.integerSize = 16; + break; + case 0x21e: + result.integerSize = 32; + break; + default: + throw new Error( + "invalid magic number in buffer for " + + term + + ": " + + magic.toString(16), + ); + } + + if (opts?.debug) console.log("Magic number:", magic, "Integer size:", result.integerSize); + + //offset += 2; + + // parse section sizes + const sizes = { + names: readInt(), + booleans: readInt(), + numbers: readInt(), + strings: readInt(), + table: readInt(), + }; + + if (opts?.debug) console.log("Section sizes:", sizes); + + //offset += 10; + + // parse names section + const names = buffer + .toString("ascii", offset, offset + sizes.names - 1) + .split("|"); + result.term = names[0].split("|"); + result.description = names[1]; + if (opts?.debug) + console.log("Got info:", { + term: result.term, + description: result.description, + }); + offset += sizes.names; + + // parse booleans + let boolean; + const numBools = Math.min(VARORDER.booleans.length, sizes.booleans); + for (let i = 0; i < numBools; i++) { + if (i >= VARORDER.booleans.length) { + if (opts?.debug) console.log("Read boolean overran length"); + continue; + } // doesn't (yet) support extended terminfo + + const data = buffer.readInt8(offset + i); + if (opts?.debug && data != 0 && data != 1) + console.log("Invalid boolean data:", data.toString(16)); + + boolean = !!data; + if (boolean) + result.capabilities.booleans[ALL_VARS[VARORDER.booleans[i]]] = true; + } + offset += sizes.booleans + ((offset + sizes.booleans) % 2); // padded to short boundary + + // parse numbers + let number; + const numNumbers = Math.min(VARORDER.numbers.length, sizes.numbers); + for (let i = 0; i < numNumbers; i++) { + if (i >= VARORDER.numbers.length) continue; // doesn't (yet) support extended terminfo + + number = buffer.readInt16LE(offset + 2 * i); + if (number !== -1) + result.capabilities.numbers[ALL_VARS[VARORDER.numbers[i]]] = number; + } + offset += 2 * sizes.numbers; + if (opts?.debug) + console.log("Read numbers up to", offset, ":", result.capabilities.numbers); + + // parse strings + let tableOffset, valueEnd; + const tableStart = offset + 2 * sizes.strings; + const numStrings = Math.min(VARORDER.strings.length, sizes.strings); + for (let i = 0; i < numStrings; i++) { + if (i >= VARORDER.strings.length) continue; // doesn't (yet) support extended terminfo + + tableOffset = buffer.readInt16LE(offset + 2 * i); + if (tableOffset !== -1) { + valueEnd = tableStart + tableOffset; + while (buffer[valueEnd++] !== 0); // string values are null terminated + result.capabilities.strings[ALL_VARS[VARORDER.strings[i]]] = + buffer.toString( + "ascii", + tableStart + tableOffset, + valueEnd - 1, + ); + } + } + + return result; +}