Source: index.js

var child_process = require("child_process");
const path = require("path");
const fs = require("fs");
const { platform } = require("os");

const zenityBin = fs.existsSync(platform()=="win32"?"zenity.exe":"zenity")?platform()=="win32"?".\\zenity.exe":"./zenity":"zenity";

function run(cmd) {
    var out = "";
    try {
        out = child_process.execSync(cmd);
    }catch(err) {
        out = err.stdout;
    }
    return out.toString();
}
/** @returns {Promise<string>} */
function runAsync(cmd) {
    return new Promise(res=>{
        child_process.exec(cmd,(_,out)=>{
            res(out.toString());
        })
    })
}

/**
 * @typedef WindowOptions
 * @type {{
 *      icon?:String,
 *      width?:Number,
 *      height?:Number,
 *      parent?:String
 * }}
 */



class Progress {
    /**
     * 
     * @param {String} args 
     */
    constructor(args) {
        var cmd = [zenityBin,"--progress","--auto-close",...args];
        this._process = child_process.exec(cmd.join(" "));
    }
    /**
     * 
     * @param {Number} percentage 
     */
    progress(percentage) {
        this._process.stdin.write(`${percentage.toFixed(0)}\n`);
    }
}


class Dialog {
    command=[zenityBin]
    /** @type {String[]} */
    returnForms=[]
    /**
     * 
     * @param {String} title 
     * @param {WindowOptions} [options] 
     */
    constructor(title,options) {
        this.command.push(`--title="${title}"`);

        if(options) {
            if(options.height) this.command.push(`--height="${options.height}"`);
            if(options.width) this.command.push(`--width="${options.width}"`);
            if(options.icon) this.command.push(`--window-icon="${options.icon}"`);
            if(options.parent) this.command.push(`--attach="${options.parent}"`);
        }

        this.command.push("--forms");
        this.command.push(`--text="${title}"`);
    }

    /**
     * 
     * @param {String} id 
     * @param {String} [name] 
     * @returns 
     */
    entry(id,name) {
        this.returnForms.push(id);
        this.command.push(`--add-entry="${name||"entry"}"`);
        return this;
    }

    /**
     * 
     * @param {String} id 
     * @param {String} [name] 
     * @returns 
     */
    password(id,name) {
        this.returnForms.push(id);
        this.command.push(`--add-password="${name||"password"}"`);
        return this;
    }
    
    /**
     * 
     * @param {String} id 
     * @param {String} [name] 
     * @returns 
     */
    calendar(id,name) {
        this.returnForms.push(id);
        this.command.push(`--add-calendar="${name||"calendar"}"`);
        return this;
    }
    
    /**
     * Deprecated. Use Dialog.list() [static] if possible because of graphical glitches
     * @param {String} id 
     * @param {String} name 
     * @param {String[]|String[][]} values
     * @param {String[]} [headers]
     * @deprecated 
     * @returns 
     */
    list(id,name,values,headers=null) {
        this.returnForms.push(id);
        this.command.push(`--add-list="${name||"list"}"`);

        var listvals = "";
        var columns = values[values.length-1].join?values[values.length-1].length:null;
        if(columns) {
            listvals = values.map(v=>v.join("|")).join("|")
        }else{
            listvals = values.join("|");
        }
        this.command.push(`--list-values="${listvals}"`);
        if(headers) {
            this.command.push(`--column-values="${headers.join("|")}"`);
            this.command.push("--show-header")
        }else if(columns){
            this.command.push(`--column-values="${new Array(columns).fill("     ").join("|")}"`);
        }
        return this;
    }

    /**
     * 
     * @param {String} id 
     * @param {String} name
     * @param {String[]} values
     * @returns 
     */
    combo(id,name,values) {
        this.returnForms.push(id);
        this.command.push(`--add-combo="${name||"combo"}"`);
        this.command.push(`--combo-values="${values?.join("|")||"combo_value"}"`);
        return this;
    }

    /**
     * Add custom args to the zenity command (Only use if you know what you're doing)
     * @param {String[]} args 
     * @returns 
     */
    customArgs(args) {
        this.command.push(...args);
        return this;
    }

    /**
     * @returns {{[x:String]:String}|null}
     */
    show() {
        var ret = run(this.command.join(" "));

        if(ret.endsWith("\n")) {
            var retobj = {};
            ret.slice(0,ret.length-1).split("|").forEach((value,i)=>{
                var id = this.returnForms[i];
                retobj[id] = value;
            });
            return retobj;
        }else{
            return null;
        }
    }

    /**
     * @returns {Promise<{[x:String]:String}|null>}
     */
    async showAsync() {
        var ret = await runAsync(this.command.join(" "));

        if(ret.endsWith("\n")) {
            var retobj = {};
            ret.slice(0,ret.length-1).split("|").forEach((value,i)=>{
                var id = this.returnForms[i];
                retobj[id] = value;
            });
            return retobj;
        }else{
            return null;
        }
    }



    static notify(msg) {
        runAsync(`${zenityBin} --notification --text="${msg}"`);
    }

    /**
     * 
     * @param {"file"|"directory"} type 
     * @param {boolean} multiple 
     * @param {String} save save file name or null
     */
    static file(type="file",multiple=false,save=null) {
        var cmd = [zenityBin,"--file-selection"];
        if(type=="directory") cmd.push("--directory");
        if(multiple) cmd.push("--multiple");
        if(save) cmd.push("--save",`--filename="${save}"`,"--confirm-overwrite");
        var out = run(cmd.join(" "));
        return out.slice(0,out.length-1);
    }

    /**
     * 
     * @param {String} text 
     * @param {String} title 
     * @param {"info"|"error"|"question"} type
     * @param {WindowOptions} [options] 
     */
    static info(text,title,type="info",options) {
        var cmd = [zenityBin,`--${type}`];
        if(options) {
            if(options.height) cmd.push(`--height="${options.height}"`);
            if(options.width) cmd.push(`--width="${options.width}"`);
            if(options.icon) cmd.push(`--window-icon="${options.icon}"`);
            if(options.parent) cmd.push(`--attach="${options.parent}"`);
        }
        cmd.push(`--text="${text||"text"}"`);
        cmd.push(`--title="${title||"title"}"`);
        run(cmd.join(" "));
    }

    /**
     * 
     * @param {String} text 
     * @param {String} title 
     * @param {String} [placeholder] 
     * @param {WindowOptions} [options] 
     * @returns 
     */
    static entry(text,title,placeholder=null,options) {
        var cmd = [zenityBin,`--entry`];
        if(options) {
            if(options.height) cmd.push(`--height="${options.height}"`);
            if(options.width) cmd.push(`--width="${options.width}"`);
            if(options.icon) cmd.push(`--window-icon="${options.icon}"`);
            if(options.parent) cmd.push(`--attach="${options.parent}"`);
        }
        cmd.push(`--text="${text||"text"}"`);
        cmd.push(`--title="${title||"title"}"`);
        if(placeholder) cmd.push(`--entry-text="${placeholder}"`)
        var out = run(cmd.join(" "));
        return out?out.slice(0,out.length-1):placeholder;
    }

    /**
     * 
     * @param {String} text 
     * @param {String} title 
     * @param {WindowOptions} [options] 
     */
    static progress(text,title,options) {
        var cmd = [];
        if(options) {
            if(options.height) cmd.push(`--height="${options.height}"`);
            if(options.width) cmd.push(`--width="${options.width}"`);
            if(options.icon) cmd.push(`--window-icon="${options.icon}"`);
            if(options.parent) cmd.push(`--attach="${options.parent}"`);
        }
        cmd.push(`--text="${text||"text"}"`);
        cmd.push(`--title="${title||"title"}"`);
        return new Progress(cmd);
    }

    /**
     * 
     * @param {String} title 
     * @param {String} [color] 
     * @param {boolean} [palette] 
     * @param {WindowOptions} [options]
     */
    static color(title,color="#FFFFFF",palette=false,options) {
        var cmd = [zenityBin,"--color-selection"];
        if(options) {
            if(options.height) cmd.push(`--height="${options.height}"`);
            if(options.width) cmd.push(`--width="${options.width}"`);
            if(options.icon) cmd.push(`--window-icon="${options.icon}"`);
            if(options.parent) cmd.push(`--attach="${options.parent}"`);
        }
        if(palette) cmd.push("--palette");
        cmd.push(`--title="${title||"title"}"`,`--color="${color}"`);
        var out = run(cmd.join(" "));
        return (function rgbToHex(rgb) {
            function componentFromStr(numStr, percent) {
                var num = Math.max(0, parseInt(numStr, 10));
                return percent ?
                    Math.floor(255 * Math.min(100, num) / 100) : Math.min(255, num);
            }
            var rgbRegex = /^rgb\(\s*(-?\d+)(%?)\s*,\s*(-?\d+)(%?)\s*,\s*(-?\d+)(%?)\s*\)$/;
            var result, r, g, b, hex = "";
            if ( (result = rgbRegex.exec(rgb)) ) {
                r = componentFromStr(result[1], result[2]);
                g = componentFromStr(result[3], result[4]);
                b = componentFromStr(result[5], result[6]);
            
                hex = "0x" + (0x1000000 + (r << 16) + (g << 8) + b).toString(16).slice(1);
            }
            return hex;
        })(out.slice(0,out.length-1));
    }
    /**
     * 
     * @param {String} title 
     * @param {String} text 
     * @param {String[][]|String[]} values 
     * @param {String[]} [headers] 
     * @param {WindowOptions} [options] 
     */
    static list(title,text,values,headers,options) {
        var cmd = [zenityBin,"--list"];
        if(options) {
            if(options.height) cmd.push(`--height="${options.height}"`);
            if(options.width) cmd.push(`--width="${options.width}"`);
            if(options.icon) cmd.push(`--window-icon="${options.icon}"`);
            if(options.parent) cmd.push(`--attach="${options.parent}"`);
        }
        
        var listvals = [];
        var columns = values[values.length-1].join?values[values.length-1].length:null;
        if(columns) {
            listvals = values.reduce((p,c)=>{c.forEach(v=>p.push(v));return p},[]);
        }else{
            listvals = values;
        }
        columns = columns || 1;
        if(!headers) {
            headers = new Array(columns).fill(`" "`);
        }
        cmd.push(...headers.map(v=>`--column="${v}"`));
        cmd.push(...listvals.map(v=>`"${v}"`));
        var out = run(cmd.join(" "));
        if(!out) return null;
        return listvals.indexOf(out.slice(0,out.length-1))/columns;
    }
}

module.exports = Dialog;