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
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 });
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
}
|