Add docs to new Lua API

1000% sure I'm gonna have to rewrite my LDoc generation tool to actually work
This commit is contained in:
Ottatop 2024-01-14 18:01:41 -06:00
parent 7fe56ed949
commit a8239f171e
11 changed files with 910 additions and 127 deletions

View file

@ -12,47 +12,46 @@ require("pinnacle").setup(function(Pinnacle)
local terminal = "alacritty"
Input:set_mousebind({ mod_key }, "left", "press", function()
Window:begin_move("left")
Input:mousebind({ mod_key }, "btn_left", "press", function()
Window:begin_move("btn_left")
end)
Input:set_mousebind({ mod_key }, "right", "press", function()
Window:begin_resize("right")
Input:mousebind({ mod_key }, "btn_right", "press", function()
Window:begin_resize("btn_right")
end)
------
Input:set_keybind({ mod_key, "alt" }, "q", function()
print("GOT QUIT")
Input:keybind({ mod_key, "alt" }, "q", function()
Pinnacle:quit()
end)
Input:set_keybind({ mod_key, "alt" }, "c", function()
Input:keybind({ mod_key, "alt" }, "c", function()
local focused = Window:get_focused()
if focused then
focused:close()
end
end)
Input:set_keybind({ mod_key }, key.Return, function()
Input:keybind({ mod_key }, key.Return, function()
Process:spawn(terminal)
end)
Input:set_keybind({ mod_key, "alt" }, key.space, function()
Input:keybind({ mod_key, "alt" }, key.space, function()
local focused = Window:get_focused()
if focused then
focused:toggle_floating()
end
end)
Input:set_keybind({ mod_key }, "f", function()
Input:keybind({ mod_key }, "f", function()
local focused = Window:get_focused()
if focused then
focused:toggle_fullscreen()
end
end)
Input:set_keybind({ mod_key }, "m", function()
Input:keybind({ mod_key }, "m", function()
local focused = Window:get_focused()
if focused then
focused:toggle_maximized()
@ -78,14 +77,14 @@ require("pinnacle").setup(function(Pinnacle)
"corner_bottom_right",
})
Input:set_keybind({ mod_key }, key.space, function()
Input:keybind({ mod_key }, key.space, function()
local focused_op = Output:get_focused()
if focused_op then
layout_cycler.next(focused_op)
end
end)
Input:set_keybind({ mod_key, "shift" }, key.space, function()
Input:keybind({ mod_key, "shift" }, key.space, function()
local focused_op = Output:get_focused()
if focused_op then
layout_cycler.prev(focused_op)
@ -94,19 +93,19 @@ require("pinnacle").setup(function(Pinnacle)
for _, tag_name in ipairs(tag_names) do
-- nil-safety: tags are guaranteed to be on the outputs due to connect_for_all above
Input:set_keybind({ mod_key }, tag_name, function()
Input:keybind({ mod_key }, tag_name, function()
Tag:get(tag_name):switch_to()
end)
Input:set_keybind({ mod_key, "shift" }, tag_name, function()
Input:keybind({ mod_key, "shift" }, tag_name, function()
Tag:get(tag_name):toggle_active()
end)
Input:set_keybind({ mod_key, "alt" }, tag_name, function()
Input:keybind({ mod_key, "alt" }, tag_name, function()
local focused = Window:get_focused()
if focused then
focused:move_to_tag(Tag:get(tag_name) --[[@as TagHandle]])
end
end)
Input:set_keybind({ mod_key, "shift", "alt" }, tag_name, function()
Input:keybind({ mod_key, "shift", "alt" }, tag_name, function()
local focused = Window:get_focused()
if focused then
focused:toggle_tag(Tag:get(tag_name) --[[@as TagHandle]])

View file

@ -68,23 +68,13 @@ function Client:unary_request(grpc_request_params)
stream:write_headers(create_request_headers(service, method), false)
stream:write_chunk(body, true)
-- for chunk in stream:each_chunk() do
-- print(chunk, ":", pb.tohex(chunk))
-- os.exit(1)
-- end
local response_headers = stream:get_headers()
-- TODO: check headers for errors
local response_body = stream:get_next_chunk()
print("unary body", response_body, "end")
print(pb.tohex(response_body))
print("--------------------------------")
local trailers = stream:get_headers()
print("trailers", trailers)
print("------------------------------")
if trailers then
if trailers then -- idk if im big dummy or not but there are never any trailers
for name, value, never_index in trailers:each() do
print(name, value, never_index)
end
@ -134,18 +124,10 @@ function Client:server_streaming_request(grpc_request_params, callback)
stream:write_chunk(body, true)
local response_headers = stream:get_headers()
for name, value, never_index in response_headers:each() do
print(name, value, never_index)
end
-- 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
print("stream chunk", response_body, "end")
print(pb.tohex(response_body))
print("-----------------------------------")
-- Skip the 1-byte compressed flag and the 4-byte message length
local response_body = response_body:sub(6)

View file

@ -32,6 +32,8 @@ function protobuf.build_protos()
pinnacle_pb:close()
assert(pb.load(pinnacle_pb_data), "failed to load .pb file")
pb.option("enum_as_value")
end
return protobuf

View file

@ -34,6 +34,8 @@ local function build_grpc_request_params(method, data)
}
end
-- This is an @enum and not an @alias because with an @alias the completion replaces tables with a string,
-- which is annoying
---@enum (key) Modifier
local modifier_values = {
shift = 1,
@ -42,7 +44,6 @@ local modifier_values = {
super = 4,
}
---@enum (key) MouseButton
local mouse_button_values = {
--- Left
[1] = 0x110,
@ -58,20 +59,38 @@ local mouse_button_values = {
[6] = 0x115,
--- Back
[7] = 0x116,
left = 0x110,
right = 0x111,
middle = 0x112,
side = 0x113,
extra = 0x114,
forward = 0x115,
back = 0x116,
btn_left = 0x110,
btn_right = 0x111,
btn_middle = 0x112,
btn_side = 0x113,
btn_extra = 0x114,
btn_forward = 0x115,
btn_back = 0x116,
}
-- This alias is because I can't get @enum completion to work
---@alias MouseButton
---| 1 Left
---| 2 Right
---| 3 Middle
---| 4 Side
---| 5 Extra
---| 6 Forward
---| 7 Back,
---| "btn_left"
---| "btn_right"
---| "btn_middle"
---| "btn_side"
---| "btn_extra"
---| "btn_forward"
---| "btn_back"
---@enum (key) MouseEdge
local mouse_edge_values = {
press = 1,
release = 2,
}
---@alias MouseEdge
---| "press" Trigger on mouse button press
---| "release" Trigger on mouse button release
---@class InputModule
---@field private btn table
@ -84,10 +103,44 @@ local Input = {
key = require("pinnacle.input.keys"),
}
---@param mods Modifier[]
---@param key Key | string
---@param action fun()
function Input:set_keybind(mods, key, action)
---Set a keybind. If called with an already existing keybind, it gets replaced.
---
---You must provide three arguments:
---
--- - `mods`: An array of `Modifier`s. If you don't want any, provide an empty table.
--- - `key`: The key that will trigger `action`. You can provide three types of key:
--- - Something from the `Key` table in `Input.key`, which lists every xkbcommon key. The naming pattern is the xkbcommon key without the `KEY_` prefix, unless that would make it start with a number or the reserved lua keyword `function`, in which case the `KEY_` prefix is included.
--- - A single character representing your key. This can be something like "g", "$", "~", "1", and so on.
--- - A string of the key's name. This is the name of the xkbcommon key without the `KEY_` prefix.
--- - `action`: The function that will be run when the keybind is pressed.
---
---It is important to note that `"a"` is different than `"A"`. Similarly, `key.a` is different than `key.A`.
---Usually, it's best to use the non-modified key to prevent confusion and unintended behavior.
---
---```lua
---Input:keybind({ "shift" }, "a", function() end) -- This is preferred
---Input:keybind({ "shift" }, "A", function() end) -- over this
---
--- -- This keybind will only work with capslock on.
---Input:keybind({}, "A", function() end)
---
--- -- This keybind won't work at all because to get `@` you need to hold shift,
--- -- which this keybind doesn't accept.
---Input:keybind({ "ctrl" }, "@", function() end)
---```
---
---### Example
---```lua
--- -- Set `super + Return` to open Alacritty
---Input:keybind({ "super" }, Input.key.Return, function()
--- Process:spawn("alacritty")
---end)
---```
---
---@param mods Modifier[] The modifiers that need to be held down for the bind to trigger
---@param key Key | string The key used to trigger the bind
---@param action fun() The function to run when the bind is triggered
function Input:keybind(mods, key, action)
local raw_code = nil
local xkb_name = nil
@ -112,13 +165,23 @@ function Input:set_keybind(mods, key, action)
)
end
---Set a mousebind.
---Set a mousebind. If called with an already existing mousebind, it gets replaced.
---
---@param mods Modifier[]
---@param button MouseButton
---@param edge MouseEdge
---@param action fun()
function Input:set_mousebind(mods, button, edge, action)
---You must specify whether the keybind happens on button press or button release.
---
---### Example
---```lua
--- -- Set `super + left mouse button` to move a window on press
---Input:mousebind({ "super" }, "btn_left", "press", function()
--- Window:begin_move("btn_left")
---end)
---```
---
---@param mods Modifier[] The modifiers that need to be held down for the bind to trigger
---@param button MouseButton The mouse button used to trigger the bind
---@param edge MouseEdge "press" or "release" to trigger on button press or release
---@param action fun() The function to run when the bind is triggered
function Input:mousebind(mods, button, edge, action)
local edge = mouse_edge_values[edge]
local mod_values = {}
@ -145,15 +208,32 @@ end
---Set the xkbconfig for your keyboard.
---
---@param xkb_config XkbConfig
---Fields not present will be set to their default values.
---
---Read `xkeyboard-config(7)` for more information.
---
---### Example
---```lua
---Input:set_xkb_config({
--- layout = "us,fr,ge",
--- options = "ctrl:swapcaps,caps:shift"
---})
---```
---
---@param xkb_config XkbConfig The new 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
---### Example
---```lua
---Input:set_repeat_rate(100, 1000) -- Key must be held down for 1 second, then repeats 10 times per second.
---```
---
---@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,

View file

@ -54,6 +54,11 @@ local Output = {}
---Get all outputs.
---
---### Example
---```lua
---local outputs = Output:get_all()
---```
---
---@return OutputHandle[]
function Output:get_all()
local response = self.config_client:unary_request(build_grpc_request_params("Get", {}))
@ -68,7 +73,14 @@ function Output:get_all()
return handles
end
---@param name string The name of the port the output is connected to
---Get an output by its name (the connector it's plugged into).
---
---### Example
---```lua
---local output = Output:get_by_name("eDP-1")
---```
---
---@param name string The name of the connector the output is connected to
---@return OutputHandle | nil
function Output:get_by_name(name)
local handles = self:get_all()
@ -82,6 +94,15 @@ function Output:get_by_name(name)
return nil
end
---Get the currently focused output.
---
---This is currently defined as the most recent one that has had pointer motion.
---
---### Example
---```lua
---local output = Output:get_focused()
---```
---
---@return OutputHandle | nil
function Output:get_focused()
local handles = self:get_all()
@ -95,6 +116,25 @@ function Output:get_focused()
return nil
end
---Connect a function to be run with all current and future outputs.
---
---This method does two things:
---1. Immediately runs `callback` with all currently connected outputs.
---2. Calls `callback` whenever a new output is plugged in.
---
---This will *not* run `callback` with an output that has been unplugged and replugged
---to prevent duplicate setup. Instead, the compositor keeps track of the tags and other
---state associated with that output and restores it when replugged.
---
---### Example
---```lua
--- -- Add tags "1" through "5" to all outputs
---Output:connect_for_all(function(output)
--- local tags = Tag:add(output, "1", "2", "3", "4", "5")
--- tags[1]:toggle_active()
---end)
---```
---
---@param callback fun(output: OutputHandle)
function Output:connect_for_all(callback)
local handles = self:get_all()
@ -109,7 +149,39 @@ function Output:connect_for_all(callback)
end)
end
---Set the location of this output in the global space.
---
---On startup, Pinnacle will lay out all connected outputs starting at (0, 0)
---and going to the right, with their top borders aligned.
---
---This method allows you to move outputs where necessary.
---
---Note: If you have space between two outputs when setting their locations,
---the pointer will not be able to move between them.
---
---### Example
---```lua
--- -- Assume two monitors in order, "DP-1" and "HDMI-1", with the following dimensions:
--- -- - "DP-1": ┌─────┐
--- -- │ │1920x1080
--- -- └─────┘
--- -- - "HDMI-1": ┌───────┐
--- -- │ 2560x │
--- -- │ 1440 │
--- -- └───────┘
---Output:get_by_name("DP-1"):set_location({ x = 0, y = 0 })
---Output:get_by_name("HDMI-1"):set_location({ x = 1920, y = -360 })
--- -- Results in:
--- -- ┌───────┐
--- -- ┌─────┤ │
--- -- │DP-1 │HDMI-1 │
--- -- └─────┴───────┘
--- -- Notice that x = 0 aligns with the top of "DP-1", and the top of "HDMI-1" is at x = -360.
---```
---
---@param loc { x: integer?, y: integer? }
---
---@see OutputHandle.set_loc_adj_to
function OutputHandle:set_location(loc)
self.config_client:unary_request(build_grpc_request_params("SetLocation", {
output_name = self.name,
@ -119,19 +191,47 @@ function OutputHandle:set_location(loc)
end
---@alias Alignment
---| "top_align_left"
---| "top_align_center"
---| "top_align_right"
---| "bottom_align_left"
---| "bottom_align_center"
---| "bottom_align_right"
---| "left_align_top"
---| "left_align_center"
---| "left_align_bottom"
---| "right_align_top"
---| "right_align_center"
---| "right_align_bottom"
---| "top_align_left" Set above, align left borders
---| "top_align_center" Set above, align centers
---| "top_align_right" Set above, align right borders
---| "bottom_align_left" Set below, align left borders
---| "bottom_align_center" Set below, align centers
---| "bottom_align_right" Set below, align right border
---| "left_align_top" Set to left, align top borders
---| "left_align_center" Set to left, align centers
---| "left_align_bottom" Set to left, align bottom borders
---| "right_align_top" Set to right, align top borders
---| "right_align_center" Set to right, align centers
---| "right_align_bottom" Set to right, align bottom borders
---Set the location of this output adjacent to another one.
---
---`alignment` is how you want this output to be placed.
---For example, "top_align_left" will place this output above `other` and align the left borders.
---Similarly, "right_align_center" will place this output to the right of `other` and align their centers.
---
---### Example
---```lua
--- -- Assume two monitors in order, "DP-1" and "HDMI-1", with the following dimensions:
--- -- - "DP-1": ┌─────┐
--- -- │ │1920x1080
--- -- └─────┘
--- -- - "HDMI-1": ┌───────┐
--- -- │ 2560x │
--- -- │ 1440 │
--- -- └───────┘
---Output:get_by_name("DP-1"):set_loc_adj_to(Output:get_by_name("HDMI-1"), "bottom_align_right")
--- -- Results in:
--- -- ┌───────┐
--- -- │ │
--- -- │HDMI-1 │
--- -- └──┬────┤
--- -- │DP-1│
--- -- └────┘
--- -- Notice that "DP-1" now has the coordinates (2280, 1440) because "DP-1" is getting moved, not "HDMI-1".
--- -- "HDMI-1" was placed at (1920, 0) during the compositor's initial output layout.
---```
---
---@param other OutputHandle
---@param alignment Alignment
function OutputHandle:set_loc_adj_to(other, alignment)
@ -203,15 +303,16 @@ end
---@field physical_width integer?
---@field physical_height integer?
---@field focused boolean?
---@field tags TagHandle[]
---@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)
local handles = require("pinnacle.tag").handle.new_from_table(self.config_client, response.tag_ids or {})
response.tags = handles
response.tag_ids = nil
@ -219,6 +320,113 @@ function OutputHandle:props()
return response
end
---Get this output's make.
---
---Note: make and model detection are currently somewhat iffy and may not work.
---
---Shorthand for `handle:props().make`.
---
---@return string?
function OutputHandle:make()
return self:props().make
end
---Get this output's model.
---
---Note: make and model detection are currently somewhat iffy and may not work.
---
---Shorthand for `handle:props().model`.
---
---@return string?
function OutputHandle:model()
return self:props().model
end
---Get this output's x-coordinate in the global space.
---
---Shorthand for `handle:props().x`.
---
---@return integer?
function OutputHandle:x()
return self:props().x
end
---Get this output's y-coordinate in the global space.
---
---Shorthand for `handle:props().y`.
---
---@return integer?
function OutputHandle:y()
return self:props().y
end
---Get this output's width in pixels.
---
---Shorthand for `handle:props().pixel_width`.
---
---@return integer?
function OutputHandle:pixel_width()
return self:props().pixel_width
end
---Get this output's height in pixels.
---
---Shorthand for `handle:props().pixel_height`.
---
---@return integer?
function OutputHandle:pixel_height()
return self:props().pixel_height
end
---Get this output's refresh rate in millihertz.
---
---For example, 144Hz is returned as 144000.
---
---Shorthand for `handle:props().refresh_rate`.
---
---@return integer?
function OutputHandle:refresh_rate()
return self:props().refresh_rate
end
---Get this output's physical width in millimeters.
---
---Shorthand for `handle:props().physical_width`.
---
---@return integer?
function OutputHandle:physical_width()
return self:props().physical_width
end
---Get this output's physical height in millimeters.
---
---Shorthand for `handle:props().physical_height`.
---
---@return integer?
function OutputHandle:physical_height()
return self:props().physical_height
end
---Get whether or not this output is focused.
---
---The focused output is currently implemented as the one that last had pointer motion.
---
---Shorthand for `handle:props().focused`.
---
---@return boolean?
function OutputHandle:focused()
return self:props().focused
end
---Get the tags this output has.
---
---Shorthand for `handle:props().tags`.
---
---@return TagHandle[]?
function OutputHandle:tags()
return self:props().tags
end
---@return Output
function output.new(config_client)
---@type Output

View file

@ -67,8 +67,21 @@ local function spawn_inner(config_client, args, callbacks, once)
)
end
---@param args string | string[]
---@param callbacks { stdout: fun(line: string)?, stderr: fun(line: string)?, exit: fun(code: integer, msg: string)? }?
---Spawn a program with optional callbacks for its stdout, stderr, and exit information.
---
---`callbacks` is an optional table with the following optional fields:
--- - `stdout`: function(line: string)
--- - `stderr`: function(line: string)
--- - `exit`: function(code: integer, msg: string)
---
---Note: if `args` is a string then it will be wrapped in a table and sent to the compositor.
---If you need multiple arguments, use a string array instead.
---
---Note 2: If you spawn a window before tags are added it will spawn without any tags and
---won't be displayed in the compositor. TODO: Do what awesome does and display on all tags instead
---
---@param args string | string[] The program arguments; a string instead of an array should be for only 1 argument
---@param callbacks { stdout: fun(line: string)?, stderr: fun(line: string)?, exit: fun(code: integer, msg: string)? }? Callbacks that will be run whenever the program outputs to stdout, stderr, or exits.
function Process:spawn(args, callbacks)
if type(args) == "string" then
args = { args }
@ -77,8 +90,12 @@ function Process:spawn(args, callbacks)
spawn_inner(self.config_client, args, callbacks, false)
end
---Like `Process:spawn` but will only spawn the program if it isn't already running.
---
---@param args string | string[]
---@param callbacks { stdout: fun(line: string)?, stderr: fun(line: string)?, exit: fun(code: integer, msg: string)? }?
---
---@see Process.spawn
function Process:spawn_once(args, callbacks)
if type(args) == "string" then
args = { args }

View file

@ -71,8 +71,24 @@ function Tag:get_all()
return handles
end
---Get the tag with the given name and output.
---
---If `output` is not specified, this uses the focused output.
---
---If an output has more than one tag with the same name, this returns the first.
---
---### Example
---```lua
--- -- Get tags on the focused output
---local tag = Tag:get("Tag")
---
--- -- Get tags on a specific output
---local tag_on_hdmi1 = Tag:get("Tag", Output:get_by_name("HDMI-1"))
---```
---
---@param name string
---@param output OutputHandle?
---
---@return TagHandle | nil
function Tag:get(name, output)
output = output or require("pinnacle.output").new(self.config_client):get_focused()
@ -97,10 +113,19 @@ end
---
---Returns handles to the created tags.
---
---### Example
---```lua
---local tags = Tag:add(Output:get_by_name("HDMI-1"), "1", "2", "Buckle", "Shoe")
---
--- -- With a table
---local tag_names = { "1", "2", "Buckle", "Shoe" }
---local tags = Tag:add(Output:get_by_name("HDMI-1"), tag_names)
---```
---
---@param output OutputHandle
---@param ... string
---
---@return TagHandle[]
---@return TagHandle[] tags Handles to the created tags
---
---@overload fun(self: self, output: OutputHandle, tag_names: string[])
function Tag:add(output, ...)
@ -126,6 +151,13 @@ end
---Remove the given tags.
---
---### Example
---```lua
---local tags = Tag:add(Output:get_by_name("HDMI-1"), "1", "2", "Buckle", "Shoe")
---
---Tag:remove(tags) -- "HDMI-1" no longer has those tags
---```
---
---@param tags TagHandle[]
function Tag:remove(tags)
---@type integer[]
@ -139,10 +171,44 @@ function Tag:remove(tags)
end
---@class LayoutCycler
---@field next fun(output: OutputHandle)
---@field prev fun(output: OutputHandle)
---@field next fun(output: OutputHandle?)
---@field prev fun(output: OutputHandle?)
--- TODO: docs
---Create a layout cycler that will cycle layouts on the given output.
---
---This returns a `LayoutCycler` table with two fields, both functions that take in an optional `OutputHandle`:
--- - `next`: Cycle to the next layout on the given output
--- - `prev`: Cycle to the previous layout on the given output
---
---If the output isn't specified then the focused one will be used.
---
---Internally, this will only change the layout of the first active tag on the output
---because that is the one that determines the layout.
---
---### Example
---```lua
--- ---@type LayoutCycler[]
---local layouts = {
--- "master_stack",
--- "dwindle",
--- "corner_top_left",
--- "corner_top_right".
---} -- Only cycle between these four layouts
---
---local layout_cycler = Tag:new_layout_cycler()
---
--- -- Assume the focused output starts with the "master_stack" layout
---layout_cycler.next() -- Layout is now "dwindle"
---layout_cycler.next() -- Layout is now "corner_top_left"
---layout_cycler.next() -- Layout is now "corner_top_right"
---layout_cycler.next() -- Layout is now "dwindle"
---layout_cycler.next() -- Layout is now "corner_top_right"
---
--- -- Cycling on another output
---layout_cycler.next(Output:get_by_name("eDP-1"))
---layout_cycler.prev(Output:get_by_name("HDMI-1"))
---```
---
---@param layouts Layout[]
---
---@return LayoutCycler
@ -159,6 +225,11 @@ function Tag:new_layout_cycler(layouts)
---@type LayoutCycler
return {
next = function(output)
local output = output or require("pinnacle.output").new(self.config_client):get_focused()
if not output then
return
end
local tags = output:props().tags
for _, tg in ipairs(tags) do
@ -182,6 +253,11 @@ function Tag:new_layout_cycler(layouts)
end
end,
prev = function(output)
local output = output or require("pinnacle.output").new(self.config_client):get_focused()
if not output then
return
end
local tags = output:props().tags
for _, tg in ipairs(tags) do
@ -209,11 +285,19 @@ function Tag:new_layout_cycler(layouts)
end
---Remove this tag.
---
---### Example
---```lua
---local tags = Tag:add(Output:get_by_name("HDMI-1"), "1", "2", "Buckle", "Shoe")
---
---tags[2]:remove()
---tags[4]:remove()
--- -- "HDMI-1" now only has tags "1" and "Buckle"
---```
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,
@ -223,7 +307,25 @@ local _layouts = {
corner_bottom_left = 6,
corner_bottom_right = 7,
}
---@alias Layout
---| "master_stack" # One master window on the left with all other windows stacked to the right.
---| "dwindle" # Windows split in half towards the bottom right corner.
---| "spiral" # Windows split in half in a spiral.
---| "corner_top_left" # One main corner window in the top left with a column of windows on the right and a row on the bottom.
---| "corner_top_right" # One main corner window in the top right with a column of windows on the left and a row on the bottom.
---| "corner_bottom_left" # One main corner window in the bottom left with a column of windows on the right and a row on the top.
---| "corner_bottom_right" # One main corner window in the bottom right with a column of windows on the left and a row on the top.
---Set this tag's layout.
---
---If this is the first active tag on its output, its layout will be used to tile windows.
---
---### Example
---```lua
--- -- Assume the focused output has tag "Tag"
---Tag:get("Tag"):set_layout("dwindle")
---```
---
---@param layout Layout
function TagHandle:set_layout(layout)
local layout = _layouts[layout]
@ -235,26 +337,57 @@ function TagHandle:set_layout(layout)
end
---Activate this tag and deactivate all other ones on the same output.
---
---### Example
---```lua
--- -- Assume the focused output has the following inactive tags and windows:
--- -- - "1": Alacritty
--- -- - "2": Firefox, Discord
--- -- - "3": Steam
---Tag:get("2"):switch_to() -- Displays Firefox and Discord
---Tag:get("3"):switch_to() -- Displays Steam
---```
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.
---
---### Example
---```lua
--- -- Assume the focused output has the following inactive tags and windows:
--- -- - "1": Alacritty
--- -- - "2": Firefox, Discord
--- -- - "3": Steam
---Tag:get("2"):set_active(true) -- Displays Firefox and Discord
---Tag:get("3"):set_active(true) -- Displays Firefox, Discord, and Steam
---Tag:get("2"):set_active(false) -- Displays Steam
---```
---
---@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.
---
---### Example
---```lua
--- -- Assume the focused output has the following inactive tags and windows:
--- -- - "1": Alacritty
--- -- - "2": Firefox, Discord
--- -- - "3": Steam
---Tag:get("2"):toggle_active() -- Displays Firefox and Discord
---Tag:get("2"):toggle_active() -- Displays nothing
---```
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?
---@field active boolean? Whether or not the tag is currently being displayed
---@field name string? The name of the tag
---@field output OutputHandle? The output the tag is on
---Get all properties of this tag.
---
@ -270,6 +403,33 @@ function TagHandle:props()
}
end
---Get whether or not this tag is being displayed.
---
---Shorthand for `handle:props().active`.
---
---@return boolean?
function TagHandle:active()
return self:props().active
end
---Get this tag's name.
---
---Shorthand for `handle:props().name`.
---
---@return string?
function TagHandle:name()
return self:props().name
end
---Get the output this tag is on.
---
---Shorthand for `handle:props().output`.
---
---@return OutputHandle?
function TagHandle:output()
return self:props().output
end
---@return Tag
function tag.new(config_client)
---@type Tag

View file

@ -60,7 +60,14 @@ local Window = {}
---Get all windows.
---
---@return WindowHandle[]
---### Example
---```lua
---local windows = Window:get_all()
---for _, window in ipairs(windows) do
--- print(window:props().class)
---end
---```
---@return WindowHandle[] windows Handles to all windows
function Window:get_all()
local response = self.config_client:unary_request(build_grpc_request_params("Get", {}))
@ -69,7 +76,16 @@ function Window:get_all()
return handles
end
---@return WindowHandle | nil
---Get the currently focused window.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- print(focused:props().class)
---end
---```
---@return WindowHandle | nil window A handle to the currently focused window
function Window:get_focused()
local handles = self:get_all()
@ -82,15 +98,35 @@ function Window:get_focused()
return nil
end
--- TODO: docs
---@param button MouseButton
---Begin moving this window using the specified mouse button.
---
---The button must be pressed at the time this method is called.
---If the button is lifted, the move will end.
---
---### Example
---```lua
---Input:mousebind({ "super" }, "btn_left", function()
--- Window:begin_move("btn_left")
---end)
---```
---@param button MouseButton The button that will initiate the move
function Window:begin_move(button)
local button = require("pinnacle.input").btn[button]
self.config_client:unary_request(build_grpc_request_params("MoveGrab", { button = button }))
end
--- TODO: docs
---@param button MouseButton
---Begin resizing this window using the specified mouse button.
---
---The button must be pressed at the time this method is called.
---If the button is lifted, the resize will end.
---
---### Example
---```lua
---Input:mousebind({ "super" }, "btn_right", function()
--- Window:begin_resize("btn_right")
---end)
---```
---@param button MouseButton The button that will initiate the resize
function Window:begin_resize(button)
local button = require("pinnacle.input").btn[button]
self.config_client:unary_request(build_grpc_request_params("ResizeGrab", { button = button }))
@ -126,7 +162,95 @@ local _fullscreen_or_maximized_keys = {
[3] = "maximized",
}
---@param rule { cond: WindowRuleCondition, rule: WindowRule }
---Add a window rule.
---
---A window rule defines what properties a window will spawn with given certain conditions.
---For example, if Firefox is spawned, you can set it to open on a specific tag.
---
---This method takes in a table with two keys:
---
--- - `cond`: The condition for `rule` to apply to a new window.
--- - `rule`: What gets applied to the new window if `cond` is true.
---
---There are some important mechanics you should know when using window rules:
---
--- - All children inside an `all` block must be true for the block to be true.
--- - At least one child inside an `any` block must be true for the block to be true.
--- - The outermost block of a window rule condition is implicitly an `all` block.
--- - Within an `all` block, all items in each array must be true for the attribute to be true.
--- - Within an `any` block, only one item in each array needs to be true for the attribute to be true.
---
---`cond` can be a bit confusing and quite table heavy. Examples are shown below for guidance.
---
---### Examples
---```lua
--- -- A simple window rule. This one will cause Firefox to open on tag "Browser".
---Window:add_window_rule({
--- cond = { classes = { "firefox" } },
--- rule = { tags = { "Browser" } },
---})
---
--- -- To apply rules when *all* provided conditions are true, use `all`.
--- -- `all` takes an array of conditions and checks if all are true.
--- -- The following will open Steam fullscreen only if it opens on tag "5".
---Window:add_window_rule({
--- cond = {
--- all = {
--- {
--- class = "steam",
--- tag = Tag:get("5"),
--- }
--- }
--- },
--- rule = { fullscreen_or_maximized = "fullscreen" },
---})
---
--- -- The outermost block of a `cond` is implicitly an `all` block.
--- -- Thus, the above can be shortened to:
---Window:add_window_rule({
--- cond = {
--- class = "steam",
--- tag = Tag:get("5"),
--- },
--- rule = { fullscreen_or_maximized = "fullscreen" },
---})
---
--- -- `any` also exists to allow at least one provided condition to match.
--- -- The following will open either xterm or Alacritty floating.
---Window:add_window_rule({
--- cond = {
--- any = { { classes = { "xterm", "Alacritty" } } }
--- },
--- rule = { floating = true },
---})
---
--- -- You can arbitrarily nest `any` and `all` to achieve desired logic.
--- -- The following will open Discord, Thunderbird, or Firefox floating if they
--- -- open on either *all* of tags "A", "B", and "C" or both tags "1" and "2".
---Window:add_window_rule({
--- cond = {
--- all = { -- This `all` block is needed because the outermost block cannot be an array.
--- { any = {
--- { class = { "firefox", "thunderbird", "discord" } }
--- } },
--- { any = {
--- -- Because `tag` is inside an `all` block,
--- -- the window must have all these tags for this to be true.
--- -- If it was in an `any` block, only one tag would need to match.
--- { all = {
--- { tag = { "A", "B", "C" } }
--- } },
--- { all = {
--- { tag = { "1", "2" } }
--- } },
--- } }
--- }
--- },
--- rule = { floating = true },
---})
---```
---
---@param rule { cond: WindowRuleCondition, rule: WindowRule } The condition and rule
function Window:add_window_rule(rule)
if rule.cond.tags then
local ids = {}
@ -159,18 +283,57 @@ function Window:add_window_rule(rule)
end
---Send a close request to this window.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then focused:close() end
---```
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? }
---The coordinate system has the following axes:
---```
--- ^ -y
--- |
--- -x <--+--> +x
--- |
--- v +y
---```
---
---*Tiled windows will not reflect these changes.*
---This method only applies to this window's floating geometry.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:set_floating(true) -- `set_geometry` only applies to floating geometry.
---
--- focused:set_geometry({ x = 50, y = 300 }) -- Move this window to (50, 300)
--- focused:set_geometry({ y = 0, height = 1080 }) -- Move this window to y = 0 and make its height 1080 pixels
--- focused:set_geometry({}) -- Do nothing useful
---end
---```
---@param geo { x: integer?, y: integer, width: integer?, height: integer? } The new location and/or size
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.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:set_fullscreen(true)
--- focused:set_fullscreen(false)
---end
---```
---
---@param fullscreen boolean
function WindowHandle:set_fullscreen(fullscreen)
self.config_client:unary_request(
@ -178,35 +341,114 @@ function WindowHandle:set_fullscreen(fullscreen)
)
end
---Toggle this window to and from fullscreen.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:toggle_fullscreen()
---end
---```
function WindowHandle:toggle_fullscreen()
self.config_client:unary_request(build_grpc_request_params("SetFullscreen", { window_id = self.id, toggle = {} }))
end
---Set this window to maximized or not.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:set_maximized(true)
--- focused:set_maximized(false)
---end
---```
---
---@param maximized boolean
function WindowHandle:set_maximized(maximized)
self.config_client:unary_request(
build_grpc_request_params("SetMaximized", { window_id = self.id, set = maximized })
)
end
---Toggle this window to and from maximized.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:toggle_maximized()
---end
---```
function WindowHandle:toggle_maximized()
self.config_client:unary_request(build_grpc_request_params("SetMaximized", { window_id = self.id, toggle = {} }))
end
---Set this window to floating or not.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:set_floating(true)
--- focused:set_floating(false)
---end
---```
---
---@param floating boolean
function WindowHandle:set_floating(floating)
self.config_client:unary_request(build_grpc_request_params("SetFloating", { window_id = self.id, set = floating }))
end
---Toggle this window to and from floating.
---
---### Example
---```lua
---local focused = Window:get_focused()
---if focused then
--- focused:toggle_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
---Move this window to the specified tag.
---
---This will remove all tags from this window and tag it with `tag`.
---
---### Example
---```lua
--- -- Assume the focused output has the tag "Tag"
---local focused = Window:get_focused()
---if focused then
--- focused:move_to_tag(Tag:get("Tag"))
---end
---```
---
---@param tag TagHandle The tag to move this window to
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
---
---### Example
---```lua
--- -- Assume the focused output has the tag "Tag"
---local focused = Window:get_focused()
---if focused then
--- local tag = Tag:get("Tag")
---
--- focused:set_tag(tag, true)
--- -- `focused` now has tag "Tag"
--- focused:set_tag(tag, false)
--- -- `focused` no longer has tag "Tag"
---end
---```
---
---@param tag TagHandle The tag to set or unset
---@param set boolean
function WindowHandle:set_tag(tag, set)
self.config_client:unary_request(
@ -215,7 +457,23 @@ function WindowHandle:set_tag(tag, set)
end
---Toggle the given tag on this window.
---@param tag TagHandle
---
---### Example
---```lua
--- -- Assume the focused output has the tag "Tag"
---local focused = Window:get_focused()
---if focused then
--- local tag = Tag:get("Tag")
--- focused:set_tag(tag, false)
---
--- focused:toggle_tag(tag)
--- -- `focused` now has tag "Tag"
--- focused:toggle_tag(tag)
--- -- `focused` no longer has tag "Tag"
---end
---```
---
---@param tag TagHandle The tag to toggle
function WindowHandle:toggle_tag(tag)
self.config_client:unary_request(
build_grpc_request_params("SetTag", { window_id = self.id, tag_id = tag.id, toggle = {} })
@ -223,13 +481,16 @@ function WindowHandle:toggle_tag(tag)
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?
---@field geometry { x: integer?, y: integer?, width: integer?, height: integer? }? The location and size of the window
---@field class string? The window's class
---@field title string? The window's title
---@field focused boolean? Whether or not the window is focused
---@field floating boolean? Whether or not the window is floating
---@field fullscreen_or_maximized FullscreenOrMaximized? Whether the window is fullscreen, maximized, or neither
---@field tags TagHandle[]? The tags the window has
---Get all the properties of this window.
---
---@return WindowProperties
function WindowHandle:props()
local response =
@ -237,9 +498,76 @@ function WindowHandle:props()
response.fullscreen_or_maximized = _fullscreen_or_maximized_keys[response.fullscreen_or_maximized]
response.tags = response.tag_ids
and require("pinnacle.tag").handle.new_from_table(self.config_client, response.tag_ids)
response.tag_ids = nil
return response
end
---Get this window's location and size.
---
---Shorthand for `handle:props().geometry`.
---
---@return { x: integer?, y: integer?, width: integer?, height: integer? }?
function WindowHandle:geometry()
return self:props().geometry
end
---Get this window's class.
---
---Shorthand for `handle:props().class`.
---
---@return string?
function WindowHandle:class()
return self:props().class
end
---Get this window's title.
---
---Shorthand for `handle:props().title`.
---
---@return string?
function WindowHandle:title()
return self:props().title
end
---Get whether or not this window is focused.
---
---Shorthand for `handle:props().focused`.
---
---@return boolean?
function WindowHandle:focused()
return self:props().focused
end
---Get whether or not this window is floating.
---
---Shorthand for `handle:props().floating`.
---
---@return boolean?
function WindowHandle:floating()
return self:props().floating
end
---Get whether this window is fullscreen, maximized, or neither.
---
---Shorthand for `handle:props().fullscreen_or_maximized`.
---
---@return FullscreenOrMaximized?
function WindowHandle:fullscreen_or_maximized()
return self:props().fullscreen_or_maximized
end
---Get all tags on this window.
---
---Shorthand for `handle:props().tags`.
---
---@return TagHandle[]?
function WindowHandle:tags()
return self:props().tags
end
---@param config_client Client
---@return Window
function window.new(config_client)

View file

@ -1,29 +1,18 @@
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
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
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)
input:set_keybind({ 1 }, "Q", function()
pinnacle:quit()
Input:keybind({ "shift" }, "f", function()
local focused = Window:get_focused()
if focused then
print(focused:fullscreen_or_maximized())
-- assert(focused:fullscreen_or_maximized() == "neither")
focused:set_fullscreen(true)
print(focused:fullscreen_or_maximized())
-- assert(focused:fullscreen_or_maximized() == "fullscreen")
end
end)
end)

View file

@ -77,6 +77,7 @@ message GetPropertiesResponse {
optional bool focused = 4;
optional bool floating = 5;
optional .pinnacle.window.rules.v0alpha1.FullscreenOrMaximized fullscreen_or_maximized = 6;
repeated uint32 tag_ids = 7;
}
service WindowService {

View file

@ -1217,7 +1217,7 @@ impl pinnacle_api_defs::pinnacle::window::v0alpha1::window_service_server::Windo
window_size.h = height.unwrap_or(window_size.h);
let rect = Rectangle::from_loc_and_size(window_loc, window_size);
window.change_geometry(rect);
// window.change_geometry(rect);
window.with_state(|state| {
use crate::window::window_state::FloatingOrTiled;
state.floating_or_tiled = match state.floating_or_tiled {
@ -1727,6 +1727,22 @@ impl pinnacle_api_defs::pinnacle::window::v0alpha1::window_service_server::Windo
}
} as i32);
let tag_ids = window
.as_ref()
.map(|win| {
win.with_state(|state| {
state
.tags
.iter()
.map(|tag| match tag.id() {
TagId::Some(id) => id,
TagId::None => unreachable!(),
})
.collect::<Vec<_>>()
})
})
.unwrap_or_default();
let _ = sender.send(
pinnacle_api_defs::pinnacle::window::v0alpha1::GetPropertiesResponse {
geometry,
@ -1735,6 +1751,7 @@ impl pinnacle_api_defs::pinnacle::window::v0alpha1::window_service_server::Windo
focused,
floating,
fullscreen_or_maximized,
tag_ids,
},
);
});