Add (crappy) but functional interactive cqueues-based chat client

This commit is contained in:
Adrian Perez de Castro
2016-07-08 21:17:41 +03:00
parent 522908d984
commit eee489678b

182
examples/client-cqchat.lua Normal file
View File

@@ -0,0 +1,182 @@
#! /usr/bin/env lua
--
-- client-cqchat.lua
-- Copyright (C) 2016 Adrian Perez <aperez@igalia.com>
--
-- Distributed under terms of the MIT license.
--
local cqueues = require "cqueues"
local posix = require "posix"
local bit = require "bit32"
local matrix = require "matrix"
local function xpcall_traceback(errmsg)
local tb = debug.traceback(nil, nil, 2)
return errmsg and (errmsg .. "\n" .. tb) or tb
end
--
-- Wraps the controlling terminal into an object which can be polled
-- with cqueues.poll() and uses O_NONBLOCK for input.
--
local tty = {
file = (function ()
local f = io.open(posix.ctermid(), "a+")
local fd = posix.fileno(f)
local flags = posix.fcntl(fd, posix.F_GETFL, 0)
flags = bit.bor(flags, assert(posix.O_NONBLOCK))
if posix.O_CLOEXEC then
flags = bit.bor(flags, posix.O_CLOEXEC)
else
io.stderr:write("no O_CLOEXEC, the TTY file descriptor might leak")
io.stderr:flush()
end
if posix.fcntl(fd, posix.F_SETFL, flags) ~= 0 then
error("cannot set O_NONBLOCK/O_CLOEXEC: " .. posix.errno())
end
return f
end)(),
-- Functions expected by cqueues
pollfd = function (self) return posix.fileno(self.file) end,
events = function () return "r" end, -- Read only
timeout = function () return nil end, -- Set in the call to cqueues.poll()
-- Wrappers for tcsetattr/tcgetattr
tcgetattr = function (self) return posix.tcgetattr(self:pollfd()) end,
tcsetattr = function (self, ...) return posix.tcsetattr(self:pollfd(), ...) end,
-- Obtain the terminal size using TIOCGWINSZ
_size_width = false,
_size_height = false,
size = function (self, force)
if force then
self._size_width = false
self._size_height = false
end
if self._size_width == false then
local p = io.popen("tput cols", "r")
self._size_width = p:read("*n")
p:close()
end
if self._size_height == false then
local p = io.popen("tput lines", "r")
self._size_height = p:read("*n")
p:close()
end
return self._size_width, self._size_height
end,
-- Saves terminal attributes, runs a function, and restores attributes
-- even if the function raises an error.
wrap = function (self, f, ...)
local saved_attr = self:tcgetattr()
local ok, err = xpcall(f, xpcall_traceback, self, ...)
self:tcsetattr(posix.TCSANOW, saved_attr)
if not ok then
error("tty:wrap: Error in wrapped function:\n" .. err)
end
return self
end
}
local command_pattern = "^%s*/(%a+)%s+(.*)$"
local function main(tty, client, username, password)
do
local a = tty:tcgetattr()
a.cc[posix.VMIN] = 1
a.cc[posix.VTIME] = 0
a.lflag = bit.band(a.lflag, bit.bnot(bit.bor(posix.ECHO, posix.ICANON)))
a.iflag = bit.band(a.iflag, bit.bnot(bit.bor(posix.IXON, posix.ISTRIP)))
a.cflag = bit.band(a.cflag, bit.bnot(bit.bor(posix.CSIZE, posix.PARENB)))
a.cflag = bit.bor(a.cflag, posix.CS8)
a.oflag = bit.band(a.oflag, bit.bnot(posix.OPOST))
if tty:tcsetattr(posix.TCSANOW, a) ~= 0 then
error("tcsetattr: " .. posix.errno())
end
end
local cq = cqueues.new()
local ok, err, obj = cq:wrap(function ()
print(string.format("Terminal size: %dx%d", tty:size()))
client:login_with_password(username, password)
local clientqueue = cq:wrap(function ()
client:sync()
end)
local current_room
while true do
local line = ""
while true do
io.stdout:write(string.format("\r[%s] %s",
current_room and current_room.room_id or "*",
line))
io.stdout:flush()
local handle_tty = false
cqueues.poll(tty, 0.05)
local ch = tty.file:read(1)
if ch then
if ch == "\4" and #line == 0 then
print("\r")
cq:cancel()
return
elseif ch == "\127" then
line = line:sub(1, -2)
elseif ch == "\n" then
break
else
line = line .. ch
end
end
end
local command, params = line:match(command_pattern)
if command then
if command == "room" then
if client.rooms[params] then
current_room = client.rooms[params]
else
print("\r ! No such room")
end
end
else
if current_room then
current_room:send_text(line)
else
print("\r ! Choose a room using '/room <room_id>'")
end
end
end
end):loop()
if not ok then
error(err)
end
end
local function print_room_message(room, sender, message, event)
print(string.format("\rK[%s] <%s> %s", room.room_id, sender, message.body))
end
if #arg ~= 3 then
io.stderr:write(string.format("Usage: %s <homeserver> <username> <password>\n", arg[0]))
os.exit(1)
end
local client = matrix.client(arg[1])
:hook("logged-in", function (client)
print("\r * Logged in as " .. client.user_id)
end)
:hook("joined", function (client, room)
print("\r * Joined room " .. room.room_id)
room:hook("message", print_room_message)
end)
:hook("left", function (client, room)
print("\r * Left room " .. room.room_id)
end)
tty:wrap(main, client, arg[2], arg[3])