2023-06-26 00:18:50 +02:00
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
2023-06-26 00:49:06 +02:00
--
-- SPDX-License-Identifier: MPL-2.0
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 " )
local SOCKET_PATH = " /tmp/pinnacle_socket "
---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
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-06-20 02:07:45 +02:00
client = require ( " client " ) ,
2023-06-22 01:03:27 +02:00
---Process spawning
2023-06-21 21:48:38 +02:00
process = require ( " process " ) ,
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
local socket_fd = assert ( socket.socket ( socket.AF_UNIX , socket.SOCK_STREAM , 0 ) , " Failed to create socket " )
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-06-27 04:05:29 +02:00
---@param data Msg
2023-06-20 02:07:45 +02:00
function SendMsg ( data )
local encoded = msgpack.encode ( data )
assert ( encoded )
local len = encoded : len ( )
socket.send ( socket_fd , string.pack ( " =I4 " , len ) )
socket.send ( socket_fd , encoded )
end
2023-06-27 04:05:29 +02:00
---@param data Request
2023-06-27 01:48:29 +02:00
function SendRequest ( data )
SendMsg ( {
Request = data ,
} )
end
2023-06-20 02:07:45 +02:00
2023-06-27 01:48:29 +02:00
function ReadMsg ( )
2023-06-20 02:07:45 +02:00
local msg_len_bytes , err_msg , err_num = read_exact ( socket_fd , 4 )
assert ( msg_len_bytes )
---@type integer
local msg_len = string.unpack ( " =I4 " , msg_len_bytes )
local msg_bytes , err_msg2 , err_num2 = read_exact ( socket_fd , msg_len )
assert ( msg_bytes )
2023-06-27 04:05:29 +02:00
---@type IncomingMsg
2023-06-20 02:07:45 +02:00
local tb = msgpack.decode ( msg_bytes )
2023-06-29 18:59:17 +02:00
-- print(msg_bytes)
2023-06-21 21:48:38 +02:00
2023-06-27 01:48:29 +02:00
return tb
end
Requests = {
id = 1 ,
}
function Requests : next ( )
local id = self.id
self.id = self.id + 1
return id
end
config_func ( pinnacle )
while true do
local tb = ReadMsg ( )
2023-06-21 21:48:38 +02:00
if tb.CallCallback and tb.CallCallback . callback_id then
if tb.CallCallback . args then -- TODO: can just inline
CallbackTable [ tb.CallCallback . callback_id ] ( tb.CallCallback . args )
else
CallbackTable [ tb.CallCallback . callback_id ] ( nil )
end
2023-06-20 02:07:45 +02:00
end
2023-06-27 01:48:29 +02:00
-- if tb.RequestResponse then
-- local req_id = tb.RequestResponse.request_id
-- Requests[req_id] = tb.RequestResponse.response
-- end
2023-06-20 02:07:45 +02:00
end
end
2023-06-27 01:48:29 +02:00
return pinnacle