2023-08-01 18:06:35 +02:00
-- SPDX-License-Identifier: GPL-3.0-or-later
2023-06-26 00:18:50 +02:00
2023-06-20 02:07:45 +02:00
local socket = require ( " posix.sys.socket " )
local msgpack = require ( " msgpack " )
2023-08-07 18:53:56 +02:00
local socket_dir = os.getenv ( " SOCKET_DIR " )
if socket_dir then
if socket_dir : match ( " /$ " ) then
socket_dir = socket_dir : sub ( 0 , socket_dir : len ( ) - 1 )
end
end
local SOCKET_PATH = ( socket_dir or " /tmp " ) .. " /pinnacle_socket "
2023-06-20 02:07:45 +02:00
2023-07-21 22:04:39 +02:00
---From https://gist.github.com/stuby/5445834#file-rprint-lua
---rPrint(struct, [limit], [indent]) Recursively print arbitrary data.
--- Set limit (default 100) to stanch infinite loops.
--- Indents tables as [KEY] VALUE, nested tables as [KEY] [KEY]...[KEY] VALUE
--- Set indent ("") to prefix each line: Mytable [KEY] [KEY]...[KEY] VALUE
---@param s table The table
---@param l integer? Recursion limit
---@param i string? The indent string
---@return integer l The remaining depth limit
2023-07-21 21:36:32 +02:00
function RPrint ( s , l , i ) -- recursive Print (structure, limit, indent)
l = l or 100
i = i or " " -- default item limit, indent string
if l < 1 then
print ( " ERROR: Item limit reached. " )
return l - 1
end
local ts = type ( s )
if ts ~= " table " then
print ( i , ts , s )
return l - 1
end
print ( i , ts ) -- print "table"
for k , v in pairs ( s ) do -- print "[KEY] VALUE"
l = RPrint ( v , l , i .. " \t [ " .. tostring ( k ) .. " ] " )
if l < 0 then
break
end
end
return l
end
2023-06-20 02:07:45 +02:00
---Read the specified number of bytes.
---@param socket_fd integer The socket file descriptor
---@param count integer The amount of bytes to read
---@return string|nil data
---@return string|nil err_msg
---@return integer|nil err_num
local function read_exact ( socket_fd , count )
local len_to_read = count
local data = " "
while len_to_read > 0 do
2023-07-18 19:37:40 +02:00
-- print("need to read " .. tostring(len_to_read) .. " bytes")
2023-06-20 02:07:45 +02:00
local bytes , err_msg , errnum = socket.recv ( socket_fd , len_to_read )
if bytes == nil then
-- TODO: handle errors
print ( " bytes was nil " )
return bytes , err_msg , errnum
end
---@type integer
local recv_len = bytes : len ( )
if recv_len == 0 then
print ( " stream closed " )
break
end
len_to_read = len_to_read - recv_len
assert ( len_to_read >= 0 , " Overread message boundary " )
data = data .. bytes
end
return data
end
---@class Pinnacle
2023-06-22 01:03:27 +02:00
---The main Pinnacle table, where all of the config options come from.
---
---While you *can* import the fields directly, all config must be in the `setup` function, so you might as well just use the provided table. The ability to directly `require` fields may be dropped in the future.
2023-06-20 02:07:45 +02:00
local pinnacle = {
2023-06-22 01:03:27 +02:00
---Key and mouse binds
2023-06-20 02:07:45 +02:00
input = require ( " input " ) ,
2023-06-22 01:03:27 +02:00
---Window management
2023-07-02 17:26:07 +02:00
window = require ( " window " ) ,
2023-06-22 01:03:27 +02:00
---Process spawning
2023-06-21 21:48:38 +02:00
process = require ( " process " ) ,
2023-07-01 04:34:07 +02:00
---Tag management
tag = require ( " tag " ) ,
2023-07-11 18:59:38 +02:00
---Output management
output = require ( " output " ) ,
2023-06-20 02:07:45 +02:00
}
2023-06-22 00:36:51 +02:00
---Quit Pinnacle.
function pinnacle . quit ( )
SendMsg ( " Quit " )
end
2023-06-20 02:07:45 +02:00
---Configure Pinnacle. You should put mostly eveything into the config_func to avoid invalid state.
---The function takes one argument: the Pinnacle table, which is how you'll access all of the available config options.
---@param config_func fun(pinnacle: Pinnacle)
2023-06-27 01:48:29 +02:00
function pinnacle . setup ( config_func )
2023-06-20 02:07:45 +02:00
---@type integer
2023-08-07 18:53:56 +02:00
local socket_fd = assert (
socket.socket ( socket.AF_UNIX , socket.SOCK_STREAM , 0 ) ,
" Failed to create socket "
)
2023-06-20 02:07:45 +02:00
print ( " created socket at fd " .. socket_fd )
assert ( 0 == socket.connect ( socket_fd , {
family = socket.AF_UNIX ,
path = SOCKET_PATH ,
} ) , " Failed to connect to Pinnacle socket " )
2023-06-21 21:48:38 +02:00
---@type fun(args: table?)[]
2023-06-20 02:07:45 +02:00
CallbackTable = { }
2023-07-20 01:55:22 +02:00
---This is an internal global function used to send serialized messages to the Pinnacle server.
2023-06-27 04:05:29 +02:00
---@param data Msg
2023-06-20 02:07:45 +02:00
function SendMsg ( data )
2023-07-21 21:36:32 +02:00
-- RPrint(data)
2023-06-20 02:07:45 +02:00
local encoded = msgpack.encode ( data )
assert ( encoded )
2023-07-19 04:10:43 +02:00
-- print(encoded)
2023-06-20 02:07:45 +02:00
local len = encoded : len ( )
socket.send ( socket_fd , string.pack ( " =I4 " , len ) )
socket.send ( socket_fd , encoded )
end
2023-07-21 22:04:39 +02:00
local request_id = 1
2023-07-21 21:36:32 +02:00
---Get the next request id.
---@return integer
local function next_request_id ( )
local ret = request_id
request_id = request_id + 1
return ret
end
---@type table<integer, IncomingMsg>
local unread_req_msgs = { }
---@type table<integer, IncomingMsg>
local unread_cb_msgs = { }
2023-07-20 01:55:22 +02:00
---This is an internal global function used to send requests to the Pinnacle server for information.
2023-07-21 21:36:32 +02:00
---@param data _Request
2023-07-21 22:04:39 +02:00
---@return IncomingMsg
function Request ( data )
2023-07-21 21:36:32 +02:00
local req_id = next_request_id ( )
2023-06-27 01:48:29 +02:00
SendMsg ( {
2023-07-21 21:36:32 +02:00
Request = {
request_id = req_id ,
request = data ,
} ,
2023-06-27 01:48:29 +02:00
} )
2023-07-21 22:04:39 +02:00
return ReadMsg ( req_id )
2023-06-27 01:48:29 +02:00
end
2023-06-20 02:07:45 +02:00
2023-07-20 01:55:22 +02:00
---This is an internal global function used to read messages sent from the server.
---These are used to call user-defined functions and provide requested information.
2023-07-21 21:36:32 +02:00
---@return IncomingMsg
---@param req_id integer? A request id if you're looking for that specific message.
function ReadMsg ( req_id )
while true do
if req_id then
if unread_req_msgs [ req_id ] then
local msg = unread_req_msgs [ req_id ]
unread_req_msgs [ req_id ] = nil -- INFO: is this a reference?
return msg
end
end
2023-06-20 02:07:45 +02:00
2023-07-21 21:36:32 +02:00
local msg_len_bytes , err_msg , err_num = read_exact ( socket_fd , 4 )
assert ( msg_len_bytes )
2023-07-04 22:20:41 +02:00
2023-07-21 21:36:32 +02:00
-- TODO: break here if error in read_exact
---@type integer
local msg_len = string.unpack ( " =I4 " , msg_len_bytes )
-- print(msg_len)
2023-06-20 02:07:45 +02:00
2023-07-21 21:36:32 +02:00
local msg_bytes , err_msg2 , err_num2 = read_exact ( socket_fd , msg_len )
assert ( msg_bytes )
-- print(msg_bytes)
2023-06-20 02:07:45 +02:00
2023-07-21 21:36:32 +02:00
---@type IncomingMsg
local inc_msg = msgpack.decode ( msg_bytes )
-- print(msg_bytes)
2023-06-21 21:48:38 +02:00
2023-07-21 21:36:32 +02:00
if req_id then
if inc_msg.CallCallback then
unread_cb_msgs [ inc_msg.CallCallback . callback_id ] = inc_msg
elseif inc_msg.RequestResponse . request_id ~= req_id then
2023-08-07 18:53:56 +02:00
unread_req_msgs [ inc_msg.RequestResponse . request_id ] =
inc_msg
2023-07-21 21:36:32 +02:00
else
return inc_msg
end
else
return inc_msg
end
end
2023-06-27 01:48:29 +02:00
end
config_func ( pinnacle )
while true do
2023-07-21 21:36:32 +02:00
for cb_id , inc_msg in pairs ( unread_cb_msgs ) do
2023-08-07 18:53:56 +02:00
CallbackTable [ inc_msg.CallCallback . callback_id ] (
inc_msg.CallCallback . args
)
2023-07-21 21:36:32 +02:00
unread_cb_msgs [ cb_id ] = nil -- INFO: does this shift the table and frick everything up?
end
local inc_msg = ReadMsg ( )
2023-06-27 01:48:29 +02:00
2023-07-21 21:36:32 +02:00
assert ( inc_msg.CallCallback ) -- INFO: is this gucci or no
if inc_msg.CallCallback and inc_msg.CallCallback . callback_id then
if inc_msg.CallCallback . args then -- TODO: can just inline
2023-08-07 18:53:56 +02:00
CallbackTable [ inc_msg.CallCallback . callback_id ] (
inc_msg.CallCallback . args
)
2023-06-21 21:48:38 +02:00
else
2023-07-21 21:36:32 +02:00
CallbackTable [ inc_msg.CallCallback . callback_id ] ( nil )
2023-06-21 21:48:38 +02:00
end
2023-06-20 02:07:45 +02:00
end
end
end
2023-06-27 01:48:29 +02:00
return pinnacle