mousekey.mjs

import { execFile } from 'child_process'
import { promisify } from 'util'
import genPm from 'wsemi/src/genPm.mjs'
import getAhk from './getAhk.mjs'
import ks from './keySyntax.mjs'


//execFileAsync
let execFileAsync = promisify(execFile)

let _initialized = false
let _moukeyExternal = null


/**
 * @param {Object} opt
 * @param {Function} opt.moukey - async (action, args) => void
 *   action: 'click' | 'drag' | 'scroll' | 'sendChar' | 'sendString' | 'sendCombo'
 *   args: 依action而定
 *     click:      [x, y, btn?, count?]      btn: 'left'|'right'|'middle' (default 'left'), count: int (default 1)
 *     drag:       [x1, y1, x2, y2, btn?]    btn: 同click (default 'left')
 *     scroll:     [x, y, dir, amount?]      dir: 'up'|'down'|'left'|'right', amount: int (default 3)
 *     sendChar:   [char]                    純文字單字元 (SendText)
 *     sendString: [str]                     純文字字串 (SendText)
 *     sendCombo:  [combo]                   xdotool風格組合鍵字串 (如 'ctrl+v', 'F5')
 */
function init(opt = {}) {
    if (typeof opt.moukey === 'function') {
        _moukeyExternal = opt.moukey
        _initialized = true
    }
}


//按鈕名稱正規化
let VALID_BTNS = new Set(['left', 'right', 'middle'])
function normalizeBtn(btn) {
    let s = String(btn).toLowerCase()
    if (!VALID_BTNS.has(s)) {
        throw new Error(`unknown button: ${btn}`)
    }
    return s
}

//滾輪方向正規化
let VALID_DIRS = new Set(['up', 'down', 'left', 'right'])
function normalizeDir(dir) {
    let s = String(dir).toLowerCase()
    if (!VALID_DIRS.has(s)) {
        throw new Error(`unknown direction: ${dir}`)
    }
    return s
}


async function moukeyAhk(action, args = []) {
    let ak = getAhk()
    let pm = genPm()

    let ahkExePath = ak.ahkExePath
    let ahkScriptPath = null
    let scriptArgs = args
    switch (action) {
    case 'click':
        ahkScriptPath = ak.ahkScriptPathClick
        break
    case 'drag':
        ahkScriptPath = ak.ahkScriptPathDrag
        break
    case 'scroll':
        ahkScriptPath = ak.ahkScriptPathWheel
        break
    case 'sendChar':
    case 'sendString':
        ahkScriptPath = ak.ahkScriptPathSend
        break
    case 'sendCombo':
        ahkScriptPath = ak.ahkScriptPathSendCombo
        //中性語法翻譯為AHK Send語法
        scriptArgs = [ks.xdotoolToAhk(args[0])]
        break
    default:
        throw new Error(`unknown action: ${action}`)
    }

    //execFile要求args為string
    let argsStr = scriptArgs.map((a) => String(a))

    try {
        let { stdout, stderr } = await execFileAsync(ahkExePath, ['/force', ahkScriptPath, ...argsStr])
        if (stderr) {
            pm.reject(stderr)
        }
        else {
            pm.resolve(stdout)
        }
    }
    catch (err) {
        pm.reject(err)
    }

    return pm
}


function getMoukey() {
    if (_initialized && _moukeyExternal) {
        return _moukeyExternal
    }
    return moukeyAhk
}


async function mouseClick(x, y, button = 'left', count = 1) {
    let mkc = getMoukey()
    let btn = normalizeBtn(button)
    await mkc('click', [x, y, btn, count])
}

async function mouseDblClick(x, y, button = 'left') {
    let mkc = getMoukey()
    let btn = normalizeBtn(button)
    await mkc('click', [x, y, btn, 2])
}

async function mouseDrag(x1, y1, x2, y2, button = 'left') {
    let mkc = getMoukey()
    let btn = normalizeBtn(button)
    await mkc('drag', [x1, y1, x2, y2, btn])
}

async function mouseScroll(x, y, direction, amount = 3) {
    let mkc = getMoukey()
    let dir = normalizeDir(direction)
    await mkc('scroll', [x, y, dir, amount])
}

async function sendChar(char) {
    let mkc = getMoukey()
    await mkc('sendChar', [`${char}`])
}

async function sendString(str) {
    let mkc = getMoukey()
    await mkc('sendString', [`${str}`])
}

async function sendCombo(combo) {
    let mkc = getMoukey()
    await mkc('sendCombo', [`${combo}`])
}


let obj = {
    init,
    mouseClick,
    mouseDblClick,
    mouseDrag,
    mouseScroll,
    sendChar,
    sendString,
    sendCombo,
}


export default obj