UNPKG

@cocalc/project

Version:
148 lines (147 loc) 4.92 kB
"use strict"; /* * This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.x11_channel = exports.get_path_for_pid = void 0; /* X11 server channel. TODO: - [ ] other user activity - [ ] when stopping project, kill xpra's */ const child_process_1 = require("child_process"); const awaiting_1 = require("awaiting"); const abspath_1 = __importDefault(require("@cocalc/backend/misc/abspath")); const misc_1 = require("@cocalc/util/misc"); const underscore_1 = require("underscore"); const x11_channels = {}; // this is used to map a (not necessarily) running process to a path for the "project info" const pid2path = {}; function get_path_for_pid(pid) { return pid2path[pid]; } exports.get_path_for_pid = get_path_for_pid; class X11Channel { constructor({ primus, path, name, logger, display, }) { this.logger = logger; this.log("creating new x11 channel"); this.display = display; // needed for copy/paste support this.path = path; this.name = name; this.channel = primus.channel(this.name); this.init_handlers(); } log(...args) { this.logger.debug(`x11 channel ${this.path} -- `, ...args); } new_connection(spark) { if (this.channel === undefined) { return; } // Now handle the connection this.log(`new connection from ${spark.address.ip} -- ${spark.id}`); spark.on("data", async (data) => { try { await this.handle_data(spark, data); } catch (err) { spark.write({ error: `error handling command -- ${err}` }); } }); } init_handlers() { this.channel.on("connection", this.new_connection.bind(this)); } async handle_data(_, data) { this.log("handle_data ", data); if (typeof data !== "object") { return; // nothing defined yet } switch (data.cmd) { case "paste": await this.paste(data.value, data.wid ? data.wid : 0); break; case "launch": await this.launch(data.command, data.args); break; default: throw Error("WARNING: unknown command -- " + data.cmd); } } async paste(value, wid) { this.log("paste", value, wid); await this.set_clipboard(value); await this.cause_paste(wid); } async set_clipboard(value) { this.log("set_clipboard to string of length", value.length); const p = (0, child_process_1.spawn)("xclip", [ "-selection", "clipboard", "-d", `:${this.display}`, ]); p.stdin.write(value); p.stdin.end(); // wait for exit event. await (0, awaiting_1.callback)((cb) => p.on("exit", cb)); } cause_paste(wid) { this.log("paste to window ", wid); const env = { DISPLAY: `:${this.display}` }; const args = ["key", "Control_L+v"]; if (wid) { args.push("--window"); args.push(`${wid}`); } this.log("xdotool", args); (0, child_process_1.spawn)("xdotool", args, { env }).on("close", (code) => { console.log(`xdotool exited with code ${code}`); }); } // launch a command and detach -- used to start x11 applications running. launch(command, args) { const env = (0, underscore_1.clone)(process.env); env.DISPLAY = `:${this.display}`; const cwd = this.get_cwd(); const options = { cwd, env, detached: true, stdio: "ignore" }; args = args != null ? args : []; try { const sub = (0, child_process_1.spawn)(command, args, options); sub.unref(); pid2path[sub.pid] = this.path; sub.on("exit", () => { delete pid2path[sub.pid]; }); } catch (err) { this.channel.write({ error: `error launching ${command} -- ${err}`, }); return; } } get_cwd() { return (0, misc_1.path_split)((0, abspath_1.default)(this.path)).head; // containing path } } async function x11_channel(_, primus, logger, path, display) { const name = `x11:${path}`; if (x11_channels[name] === undefined) { x11_channels[name] = new X11Channel({ primus, path, name, logger, display, }); } return name; } exports.x11_channel = x11_channel; //# sourceMappingURL=server.js.map