thornWM/lib/async.lua
2025-05-23 15:41:23 +00:00

136 lines
5 KiB
Lua

-- This source code form is subject to the terms of the MPL-v2 https://mozilla.org/MPL/2.0/ --
--* .name Asynchronous callbacks for anything that needs it
local C = require("c")
local X = require("x")
local Conf = require("conf")
local ev = require("ev") -- https://github.com/brimworks/lua-ev
local Async = {ev = ev}
--* fd(int fd, int events, function callback) Raw file descriptor callbacks
function Async.fd(fd, evs, cb)
ev.IO.new(cb, fd, evs):start(ev.Loop.default)
end
function Async.idle(cb)
local id
id = ev.Idle.new(function()
id:stop(ev.Loop.default)
cb()
X.sync()
end)
id:start(ev.Loop.default)
end
--* timer(float after, [float repeat], function callback) Calls **callback** after **after** seconds, and then repeats on a **repeat** second loop if it's provided
function Async.timer(after, rep, cb)
if not cb then rep, cb = cb, rep end
if after <= 0 then after = 0.00000001 end
ev.Timer.new(cb, after, rep):start(ev.Loop.default)
end
--* .desc The *spawn** functions return a table with the **pid**, as well as *write*(*str* **data**) and *close*() for stdin
local StdinWrapper = {
write = function(self, text)
C.write(self.stdin, text)
return self
end,
close = function(self)
C.close(self.stdin)
end
}
StdinWrapper.__index = StdinWrapper
--* table stdin spawn(str program, [str arg, ...], [function callback]) Spawns a program and calls **callback** once it exits. Parameters passed to **callback** are (*str* **stdout**, *str* **stderr**, *int* **exitStatus**)
function Async.spawn(...)
local args = {...}
local cb
if #args == 0 then error("Need a program") end
if type(args[#args]) == "function" then cb = table.remove(args, #args) end
local pid, stdin, stdout, stderr
if cb then
pid, stdin, stdout, stderr = C.spawn(args)
if not pid then return nil, "Failed to spawn" end
local childWatcher
childWatcher = ev.Child.new(function(loop, child)
childWatcher:stop(loop)
local out = C.read(stdout)
local err = C.read(stderr)
C.close(stdin, stdout, stderr)
cb(out, err, child:getstatus().exit_status)
end, pid, false)
childWatcher:start(ev.Loop.default)
else
pid, stdin, stdout, stderr = C.spawn(args)
if not pid then return nil, "Failed to spawn" end
end
return setmetatable({ pid = pid, stdin = stdin }, StdinWrapper)
end
--* table stdin spawnShell(str command, [str shell], [function callback]) Runs **command** in a shell instace. The shell is the first of **shell**, **Conf.shell**, **$SHELL**, "/bin/sh". Parameters passed to **callback** are the same as with *spawn*
function Async.spawnShell(str, shell, cb)
if not cb then cb, shell = shell, cb end
return Async.spawn(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, cb)
end
--* table stdin spawnRead(str program, [str arg, ...], function dataCallback, [function exitCallback]) Spawns a program and calls **dataCallback** when the program writes to stdout or stderr, then **exitCallback** once it exits. Parameters to **dataCallback** are (*str* **stdout**, *str* **stderr**). Paramaters to **exitCallback** are (*int* **exitStatus**)
function Async.spawnRead(...)
local args = {...}
if #args < 2 then error("Need a program and callback") end
local outcb = table.remove(args, #args)
if type(outcb) ~= "function" then error("Need a callback") end
local exitcb
if type(args[#args]) == "function" then
if #args < 2 then error("Need a program") end
outcb, exitcb = table.remove(args, #args), outcb
if type(outcb) ~= "function" then error("Need a callback") end
end
local pid, stdin, stdout, stderr = C.spawn(args)
C.setNonBlocking(stdout)
C.setNonBlocking(stderr)
local stdoutWatcher = ev.IO.new(function(loop, io, revents)
local d = C.read(stdout)
print(d, #d)
if d and #d > 0 then
outcb(d, nil)
end
end, stdout, ev.READ)
stdoutWatcher:start(ev.Loop.default)
local exitWatcher
exitWatcher = ev.Child.new(function(loop, child, revents)
local out = C.read(stdout)
local err = C.read(stderr)
C.close(stdout, stderr, stdin)
stdoutWatcher:stop(loop)
exitWatcher:stop(loop)
outcb(out, err)
if exitcb then exitcb(child:getstatus().exit_status) end
end, pid, false)
exitWatcher:start(ev.Loop.default)
return setmetatable({pid = pid, stdin = stdin}, StdinWrapper)
end
--* table stdin spawnReadShell(str command, [str shell], function dataCallback, [function exitCallback]) Combination of *spawnRead* and *spawnShell*
function Async.spawnReadShell(str, shell, outcb, exitcb)
if type(shell) == "function" then
shell, outcb, exitcb = nil, shell, outcb
end
return Async.spawnRead(shell or Conf.shell or os.getenv("SHELL") or "/bin/sh", "-c", str, outcb, exitcb)
end
function Async.readFile(path, cb)
local stdout = C.getFD(assert(io.open(path, "r")))
C.setNonBlocking(stdout)
local chunks = {}
local stdoutWatcher
stdoutWatcher = ev.IO.new(function()
local c = C.read(stdout)
if c and #c > 0 then
table.insert(chunks, c)
else
C.close(stdout)
stdoutWatcher:stop(ev.Loop.default)
cb(table.concat(chunks))
end
end, stdout, ev.READ)
stdoutWatcher:start(ev.Loop.default)
end
function Async.loop()
ev.Loop.default:loop()
end
return Async