You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
4.3 KiB
TypeScript

4 years ago
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import { EventEmitter } from "events";
import { promisify } from "util";
import simpleSpawn from "./simple-spawn";
import sleep from "./sleep";
export type DialogOptions = Record<
string,
true | string | undefined | string[]
>; // array value means use this flag this many times
let last_position: { x: number; y: number } | null = null;
async function get_geometry() {
const result = (await simpleSpawn("bash", [
"-c",
'wmctrl -l -x node-zenity -G | grep node-zenity | awk \'{print $3 " " $4 " " $5 " " $6}\'',
])) as string;
const [x, y, width, height] = result
.split("\n")[0]
.split(" ")
.map((s) => parseInt(s));
return { x, y, width, height };
}
async function set_geometry({
x,
y,
width,
height,
}: {
x: number;
y: number;
width?: number;
height?: number;
}) {
if (width === undefined) {
width = -1;
}
if (height === undefined) {
height = -1;
}
await simpleSpawn("wmctrl", [
"-x",
"-a",
"node-zenity",
"-e",
`0,${Math.round(x)},${Math.round(y)},${Math.round(width)},${Math.round(
height
)}`,
]);
}
type DialogResponse = {
code: number;
output: string;
error: string;
};
export default class Dialog extends EventEmitter {
shown = false;
process: ChildProcessWithoutNullStreams;
output: string = "";
error: string = "";
constructor(
public type_option: string,
public args: DialogOptions,
public positional_args: string[] = []
) {
super();
}
private prepareArguments(): string[] {
const ret = [`--${this.type_option}`];
for (const arg in this.args) {
const value = this.args[arg];
if (typeof value === "boolean") {
ret.push(`--${arg}`);
} else if (typeof value === "string") {
ret.push(`--${arg}=${value}`);
} else if (Array.isArray(value)) {
value.forEach((element) => ret.push(`--${arg}=${element}`));
} else if (value === undefined) {
//void
} else {
console.log("unknown value:", value);
throw new Error("uknown arg value type");
}
}
return ret;
}
async show(): Promise<void> {
const args: string[] = [
"--class=node-zenity",
...this.prepareArguments(),
...this.positional_args,
];
console.log("spawning process!", args.join(" "));
this.process = spawn("zenity", args);
let position_listener_interval = setInterval(async () => {
const { x, y, width, height } = await get_geometry();
console.log(
{ x, y, width, height },
{ x: Math.round(x + width / 2), y: Math.round(y + height / 2) }
);
last_position = {
x: Math.round(x + width / 2),
y: Math.round(y + height / 2),
};
}, 2000);
let spawned = false;
return new Promise((resolve, reject) => {
this.process.on("spawn", async () => {
spawned = true;
resolve();
let tries = 0;
let succeded = false;
while (!succeded && tries < 15) {
try {
tries++;
await sleep(50);
await simpleSpawn("wmctrl", [
"-x",
"-r",
"node-zenity",
"-b",
"add,above",
]); // to pin to top
await simpleSpawn("wmctrl", [
"-x",
"-a",
"node-zenity",
]); // to activate it
if (last_position) {
const { width, height } = await get_geometry();
await set_geometry({
x: last_position.x - width / 2 - 11,
y: last_position.y - height / 2 - 82,
}); // to put it where it last was
}
succeded = true;
console.log("WMCTRL SUCCEEDED!");
} catch (e) {
console.error(e);
continue;
}
}
});
this.process.stdout.on("data", (chunk) => {
this.output += chunk;
});
this.process.stderr.on("data", (chunk) => {
this.error += chunk;
});
this.process.on("close", (code) => {
clearInterval(position_listener_interval);
console.log("process closed. output:", this.output);
if (code !== 0) {
if (!spawned) {
reject({ code, message: this.error });
} else {
this.emit("error", code);
}
} else {
this.emit("done");
}
});
});
}
getAnswer(): Promise<DialogResponse> {
return new Promise((resolve, reject) => {
this.on("done", () =>
resolve({ code: 0, output: this.output, error: this.error })
);
this.on("error", (code) => {
resolve({ code, output: this.output, error: this.error });
});
});
}
}