mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-25 09:59:21 +01:00
Write rest of new Lua API
This commit is contained in:
parent
97f9cf82d6
commit
f10bd933ca
10 changed files with 468 additions and 48 deletions
|
@ -10,6 +10,10 @@ local pinnacle = {
|
|||
---@class Pinnacle
|
||||
---@field private config_client Client
|
||||
---@field input Input
|
||||
---@field output Output
|
||||
---@field process Process
|
||||
---@field tag Tag
|
||||
---@field window Window
|
||||
local Pinnacle = {}
|
||||
|
||||
function Pinnacle:quit()
|
||||
|
@ -34,6 +38,10 @@ function pinnacle.setup(config_fn)
|
|||
local self = {
|
||||
config_client = config_client,
|
||||
input = require("pinnacle.input").new(config_client),
|
||||
process = require("pinnacle.process").new(config_client),
|
||||
window = require("pinnacle.window").new(config_client),
|
||||
output = require("pinnacle.output").new(config_client),
|
||||
tag = require("pinnacle.tag").new(config_client),
|
||||
}
|
||||
setmetatable(self, { __index = Pinnacle })
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ local Client = {}
|
|||
---@class GrpcRequestParams
|
||||
---@field service string
|
||||
---@field method string
|
||||
---@field request_type string?
|
||||
---@field request_type string
|
||||
---@field response_type string?
|
||||
---@field data table
|
||||
|
||||
|
@ -45,44 +45,6 @@ local Client = {}
|
|||
function Client:unary_request(grpc_request_params)
|
||||
local stream = self.conn:new_stream()
|
||||
|
||||
local service = grpc_request_params.service
|
||||
local method = grpc_request_params.method
|
||||
local request_type = grpc_request_params.request_type or method .. "Request"
|
||||
local response_type = grpc_request_params.response_type or "google.protobuf.Empty"
|
||||
local data = grpc_request_params.data
|
||||
|
||||
local encoded_protobuf = assert(pb.encode(request_type, data), "wrong table schema")
|
||||
|
||||
local packed_prefix = string.pack("I1", 0)
|
||||
local payload_len = string.pack(">I4", encoded_protobuf:len())
|
||||
|
||||
local body = packed_prefix .. payload_len .. encoded_protobuf
|
||||
|
||||
stream:write_headers(create_request_headers(service, method), false)
|
||||
stream:write_chunk(body, true)
|
||||
|
||||
local response_headers = stream:get_headers()
|
||||
-- TODO: check headers for errors
|
||||
|
||||
local response_body = stream:get_next_chunk()
|
||||
local response = pb.decode(response_type, response_body)
|
||||
|
||||
print(inspect(response))
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
---Send a async server streaming request to the compositor.
|
||||
---
|
||||
---`callback` will be called with every streamed response.
|
||||
---
|
||||
---If `response_type` is not specified then it will default to
|
||||
---`google.protobuf.Empty`.
|
||||
---@param grpc_request_params GrpcRequestParams
|
||||
---@param callback fun(response: table)
|
||||
function Client:server_streaming_request(grpc_request_params, callback)
|
||||
local stream = self.conn:new_stream()
|
||||
|
||||
local service = grpc_request_params.service
|
||||
local method = grpc_request_params.method
|
||||
local request_type = grpc_request_params.request_type
|
||||
|
@ -102,9 +64,61 @@ function Client:server_streaming_request(grpc_request_params, callback)
|
|||
local response_headers = stream:get_headers()
|
||||
-- TODO: check headers for errors
|
||||
|
||||
local response_body = stream:get_next_chunk()
|
||||
-- Skip the 1-byte compressed flag and the 4-byte message length
|
||||
local response_body = response_body:sub(6)
|
||||
local response = pb.decode(response_type, response_body)
|
||||
|
||||
print(inspect(response))
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
---Send a async server streaming request to the compositor.
|
||||
---
|
||||
---`callback` will be called with every streamed response.
|
||||
---
|
||||
---If `response_type` is not specified then it will default to
|
||||
---`google.protobuf.Empty`.
|
||||
---@param grpc_request_params GrpcRequestParams
|
||||
---@param callback fun(response: table)
|
||||
function Client:server_streaming_request(grpc_request_params, callback)
|
||||
-- print(inspect(grpc_request_params))
|
||||
local stream = self.conn:new_stream()
|
||||
|
||||
local service = grpc_request_params.service
|
||||
local method = grpc_request_params.method
|
||||
local request_type = grpc_request_params.request_type
|
||||
local response_type = grpc_request_params.response_type or "google.protobuf.Empty"
|
||||
local data = grpc_request_params.data
|
||||
|
||||
local encoded_protobuf = assert(pb.encode(request_type, data), "wrong table schema")
|
||||
|
||||
local packed_prefix = string.pack("I1", 0)
|
||||
local payload_len = string.pack(">I4", encoded_protobuf:len())
|
||||
|
||||
local body = packed_prefix .. payload_len .. encoded_protobuf
|
||||
|
||||
stream:write_headers(create_request_headers(service, method), false)
|
||||
stream:write_chunk(body, true)
|
||||
|
||||
local response_headers = stream:get_headers()
|
||||
-- local chunk = stream:get_next_chunk()
|
||||
-- print(chunk, chunk:len())
|
||||
-- TODO: check headers for errors
|
||||
|
||||
self.loop:wrap(function()
|
||||
for response_body in stream:each_chunk() do
|
||||
local response = pb.decode(response_type, response_body)
|
||||
-- Skip the 1-byte compressed flag and the 4-byte message length
|
||||
local response_body = response_body:sub(6)
|
||||
|
||||
local success, obj = pcall(pb.decode, response_type, response_body)
|
||||
if not success then
|
||||
print(obj)
|
||||
os.exit(1)
|
||||
end
|
||||
|
||||
local response = obj
|
||||
callback(response)
|
||||
end
|
||||
end)
|
||||
|
|
|
@ -28,7 +28,7 @@ local function build_grpc_request_params(method, data)
|
|||
return {
|
||||
service = service,
|
||||
method = method,
|
||||
request_type = req_type and prefix .. req_type,
|
||||
request_type = req_type and prefix .. req_type or prefix .. method .. "Request",
|
||||
response_type = resp_type and prefix .. resp_type,
|
||||
data = data,
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ local function build_grpc_request_params(method, data)
|
|||
return {
|
||||
service = service,
|
||||
method = method,
|
||||
request_type = req_type and prefix .. req_type,
|
||||
request_type = req_type and prefix .. req_type or prefix .. method .. "Request",
|
||||
response_type = resp_type and prefix .. resp_type,
|
||||
data = data,
|
||||
}
|
||||
|
|
75
api/lua_grpc/pinnacle/process.lua
Normal file
75
api/lua_grpc/pinnacle/process.lua
Normal file
|
@ -0,0 +1,75 @@
|
|||
---The protobuf absolute path prefix
|
||||
local prefix = "pinnacle.process." .. require("pinnacle").version .. "."
|
||||
local service = prefix .. "ProcessService"
|
||||
|
||||
---@type table<string, { request_type: string?, response_type: string? }>
|
||||
---@enum (key) ProcessServiceMethod
|
||||
local rpc_types = {
|
||||
Spawn = {
|
||||
response_type = "SpawnResponse",
|
||||
},
|
||||
SetEnv = {},
|
||||
}
|
||||
|
||||
---Build GrpcRequestParams
|
||||
---@param method ProcessServiceMethod
|
||||
---@param data table
|
||||
---@return GrpcRequestParams
|
||||
local function build_grpc_request_params(method, data)
|
||||
local req_type = rpc_types[method].request_type
|
||||
local resp_type = rpc_types[method].response_type
|
||||
|
||||
---@type GrpcRequestParams
|
||||
return {
|
||||
service = service,
|
||||
method = method,
|
||||
request_type = req_type and prefix .. req_type or prefix .. method .. "Request",
|
||||
response_type = resp_type and prefix .. resp_type,
|
||||
data = data,
|
||||
}
|
||||
end
|
||||
|
||||
---@class ProcessModule
|
||||
local process = {}
|
||||
|
||||
---@class Process
|
||||
---@field private config_client Client
|
||||
local Process = {}
|
||||
|
||||
---@param args string[]
|
||||
---@param callbacks { stdout: fun(line: string)?, stderr: fun(line: string)?, exit: fun(code: integer, msg: string)? }?
|
||||
function Process:spawn(args, callbacks)
|
||||
local callback = function() end
|
||||
|
||||
if callbacks then
|
||||
callback = function(response)
|
||||
if callbacks.stdout and response.stdout then
|
||||
callbacks.stdout(response.stdout)
|
||||
end
|
||||
if callbacks.stderr and response.stderr then
|
||||
callbacks.stderr(response.stderr)
|
||||
end
|
||||
if callbacks.exit and (response.exit_code or response.exit_message) then
|
||||
callbacks.exit(response.exit_code, response.exit_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.config_client:server_streaming_request(
|
||||
build_grpc_request_params("Spawn", {
|
||||
args = args,
|
||||
once = false,
|
||||
has_callback = callbacks ~= nil,
|
||||
}),
|
||||
callback
|
||||
)
|
||||
end
|
||||
|
||||
function process.new(config_client)
|
||||
---@type Process
|
||||
local self = { config_client = config_client }
|
||||
setmetatable(self, { __index = Process })
|
||||
return self
|
||||
end
|
||||
|
||||
return process
|
|
@ -32,7 +32,7 @@ local function build_grpc_request_params(method, data)
|
|||
return {
|
||||
service = service,
|
||||
method = method,
|
||||
request_type = req_type and prefix .. req_type,
|
||||
request_type = req_type and prefix .. req_type or prefix .. method .. "Request",
|
||||
response_type = resp_type and prefix .. resp_type,
|
||||
data = data,
|
||||
}
|
||||
|
|
268
api/lua_grpc/pinnacle/window.lua
Normal file
268
api/lua_grpc/pinnacle/window.lua
Normal file
|
@ -0,0 +1,268 @@
|
|||
---The protobuf absolute path prefix
|
||||
local prefix = "pinnacle.prefix." .. require("pinnacle").version .. "."
|
||||
local service = prefix .. "WindowService"
|
||||
|
||||
---@type table<string, { request_type: string?, response_type: string? }>
|
||||
---@enum (key) WindowServiceMethod
|
||||
local rpc_types = {
|
||||
Close = {},
|
||||
SetGeometry = {},
|
||||
SetFullscreen = {},
|
||||
SetMaximized = {},
|
||||
SetFloating = {},
|
||||
MoveToTag = {},
|
||||
SetTag = {},
|
||||
MoveGrab = {},
|
||||
ResizeGrab = {},
|
||||
Get = {
|
||||
response_type = "GetResponse",
|
||||
},
|
||||
GetProperties = {
|
||||
response_type = "GetPropertiesResponse",
|
||||
},
|
||||
AddWindowRule = {},
|
||||
}
|
||||
|
||||
---Build GrpcRequestParams
|
||||
---@param method WindowServiceMethod
|
||||
---@param data table
|
||||
---@return GrpcRequestParams
|
||||
local function build_grpc_request_params(method, data)
|
||||
local req_type = rpc_types[method].request_type
|
||||
local resp_type = rpc_types[method].response_type
|
||||
|
||||
---@type GrpcRequestParams
|
||||
return {
|
||||
service = service,
|
||||
method = method,
|
||||
request_type = req_type and prefix .. req_type or prefix .. method .. "Request",
|
||||
response_type = resp_type and prefix .. resp_type,
|
||||
data = data,
|
||||
}
|
||||
end
|
||||
|
||||
---@class WindowHandleModule
|
||||
local window_handle = {}
|
||||
|
||||
---@class WindowHandle
|
||||
---@field private config_client Client
|
||||
---@field id integer
|
||||
local WindowHandle = {}
|
||||
|
||||
---@class WindowModule
|
||||
---@field private handle WindowHandleModule
|
||||
local window = {}
|
||||
window.handle = window_handle
|
||||
|
||||
---@class Window
|
||||
---@field private config_client Client
|
||||
local Window = {}
|
||||
|
||||
---Get all windows.
|
||||
---
|
||||
---@return WindowHandle[]
|
||||
function Window:get_all()
|
||||
local response = self.config_client:unary_request(build_grpc_request_params("Get", {}))
|
||||
|
||||
local handles = window_handle.new_from_table(self.config_client, response.window_ids)
|
||||
|
||||
return handles
|
||||
end
|
||||
|
||||
--- TODO: docs
|
||||
---@param button MouseButton
|
||||
function Window:begin_move(button)
|
||||
self.config_client:unary_request(build_grpc_request_params("MoveGrab", { button = button }))
|
||||
end
|
||||
|
||||
--- TODO: docs
|
||||
---@param button MouseButton
|
||||
function Window:begin_resize(button)
|
||||
self.config_client:unary_request(build_grpc_request_params("ResizeGrab", { button = button }))
|
||||
end
|
||||
|
||||
---@class WindowRuleCondition
|
||||
---@field any WindowRuleCondition[]?
|
||||
---@field all WindowRuleCondition[]?
|
||||
---@field classes string[]?
|
||||
---@field titles string[]?
|
||||
---@field tags TagHandle[]?
|
||||
|
||||
---@class WindowRule
|
||||
---@field output OutputHandle?
|
||||
---@field tags TagHandle[]?
|
||||
---@field floating boolean?
|
||||
---@field fullscreen_or_maximized FullscreenOrMaximized?
|
||||
---@field x integer?
|
||||
---@field y integer?
|
||||
---@field width integer?
|
||||
---@field height integer?
|
||||
|
||||
---@enum (key) FullscreenOrMaximized
|
||||
local _fullscreen_or_maximized = {
|
||||
neither = 1,
|
||||
fullscreen = 2,
|
||||
maximized = 3,
|
||||
}
|
||||
|
||||
local _fullscreen_or_maximized_keys = {
|
||||
[1] = "neither",
|
||||
[2] = "fullscreen",
|
||||
[3] = "maximized",
|
||||
}
|
||||
|
||||
---@param rule { cond: WindowRuleCondition, rule: WindowRule }
|
||||
function Window:add_window_rule(rule)
|
||||
if rule.cond.tags then
|
||||
local ids = {}
|
||||
for _, tg in pairs(rule.cond.tags) do
|
||||
table.insert(ids, tg.id)
|
||||
end
|
||||
rule.cond.tags = ids
|
||||
end
|
||||
|
||||
if rule.rule.output then
|
||||
rule.rule.output = rule.rule.output.name
|
||||
end
|
||||
|
||||
if rule.rule.tags then
|
||||
local ids = {}
|
||||
for _, tg in pairs(rule.cond.tags) do
|
||||
table.insert(ids, tg.id)
|
||||
end
|
||||
rule.cond.tags = ids
|
||||
end
|
||||
|
||||
if rule.rule.fullscreen_or_maximized then
|
||||
rule.rule.fullscreen_or_maximized = _fullscreen_or_maximized[rule.rule.fullscreen_or_maximized]
|
||||
end
|
||||
|
||||
self.config_client:unary_request(build_grpc_request_params("AddWindowRule", {
|
||||
cond = rule.cond,
|
||||
rule = rule.rule,
|
||||
}))
|
||||
end
|
||||
|
||||
---Send a close request to this window.
|
||||
function WindowHandle:close()
|
||||
self.config_client:unary_request(build_grpc_request_params("Close", { window_id = self.id }))
|
||||
end
|
||||
|
||||
---Set this window's location and/or size.
|
||||
---
|
||||
---@param geo { x: integer?, y: integer, width: integer?, height: integer? }
|
||||
function WindowHandle:set_geometry(geo)
|
||||
self.config_client:unary_request(build_grpc_request_params("SetGeometry", { window_id = self.id, geometry = geo }))
|
||||
end
|
||||
|
||||
---Set this window to fullscreen or not.
|
||||
---@param fullscreen boolean
|
||||
function WindowHandle:set_fullscreen(fullscreen)
|
||||
self.config_client:unary_request(
|
||||
build_grpc_request_params("SetFullscreen", { window_id = self.id, set = fullscreen })
|
||||
)
|
||||
end
|
||||
|
||||
function WindowHandle:toggle_fullscreen()
|
||||
self.config_client:unary_request(build_grpc_request_params("SetFullscreen", { window_id = self.id, toggle = {} }))
|
||||
end
|
||||
|
||||
function WindowHandle:set_maximized(maximized)
|
||||
self.config_client:unary_request(
|
||||
build_grpc_request_params("SetMaximized", { window_id = self.id, set = maximized })
|
||||
)
|
||||
end
|
||||
|
||||
function WindowHandle:toggle_maximized()
|
||||
self.config_client:unary_request(build_grpc_request_params("SetMaximized", { window_id = self.id, toggle = {} }))
|
||||
end
|
||||
|
||||
function WindowHandle:set_floating(floating)
|
||||
self.config_client:unary_request(build_grpc_request_params("SetFloating", { window_id = self.id, set = floating }))
|
||||
end
|
||||
|
||||
function WindowHandle:toggle_floating()
|
||||
self.config_client:unary_request(build_grpc_request_params("SetFloating", { window_id = self.id, toggle = {} }))
|
||||
end
|
||||
|
||||
---@param tag TagHandle
|
||||
function WindowHandle:move_to_tag(tag)
|
||||
self.config_client:unary_request(build_grpc_request_params("MoveToTag", { window_id = self.id, tag_id = tag.id }))
|
||||
end
|
||||
|
||||
---Tag or untag the given tag on this window.
|
||||
---@param tag TagHandle
|
||||
---@param set boolean
|
||||
function WindowHandle:set_tag(tag, set)
|
||||
self.config_client:unary_request(
|
||||
build_grpc_request_params("SetTag", { window_id = self.id, tag_id = tag.id, set = set })
|
||||
)
|
||||
end
|
||||
|
||||
---Toggle the given tag on this window.
|
||||
---@param tag TagHandle
|
||||
function WindowHandle:toggle_tag(tag)
|
||||
self.config_client:unary_request(
|
||||
build_grpc_request_params("SetTag", { window_id = self.id, tag_id = tag.id, toggle = {} })
|
||||
)
|
||||
end
|
||||
|
||||
---@class WindowProperties
|
||||
---@field geometry { x: integer?, y: integer?, width: integer?, height: integer? }?
|
||||
---@field class string?
|
||||
---@field title string?
|
||||
---@field focused boolean?
|
||||
---@field floating boolean?
|
||||
---@field fullscreen_or_maximized FullscreenOrMaximized?
|
||||
|
||||
---@return WindowProperties
|
||||
function WindowHandle:props()
|
||||
local response =
|
||||
self.config_client:unary_request(build_grpc_request_params("GetProperties", { window_id = self.id }))
|
||||
|
||||
response.fullscreen_or_maximized = _fullscreen_or_maximized_keys[response.fullscreen_or_maximized]
|
||||
|
||||
return response
|
||||
end
|
||||
|
||||
---@param config_client Client
|
||||
---@return Window
|
||||
function window.new(config_client)
|
||||
---@type Window
|
||||
local self = {
|
||||
config_client = config_client,
|
||||
}
|
||||
setmetatable(self, { __index = Window })
|
||||
return self
|
||||
end
|
||||
|
||||
---Create a new `WindowHandle` from an id.
|
||||
---@param config_client Client
|
||||
---@param window_id integer
|
||||
---@return WindowHandle
|
||||
function window_handle.new(config_client, window_id)
|
||||
---@type WindowHandle
|
||||
local self = {
|
||||
config_client = config_client,
|
||||
id = window_id,
|
||||
}
|
||||
setmetatable(self, { __index = WindowHandle })
|
||||
return self
|
||||
end
|
||||
|
||||
---@param config_client Client
|
||||
---@param window_ids integer[]
|
||||
---
|
||||
---@return WindowHandle[]
|
||||
function window_handle.new_from_table(config_client, window_ids)
|
||||
---@type WindowHandle[]
|
||||
local handles = {}
|
||||
|
||||
for _, id in pairs(window_ids) do
|
||||
table.insert(handles, window_handle.new(config_client, id))
|
||||
end
|
||||
|
||||
return handles
|
||||
end
|
||||
|
||||
return window
|
|
@ -1,10 +1,29 @@
|
|||
local pinnacle = require("pinnacle")
|
||||
require("pinnacle").setup(function(pinnacle)
|
||||
local input = pinnacle.input
|
||||
local process = pinnacle.process
|
||||
local output = pinnacle.output
|
||||
local tag = pinnacle.tag
|
||||
local window = pinnacle.window
|
||||
|
||||
pinnacle.setup(function(pinnacle)
|
||||
pinnacle.input:set_keybind({ 1 }, "A", function()
|
||||
print("hi from grpc keybind")
|
||||
local mods = input.mod
|
||||
|
||||
input:set_keybind({ mods.SHIFT }, "A", function()
|
||||
process:spawn({ "alacritty" }, {
|
||||
stdout = function(line)
|
||||
print("stdout")
|
||||
print(line)
|
||||
end,
|
||||
stderr = function(line)
|
||||
print("stderr")
|
||||
print(line)
|
||||
end,
|
||||
exit = function(code, msg)
|
||||
print(code, msg)
|
||||
end,
|
||||
})
|
||||
end)
|
||||
pinnacle.input:set_keybind({ 1 }, "Q", function()
|
||||
|
||||
input:set_keybind({ 1 }, "Q", function()
|
||||
pinnacle:quit()
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -2,6 +2,8 @@ syntax = "proto2";
|
|||
|
||||
package pinnacle.process.v0alpha1;
|
||||
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
message SpawnRequest {
|
||||
repeated string args = 1;
|
||||
// Whether or not to spawn `args` if it is already running.
|
||||
|
@ -18,6 +20,12 @@ message SpawnResponse {
|
|||
optional string exit_message = 4;
|
||||
}
|
||||
|
||||
message SetEnvRequest {
|
||||
optional string key = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
service ProcessService {
|
||||
rpc Spawn(SpawnRequest) returns (stream SpawnResponse);
|
||||
rpc SetEnv(SetEnvRequest) returns (google.protobuf.Empty);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use pinnacle_api_defs::pinnacle::{
|
|||
AccelProfile, ClickMethod, ScrollMethod, TapButtonMap,
|
||||
},
|
||||
output::v0alpha1::{ConnectForAllRequest, ConnectForAllResponse, SetLocationRequest},
|
||||
process::v0alpha1::SetEnvRequest,
|
||||
tag::v0alpha1::{
|
||||
AddRequest, AddResponse, RemoveRequest, SetActiveRequest, SetLayoutRequest, SwitchToRequest,
|
||||
},
|
||||
|
@ -524,6 +525,33 @@ impl pinnacle::process::v0alpha1::process_service_server::ProcessService for Pro
|
|||
|
||||
Ok(Response::new(Box::pin(receiver_stream)))
|
||||
}
|
||||
|
||||
async fn set_env(&self, request: Request<SetEnvRequest>) -> Result<Response<()>, Status> {
|
||||
let request = request.into_inner();
|
||||
|
||||
let key = request
|
||||
.key
|
||||
.ok_or_else(|| Status::invalid_argument("no key specified"))?;
|
||||
let value = request
|
||||
.value
|
||||
.ok_or_else(|| Status::invalid_argument("no value specified"))?;
|
||||
|
||||
if key.is_empty() {
|
||||
return Err(Status::invalid_argument("key was empty"));
|
||||
}
|
||||
|
||||
if key.contains(['\0', '=']) {
|
||||
return Err(Status::invalid_argument("key contained NUL or ="));
|
||||
}
|
||||
|
||||
if value.contains('\0') {
|
||||
return Err(Status::invalid_argument("value contained NUL"));
|
||||
}
|
||||
|
||||
std::env::set_var(key, value);
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TagService {
|
||||
|
|
Loading…
Reference in a new issue