Write half of new Lua API

This commit is contained in:
Ottatop 2024-01-12 17:20:34 -06:00
parent 3b88b3ff11
commit 97f9cf82d6
7 changed files with 605 additions and 34 deletions

8
api/lua_grpc/.luarc.json Normal file
View file

@ -0,0 +1,8 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"workspace.library": ["./"],
"runtime.version": "Lua 5.4",
"--comment": "Format using Stylua instead",
"format.enable": false,
}

View file

@ -1,14 +1,14 @@
local cqueues = require("cqueues") local cqueues = require("cqueues")
---@type ClientModule
local client = require("pinnacle.grpc.client") local client = require("pinnacle.grpc.client")
---@class PinnacleModule ---@class PinnacleModule
local pinnacle = {} local pinnacle = {
version = "v0alpha1",
}
---@class Pinnacle ---@class Pinnacle
---@field private config_client Client ---@field private config_client Client
---@field private loop CqueuesLoop
---@field input Input ---@field input Input
local Pinnacle = {} local Pinnacle = {}
@ -25,21 +25,21 @@ end
---@param config_fn fun(pinnacle: Pinnacle) ---@param config_fn fun(pinnacle: Pinnacle)
function pinnacle.setup(config_fn) function pinnacle.setup(config_fn)
require("pinnacle.grpc.protobuf").build_protos() require("pinnacle.grpc.protobuf").build_protos()
local loop = cqueues.new() local loop = cqueues.new()
---@type Client
local config_client = client.new(loop) local config_client = client.new(loop)
---@type Pinnacle ---@type Pinnacle
local self = { local self = {
config_client = config_client, config_client = config_client,
loop = loop,
input = require("pinnacle.input").new(config_client), input = require("pinnacle.input").new(config_client),
} }
setmetatable(self, { __index = Pinnacle }) setmetatable(self, { __index = Pinnacle })
config_fn(self) config_fn(self)
self.loop:loop() loop:loop()
end end
return pinnacle return pinnacle

View file

@ -26,20 +26,18 @@ local client = {}
---@field loop CqueuesLoop ---@field loop CqueuesLoop
local Client = {} local Client = {}
---@return H2Stream stream An http2 stream
function Client:new_stream()
return self.conn:new_stream()
end
---@class GrpcRequestParams ---@class GrpcRequestParams
---@field service string ---@field service string
---@field method string ---@field method string
---@field request_type string ---@field request_type string?
---@field response_type string? ---@field response_type string?
---@field data table ---@field data table
---Send a synchronous unary request to the compositor. ---Send a synchronous unary request to the compositor.
--- ---
---If `request_type` is not specified then it will default to
---`method` .. "Request".
---
---If `response_type` is not specified then it will default to ---If `response_type` is not specified then it will default to
---`google.protobuf.Empty`. ---`google.protobuf.Empty`.
---@param grpc_request_params GrpcRequestParams ---@param grpc_request_params GrpcRequestParams
@ -49,7 +47,7 @@ function Client:unary_request(grpc_request_params)
local service = grpc_request_params.service local service = grpc_request_params.service
local method = grpc_request_params.method local method = grpc_request_params.method
local request_type = grpc_request_params.request_type local request_type = grpc_request_params.request_type or method .. "Request"
local response_type = grpc_request_params.response_type or "google.protobuf.Empty" local response_type = grpc_request_params.response_type or "google.protobuf.Empty"
local data = grpc_request_params.data local data = grpc_request_params.data
@ -129,6 +127,7 @@ function client.new(loop)
loop = loop, loop = loop,
} }
setmetatable(self, { __index = Client }) setmetatable(self, { __index = Client })
return self return self
end end

View file

@ -1,9 +1,38 @@
---@class InputModule ---The protobuf absolute path prefix
local input = {} local prefix = "pinnacle.input." .. require("pinnacle").version .. "."
local service = prefix .. "InputService"
---@class Input ---@type table<string, { request_type: string?, response_type: string? }>
---@field private config_client Client ---@enum (key) InputServiceMethod
local Input = {} local rpc_types = {
SetKeybind = {
response_type = "SetKeybindResponse",
},
SetMousebind = {
response_type = "SetMousebindResponse",
},
SetXkbConfig = {},
SetRepeatRate = {},
SetLibinputSetting = {},
}
---Build GrpcRequestParams
---@param method InputServiceMethod
---@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,
response_type = resp_type and prefix .. resp_type,
data = data,
}
end
---@enum Modifier ---@enum Modifier
local modifier = { local modifier = {
@ -13,7 +42,49 @@ local modifier = {
SUPER = 4, SUPER = 4,
} }
---@param mods Modifier[] ---@enum MouseButton
local mouse_button = {
--- Left
[1] = 0x110,
--- Right
[2] = 0x111,
--- Middle
[3] = 0x112,
--- Side
[4] = 0x113,
--- Extra
[5] = 0x114,
--- Forward
[6] = 0x115,
--- Back
[7] = 0x116,
left = 0x110,
right = 0x111,
middle = 0x112,
side = 0x113,
extra = 0x114,
forward = 0x115,
back = 0x116,
}
---@enum MouseEdge
local mouse_edge = {
PRESS = 1,
RELEASE = 2,
}
---@class InputModule
local input = {}
---@class Input
---@field private config_client Client
local Input = {
mod = modifier,
btn = mouse_button,
edge = mouse_edge,
}
---@param mods Modifier[] TODO: accept strings of mods
---@param key integer | string ---@param key integer | string
---@param action fun() ---@param action fun()
function Input:set_keybind(mods, key, action) function Input:set_keybind(mods, key, action)
@ -26,17 +97,63 @@ function Input:set_keybind(mods, key, action)
xkb_name = key xkb_name = key
end end
self.config_client:server_streaming_request({ self.config_client:server_streaming_request(
service = "pinnacle.input.v0alpha1.InputService", build_grpc_request_params("SetKeybind", {
method = "SetKeybind",
request_type = "pinnacle.input.v0alpha1.SetKeybindRequest",
data = {
modifiers = mods, modifiers = mods,
-- oneof not violated because `key` can't be both an int and string
raw_code = raw_code, raw_code = raw_code,
xkb_name = xkb_name, xkb_name = xkb_name,
}, }),
}, action) action
)
end
---Set a mousebind.
---
---@param mods Modifier[]
---@param button MouseButton
---@param edge MouseEdge|"press"|"release"
---@param action fun()
function Input:set_mousebind(mods, button, edge, action)
local edge = edge
if edge == "press" then
edge = mouse_edge.PRESS
elseif edge == "release" then
edge = mouse_edge.RELEASE
end
self.config_client:server_streaming_request(
build_grpc_request_params("SetMousebind", {
modifiers = mods,
button = button,
edge = edge,
}),
action
)
end
---@class XkbConfig
---@field rules string?
---@field model string?
---@field layout string?
---@field variant string?
---@field options string?
---Set the xkbconfig for your keyboard.
---
---@param xkb_config XkbConfig
function Input:set_xkb_config(xkb_config)
self.config_client:unary_request(build_grpc_request_params("SetXkbConfig", xkb_config))
end
---Set the keyboard's repeat rate and delay.
---
---@param rate integer The time between repeats, in milliseconds
---@param delay integer The duration a key needs to be held down before repeating starts, in milliseconds
function Input:set_repeat_rate(rate, delay)
self.config_client:unary_request(build_grpc_request_params("SetRepeatRate", {
rate = rate,
delay = delay,
}))
end end
function input.new(config_client) function input.new(config_client)

View file

@ -1,7 +0,0 @@
local cqueues = require("cqueues")
local loop = {
loop = cqueues.new(),
}
return loop

View file

@ -0,0 +1,165 @@
---The protobuf absolute path prefix
local prefix = "pinnacle.output." .. require("pinnacle").version .. "."
local service = prefix .. "OutputService"
---@type table<string, { request_type: string?, response_type: string? }>
---@enum (key) OutputServiceMethod
local rpc_types = {
SetLocation = {},
ConnectForAll = {
response_type = "ConnectForAllResponse",
},
Get = {
response_type = "GetResponse",
},
GetProperties = {
response_type = "GetPropertiesResponse",
},
}
---Build GrpcRequestParams
---@param method OutputServiceMethod
---@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,
response_type = resp_type and prefix .. resp_type,
data = data,
}
end
---@class OutputHandleModule
local output_handle = {}
---@class OutputHandle
---@field private config_client Client
---@field name string The unique name of this output
local OutputHandle = {}
---@class OutputModule
---@field private handle OutputHandleModule
local output = {}
output.handle = output_handle
---@class Output
---@field private config_client Client
local Output = {}
---Get all outputs.
---
---@return OutputHandle[]
function Output:get_all()
local response = self.config_client:unary_request(build_grpc_request_params("Get", {}))
---@type OutputHandle[]
local handles = {}
for _, output_name in pairs(response.output_names) do
table.insert(handles, output_handle.new(self.config_client, output_name))
end
return handles
end
---@param name string The name of the port the output is connected to
---@return OutputHandle | nil
function Output:get_by_name(name)
local handles = self:get_all()
for _, handle in pairs(handles) do
if handle.name == name then
return handle
end
end
return nil
end
---@return OutputHandle | nil
function Output:get_focused()
local handles = self:get_all()
for _, handle in pairs(handles) do
if handle:props().focused then
return handle
end
end
return nil
end
---@param callback fun(output: OutputHandle)
function Output:connect_for_all(callback)
self.config_client:server_streaming_request(build_grpc_request_params("ConnectForAll", {}), function(response)
local output_name = response.output_name
local handle = output_handle.new(self.config_client, output_name)
callback(handle)
end)
end
---@param loc { x: integer?, y: integer? }
function OutputHandle:set_location(loc)
self.config_client:unary_request(build_grpc_request_params("SetLocation", {
output_name = self.name,
x = loc.x,
y = loc.y,
}))
end
---@class OutputProperties
---@field make string?
---@field model string?
---@field x integer?
---@field y integer?
---@field pixel_width integer?
---@field pixel_height integer?
---@field refresh_rate integer?
---@field physical_width integer?
---@field physical_height integer?
---@field focused boolean?
---@field tags TagHandle[]
---Get all properties of this output.
---@return OutputProperties
function OutputHandle:props()
local response =
self.config_client:unary_request(build_grpc_request_params("GetProperties", { output_name = self.name }))
local handles = require("pinnacle.tag").handle.new_from_table(self.config_client, response.tag_ids)
response.tags = handles
response.tag_ids = nil
return response
end
---@return Output
function output.new(config_client)
---@type Output
local self = {
config_client = config_client,
}
setmetatable(self, { __index = Output })
return self
end
---Create a new `OutputHandle` from its raw name.
---@param output_name string
function output_handle.new(config_client, output_name)
---@type OutputHandle
local self = {
config_client = config_client,
name = output_name,
}
setmetatable(self, { __index = OutputHandle })
return self
end
return output

View file

@ -0,0 +1,289 @@
---The protobuf absolute path prefix
local prefix = "pinnacle.tag." .. require("pinnacle").version .. "."
local service = prefix .. "TagService"
---@type table<string, { request_type: string?, response_type: string? }>
---@enum (key) TagServiceMethod
local rpc_types = {
SetActive = {},
SwitchTo = {},
Add = {
response_type = "AddResponse",
},
Remove = {},
SetLayout = {},
Get = {
response_type = "GetResponse",
},
GetProperties = {
response_type = "GetPropertiesResponse",
},
}
---Build GrpcRequestParams
---@param method TagServiceMethod
---@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,
response_type = resp_type and prefix .. resp_type,
data = data,
}
end
---@class TagHandleModule
local tag_handle = {}
---@class TagHandle
---@field private config_client Client
---@field id integer
local TagHandle = {}
---@class TagModule
---@field private handle TagHandleModule
local tag = {}
tag.handle = tag_handle
---@class Tag
---@field private config_client Client
local Tag = {}
---Get all tags across all outputs.
---
---@return TagHandle[]
function Tag:get_all()
local response = self.config_client:unary_request(build_grpc_request_params("Get", {}))
---@type TagHandle[]
local handles = {}
for _, id in pairs(response.tag_ids) do
table.insert(handles, tag_handle.new(self.config_client, id))
end
return handles
end
---Add tags with the given names to the specified output.
---
---Returns handles to the created tags.
---
---@param output OutputHandle
---@param ... string
---
---@return TagHandle[]
---
---@overload fun(output: OutputHandle, tag_names: string[])
function Tag:add(output, ...)
local tag_names = { ... }
if type(tag_names[1]) == "table" then
tag_names = tag_names[1] --[=[@as string[]]=]
end
local response = self.config_client:unary_request(build_grpc_request_params("Add", {
output_name = output.name,
tag_names = tag_names,
}))
---@type TagHandle[]
local handles = {}
for _, id in pairs(response.tag_ids) do
table.insert(handles, tag_handle.new(self.config_client, id))
end
return handles
end
---Remove the given tags.
---
---@param tags TagHandle[]
function Tag:remove(tags)
---@type integer[]
local ids = {}
for _, tg in pairs(tags) do
table.insert(ids, tg.id)
end
self.config_client:unary_request(build_grpc_request_params("Remove", { tag_ids = ids }))
end
---@class LayoutCycler
---@field next fun(output: OutputHandle)
---@field prev fun(output: OutputHandle)
--- TODO: docs
---@param layouts Layout[]
---
---@return LayoutCycler
function Tag:new_layout_cycler(layouts)
local indices = {}
if #layouts == 0 then
return {
next = function(_) end,
prev = function(_) end,
}
end
---@type LayoutCycler
return {
next = function(output)
local tags = output:props().tags
for _, tg in pairs(tags) do
if tg:props().active then
local id = tg.id
if #layouts == 1 then
indices[id] = 1
elseif indices[id] == nil then
indices[id] = 2
else
if indices[id] + 1 > #layouts then
indices[id] = 1
else
indices[id] = indices[id] + 1
end
end
tg:set_layout(layouts[indices[id]])
break
end
end
end,
prev = function(output)
local tags = output:props().tags
for _, tg in pairs(tags) do
if tg:props().active then
local id = tg.id
if #layouts == 1 then
indices[id] = 1
elseif indices[id] == nil then
indices[id] = #layouts - 1
else
if indices[id] - 1 < 1 then
indices[id] = #layouts
else
indices[id] = indices[id] - 1
end
end
tg:set_layout(layouts[indices[id]])
break
end
end
end,
}
end
---Remove this tag.
function TagHandle:remove()
self.config_client:unary_request(build_grpc_request_params("Remove", { tag_ids = { self.id } }))
end
---@enum (key) Layout
local _layouts = {
master_stack = 1,
dwindle = 2,
spiral = 3,
corner_top_left = 4,
corner_top_right = 5,
corner_bottom_left = 6,
corner_bottom_right = 7,
}
---@param layout Layout
function TagHandle:set_layout(layout)
local layout = _layouts[layout]
self.config_client:unary_request(build_grpc_request_params("SetLayout", {
tag_id = self.id,
layout = layout,
}))
end
---Activate this tag and deactivate all other ones on the same output.
function TagHandle:switch_to()
self.config_client:unary_request(build_grpc_request_params("SwitchTo", { tag_id = self.id }))
end
---Set whether or not this tag is active.
---
---@param active boolean
function TagHandle:set_active(active)
self.config_client:unary_request(build_grpc_request_params("SetActive", { tag_id = self.id, set = active }))
end
---Toggle this tag's active state.
function TagHandle:toggle_active()
self.config_client:unary_request(build_grpc_request_params("SetActive", { tag_id = self.id, toggle = {} }))
end
---@class TagProperties
---@field active boolean?
---@field name string?
---@field output OutputHandle?
---Get all properties of this tag.
---
---@return TagProperties
function TagHandle:props()
local response = self.config_client:unary_request(build_grpc_request_params("GetProperties", { tag_id = self.id }))
return {
active = response.active,
name = response.name,
output = response.output_name
and require("pinnacle.output").handle.new(self.config_client, response.output_name),
}
end
---@return Tag
function tag.new(config_client)
---@type Tag
local self = {
config_client = config_client,
}
setmetatable(self, { __index = Tag })
return self
end
---Create a new `TagHandle` from an id.
---@param config_client Client
---@param tag_id integer
---@return TagHandle
function tag_handle.new(config_client, tag_id)
---@type TagHandle
local self = {
config_client = config_client,
id = tag_id,
}
setmetatable(self, { __index = TagHandle })
return self
end
---@param config_client Client
---@param tag_ids integer[]
---@return TagHandle[]
function tag_handle.new_from_table(config_client, tag_ids)
---@type TagHandle[]
local handles = {}
for _, id in pairs(tag_ids) do
table.insert(handles, tag_handle.new(config_client, id))
end
return handles
end
return tag