#!/opt/lnxall_app/bin/lua -- Created by hongbe.he@lnxall.com -- Software Hotplug Detector -- 2025/10/21 local posix = require "posix" local socket = require "posix.sys.socket" local bit32 = require "bit32" local invoker = require "invoker" local hal = require "lnxhal" local hwt_table = { [hal.HWTYPE_RK3568] = true, [hal.HWTYPE_RK3588] = true, } local G_HANDLERS = {} local function is_target_board() return hwt_table[hal.hw_gettype()] == true end -- sync with another services and apps -- local function display_mode_sync(mode) local fp = io.open('/tmp/.screen_mode', 'wb') if fp then fp:write(mode) fp:close() return true end return false end local function call_display_detector() local status = invoker.readfile('/sys/class/drm/card0-HDMI-A-1/status') if type(status) ~= 'string' then return false end local hdmi_plugged = string.find(status, 'connected', 1, true) and not string.find(status, 'disconnected', 1, true) local rval, xr_out = invoker.invoke(invoker.OUTPUT, 'xrandr') if rval ~= 0 or type(xr_out) ~= 'string' then return false end local dsi_is_on = string.find(xr_out, 'DSI%-1 connected%s+%d+x%d+') -- hdmi plugged & dsi on => shut the dsi fuck up if hdmi_plugged then if dsi_is_on then invoker.msleep(500) local mode = string.match(xr_out, 'HDMI%-1 connected[%s%w]-(%d+x%d+)%+%d+%+%d+') or '1920x1080' display_mode_sync(mode) invoker.invoke(0, 'xrandr', '--output', 'DSI-1', '--off') io.stdout:write('INFO: HDMI Active, Force DSI OFF\n') io.stdout:flush() end else -- hdmi disconn & dsi off => open dsi if not dsi_is_on then invoker.msleep(500) display_mode_sync('1024x600') invoker.invoke(0, 'xrandr', '--output', 'DSI-1', '--auto') -- touch remap local _, xinput_out = invoker.invoke(invoker.OUTPUT, 'xinput') local gid = string.match(xinput_out, 'goodix%-ts%s+id=(%d+)') if gid then invoker.invoke(0, 'xinput', 'map-to-output', gid, 'DSI-1') end io.stdout:write('INFO: HDMI Disconnected, DSI ON\n') io.stdout:flush() end end return true end local function wait_for_x_server(timeout) local start = invoker.uptime() while (invoker.uptime() - start) < timeout do local rval = invoker.invoke(invoker.NOSTDIO, 'xrandr', '--query') if rval == 0 then return true end invoker.msleep(500) end return false end local function netlink_fds_create() local fd = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, 15) if not fd then return -1 end if not socket.bind(fd, { family = socket.AF_NETLINK, pid = 0, groups = 1 }) then return -2 end return fd end local function dbus_fds_create() local rval = invoker.invoke(invoker.NOSTDIO, "which", "gdbus") if rval > 0 then io.stderr:write("Error, 'gdbus' not found.\n") io.stderr:flush() return -1, -1 end local iflags = bit32.bor(invoker.NOWAIT, invoker.OUTPUT) local command = 'exec stdbuf -oL gdbus monitor --system --dest org.freedesktop.login1 --object-path /org/freedesktop/login1/seat/seat0' local pid, fd = invoker.invoke(iflags, 'bash', '-c', command) if type(pid) ~= 'number' or pid <= 0 then return -1, -1 end invoker.nonblock(fd, true) return fd, pid end G_HANDLERS.on_netlink_event = function(fd, subscribers) local evdata = socket.recv(fd, 4096) if type(evdata) == 'string' and string.find(evdata, 'drm') then io.stdout:write('Netlink Event Triggered\n') io.stdout:flush() call_display_detector() end end G_HANDLERS.on_dbus_event = function(fd, subscribers) local act = subscribers[fd] if act.revents.HUP or act.revents.ERR then io.stderr:write('DBUS loss connection, try reconnectin ...\n') io.stderr:flush() invoker.kill(act.pid, posix.SIGKILL) invoker.waitpid(act.pid, false) subscribers[fd] = nil invoker.closefd(fd) local fd, pid = dbus_fds_create() if fd > 0 then subscribers[fd] = { events = { IN = true, HUP = true, ERR = true }, callback = G_HANDLERS.on_dbus_event, pid = pid } io.stdout:write('DBUS reconnected\n') io.stdout:flush() else invoker.msleep(1000) end return end local evdata = invoker.readfd(fd, 4096) if type(evdata) == 'string' and string.find(evdata, 'PropertiesChanged') then if wait_for_x_server(10000) then io.stdout:write('DBUS Event Triggered\n') io.stdout:flush() call_display_detector() else io.stderr:write('Error, X server not ready after 10s\n') io.stderr:flush() end end end local function main_func() invoker.setenv('LANG', 'C') invoker.setname('SOFTHPD') invoker.setenv('DISPLAY', ":0", true) if not is_target_board() then io.stderr:write('Unsupported hardware, softhpd sleeping ...\n') io.stderr:flush() -- service will be restart after 1 days invoker.invoke(invoker.NOFORK, 'sleep', '86400') return false end if wait_for_x_server(15000) then io.stdout:write('X Server is ready, performing initial check.\n') else io.stdout:write('Warning: X Server not ready after 15s, initial check skipped.\n') end io.stdout:flush() call_display_detector() local nlfd = netlink_fds_create() local gdfd, gd_pid = dbus_fds_create() if nlfd < 0 or gdfd < 0 then io.stderr:write(string.format('Error, failed to create NETLINK(%d) / DBUS-PIPE(%d)\n', nlfd, gdfd)) io.stderr:flush() invoker.invoke(invoker.NOFORK, 'sleep', '86400') return false end local subscribers = { [nlfd] = { events = { IN = true }, callback = G_HANDLERS.on_netlink_event }, [gdfd] = { events = { IN = true, HUP = true, ERR = true }, callback = G_HANDLERS.on_dbus_event, pid = gd_pid } } while true do posix.poll(subscribers, -1) for fd, act in pairs(subscribers) do if act.revents.IN or act.revents.HUP or act.revents.ERR then act.callback(fd, subscribers) end end end end main_func(arg[1]) os.exit(1)