@cocalc/project
Version:
CoCalc: project daemon
148 lines (147 loc) • 4.92 kB
JavaScript
;
/*
* 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