#!/opt/lnxall_app/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- Simple Touchscreen press emulator
-- 2025/11/25

local struct = require 'struct'
local invoker = require 'invoker'

local gfmt = string.format
local x_max, y_max = 16384, 16384
local g_fd = nil
local g_dev = nil
local g_xpos = 7788
local g_ypos = 8899

local EV_ABS               = 3
local ABS_MT_TRACKING_ID   = 57
local ABS_MT_POSITION_X    = 53
local ABS_MT_POSITION_Y    = 54
local EV_KEY               = 1
local BTN_TOUCH            = 330
local ABS_X                = 0
local ABS_Y                = 1
local EV_MSC               = 4
local MSC_TIMESTAMP        = 5
local UP                   = 0
local DOWN                 = 1
local EV_SYN               = 0
local SYN_REPORT           = 0

local function press_init()
	local dev, output = arg[1], ""
	if invoker.statfile(dev) ~= "character" then
		invoker.setenv("LANG", nil)
		_, output = invoker.invoke(invoker.OUTPUT, "/bin/bash", "-c",
			"libinput list-devices 2>/dev/null | grep -e Kernel: -e Capabilities:")
		if type(output) ~= "string" then output = "" end
	end

	local devn = nil
	for line in string.gmatch(output, "[^\r\n]+") do
		local what = string.match(line, "^Kernel:%s+([^%s]+)")
		local that = string.match(line, "^Capabilities:%s+([^%s]+)")
		if what and invoker.statfile(what) == "character" then
			devn = what
		elseif that == "touch" and devn then
			dev = devn
			break
		end
	end

	if invoker.statfile(dev) ~= "character" then
		io.stderr:write("Error, input device not specified.\n")
		io.stderr:flush()
		return false
	end

	g_dev = dev
	local pos = arg[2]
	if pos and #pos > 0 then pos = tonumber(pos) end
	if type(pos) == "number" and pos >= 0 and pos <= x_max then g_xpos = pos end

	pos = arg[3]
	if pos and #pos > 0 then pos = tonumber(pos) end
	if type(pos) == "number" and pos >= 0 and pos <= y_max then g_ypos = pos end

	io.stdout:write(gfmt("Touch at position: %dx%d -> %s\n",
		g_xpos, g_ypos, dev))
	io.stdout:flush()
	return true
end

local function open_device()
	local fd = invoker.open(g_dev, 524290)
	if not fd then
		io.stderr:write(gfmt("Eror, failed to open device: %s\n", g_dev))
		io.stderr:flush()
		return false
	end
	g_fd = fd
	return true
end

local function write_event(evkey, evcode, value)
	local ups, upm = invoker.uptime()
	local wbuf = struct.pack("<I8I8HHi4", ups, upm * 1000, evkey, evcode, value)
	if invoker.writefd(g_fd, wbuf) ~= #wbuf then
		io.stderr:write("Error, write Event has failed.\n")
		io.stderr:flush()
		return false
	end
	return true
end

local function touchscreen(xpos, ypos)
	if xpos < 0 or xpos > x_max then
		io.stderr:write(gfmt("Error, invalid X position: %d\n", xpos))
		io.stderr:flush()
	end

	if ypos < 0 or ypos > y_max then
		io.stderr:write(gfmt("Error, invalid Y position: %d\n", ypos))
		io.stderr:flush()
	end

	write_event(EV_ABS, ABS_MT_TRACKING_ID,   0x0)
	write_event(EV_ABS, ABS_MT_POSITION_X,    xpos)
	write_event(EV_ABS, ABS_MT_POSITION_Y,    ypos)
	write_event(EV_KEY, BTN_TOUCH,            DOWN)
	write_event(EV_ABS, ABS_X,                xpos)
	write_event(EV_ABS, ABS_Y,                ypos)
	write_event(EV_SYN, SYN_REPORT,           0x0)
	invoker.msleep(90)

	write_event(EV_ABS, ABS_MT_POSITION_X,    xpos)
	write_event(EV_ABS, ABS_MT_POSITION_Y,    ypos)
	write_event(EV_ABS, ABS_X,                xpos)
	write_event(EV_ABS, ABS_Y,                ypos)
	write_event(EV_SYN, SYN_REPORT,           0x0)
	invoker.msleep(90)

	write_event(EV_ABS, ABS_MT_TRACKING_ID,   0xffffffff)
	write_event(EV_KEY, BTN_TOUCH,            UP)
	write_event(EV_SYN, SYN_REPORT,           0x0)
	return true
end

if not press_init() then os.exit(1) end
if not open_device() then os.exit(2) end
touchscreen(g_xpos, g_ypos)
os.exit(0)
