mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-25 09:59:21 +01:00
commit
d52feb8401
18 changed files with 3322 additions and 742 deletions
|
@ -11,6 +11,9 @@
|
|||
A very, VERY WIP Smithay-based wayland compositor
|
||||
</div>
|
||||
|
||||
## API Documentation
|
||||
There is now *finally* [some form of documentation](https://github.com/Ottatop/pinnacle/wiki/API-Documentation) so you don't have to dig around in the code. It isn't great and automating it seems like a pain, but hey it's something! This may become out of date real quick though, and I'm probably going to need to move to LDoc in the future.
|
||||
|
||||
## Features
|
||||
- [x] Winit backend
|
||||
- [x] Udev backend
|
||||
|
|
|
@ -2,7 +2,7 @@ MIT License
|
|||
|
||||
Copyright (c) 2023 Ottatop
|
||||
|
||||
This license applies to the example_config.lua file only.
|
||||
This license applies to the *_config.lua files.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
1395
api/lua/doc.md
Normal file
1395
api/lua/doc.md
Normal file
File diff suppressed because it is too large
Load diff
|
@ -41,7 +41,12 @@ require("pinnacle").setup(function(pinnacle)
|
|||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.space, window.toggle_floating)
|
||||
input.keybind({ mod_key, "Alt" }, keys.space, function()
|
||||
local win = window.get_focused()
|
||||
if win ~= nil then
|
||||
win:toggle_floating()
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key }, keys.Return, function()
|
||||
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
|
||||
|
@ -86,6 +91,9 @@ require("pinnacle").setup(function(pinnacle)
|
|||
for _, tg in pairs(tags) do
|
||||
if tg:active() then
|
||||
local name = tg:name()
|
||||
if name == nil then
|
||||
return
|
||||
end
|
||||
tg:set_layout(layouts[indices[name] or 1])
|
||||
if indices[name] == nil then
|
||||
indices[name] = 2
|
||||
|
@ -105,6 +113,9 @@ require("pinnacle").setup(function(pinnacle)
|
|||
for _, tg in pairs(tags) do
|
||||
if tg:active() then
|
||||
local name = tg:name()
|
||||
if name == nil then
|
||||
return
|
||||
end
|
||||
tg:set_layout(layouts[indices[name] or #layouts])
|
||||
if indices[name] == nil then
|
||||
indices[name] = #layouts - 1
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
local input = {
|
||||
---@class InputModule
|
||||
local input_module = {
|
||||
keys = require("keys"),
|
||||
}
|
||||
|
||||
|
@ -13,8 +14,7 @@ local input = {
|
|||
---### Example
|
||||
---
|
||||
---```lua
|
||||
----- The following sets Super + Return to open Alacritty
|
||||
---
|
||||
----- Set `Super + Return` to open Alacritty
|
||||
---input.keybind({ "Super" }, input.keys.Return, function()
|
||||
--- process.spawn("Alacritty")
|
||||
---end)
|
||||
|
@ -22,7 +22,7 @@ local input = {
|
|||
---@param key Keys The key for the keybind.
|
||||
---@param modifiers (Modifier)[] Which modifiers need to be pressed for the keybind to trigger.
|
||||
---@param action fun() What to do.
|
||||
function input.keybind(modifiers, key, action)
|
||||
function input_module.keybind(modifiers, key, action)
|
||||
table.insert(CallbackTable, action)
|
||||
SendMsg({
|
||||
SetKeybind = {
|
||||
|
@ -33,4 +33,4 @@ function input.keybind(modifiers, key, action)
|
|||
})
|
||||
end
|
||||
|
||||
return input
|
||||
return input_module
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super"
|
||||
---@alias Modifier
|
||||
---| "Alt" # The "Alt" key
|
||||
---| "Ctrl" # The "Control" key
|
||||
---| "Shift" # The "Shift" key
|
||||
---| "Super" # The "Super" key, aka "Meta", "Mod4" in X11, the Windows key, etc.
|
||||
|
||||
---@enum Keys
|
||||
local keys = {
|
||||
|
|
|
@ -10,20 +10,20 @@
|
|||
---@field SetKeybind { key: Keys, modifiers: Modifier[], callback_id: integer }
|
||||
---@field SetMousebind { button: integer }
|
||||
--Windows
|
||||
---@field CloseWindow { window_id: integer }
|
||||
---@field ToggleFloating { window_id: integer }
|
||||
---@field SetWindowSize { window_id: integer, size: integer[] }
|
||||
---@field MoveWindowToTag { window_id: integer, tag_id: string }
|
||||
---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
|
||||
---@field CloseWindow { window_id: WindowId }
|
||||
---@field ToggleFloating { window_id: WindowId }
|
||||
---@field SetWindowSize { window_id: WindowId, width: integer?, height: integer? }
|
||||
---@field MoveWindowToTag { window_id: WindowId, tag_id: TagId }
|
||||
---@field ToggleTagOnWindow { window_id: WindowId, tag_id: TagId }
|
||||
--
|
||||
---@field Spawn { command: string[], callback_id: integer? }
|
||||
---@field Request Request
|
||||
--Tags
|
||||
---@field ToggleTag { output_name: string, tag_name: string }
|
||||
---@field SwitchToTag { output_name: string, tag_name: string }
|
||||
---@field ToggleTag { tag_id: TagId }
|
||||
---@field SwitchToTag { tag_id: TagId }
|
||||
---@field AddTags { output_name: string, tag_names: string[] }
|
||||
---@field RemoveTags { output_name: string, tag_names: string[] }
|
||||
---@field SetLayout { output_name: string, tag_name: string, layout: Layout }
|
||||
---@field RemoveTags { tag_ids: TagId[] }
|
||||
---@field SetLayout { tag_id: TagId, layout: Layout }
|
||||
--Outputs
|
||||
---@field ConnectForAllOutputs { callback_id: integer }
|
||||
|
||||
|
@ -31,23 +31,20 @@
|
|||
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
---@class _Request
|
||||
---@class __Request
|
||||
--Windows
|
||||
---@field GetWindowByAppId { app_id: string }
|
||||
---@field GetWindowByTitle { title: string }
|
||||
---@field GetWindowProps { window_id: WindowId }
|
||||
--Outputs
|
||||
---@field GetOutputByName { output_name: OutputName }
|
||||
---@field GetOutputsByModel { model: string }
|
||||
---@field GetOutputsByRes { res: integer[] }
|
||||
---@field GetTagsByOutput { output_name: string }
|
||||
---@field GetTagActive { tag_id: TagId }
|
||||
---@field GetTagName { tag_id: TagId }
|
||||
---@field GetOutputProps { output_name: string }
|
||||
--Tags
|
||||
---@field GetTagProps { tag_id: TagId }
|
||||
|
||||
---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus"
|
||||
---@alias _Request __Request | "GetWindows" | "GetOutputs" | "GetTags"
|
||||
---@alias Request { request_id: integer, request: _Request }
|
||||
|
||||
---@class IncomingMsg
|
||||
---@field CallCallback { callback_id: integer, args: Args }
|
||||
---@field RequestResponse { response: RequestResponse }
|
||||
---@field CallCallback { callback_id: integer, args: Args? }
|
||||
---@field RequestResponse { request_id: integer, response: RequestResponse }
|
||||
|
||||
---@class Args
|
||||
---@field Spawn { stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string? }
|
||||
|
@ -55,12 +52,18 @@
|
|||
|
||||
---@alias WindowId integer
|
||||
---@alias TagId integer
|
||||
---@alias RequestId integer
|
||||
---@alias OutputName string
|
||||
|
||||
---@class RequestResponse
|
||||
--Windows
|
||||
---@field Window { window_id: WindowId|nil }
|
||||
---@field Windows { window_ids: WindowId[] }
|
||||
---@field WindowProps { size: integer[]?, loc: integer[]?, class: string?, title: string?, floating: boolean?, focused: boolean? }
|
||||
--Outputs
|
||||
---@field Output { output_name: OutputName? }
|
||||
---@field Outputs { output_names: OutputName[] }
|
||||
---@field OutputProps { make: string?, model: string?, loc: integer[]?, res: integer[]?, refresh_rate: integer?, physical_size: integer[]?, focused: boolean?, tag_ids: integer[]? }
|
||||
--Tags
|
||||
---@field Tags { tag_ids: TagId[] }
|
||||
---@field TagActive { active: boolean }
|
||||
---@field TagName { name: string }
|
||||
---@field TagProps { active: boolean?, name: string?, output_name: string? }
|
||||
|
|
|
@ -4,44 +4,101 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
---@class OutputModule
|
||||
local output_module = {}
|
||||
|
||||
---@class Output A display.
|
||||
---@field name string The name of this output (or rather, of its connector).
|
||||
local op = {}
|
||||
---@field private _name string The name of this output (or rather, of its connector).
|
||||
local output = {}
|
||||
|
||||
---Get all tags on this output. See `tag.get_on_output`.
|
||||
---@return Tag[]
|
||||
function op:tags()
|
||||
return require("tag").get_on_output(self)
|
||||
end
|
||||
|
||||
---Add tags to this output. See `tag.add`.
|
||||
---@param ... string The names of the tags you want to add.
|
||||
function op:add_tags(...)
|
||||
require("tag").add(self, ...)
|
||||
end
|
||||
|
||||
---Add tags to this output as a table. See `tag.add_table`.
|
||||
---@param names string[] The names of the tags you want to add, as a table.
|
||||
function op:add_tags_table(names)
|
||||
require("tag").add_table(self, names)
|
||||
end
|
||||
|
||||
---Add methods to this output.
|
||||
---@param props Output
|
||||
---Create a new output object from a name.
|
||||
---The name is the unique identifier for each output.
|
||||
---@param name string
|
||||
---@return Output
|
||||
local function new_output(props)
|
||||
local function create_output(name)
|
||||
---@type Output
|
||||
local o = { _name = name }
|
||||
-- Copy functions over
|
||||
for k, v in pairs(op) do
|
||||
props[k] = v
|
||||
for k, v in pairs(output) do
|
||||
o[k] = v
|
||||
end
|
||||
|
||||
return props
|
||||
return o
|
||||
end
|
||||
|
||||
---Get this output's name. This is something like "eDP-1" or "HDMI-A-0".
|
||||
---@return string
|
||||
function output:name()
|
||||
return self._name
|
||||
end
|
||||
|
||||
---Get all tags on this output.
|
||||
---@return Tag[]
|
||||
---@see OutputModule.tags — The corresponding module function
|
||||
function output:tags()
|
||||
return output_module.tags(self)
|
||||
end
|
||||
|
||||
---Add tags to this output.
|
||||
---@param ... string The names of the tags you want to add. You can also pass in a table.
|
||||
---@overload fun(self: self, tag_names: string[])
|
||||
---@see OutputModule.add_tags — The corresponding module function
|
||||
function output:add_tags(...)
|
||||
output_module.add_tags(self, ...)
|
||||
end
|
||||
|
||||
---Get this output's make.
|
||||
---@return string|nil
|
||||
---@see OutputModule.make — The corresponding module function
|
||||
function output:make()
|
||||
return output_module.make(self)
|
||||
end
|
||||
|
||||
---Get this output's model.
|
||||
---@return string|nil
|
||||
---@see OutputModule.model — The corresponding module function
|
||||
function output:model()
|
||||
return output_module.model(self)
|
||||
end
|
||||
|
||||
---Get this output's location in the global space, in pixels.
|
||||
---@return { x: integer, y: integer }|nil
|
||||
---@see OutputModule.loc — The corresponding module function
|
||||
function output:loc()
|
||||
return output_module.loc(self)
|
||||
end
|
||||
|
||||
---Get this output's resolution in pixels.
|
||||
---@return { w: integer, h: integer }|nil
|
||||
---@see OutputModule.res — The corresponding module function
|
||||
function output:res()
|
||||
return output_module.res(self)
|
||||
end
|
||||
|
||||
---Get this output's refresh rate in millihertz.
|
||||
---For example, 60Hz will be returned as 60000.
|
||||
---@return integer|nil
|
||||
---@see OutputModule.refresh_rate — The corresponding module function
|
||||
function output:refresh_rate()
|
||||
return output_module.refresh_rate(self)
|
||||
end
|
||||
|
||||
---Get this output's physical size in millimeters.
|
||||
---@return { w: integer, h: integer }|nil
|
||||
---@see OutputModule.physical_size — The corresponding module function
|
||||
function output:physical_size()
|
||||
return output_module.physical_size(self)
|
||||
end
|
||||
|
||||
---Get whether or not this output is focused. This is currently defined as having the cursor on it.
|
||||
---@return boolean|nil
|
||||
---@see OutputModule.focused — The corresponding module function
|
||||
function output:focused()
|
||||
return output_module.focused(self)
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
|
||||
local output = {}
|
||||
|
||||
---Get an output by its name.
|
||||
---
|
||||
---"Name" in this sense does not mean its model or manufacturer;
|
||||
|
@ -54,23 +111,18 @@ local output = {}
|
|||
---print(monitor.name) -- should print `DP-1`
|
||||
---```
|
||||
---@param name string The name of the output.
|
||||
---@return Output|nil
|
||||
function output.get_by_name(name)
|
||||
SendRequest({
|
||||
GetOutputByName = {
|
||||
output_name = name,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
---@return Output|nil output The output, or nil if none have the provided name.
|
||||
function output_module.get_by_name(name)
|
||||
local response = Request("GetOutputs")
|
||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
||||
|
||||
if output_names[1] ~= nil then
|
||||
return new_output({ name = output_names[1] })
|
||||
else
|
||||
return nil
|
||||
for _, output_name in pairs(output_names) do
|
||||
if output_name == name then
|
||||
return create_output(output_name)
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---Note: This may or may not be what is reported by other monitor listing utilities. Pinnacle currently fails to pick up one of my monitors' models when it is correctly picked up by tools like wlr-randr. I'll fix this in the future.
|
||||
|
@ -78,22 +130,18 @@ end
|
|||
---Get outputs by their model.
|
||||
---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays.
|
||||
---@param model string The model of the output(s).
|
||||
---@return Output[] outputs All outputs with this model. If there are none, the returned table will be empty.
|
||||
function output.get_by_model(model)
|
||||
SendRequest({
|
||||
GetOutputsByModel = {
|
||||
model = model,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
---@return Output[] outputs All outputs with this model.
|
||||
function output_module.get_by_model(model)
|
||||
local response = Request("GetOutputs")
|
||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
||||
|
||||
---@type Output
|
||||
---@type Output[]
|
||||
local outputs = {}
|
||||
for _, v in pairs(output_names) do
|
||||
table.insert(outputs, new_output({ name = v }))
|
||||
for _, output_name in pairs(output_names) do
|
||||
local o = create_output(output_name)
|
||||
if o:model() == model then
|
||||
table.insert(outputs, o)
|
||||
end
|
||||
end
|
||||
|
||||
return outputs
|
||||
|
@ -103,22 +151,19 @@ end
|
|||
---
|
||||
---@param width integer The width of the outputs, in pixels.
|
||||
---@param height integer The height of the outputs, in pixels.
|
||||
---@return Output[] outputs All outputs with this resolution. If there are none, the returned table will be empty.
|
||||
function output.get_by_res(width, height)
|
||||
SendRequest({
|
||||
GetOutputsByRes = {
|
||||
res = { width, height },
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
---@return Output[] outputs All outputs with this resolution.
|
||||
function output_module.get_by_res(width, height)
|
||||
local response = Request("GetOutputs")
|
||||
|
||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
||||
|
||||
---@type Output
|
||||
local outputs = {}
|
||||
for _, output_name in pairs(output_names) do
|
||||
table.insert(outputs, new_output({ name = output_name }))
|
||||
local o = create_output(output_name)
|
||||
if o:res() and o:res().w == width and o:res().h == height then
|
||||
table.insert(outputs, o)
|
||||
end
|
||||
end
|
||||
|
||||
return outputs
|
||||
|
@ -144,18 +189,18 @@ end
|
|||
---local tags = output.get_focused():tags() -- will NOT warn for nil
|
||||
---```
|
||||
---@return Output|nil output The output, or nil if none are focused.
|
||||
function output.get_focused()
|
||||
SendRequest("GetOutputByFocus")
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
function output_module.get_focused()
|
||||
local response = Request("GetOutputs")
|
||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
||||
|
||||
if output_names[1] ~= nil then
|
||||
return new_output({ name = output_names[1] })
|
||||
else
|
||||
return nil
|
||||
for _, output_name in pairs(output_names) do
|
||||
local o = create_output(output_name)
|
||||
if o:focused() then
|
||||
return o
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
---Connect a function to be run on all current and future outputs.
|
||||
|
@ -166,11 +211,11 @@ end
|
|||
---Please note: this function will be run *after* Pinnacle processes your entire config.
|
||||
---For example, if you define tags in `func` but toggle them directly after `connect_for_all`, nothing will happen as the tags haven't been added yet.
|
||||
---@param func fun(output: Output) The function that will be run.
|
||||
function output.connect_for_all(func)
|
||||
function output_module.connect_for_all(func)
|
||||
---@param args Args
|
||||
table.insert(CallbackTable, function(args)
|
||||
local args = args.ConnectForAllOutputs
|
||||
func(new_output({ name = args.output_name }))
|
||||
func(create_output(args.output_name))
|
||||
end)
|
||||
SendMsg({
|
||||
ConnectForAllOutputs = {
|
||||
|
@ -179,4 +224,153 @@ function output.connect_for_all(func)
|
|||
})
|
||||
end
|
||||
|
||||
return output
|
||||
---Get the output the specified tag is on.
|
||||
---@param tag Tag
|
||||
---@return Output|nil
|
||||
---@see TagModule.output — A global method for fully qualified syntax (for you Rustaceans out there)
|
||||
---@see Tag.output — The corresponding object method
|
||||
function output_module.get_for_tag(tag)
|
||||
local response = Request({
|
||||
GetTagProps = {
|
||||
tag_id = tag:id(),
|
||||
},
|
||||
})
|
||||
local output_name = response.RequestResponse.response.TagProps.output_name
|
||||
|
||||
if output_name == nil then
|
||||
return nil
|
||||
else
|
||||
return create_output(output_name)
|
||||
end
|
||||
end
|
||||
|
||||
---Get the specified output's make.
|
||||
---@param op Output
|
||||
---@return string|nil
|
||||
---@see Output.make — The corresponding object method
|
||||
function output_module.make(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
return props.make
|
||||
end
|
||||
|
||||
---Get the specified output's model.
|
||||
---@param op Output
|
||||
---@return string|nil
|
||||
---@see Output.model — The corresponding object method
|
||||
function output_module.model(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
return props.model
|
||||
end
|
||||
|
||||
---Get the specified output's location in the global space, in pixels.
|
||||
---@param op Output
|
||||
---@return { x: integer, y: integer }|nil
|
||||
---@see Output.loc — The corresponding object method
|
||||
function output_module.loc(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
if props.loc == nil then
|
||||
return nil
|
||||
else
|
||||
return { x = props.loc[1], y = props.loc[2] }
|
||||
end
|
||||
end
|
||||
|
||||
---Get the specified output's resolution in pixels.
|
||||
---@param op Output
|
||||
---@return { w: integer, h: integer }|nil
|
||||
---@see Output.res — The corresponding object method
|
||||
function output_module.res(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
if props.res == nil then
|
||||
return nil
|
||||
else
|
||||
return { w = props.res[1], h = props.res[2] }
|
||||
end
|
||||
end
|
||||
|
||||
---Get the specified output's refresh rate in millihertz.
|
||||
---For example, 60Hz will be returned as 60000.
|
||||
---@param op Output
|
||||
---@return integer|nil
|
||||
---@see Output.refresh_rate — The corresponding object method
|
||||
function output_module.refresh_rate(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
return props.refresh_rate
|
||||
end
|
||||
|
||||
---Get the specified output's physical size in millimeters.
|
||||
---@param op Output
|
||||
---@return { w: integer, h: integer }|nil
|
||||
---@see Output.physical_size — The corresponding object method
|
||||
function output_module.physical_size(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
if props.physical_size == nil then
|
||||
return nil
|
||||
else
|
||||
return { w = props.physical_size[1], h = props.physical_size[2] }
|
||||
end
|
||||
end
|
||||
|
||||
---Get whether or not the specified output is focused. This is currently defined as having the cursor on it.
|
||||
---@param op Output
|
||||
---@return boolean|nil
|
||||
---@see Output.focused — The corresponding object method
|
||||
function output_module.focused(op)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = op:name(),
|
||||
},
|
||||
})
|
||||
local props = response.RequestResponse.response.OutputProps
|
||||
return props.focused
|
||||
end
|
||||
|
||||
---Get the specified output's tags.
|
||||
---@param op Output
|
||||
---@see TagModule.get_on_output — The called function
|
||||
---@see Output.tags — The corresponding object method
|
||||
function output_module.tags(op)
|
||||
return require("tag").get_on_output(op)
|
||||
end
|
||||
|
||||
---Add tags to the specified output.
|
||||
---@param op Output
|
||||
---@param ... string The names of the tags you want to add. You can also pass in a table.
|
||||
---@overload fun(op: Output, tag_names: string[])
|
||||
---@see TagModule.add — The called function
|
||||
---@see Output.add_tags — The corresponding object method
|
||||
function output_module.add_tags(op, ...)
|
||||
require("tag").add(op, ...)
|
||||
end
|
||||
|
||||
return output_module
|
||||
|
|
|
@ -9,6 +9,37 @@ local msgpack = require("msgpack")
|
|||
|
||||
local SOCKET_PATH = "/tmp/pinnacle_socket"
|
||||
|
||||
---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
|
||||
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
|
||||
|
||||
---Read the specified number of bytes.
|
||||
---@param socket_fd integer The socket file descriptor
|
||||
---@param count integer The amount of bytes to read
|
||||
|
@ -82,60 +113,110 @@ function pinnacle.setup(config_func)
|
|||
---@type fun(args: table?)[]
|
||||
CallbackTable = {}
|
||||
|
||||
---This is an internal global function used to send serialized messages to the Pinnacle server.
|
||||
---@param data Msg
|
||||
function SendMsg(data)
|
||||
-- RPrint(data)
|
||||
local encoded = msgpack.encode(data)
|
||||
assert(encoded)
|
||||
-- print(encoded)
|
||||
local len = encoded:len()
|
||||
socket.send(socket_fd, string.pack("=I4", len))
|
||||
socket.send(socket_fd, encoded)
|
||||
end
|
||||
|
||||
---@param data Request
|
||||
function SendRequest(data)
|
||||
SendMsg({
|
||||
Request = data,
|
||||
})
|
||||
local request_id = 1
|
||||
---Get the next request id.
|
||||
---@return integer
|
||||
local function next_request_id()
|
||||
local ret = request_id
|
||||
request_id = request_id + 1
|
||||
return ret
|
||||
end
|
||||
|
||||
function ReadMsg()
|
||||
local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4)
|
||||
assert(msg_len_bytes)
|
||||
---@type table<integer, IncomingMsg>
|
||||
local unread_req_msgs = {}
|
||||
---@type table<integer, IncomingMsg>
|
||||
local unread_cb_msgs = {}
|
||||
|
||||
-- TODO: break here if error in read_exact
|
||||
---This is an internal global function used to send requests to the Pinnacle server for information.
|
||||
---@param data _Request
|
||||
---@return IncomingMsg
|
||||
function Request(data)
|
||||
local req_id = next_request_id()
|
||||
SendMsg({
|
||||
Request = {
|
||||
request_id = req_id,
|
||||
request = data,
|
||||
},
|
||||
})
|
||||
return ReadMsg(req_id)
|
||||
end
|
||||
|
||||
---@type integer
|
||||
local msg_len = string.unpack("=I4", msg_len_bytes)
|
||||
-- print(msg_len)
|
||||
---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.
|
||||
---@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
|
||||
|
||||
local msg_bytes, err_msg2, err_num2 = read_exact(socket_fd, msg_len)
|
||||
assert(msg_bytes)
|
||||
-- print(msg_bytes)
|
||||
local msg_len_bytes, err_msg, err_num = read_exact(socket_fd, 4)
|
||||
assert(msg_len_bytes)
|
||||
|
||||
---@type IncomingMsg
|
||||
local tb = msgpack.decode(msg_bytes)
|
||||
-- print(msg_bytes)
|
||||
-- TODO: break here if error in read_exact
|
||||
|
||||
return tb
|
||||
---@type integer
|
||||
local msg_len = string.unpack("=I4", msg_len_bytes)
|
||||
-- print(msg_len)
|
||||
|
||||
local msg_bytes, err_msg2, err_num2 = read_exact(socket_fd, msg_len)
|
||||
assert(msg_bytes)
|
||||
-- print(msg_bytes)
|
||||
|
||||
---@type IncomingMsg
|
||||
local inc_msg = msgpack.decode(msg_bytes)
|
||||
-- print(msg_bytes)
|
||||
|
||||
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
|
||||
unread_req_msgs[inc_msg.RequestResponse.request_id] = inc_msg
|
||||
else
|
||||
return inc_msg
|
||||
end
|
||||
else
|
||||
return inc_msg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
config_func(pinnacle)
|
||||
|
||||
while true do
|
||||
local tb = ReadMsg()
|
||||
|
||||
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
|
||||
for cb_id, inc_msg in pairs(unread_cb_msgs) do
|
||||
CallbackTable[inc_msg.CallCallback.callback_id](inc_msg.CallCallback.args)
|
||||
unread_cb_msgs[cb_id] = nil -- INFO: does this shift the table and frick everything up?
|
||||
end
|
||||
|
||||
-- if tb.RequestResponse then
|
||||
-- local req_id = tb.RequestResponse.request_id
|
||||
-- Requests[req_id] = tb.RequestResponse.response
|
||||
-- end
|
||||
local inc_msg = ReadMsg()
|
||||
|
||||
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
|
||||
CallbackTable[inc_msg.CallCallback.callback_id](inc_msg.CallCallback.args)
|
||||
else
|
||||
CallbackTable[inc_msg.CallCallback.callback_id](nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
---@diagnostic disable: redefined-local
|
||||
|
||||
local process = {}
|
||||
---@class ProcessModule
|
||||
local process_module = {}
|
||||
|
||||
---Spawn a process with an optional callback for its stdout, stderr, and exit information.
|
||||
---
|
||||
|
@ -17,7 +18,7 @@ local process = {}
|
|||
--- - `exit_msg`: The process exited with this message.
|
||||
---@param command string|string[] The command as one whole string or a table of each of its arguments
|
||||
---@param callback fun(stdout: string|nil, stderr: string|nil, exit_code: integer|nil, exit_msg: string|nil)? A callback to do something whenever the process's stdout or stderr print a line, or when the process exits.
|
||||
function process.spawn(command, callback)
|
||||
function process_module.spawn(command, callback)
|
||||
---@type integer|nil
|
||||
local callback_id = nil
|
||||
|
||||
|
@ -58,7 +59,7 @@ end
|
|||
---`spawn_once` checks for the process using `pgrep`. If your system doesn't have `pgrep`, this won't work properly.
|
||||
---@param command string|string[] The command as one whole string or a table of each of its arguments
|
||||
---@param callback fun(stdout: string|nil, stderr: string|nil, exit_code: integer|nil, exit_msg: string|nil)? A callback to do something whenever the process's stdout or stderr print a line, or when the process exits.
|
||||
function process.spawn_once(command, callback)
|
||||
function process_module.spawn_once(command, callback)
|
||||
local proc = ""
|
||||
if type(command) == "string" then
|
||||
proc = command:match("%S+")
|
||||
|
@ -71,7 +72,7 @@ function process.spawn_once(command, callback)
|
|||
if procs:len() ~= 0 then -- if process exists, return
|
||||
return
|
||||
end
|
||||
process.spawn(command, callback)
|
||||
process_module.spawn(command, callback)
|
||||
end
|
||||
|
||||
return process
|
||||
return process_module
|
||||
|
|
380
api/lua/tag.lua
380
api/lua/tag.lua
|
@ -4,7 +4,8 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
local tag = {}
|
||||
---@class TagModule
|
||||
local tag_module = {}
|
||||
|
||||
---@alias Layout
|
||||
---| "MasterStack" # One master window on the left with all other windows stacked to the right.
|
||||
|
@ -16,100 +17,113 @@ local tag = {}
|
|||
---| "CornerBottomRight" # One main corner window in the bottom right with a column of windows on the left and a row on the top.
|
||||
|
||||
---@class Tag
|
||||
---@field private id integer The internal id of this tag.
|
||||
local tg = {}
|
||||
---@field private _id integer The internal id of this tag.
|
||||
local tag = {}
|
||||
|
||||
---@param props Tag
|
||||
---Create a tag from an id.
|
||||
---The id is the unique identifier for each tag.
|
||||
---@param id TagId
|
||||
---@return Tag
|
||||
local function new_tag(props)
|
||||
local function create_tag(id)
|
||||
---@type Tag
|
||||
local t = { _id = id }
|
||||
-- Copy functions over
|
||||
for k, v in pairs(tg) do
|
||||
props[k] = v
|
||||
for k, v in pairs(tag) do
|
||||
t[k] = v
|
||||
end
|
||||
|
||||
return props
|
||||
return t
|
||||
end
|
||||
|
||||
---Get this tag's internal id.
|
||||
---***You probably won't need to use this.***
|
||||
---@return integer
|
||||
function tag:id()
|
||||
return self._id
|
||||
end
|
||||
|
||||
---Get this tag's active status.
|
||||
---@return boolean active True if the tag is active, otherwise false.
|
||||
function tg:active()
|
||||
SendRequest({
|
||||
GetTagActive = {
|
||||
tag_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local active = response.RequestResponse.response.TagActive.active
|
||||
return active
|
||||
---@return boolean|nil active `true` if the tag is active, `false` if not, and `nil` if the tag doesn't exist.
|
||||
---@see TagModule.active — The corresponding module function
|
||||
function tag:active()
|
||||
return tag_module.active(self)
|
||||
end
|
||||
|
||||
function tg:name()
|
||||
SendRequest({
|
||||
GetTagName = {
|
||||
tag_id = self.id,
|
||||
},
|
||||
})
|
||||
---Get this tag's name.
|
||||
---@return string|nil name The name of this tag, or nil if it doesn't exist.
|
||||
---@see TagModule.name — The corresponding module function
|
||||
function tag:name()
|
||||
return tag_module.name(self)
|
||||
end
|
||||
|
||||
local response = ReadMsg()
|
||||
local name = response.RequestResponse.response.TagName.name
|
||||
return name
|
||||
---Get this tag's output.
|
||||
---@return Output|nil output The output this tag is on, or nil if the tag doesn't exist.
|
||||
---@see TagModule.output — The corresponding module function
|
||||
function tag:output()
|
||||
return tag_module.output(self)
|
||||
end
|
||||
|
||||
---Switch to this tag.
|
||||
---@see TagModule.switch_to — The corresponding module function
|
||||
function tag:switch_to()
|
||||
tag_module.switch_to(self)
|
||||
end
|
||||
|
||||
---Toggle this tag.
|
||||
---@see TagModule.toggle — The corresponding module function
|
||||
function tag:toggle()
|
||||
tag_module.toggle(self)
|
||||
end
|
||||
|
||||
---Set this tag's layout.
|
||||
---@param layout Layout
|
||||
function tg:set_layout(layout) -- TODO: output param
|
||||
tag.set_layout(self:name(), layout)
|
||||
---@see TagModule.set_layout — The corresponding module function
|
||||
function tag:set_layout(layout)
|
||||
tag_module.set_layout(self, layout)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
|
||||
---Add tags.
|
||||
---
|
||||
---If you need to add the names as a table, use `tag.add_table` instead.
|
||||
---
|
||||
---### Example
|
||||
---Add tags to the specified output.
|
||||
---
|
||||
---### Examples
|
||||
---```lua
|
||||
---local output = output.get_by_name("DP-1")
|
||||
---if output ~= nil then
|
||||
--- tag.add(output, "1", "2", "3", "4", "5") -- Add tags with names 1-5
|
||||
---local op = output.get_by_name("DP-1")
|
||||
---if op ~= nil then
|
||||
--- tag.add(op, "1", "2", "3", "4", "5") -- Add tags with names 1-5
|
||||
---end
|
||||
---```
|
||||
---You can also pass in a table.
|
||||
---```lua
|
||||
---local tags = {"Terminal", "Browser", "Code", "Potato", "Email"}
|
||||
---tag.add(op, tags) -- Add tags with those names
|
||||
---```
|
||||
---@param output Output The output you want these tags to be added to.
|
||||
---@param ... string The names of the new tags you want to add.
|
||||
function tag.add(output, ...)
|
||||
local tag_names = table.pack(...)
|
||||
tag_names["n"] = nil -- remove the length to make it a true array for serializing
|
||||
---@overload fun(output: Output, tag_names: string[])
|
||||
---@see Output.add_tags — The corresponding object method
|
||||
function tag_module.add(output, ...)
|
||||
local varargs = { ... }
|
||||
if type(varargs[1]) == "string" then
|
||||
local tag_names = varargs
|
||||
tag_names["n"] = nil -- remove the length to make it a true array for serializing
|
||||
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
output_name = output.name,
|
||||
tag_names = tag_names,
|
||||
},
|
||||
})
|
||||
end
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
output_name = output:name(),
|
||||
tag_names = tag_names,
|
||||
},
|
||||
})
|
||||
else
|
||||
local tag_names = varargs[1] --[=[@as string[]]=]
|
||||
|
||||
---Like `tag.add`, but with a table of strings instead.
|
||||
---
|
||||
---### Example
|
||||
---
|
||||
---```lua
|
||||
---local tags = { "Terminal", "Browser", "Mail", "Gaming", "Potato" }
|
||||
---local output = output.get_by_name("DP-1")
|
||||
---if output ~= nil then
|
||||
--- tag.add(output, tags) -- Add tags with the names above
|
||||
---end
|
||||
---```
|
||||
---@param output Output The output you want these tags to be added to.
|
||||
---@param names string[] The names of the new tags you want to add, as a table.
|
||||
function tag.add_table(output, names)
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
output_name = output.name,
|
||||
tag_names = names,
|
||||
},
|
||||
})
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
output_name = output:name(),
|
||||
tag_names = tag_names,
|
||||
},
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
---Toggle a tag on the specified output. If `output` isn't specified, toggle it on the currently focused output instead.
|
||||
|
@ -125,105 +139,207 @@ end
|
|||
---```
|
||||
---@param name string The name of the tag.
|
||||
---@param output Output? The output.
|
||||
function tag.toggle(name, output)
|
||||
if output ~= nil then
|
||||
---@overload fun(t: Tag)
|
||||
---@see Tag.toggle — The corresponding object method
|
||||
function tag_module.toggle(name, output)
|
||||
if type(name) == "table" then
|
||||
SendMsg({
|
||||
ToggleTag = {
|
||||
output_name = output.name,
|
||||
tag_name = name,
|
||||
tag_id = name--[[@as Tag]]:id(),
|
||||
},
|
||||
})
|
||||
else
|
||||
local op = require("output").get_focused()
|
||||
if op ~= nil then
|
||||
return
|
||||
end
|
||||
|
||||
local output = output or require("output").get_focused()
|
||||
|
||||
if output == nil then
|
||||
return
|
||||
end
|
||||
|
||||
print("before tag_global.get_by_name")
|
||||
local tags = tag_module.get_by_name(name)
|
||||
print("after tag_global.get_by_name")
|
||||
for _, t in pairs(tags) do
|
||||
if t:output() and t:output():name() == output:name() then
|
||||
SendMsg({
|
||||
ToggleTag = {
|
||||
output_name = op.name,
|
||||
tag_name = name,
|
||||
tag_id = t:id(),
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Switch to a tag on the specified output, deactivating any other active tags on it.
|
||||
---If `output` is not specified, this uses the currently focused output instead.
|
||||
---Alternatively, provide a tag object instead of a name and output.
|
||||
---
|
||||
---This is used to replicate what a traditional workspace is on some other Wayland compositors.
|
||||
---
|
||||
---### Example
|
||||
---
|
||||
---### Examples
|
||||
---```lua
|
||||
---tag.switch_to("3") -- Switches to and displays *only* windows on tag 3
|
||||
----- Switches to and displays *only* windows on tag `3` on the focused output.
|
||||
---tag.switch_to("3")
|
||||
---
|
||||
---local
|
||||
---```
|
||||
---@param name string The name of the tag.
|
||||
---@param output Output? The output.
|
||||
function tag.switch_to(name, output)
|
||||
if output ~= nil then
|
||||
---@overload fun(t: Tag)
|
||||
---@see Tag.switch_to — The corresponding object method
|
||||
function tag_module.switch_to(name, output)
|
||||
if type(name) == "table" then
|
||||
SendMsg({
|
||||
SwitchToTag = {
|
||||
output_name = output.name,
|
||||
tag_name = name,
|
||||
tag_id = name--[[@as Tag]]:id(),
|
||||
},
|
||||
})
|
||||
else
|
||||
local op = require("output").get_focused()
|
||||
if op ~= nil then
|
||||
return
|
||||
end
|
||||
|
||||
local output = output or require("output").get_focused()
|
||||
|
||||
if output == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local tags = tag_module.get_by_name(name)
|
||||
for _, t in pairs(tags) do
|
||||
if t:output() and t:output():name() == output:name() then
|
||||
SendMsg({
|
||||
SwitchToTag = {
|
||||
output_name = op.name,
|
||||
tag_name = name,
|
||||
tag_id = t:id(),
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Set a layout for the tag on the specified output. If there is none, set it for the tag on the currently focused one.
|
||||
---Set a layout for the tag on the specified output. If no output is provided, set it for the tag on the currently focused one.
|
||||
---Alternatively, provide a tag object instead of a name and output.
|
||||
---
|
||||
---### Examples
|
||||
---```lua
|
||||
----- Set tag `1` on `DP-1` to the `Dwindle` layout
|
||||
---tag.set_layout("1", "Dwindle", output.get_by_name("DP-1"))
|
||||
---
|
||||
----- Do the same as above. Note: if you have more than one tag named `1` then this picks the first one.
|
||||
---local t = tag.get_by_name("1")[1]
|
||||
---tag.set_layout(t, "Dwindle")
|
||||
---```
|
||||
---@param name string The name of the tag.
|
||||
---@param layout Layout The layout.
|
||||
---@param output Output? The output.
|
||||
function tag.set_layout(name, layout, output)
|
||||
if output ~= nil then
|
||||
---@overload fun(t: Tag, layout: Layout)
|
||||
---@see Tag.set_layout — The corresponding object method
|
||||
function tag_module.set_layout(name, layout, output)
|
||||
if type(name) == "table" then
|
||||
SendMsg({
|
||||
SetLayout = {
|
||||
output_name = output.name,
|
||||
tag_name = name,
|
||||
tag_id = name--[[@as Tag]]:id(),
|
||||
layout = layout,
|
||||
},
|
||||
})
|
||||
else
|
||||
local op = require("output").get_focused()
|
||||
if op ~= nil then
|
||||
return
|
||||
end
|
||||
|
||||
local output = output or require("output").get_focused()
|
||||
|
||||
if output == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local tags = tag_module.get_by_name(name)
|
||||
for _, t in pairs(tags) do
|
||||
if t:output() and t:output():name() == output:name() then
|
||||
SendMsg({
|
||||
SetLayout = {
|
||||
output_name = op.name,
|
||||
tag_name = name,
|
||||
tag_id = t:id(),
|
||||
layout = layout,
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Get all tags on the specified output.
|
||||
---
|
||||
---You can also use `output_obj:tags()`, which delegates to this function:
|
||||
---### Example
|
||||
---```lua
|
||||
---local tags_on_output = output.get_focused():tags()
|
||||
----- This is the same as
|
||||
----- local tags_on_output = tag.get_on_output(output.get_focused())
|
||||
---local op = output.get_focused()
|
||||
---if op ~= nil then
|
||||
--- local tags = tag.get_on_output(op) -- All tags on the focused output
|
||||
---end
|
||||
---```
|
||||
---@param output Output
|
||||
---@return Tag[]
|
||||
function tag.get_on_output(output)
|
||||
SendRequest({
|
||||
GetTagsByOutput = {
|
||||
output_name = output.name,
|
||||
---
|
||||
---@see Output.tags — The corresponding object method
|
||||
function tag_module.get_on_output(output)
|
||||
local response = Request({
|
||||
GetOutputProps = {
|
||||
output_name = output:name(),
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local tag_ids = response.RequestResponse.response.OutputProps.tag_ids
|
||||
|
||||
---@type Tag[]
|
||||
local tags = {}
|
||||
|
||||
if tag_ids == nil then
|
||||
return tags
|
||||
end
|
||||
|
||||
for _, tag_id in pairs(tag_ids) do
|
||||
table.insert(tags, create_tag(tag_id))
|
||||
end
|
||||
|
||||
return tags
|
||||
end
|
||||
|
||||
---Get all tags with this name across all outputs.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- Given one monitor with the tags "OBS", "OBS", "VSCode", and "Spotify"...
|
||||
---local tags = tag.get_by_name("OBS")
|
||||
----- ...will have 2 tags in `tags`, while...
|
||||
---local no_tags = tag.get_by_name("Firefox")
|
||||
----- ...will have `no_tags` be empty.
|
||||
---```
|
||||
---@param name string The name of the tag(s) you want.
|
||||
---@return Tag[]
|
||||
function tag_module.get_by_name(name)
|
||||
local t_s = tag_module.get_all()
|
||||
|
||||
---@type Tag[]
|
||||
local tags = {}
|
||||
|
||||
for _, t in pairs(t_s) do
|
||||
if t:name() == name then
|
||||
table.insert(tags, t)
|
||||
end
|
||||
end
|
||||
|
||||
return tags
|
||||
end
|
||||
|
||||
---Get all tags across all outputs.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With two monitors with the same tags: "1", "2", "3", "4", and "5"...
|
||||
---local tags = tag.get_all()
|
||||
----- ...`tags` should have 10 tags, with 5 pairs of those names across both outputs.
|
||||
---```
|
||||
---@return Tag[]
|
||||
function tag_module.get_all()
|
||||
local response = Request("GetTags")
|
||||
|
||||
local tag_ids = response.RequestResponse.response.Tags.tag_ids
|
||||
|
||||
|
@ -231,10 +347,54 @@ function tag.get_on_output(output)
|
|||
local tags = {}
|
||||
|
||||
for _, tag_id in pairs(tag_ids) do
|
||||
table.insert(tags, new_tag({ id = tag_id }))
|
||||
table.insert(tags, create_tag(tag_id))
|
||||
end
|
||||
|
||||
return tags
|
||||
end
|
||||
|
||||
return tag
|
||||
---Get the specified tag's name.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- Assuming the tag `Terminal` exists...
|
||||
---print(tag.name(tag.get_by_name("Terminal")[1]))
|
||||
----- ...should print `Terminal`.
|
||||
---```
|
||||
---@param t Tag
|
||||
---@return string|nil
|
||||
---@see Tag.name — The corresponding object method
|
||||
function tag_module.name(t)
|
||||
local response = Request({
|
||||
GetTagProps = {
|
||||
tag_id = t:id(),
|
||||
},
|
||||
})
|
||||
local name = response.RequestResponse.response.TagProps.name
|
||||
return name
|
||||
end
|
||||
|
||||
---Get whether or not the specified tag is active.
|
||||
---@param t Tag
|
||||
---@return boolean|nil
|
||||
---@see Tag.active — The corresponding object method
|
||||
function tag_module.active(t)
|
||||
local response = Request({
|
||||
GetTagProps = {
|
||||
tag_id = t:id(),
|
||||
},
|
||||
})
|
||||
local active = response.RequestResponse.response.TagProps.active
|
||||
return active
|
||||
end
|
||||
|
||||
---Get the output the specified tag is on.
|
||||
---@param t Tag
|
||||
---@return Output|nil
|
||||
---@see OutputModule.get_for_tag — The called function
|
||||
---@see Tag.output — The corresponding object method
|
||||
function tag_module.output(t)
|
||||
return require("output").get_for_tag(t)
|
||||
end
|
||||
|
||||
return tag_module
|
||||
|
|
356
api/lua/test_config.lua
Normal file
356
api/lua/test_config.lua
Normal file
|
@ -0,0 +1,356 @@
|
|||
-- SPDX-License-Identifier: MIT
|
||||
|
||||
-- Just like in Awesome, if you want access to Luarocks packages, this needs to be called.
|
||||
-- NOTE: The loader doesn't load from the local Luarocks directory (probably in ~/.luarocks),
|
||||
-- | so if you have any rocks installed with --local,
|
||||
-- | you may need to add those paths to package.path and package.cpath.
|
||||
-- Alternatively, you can add
|
||||
-- eval $(luarocks path --bin)
|
||||
-- to your shell's startup script to permanently have access to Luarocks in all your Lua files.
|
||||
pcall(require, "luarocks.loader")
|
||||
|
||||
-- Neovim users be like:
|
||||
require("pinnacle").setup(function(pinnacle)
|
||||
local input = pinnacle.input -- Key and mouse binds
|
||||
local window = pinnacle.window -- Window management
|
||||
local process = pinnacle.process -- Process spawning
|
||||
local tag = pinnacle.tag -- Tag management
|
||||
local output = pinnacle.output -- Output management
|
||||
|
||||
-- Every key supported by xkbcommon.
|
||||
-- Support for just putting in a string of a key is intended.
|
||||
local keys = input.keys
|
||||
|
||||
---@type Modifier
|
||||
local mod_key = "Ctrl" -- This is set to `Ctrl` instead of `Super` to not conflict with your WM/DE keybinds
|
||||
-- ^ Add type annotations for that sweet, sweet autocomplete
|
||||
|
||||
local terminal = "alacritty"
|
||||
|
||||
-- Keybinds ----------------------------------------------------------------------
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.c, function()
|
||||
-- The commented out line may crash the config process if you have no windows open.
|
||||
-- There is no nil warning here due to limitations in Lua LS type checking, so check for nil as shown below.
|
||||
-- window.get_focused():close()
|
||||
local win = window.get_focused()
|
||||
if win ~= nil then
|
||||
win:close()
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.space, function()
|
||||
local win = window.get_focused()
|
||||
if win ~= nil then
|
||||
win:toggle_floating()
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key }, keys.Return, function()
|
||||
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
|
||||
-- do something with the output here
|
||||
end)
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key }, keys.l, function()
|
||||
process.spawn("kitty")
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.k, function()
|
||||
process.spawn("foot")
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.j, function()
|
||||
process.spawn("nautilus")
|
||||
end)
|
||||
|
||||
-- Just testing stuff
|
||||
input.keybind({ mod_key }, keys.h, function()
|
||||
local wins = window.get_all()
|
||||
for _, win in pairs(wins) do
|
||||
print("loc: " .. (win:loc() and win:loc().x or "nil") .. ", " .. (win:loc() and win:loc().y or "nil"))
|
||||
print("size: " .. (win:size() and win:size().w or "nil") .. ", " .. (win:size() and win:size().h or "nil"))
|
||||
print("class: " .. (win:class() or "nil"))
|
||||
print("title: " .. (win:title() or "nil"))
|
||||
print("float: " .. tostring(win:floating()))
|
||||
end
|
||||
|
||||
print("----------------------")
|
||||
|
||||
local op = output.get_focused() --[[@as Output]]
|
||||
print("res: " .. (op:res() and (op:res().w .. ", " .. op:res().h) or "nil"))
|
||||
print("loc: " .. (op:loc() and (op:loc().x .. ", " .. op:loc().y) or "nil"))
|
||||
print("rr: " .. (op:refresh_rate() or "nil"))
|
||||
print("make: " .. (op:make() or "nil"))
|
||||
print("model: " .. (op:model() or "nil"))
|
||||
print("focused: " .. (tostring(op:focused())))
|
||||
|
||||
print("----------------------")
|
||||
|
||||
local wins = window.get_by_class("Alacritty")
|
||||
for _, win in pairs(wins) do
|
||||
print("loc: " .. (win:loc() and win:loc().x or "nil") .. ", " .. (win:loc() and win:loc().y or "nil"))
|
||||
print("size: " .. (win:size() and win:size().w or "nil") .. ", " .. (win:size() and win:size().h or "nil"))
|
||||
print("class: " .. (win:class() or "nil"))
|
||||
print("title: " .. (win:title() or "nil"))
|
||||
print("float: " .. tostring(win:floating()))
|
||||
end
|
||||
|
||||
print("----------------------")
|
||||
|
||||
local wins = window.get_by_title("~/p/pinnacle")
|
||||
for _, win in pairs(wins) do
|
||||
print("loc: " .. (win:loc() and win:loc().x or "nil") .. ", " .. (win:loc() and win:loc().y or "nil"))
|
||||
print("size: " .. (win:size() and win:size().w or "nil") .. ", " .. (win:size() and win:size().h or "nil"))
|
||||
print("class: " .. (win:class() or "nil"))
|
||||
print("title: " .. (win:title() or "nil"))
|
||||
print("float: " .. tostring(win:floating()))
|
||||
end
|
||||
|
||||
print("----------------------")
|
||||
|
||||
local tags = tag.get_on_output(output.get_focused() --[[@as Output]])
|
||||
for _, tg in pairs(tags) do
|
||||
print(tg:name())
|
||||
print((tg:output() and tg:output():name()) or "nil output")
|
||||
print(tg:active())
|
||||
end
|
||||
|
||||
print("----------------------")
|
||||
|
||||
local tags = tag.get_by_name("2")
|
||||
for _, tg in pairs(tags) do
|
||||
print(tg:name())
|
||||
print((tg:output() and tg:output():name()) or "nil output")
|
||||
print(tg:active())
|
||||
end
|
||||
|
||||
print("----------------------")
|
||||
|
||||
local tags = tag.get_all()
|
||||
for _, tg in pairs(tags) do
|
||||
print(tg:name())
|
||||
print((tg:output() and tg:output():name()) or "nil output")
|
||||
print(tg:active())
|
||||
end
|
||||
end)
|
||||
|
||||
-- Tags ---------------------------------------------------------------------------
|
||||
|
||||
output.connect_for_all(function(op)
|
||||
op:add_tags("1", "2", "3", "4", "5")
|
||||
-- Same as tag.add(op, "1", "2", "3", "4", "5")
|
||||
|
||||
-- local tags_table = { "Terminal", "Browser", "Code", "Email", "Potato" }
|
||||
-- op:add_tags(tags_table)
|
||||
|
||||
for _, t in pairs(tag.get_by_name("1")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:toggle()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
---@type Layout[]
|
||||
local layouts = {
|
||||
"MasterStack",
|
||||
"Dwindle",
|
||||
"Spiral",
|
||||
"CornerTopLeft",
|
||||
"CornerTopRight",
|
||||
"CornerBottomLeft",
|
||||
"CornerBottomRight",
|
||||
}
|
||||
local indices = {}
|
||||
|
||||
-- Layout cycling
|
||||
-- Yes, this is overly complicated and yes, I'll cook up a way to make it less so.
|
||||
input.keybind({ mod_key }, keys.space, function()
|
||||
local tags = output.get_focused():tags()
|
||||
for _, tg in pairs(tags) do
|
||||
if tg:active() then
|
||||
local name = tg:name()
|
||||
if name == nil then
|
||||
return
|
||||
end
|
||||
tg:set_layout(layouts[indices[name] or 1])
|
||||
if indices[name] == nil then
|
||||
indices[name] = 2
|
||||
else
|
||||
if indices[name] + 1 > #layouts then
|
||||
indices[name] = 1
|
||||
else
|
||||
indices[name] = indices[name] + 1
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.space, function()
|
||||
local tags = output.get_focused():tags()
|
||||
for _, tg in pairs(tags) do
|
||||
if tg:active() then
|
||||
local name = tg:name()
|
||||
if name == nil then
|
||||
return
|
||||
end
|
||||
tg:set_layout(layouts[indices[name] or #layouts])
|
||||
if indices[name] == nil then
|
||||
indices[name] = #layouts - 1
|
||||
else
|
||||
if indices[name] - 1 < 1 then
|
||||
indices[name] = #layouts
|
||||
else
|
||||
indices[name] = indices[name] - 1
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key }, keys.KEY_1, function()
|
||||
for _, t in pairs(tag.get_by_name("1")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:switch_to()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_2, function()
|
||||
for _, t in pairs(tag.get_by_name("2")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:switch_to()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_3, function()
|
||||
for _, t in pairs(tag.get_by_name("3")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:switch_to()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_4, function()
|
||||
for _, t in pairs(tag.get_by_name("4")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:switch_to()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_5, function()
|
||||
for _, t in pairs(tag.get_by_name("5")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:switch_to()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_1, function()
|
||||
for _, t in pairs(tag.get_by_name("1")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:toggle()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_2, function()
|
||||
for _, t in pairs(tag.get_by_name("2")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:toggle()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_3, function()
|
||||
for _, t in pairs(tag.get_by_name("3")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:toggle()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_4, function()
|
||||
for _, t in pairs(tag.get_by_name("4")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:toggle()
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_5, function()
|
||||
for _, t in pairs(tag.get_by_name("5")) do
|
||||
if t:output() and t:output():focused() then
|
||||
t:toggle()
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_1, function()
|
||||
for _, t in pairs(tag.get_by_name("1")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():move_to_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_2, function()
|
||||
for _, t in pairs(tag.get_by_name("2")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():move_to_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_3, function()
|
||||
for _, t in pairs(tag.get_by_name("3")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():move_to_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_4, function()
|
||||
for _, t in pairs(tag.get_by_name("4")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():move_to_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_5, function()
|
||||
for _, t in pairs(tag.get_by_name("5")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():move_to_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_1, function()
|
||||
for _, t in pairs(tag.get_by_name("1")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():toggle_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_2, function()
|
||||
for _, t in pairs(tag.get_by_name("2")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():toggle_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_3, function()
|
||||
for _, t in pairs(tag.get_by_name("3")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():toggle_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_4, function()
|
||||
for _, t in pairs(tag.get_by_name("4")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():toggle_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_5, function()
|
||||
for _, t in pairs(tag.get_by_name("5")) do
|
||||
if t:output() and t:output():focused() then
|
||||
window.get_focused():toggle_tag(t)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end)
|
|
@ -4,180 +4,541 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
---@class Window
|
||||
---@field private id integer The internal id of this window
|
||||
---@field private app_id string? The equivalent of an X11 window's class
|
||||
---@field private title string? The window's title
|
||||
---@field private size { w: integer, h: integer } The size of the window
|
||||
---@field private location { x: integer, y: integer } The location of the window
|
||||
---@field private floating boolean Whether the window is floating or not (tiled)
|
||||
local win = {}
|
||||
---@class WindowModule
|
||||
local window_module = {}
|
||||
|
||||
---@param props Window
|
||||
---@class Window
|
||||
---@field private _id integer The internal id of this window
|
||||
local window = {}
|
||||
|
||||
---@param window_id WindowId
|
||||
---@return Window
|
||||
local function new_window(props)
|
||||
local function create_window(window_id)
|
||||
---@type Window
|
||||
local w = { _id = window_id }
|
||||
-- Copy functions over
|
||||
for k, v in pairs(win) do
|
||||
props[k] = v
|
||||
for k, v in pairs(window) do
|
||||
w[k] = v
|
||||
end
|
||||
|
||||
return props
|
||||
return w
|
||||
end
|
||||
|
||||
---Set a window's size.
|
||||
---Get this window's unique id.
|
||||
---
|
||||
---***You will probably not need to use this.***
|
||||
---@return WindowId
|
||||
function window:id()
|
||||
return self._id
|
||||
end
|
||||
|
||||
---Set this window's size.
|
||||
---
|
||||
---### Examples
|
||||
---```lua
|
||||
---window.get_focused():set_size({ w = 500, h = 500 }) -- make the window square and 500 pixels wide/tall
|
||||
---window.get_focused():set_size({ h = 300 }) -- keep the window's width but make it 300 pixels tall
|
||||
---window.get_focused():set_size({}) -- do absolutely nothing useful
|
||||
---```
|
||||
---@param size { w: integer?, h: integer? }
|
||||
function win:set_size(size)
|
||||
self.size = {
|
||||
w = size.w or self.size.w,
|
||||
h = size.h or self.size.h,
|
||||
}
|
||||
SendMsg({
|
||||
SetWindowSize = {
|
||||
window_id = self.id,
|
||||
size = { self.size.w, self.size.h },
|
||||
},
|
||||
})
|
||||
---@see WindowModule.set_size — The corresponding module function
|
||||
function window:set_size(size)
|
||||
window_module.set_size(self, size)
|
||||
end
|
||||
|
||||
---Move a window to a tag, removing all other ones.
|
||||
---@param name string The name of the tag.
|
||||
function win:move_to_tag(name)
|
||||
SendMsg({
|
||||
MoveWindowToTag = {
|
||||
window_id = self.id,
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
---Move this window to a tag, removing all other ones.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With the focused window on tags 1, 2, 3, and 4...
|
||||
---window.get_focused():move_to_tag("5")
|
||||
----- ...will make the window only appear on tag 5.
|
||||
---```
|
||||
---@param name string
|
||||
---@param output Output?
|
||||
---@overload fun(self: self, t: Tag)
|
||||
---@see WindowModule.move_to_tag — The corresponding module function
|
||||
function window:move_to_tag(name, output)
|
||||
window_module.move_to_tag(self, name, output)
|
||||
end
|
||||
|
||||
---Toggle the specified tag for this window.
|
||||
---@param name string The name of the tag.
|
||||
function win:toggle_tag(name)
|
||||
SendMsg({
|
||||
ToggleTagOnWindow = {
|
||||
window_id = self.id,
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
---
|
||||
---Note: toggling off all tags currently makes a window not response to layouting.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With the focused window only on tag 1...
|
||||
---window.get_focused():toggle_tag("2")
|
||||
----- ...will also make the window appear on tag 2.
|
||||
---```
|
||||
---@param name string
|
||||
---@param output Output?
|
||||
---@overload fun(self: self, t: Tag)
|
||||
---@see WindowModule.toggle_tag — The corresponding module function
|
||||
function window:toggle_tag(name, output)
|
||||
window_module.toggle_tag(self, name, output)
|
||||
end
|
||||
|
||||
---Close this window.
|
||||
function win:close()
|
||||
SendMsg({
|
||||
CloseWindow = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
---
|
||||
---This only sends a close *event* to the window and is the same as just clicking the X button in the titlebar.
|
||||
---This will trigger save prompts in applications like GIMP.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---window.get_focused():close() -- close the currently focused window
|
||||
---```
|
||||
---@see WindowModule.close — The corresponding module function
|
||||
function window:close()
|
||||
window_module.close(self)
|
||||
end
|
||||
|
||||
---Toggle this window's floating status.
|
||||
function win:toggle_floating()
|
||||
SendMsg({
|
||||
ToggleFloating = {
|
||||
window_id = self.id,
|
||||
},
|
||||
})
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---window.get_focused():toggle_floating() -- toggles the focused window between tiled and floating
|
||||
---```
|
||||
---@see WindowModule.toggle_floating — The corresponding module function
|
||||
function window:toggle_floating()
|
||||
window_module.toggle_floating(self)
|
||||
end
|
||||
|
||||
---Get a window's size.
|
||||
---@return { w: integer, h: integer }
|
||||
function win:get_size()
|
||||
return self.size
|
||||
---Get this window's size.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With a 4K monitor, given a focused fullscreen window...
|
||||
---local size = window.get_focused():size()
|
||||
----- ...should have size equal to `{ w = 3840, h = 2160 }`.
|
||||
---```
|
||||
---@return { w: integer, h: integer }|nil size The size of the window, or nil if it doesn't exist.
|
||||
---@see WindowModule.size — The corresponding module function
|
||||
function window:size()
|
||||
return window_module.size(self)
|
||||
end
|
||||
|
||||
---Get this window's location in the global space.
|
||||
---
|
||||
---Think of your monitors as being laid out on a big sheet.
|
||||
---The top left of the sheet if you trim it down is (0, 0).
|
||||
---The location of this window is relative to that point.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With two 1080p monitors side by side and set up as such,
|
||||
----- if a window is fullscreen on the right one...
|
||||
---local loc = that_window:loc()
|
||||
----- ...should have loc equal to `{ x = 1920, y = 0 }`.
|
||||
---```
|
||||
---@return { x: integer, y: integer }|nil loc The location of the window, or nil if it's not on-screen or alive.
|
||||
---@see WindowModule.loc — The corresponding module function
|
||||
function window:loc()
|
||||
return window_module.loc(self)
|
||||
end
|
||||
|
||||
---Get this window's class. This is usually the name of the application.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused...
|
||||
---print(window.get_focused():class())
|
||||
----- ...should print "Alacritty".
|
||||
---```
|
||||
---@return string|nil class This window's class, or nil if it doesn't exist.
|
||||
---@see WindowModule.class — The corresponding module function
|
||||
function window:class()
|
||||
return window_module.class(self)
|
||||
end
|
||||
|
||||
---Get this window's title.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused...
|
||||
---print(window.get_focused():title())
|
||||
----- ...should print the directory Alacritty is in or what it's running (what's in its title bar).
|
||||
---```
|
||||
---@return string|nil title This window's title, or nil if it doesn't exist.
|
||||
---@see WindowModule.title — The corresponding module function
|
||||
function window:title()
|
||||
return window_module.title(self)
|
||||
end
|
||||
|
||||
---Get this window's floating status.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With the focused window floating...
|
||||
---print(window.get_focused():floating())
|
||||
----- ...should print `true`.
|
||||
---```
|
||||
---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist.
|
||||
---@see WindowModule.floating — The corresponding module function
|
||||
function window:floating()
|
||||
return window_module.floating(self)
|
||||
end
|
||||
|
||||
---Get whether or not this window is focused.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---print(window.get_focused():focused()) -- should print `true`.
|
||||
---```
|
||||
---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist.
|
||||
---@see WindowModule.focused — The corresponding module function
|
||||
function window:focused()
|
||||
return window_module.focused(self)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
|
||||
local window = {}
|
||||
---Get all windows with the specified class (usually the name of the application).
|
||||
---@param class string The class. For example, Alacritty's class is "Alacritty".
|
||||
---@return Window[]
|
||||
function window_module.get_by_class(class)
|
||||
local windows = window_module.get_all()
|
||||
|
||||
---TODO: This function is not implemented yet.
|
||||
---
|
||||
---Get a window by its app id (aka its X11 class).
|
||||
---@param app_id string The window's app id. For example, Alacritty's app id is "Alacritty".
|
||||
---@return Window|nil
|
||||
function window.get_by_app_id(app_id)
|
||||
SendRequest({
|
||||
GetWindowByAppId = {
|
||||
app_id = app_id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local window_id = response.RequestResponse.response.Window.window_id
|
||||
|
||||
if window_id == nil then
|
||||
return nil
|
||||
---@type Window[]
|
||||
local windows_ret = {}
|
||||
for _, w in pairs(windows) do
|
||||
if w:class() == class then
|
||||
table.insert(windows_ret, w)
|
||||
end
|
||||
end
|
||||
|
||||
---@type Window
|
||||
local wind = {
|
||||
id = window_id,
|
||||
}
|
||||
|
||||
return new_window(wind)
|
||||
return windows_ret
|
||||
end
|
||||
|
||||
---TODO: This function is not implemented yet.
|
||||
---
|
||||
---Get a window by its title.
|
||||
---@param title string The window's title.
|
||||
---@return Window|nil
|
||||
function window.get_by_title(title)
|
||||
SendRequest({
|
||||
GetWindowByTitle = {
|
||||
title = title,
|
||||
},
|
||||
})
|
||||
---Get all windows with the specified title.
|
||||
---@param title string The title.
|
||||
---@return Window[]
|
||||
function window_module.get_by_title(title)
|
||||
local windows = window_module.get_all()
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local window_id = response.RequestResponse.response.Window.window_id
|
||||
|
||||
if window_id == nil then
|
||||
return nil
|
||||
---@type Window[]
|
||||
local windows_ret = {}
|
||||
for _, w in pairs(windows) do
|
||||
if w:title() == title then
|
||||
table.insert(windows_ret, w)
|
||||
end
|
||||
end
|
||||
|
||||
---@type Window
|
||||
local wind = {
|
||||
id = window_id,
|
||||
}
|
||||
|
||||
return new_window(wind)
|
||||
return windows_ret
|
||||
end
|
||||
|
||||
---Get the currently focused window.
|
||||
---@return Window|nil
|
||||
function window.get_focused()
|
||||
SendRequest("GetWindowByFocus")
|
||||
function window_module.get_focused()
|
||||
local windows = window_module.get_all()
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local window_id = response.RequestResponse.response.Window.window_id
|
||||
|
||||
if window_id == nil then
|
||||
return nil
|
||||
for _, w in pairs(windows) do
|
||||
if w:focused() then
|
||||
return w
|
||||
end
|
||||
end
|
||||
|
||||
---@type Window
|
||||
local wind = {
|
||||
id = window_id,
|
||||
}
|
||||
|
||||
return new_window(wind)
|
||||
return nil
|
||||
end
|
||||
|
||||
---Get all windows.
|
||||
---@return Window[]
|
||||
function window.get_all()
|
||||
SendRequest("GetAllWindows")
|
||||
|
||||
local window_ids = ReadMsg().RequestResponse.response.Windows.window_ids
|
||||
function window_module.get_all()
|
||||
local window_ids = Request("GetWindows").RequestResponse.response.Windows.window_ids
|
||||
---@type Window[]
|
||||
local windows = {}
|
||||
for i, window_id in ipairs(window_ids) do
|
||||
windows[i] = new_window({ id = window_id })
|
||||
for _, window_id in pairs(window_ids) do
|
||||
table.insert(windows, create_window(window_id))
|
||||
end
|
||||
return windows
|
||||
end
|
||||
|
||||
return window
|
||||
---Toggle the tag with the given name and (optional) output for the specified window.
|
||||
---You can also provide a tag object instead of a name and output.
|
||||
---@param w Window
|
||||
---@param name string
|
||||
---@param output Output?
|
||||
---@overload fun(w: Window, t: Tag)
|
||||
---@see Window.toggle_tag — The corresponding object method
|
||||
function window_module.toggle_tag(w, name, output)
|
||||
if type(name) == "table" then
|
||||
SendMsg({
|
||||
ToggleTagOnWindow = {
|
||||
window_id = w:id(),
|
||||
tag_id = name--[[@as Tag]]:id(),
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local output = output or require("output").get_focused()
|
||||
|
||||
if output == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local tags = require("tag").get_by_name(name)
|
||||
for _, t in pairs(tags) do
|
||||
if t:output() and t:output():name() == output:name() then
|
||||
SendMsg({
|
||||
ToggleTagOnWindow = {
|
||||
window_id = w:id(),
|
||||
tag_id = t:id(),
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Move the specified window to the tag with the given name and (optional) output.
|
||||
---You can also provide a tag object instead of a name and output.
|
||||
---@param w Window
|
||||
---@param name string
|
||||
---@param output Output?
|
||||
---@overload fun(w: Window, t: Tag)
|
||||
---@see Window.move_to_tag — The corresponding object method
|
||||
function window_module.move_to_tag(w, name, output)
|
||||
if type(name) == "table" then
|
||||
SendMsg({
|
||||
MoveWindowToTag = {
|
||||
window_id = w:id(),
|
||||
tag_id = name--[[@as Tag]]:id(),
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
|
||||
local output = output or require("output").get_focused()
|
||||
|
||||
if output == nil then
|
||||
return
|
||||
end
|
||||
|
||||
local tags = require("tag").get_by_name(name)
|
||||
for _, t in pairs(tags) do
|
||||
if t:output() and t:output():name() == output:name() then
|
||||
SendMsg({
|
||||
MoveWindowToTag = {
|
||||
window_id = w:id(),
|
||||
tag_id = t:id(),
|
||||
},
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Set the specified window's size.
|
||||
---
|
||||
---### Examples
|
||||
---```lua
|
||||
---local win = window.get_focused()
|
||||
---if win ~= nil then
|
||||
--- window.set_size(win, { w = 500, h = 500 }) -- make the window square and 500 pixels wide/tall
|
||||
--- window.set_size(win, { h = 300 }) -- keep the window's width but make it 300 pixels tall
|
||||
--- window.set_size(win, {}) -- do absolutely nothing useful
|
||||
---end
|
||||
---```
|
||||
---@param win Window
|
||||
---@param size { w: integer?, h: integer? }
|
||||
---@see Window.set_size — The corresponding object method
|
||||
function window_module.set_size(win, size)
|
||||
SendMsg({
|
||||
SetWindowSize = {
|
||||
window_id = win:id(),
|
||||
width = size.w,
|
||||
height = size.h,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Close the specified window.
|
||||
---
|
||||
---This only sends a close *event* to the window and is the same as just clicking the X button in the titlebar.
|
||||
---This will trigger save prompts in applications like GIMP.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---local win = window.get_focused()
|
||||
---if win ~= nil then
|
||||
--- window.close(win) -- close the currently focused window
|
||||
---end
|
||||
---```
|
||||
---@param win Window
|
||||
---@see Window.close — The corresponding object method
|
||||
function window_module.close(win)
|
||||
SendMsg({
|
||||
CloseWindow = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Toggle the specified window between tiled and floating.
|
||||
---@param win Window
|
||||
---@see Window.toggle_floating — The corresponding object method
|
||||
function window_module.toggle_floating(win)
|
||||
SendMsg({
|
||||
ToggleFloating = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Get the specified window's size.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With a 4K monitor, given a focused fullscreen window `win`...
|
||||
---local size = window.size(win)
|
||||
----- ...should have size equal to `{ w = 3840, h = 2160 }`.
|
||||
---```
|
||||
---@param win Window
|
||||
---@return { w: integer, h: integer }|nil size The size of the window, or nil if it doesn't exist.
|
||||
---@see Window.size — The corresponding object method
|
||||
function window_module.size(win)
|
||||
local response = Request({
|
||||
GetWindowProps = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
local size = response.RequestResponse.response.WindowProps.size
|
||||
if size == nil then
|
||||
return nil
|
||||
else
|
||||
return {
|
||||
w = size[1],
|
||||
h = size[2],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---Get the specified window's location in the global space.
|
||||
---
|
||||
---Think of your monitors as being laid out on a big sheet.
|
||||
---The top left of the sheet if you trim it down is (0, 0).
|
||||
---The location of this window is relative to that point.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With two 1080p monitors side by side and set up as such,
|
||||
----- if a window `win` is fullscreen on the right one...
|
||||
---local loc = window.loc(win)
|
||||
----- ...should have loc equal to `{ x = 1920, y = 0 }`.
|
||||
---```
|
||||
---@param win Window
|
||||
---@return { x: integer, y: integer }|nil loc The location of the window, or nil if it's not on-screen or alive.
|
||||
---@see Window.loc — The corresponding object method
|
||||
function window_module.loc(win)
|
||||
local response = Request({
|
||||
GetWindowProps = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
local loc = response.RequestResponse.response.WindowProps.loc
|
||||
if loc == nil then
|
||||
return nil
|
||||
else
|
||||
return {
|
||||
x = loc[1],
|
||||
y = loc[2],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
---Get the specified window's class. This is usually the name of the application.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused...
|
||||
---local win = window.get_focused()
|
||||
---if win ~= nil then
|
||||
--- print(window.class(win))
|
||||
---end
|
||||
----- ...should print "Alacritty".
|
||||
---```
|
||||
---@param win Window
|
||||
---@return string|nil class This window's class, or nil if it doesn't exist.
|
||||
---@see Window.class — The corresponding object method
|
||||
function window_module.class(win)
|
||||
local response = Request({
|
||||
GetWindowProps = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
local class = response.RequestResponse.response.WindowProps.class
|
||||
return class
|
||||
end
|
||||
|
||||
---Get the specified window's title.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With Alacritty focused...
|
||||
---local win = window.get_focused()
|
||||
---if win ~= nil then
|
||||
--- print(window.title(win))
|
||||
---end
|
||||
----- ...should print the directory Alacritty is in or what it's running (what's in its title bar).
|
||||
---```
|
||||
---@param win Window
|
||||
---@return string|nil title This window's title, or nil if it doesn't exist.
|
||||
---@see Window.title — The corresponding object method
|
||||
function window_module.title(win)
|
||||
local response = Request({
|
||||
GetWindowProps = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
local title = response.RequestResponse.response.WindowProps.title
|
||||
return title
|
||||
end
|
||||
|
||||
---Get this window's floating status.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
----- With the focused window floating...
|
||||
---local win = window.get_focused()
|
||||
---if win ~= nil then
|
||||
--- print(window.floating(win))
|
||||
---end
|
||||
----- ...should print `true`.
|
||||
---```
|
||||
---@param win Window
|
||||
---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist.
|
||||
---@see Window.floating — The corresponding object method
|
||||
function window_module.floating(win)
|
||||
local response = Request({
|
||||
GetWindowProps = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
local floating = response.RequestResponse.response.WindowProps.floating
|
||||
return floating
|
||||
end
|
||||
|
||||
---Get whether or not this window is focused.
|
||||
---
|
||||
---### Example
|
||||
---```lua
|
||||
---local win = window.get_focused()
|
||||
---if win ~= nil then
|
||||
--- print(window.focused(win)) -- Should print `true`
|
||||
---end
|
||||
---```
|
||||
---@param win Window
|
||||
---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist.
|
||||
---@see Window.focused — The corresponding object method
|
||||
function window_module.focused(win)
|
||||
local response = Request({
|
||||
GetWindowProps = {
|
||||
window_id = win:id(),
|
||||
},
|
||||
})
|
||||
local focused = response.RequestResponse.response.WindowProps.focused
|
||||
return focused
|
||||
end
|
||||
return window_module
|
||||
|
|
100
src/api/msg.rs
100
src/api/msg.rs
|
@ -33,25 +33,26 @@ pub enum Msg {
|
|||
},
|
||||
SetWindowSize {
|
||||
window_id: WindowId,
|
||||
size: (i32, i32),
|
||||
#[serde(default)]
|
||||
width: Option<i32>,
|
||||
#[serde(default)]
|
||||
height: Option<i32>,
|
||||
},
|
||||
MoveWindowToTag {
|
||||
window_id: WindowId,
|
||||
tag_id: String,
|
||||
tag_id: TagId,
|
||||
},
|
||||
ToggleTagOnWindow {
|
||||
window_id: WindowId,
|
||||
tag_id: String,
|
||||
tag_id: TagId,
|
||||
},
|
||||
|
||||
// Tag management
|
||||
ToggleTag {
|
||||
output_name: String,
|
||||
tag_name: String,
|
||||
tag_id: TagId,
|
||||
},
|
||||
SwitchToTag {
|
||||
output_name: String,
|
||||
tag_name: String,
|
||||
tag_id: TagId,
|
||||
},
|
||||
AddTags {
|
||||
/// The name of the output you want these tags on.
|
||||
|
@ -60,12 +61,10 @@ pub enum Msg {
|
|||
},
|
||||
RemoveTags {
|
||||
/// The name of the output you want these tags removed from.
|
||||
output_name: String,
|
||||
tag_names: Vec<String>,
|
||||
tag_ids: Vec<TagId>,
|
||||
},
|
||||
SetLayout {
|
||||
output_name: String,
|
||||
tag_name: String,
|
||||
tag_id: TagId,
|
||||
layout: Layout,
|
||||
},
|
||||
|
||||
|
@ -86,27 +85,28 @@ pub enum Msg {
|
|||
/// Quit the compositor.
|
||||
Quit,
|
||||
|
||||
Request(Request),
|
||||
Request {
|
||||
request_id: RequestId,
|
||||
request: Request,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RequestId(pub u32);
|
||||
pub struct RequestId(u32);
|
||||
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
/// Messages that require a server response, usually to provide some data.
|
||||
pub enum Request {
|
||||
GetWindowByAppId { app_id: String },
|
||||
GetWindowByTitle { title: String },
|
||||
GetWindowByFocus,
|
||||
GetAllWindows,
|
||||
GetOutputByName { output_name: String },
|
||||
GetOutputsByModel { model: String },
|
||||
GetOutputsByRes { res: (u32, u32) },
|
||||
GetOutputByFocus,
|
||||
GetTagsByOutput { output_name: String },
|
||||
GetTagActive { tag_id: TagId },
|
||||
GetTagName { tag_id: TagId },
|
||||
// Windows
|
||||
GetWindows,
|
||||
GetWindowProps { window_id: WindowId },
|
||||
// Outputs
|
||||
GetOutputs,
|
||||
GetOutputProps { output_name: String },
|
||||
// Tags
|
||||
GetTags,
|
||||
GetTagProps { tag_id: TagId },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -161,6 +161,7 @@ pub enum OutgoingMsg {
|
|||
args: Option<Args>,
|
||||
},
|
||||
RequestResponse {
|
||||
request_id: RequestId,
|
||||
response: RequestResponse,
|
||||
},
|
||||
}
|
||||
|
@ -185,10 +186,49 @@ pub enum Args {
|
|||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum RequestResponse {
|
||||
Window { window_id: Option<WindowId> },
|
||||
Windows { window_ids: Vec<WindowId> },
|
||||
Outputs { output_names: Vec<String> },
|
||||
Tags { tag_ids: Vec<TagId> },
|
||||
TagActive { active: bool },
|
||||
TagName { name: String },
|
||||
Window {
|
||||
window_id: Option<WindowId>,
|
||||
},
|
||||
Windows {
|
||||
window_ids: Vec<WindowId>,
|
||||
},
|
||||
WindowProps {
|
||||
size: Option<(i32, i32)>,
|
||||
loc: Option<(i32, i32)>,
|
||||
class: Option<String>,
|
||||
title: Option<String>,
|
||||
floating: Option<bool>,
|
||||
focused: Option<bool>,
|
||||
},
|
||||
Output {
|
||||
output_name: Option<String>,
|
||||
},
|
||||
Outputs {
|
||||
output_names: Vec<String>,
|
||||
},
|
||||
OutputProps {
|
||||
/// The make of the output.
|
||||
make: Option<String>,
|
||||
/// The model of the output.
|
||||
model: Option<String>,
|
||||
/// The location of the output in the space.
|
||||
loc: Option<(i32, i32)>,
|
||||
/// The resolution of the output.
|
||||
res: Option<(i32, i32)>,
|
||||
/// The refresh rate of the output.
|
||||
refresh_rate: Option<i32>,
|
||||
/// The size of the output, in millimeters.
|
||||
physical_size: Option<(i32, i32)>,
|
||||
/// Whether the output is focused or not.
|
||||
focused: Option<bool>,
|
||||
tag_ids: Option<Vec<TagId>>,
|
||||
},
|
||||
Tags {
|
||||
tag_ids: Vec<TagId>,
|
||||
},
|
||||
TagProps {
|
||||
active: Option<bool>,
|
||||
name: Option<String>,
|
||||
output_name: Option<String>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -159,7 +159,8 @@ impl Layout {
|
|||
state.size.expect("size should have been set")
|
||||
});
|
||||
let win1_loc = win1.with_state(|state| {
|
||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
||||
let WindowResizeState::Requested(_, loc) =
|
||||
state.resize_state else { unreachable!() };
|
||||
loc
|
||||
});
|
||||
|
||||
|
@ -271,7 +272,8 @@ impl Layout {
|
|||
state.size.expect("size should have been set")
|
||||
});
|
||||
let win1_loc = win1.with_state(|state| {
|
||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
||||
let WindowResizeState::Requested(_, loc) =
|
||||
state.resize_state else { unreachable!() };
|
||||
loc
|
||||
});
|
||||
|
||||
|
|
593
src/state.rs
593
src/state.rs
|
@ -16,7 +16,7 @@ use std::{
|
|||
|
||||
use crate::{
|
||||
api::{
|
||||
msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestResponse},
|
||||
msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestId, RequestResponse},
|
||||
PinnacleSocketSource,
|
||||
},
|
||||
focus::FocusState,
|
||||
|
@ -55,7 +55,7 @@ use smithay::{
|
|||
dmabuf::DmabufFeedback,
|
||||
fractional_scale::FractionalScaleManagerState,
|
||||
output::OutputManagerState,
|
||||
shell::xdg::XdgShellState,
|
||||
shell::xdg::{XdgShellState, XdgToplevelSurfaceData},
|
||||
shm::ShmState,
|
||||
socket::ListeningSocketSource,
|
||||
viewporter::ViewporterState,
|
||||
|
@ -119,20 +119,12 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
Msg::SetMousebind { button: _ } => todo!(),
|
||||
Msg::CloseWindow { window_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
if let Some(window) = window_id.window(self) {
|
||||
window.toplevel().send_close();
|
||||
}
|
||||
}
|
||||
Msg::ToggleFloating { window_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|win| win.with_state(|state| state.id == window_id)).cloned()
|
||||
{
|
||||
if let Some(window) = window_id.window(self) {
|
||||
crate::window::toggle_floating(self, &window);
|
||||
}
|
||||
}
|
||||
|
@ -144,150 +136,101 @@ impl<B: Backend> State<B> {
|
|||
self.handle_spawn(command, callback_id);
|
||||
}
|
||||
|
||||
Msg::SetWindowSize { window_id, size } => {
|
||||
let Some(window) = self.space.elements().find(|&win| {
|
||||
win.with_state( |state| state.id == window_id)
|
||||
}) else { return; };
|
||||
Msg::SetWindowSize {
|
||||
window_id,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
|
||||
// TODO: tiled vs floating
|
||||
let window_size = window.geometry().size;
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(size.into());
|
||||
// INFO: calling window.geometry() in with_pending_state
|
||||
// | will hang the compositor
|
||||
state.size = Some(
|
||||
(
|
||||
width.unwrap_or(window_size.w),
|
||||
height.unwrap_or(window_size.h),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
window.toplevel().send_pending_configure();
|
||||
}
|
||||
Msg::MoveWindowToTag { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
window.with_state(|state| {
|
||||
self.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.with_state(|op_state| {
|
||||
let tag = op_state.tags.iter().find(|tag| tag.name() == tag_id);
|
||||
if let Some(tag) = tag {
|
||||
state.tags = vec![tag.clone()];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let output = self.focus_state.focused_output.clone().unwrap();
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
let Some(tag) = tag_id.tag(self) else { return };
|
||||
window.with_state(|state| {
|
||||
state.tags = vec![tag.clone()];
|
||||
});
|
||||
let Some(output) = tag.output(self) else { return };
|
||||
self.re_layout(&output);
|
||||
}
|
||||
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
window.with_state(|state| {
|
||||
self.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.with_state(|op_state| {
|
||||
let tag = op_state.tags.iter().find(|tag| tag.name() == tag_id);
|
||||
if let Some(tag) = tag {
|
||||
if state.tags.contains(tag) {
|
||||
state.tags.retain(|tg| tg != tag);
|
||||
} else {
|
||||
state.tags.push(tag.clone());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
let Some(window) = window_id.window(self) else { return };
|
||||
let Some(tag) = tag_id.tag(self) else { return };
|
||||
|
||||
let output = self.focus_state.focused_output.clone().unwrap();
|
||||
self.re_layout(&output);
|
||||
}
|
||||
window.with_state(|state| {
|
||||
if state.tags.contains(&tag) {
|
||||
state.tags.retain(|tg| tg != &tag);
|
||||
} else {
|
||||
state.tags.push(tag.clone());
|
||||
}
|
||||
});
|
||||
|
||||
let Some(output) = tag.output(self) else { return };
|
||||
self.re_layout(&output);
|
||||
}
|
||||
Msg::ToggleTag { output_name, tag_name } => {
|
||||
Msg::ToggleTag { tag_id } => {
|
||||
tracing::debug!("ToggleTag");
|
||||
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
if let Some(output) = output {
|
||||
|
||||
output.with_state(|state| {
|
||||
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) {
|
||||
tracing::debug!("Setting tag {tag:?} to {}", !tag.active());
|
||||
tag.set_active(!tag.active());
|
||||
}
|
||||
});
|
||||
self.re_layout(&output);
|
||||
if let Some(tag) = tag_id.tag(self) {
|
||||
tag.set_active(!tag.active());
|
||||
if let Some(output) = tag.output(self) {
|
||||
self.re_layout(&output);
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg::SwitchToTag { output_name, tag_name } => {
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
if let Some(output) = output {
|
||||
|
||||
output.with_state(|state| {
|
||||
if !state.tags.iter().any(|tag| tag.name() == tag_name) {
|
||||
// TODO: notify error
|
||||
return;
|
||||
}
|
||||
for tag in state.tags.iter_mut() {
|
||||
tag.set_active(false);
|
||||
}
|
||||
|
||||
let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) else {
|
||||
unreachable!()
|
||||
};
|
||||
tag.set_active(true);
|
||||
|
||||
tracing::debug!(
|
||||
"focused tags: {:?}",
|
||||
state
|
||||
.tags
|
||||
.iter()
|
||||
.filter(|tag| tag.active())
|
||||
.map(|tag| tag.name())
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
});
|
||||
self.re_layout(&output);
|
||||
}
|
||||
Msg::SwitchToTag { tag_id } => {
|
||||
let Some(tag) = tag_id.tag(self) else { return };
|
||||
let Some(output) = tag.output(self) else { return };
|
||||
output.with_state(|state| {
|
||||
for op_tag in state.tags.iter_mut() {
|
||||
op_tag.set_active(false);
|
||||
}
|
||||
tag.set_active(true);
|
||||
});
|
||||
self.re_layout(&output);
|
||||
}
|
||||
// TODO: add output
|
||||
Msg::AddTags { output_name, tag_names } => {
|
||||
Msg::AddTags {
|
||||
output_name,
|
||||
tag_names,
|
||||
} => {
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
{
|
||||
output.with_state(|state| {
|
||||
state
|
||||
.tags
|
||||
.extend(tag_names.iter().cloned().map(Tag::new));
|
||||
state.tags.extend(tag_names.iter().cloned().map(Tag::new));
|
||||
tracing::debug!("tags added, are now {:?}", state.tags);
|
||||
});
|
||||
}
|
||||
}
|
||||
Msg::RemoveTags { output_name, tag_names } => {
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
{
|
||||
Msg::RemoveTags { tag_ids } => {
|
||||
let tags = tag_ids.into_iter().filter_map(|tag_id| tag_id.tag(self));
|
||||
for tag in tags {
|
||||
let Some(output) = tag.output(self) else { continue };
|
||||
output.with_state(|state| {
|
||||
state.tags.retain(|tag| !tag_names.contains(&tag.name()));
|
||||
state.tags.retain(|tg| tg != &tag);
|
||||
});
|
||||
}
|
||||
}
|
||||
Msg::SetLayout { output_name, tag_name, layout } => {
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
if let Some(output) = output {
|
||||
|
||||
output.with_state(|state| {
|
||||
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) {
|
||||
tag.set_layout(layout);
|
||||
}
|
||||
});
|
||||
self.re_layout(&output);
|
||||
}
|
||||
Msg::SetLayout { tag_id, layout } => {
|
||||
let Some(tag) = tag_id.tag(self) else { return };
|
||||
tag.set_layout(layout);
|
||||
let Some(output) = tag.output(self) else { return };
|
||||
self.re_layout(&output);
|
||||
}
|
||||
|
||||
Msg::ConnectForAllOutputs { callback_id } => {
|
||||
|
@ -316,195 +259,191 @@ impl<B: Backend> State<B> {
|
|||
self.loop_signal.stop();
|
||||
}
|
||||
|
||||
Msg::Request(request) => {
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
match request {
|
||||
Request::GetWindowByAppId { app_id: _ } => todo!(),
|
||||
Request::GetWindowByTitle { title: _ } => todo!(),
|
||||
Request::GetWindowByFocus => {
|
||||
match self.focus_state.current_focus() {
|
||||
Some(current_focus) => {
|
||||
let window_id =
|
||||
current_focus.with_state(|state| state.id);
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window { window_id: Some(window_id) },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
},
|
||||
None => {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window { window_id: None },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
},
|
||||
}
|
||||
}
|
||||
Request::GetAllWindows => {
|
||||
let window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|win| {
|
||||
win.with_state(|state| state.id)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Msg::Request {
|
||||
request_id,
|
||||
request,
|
||||
} => {
|
||||
self.handle_request(request_id, request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: figure out what to do if error
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Windows {
|
||||
window_ids,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
Request::GetOutputByName { output_name } => {
|
||||
// TODO: name better
|
||||
let names = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
.map(|output| output.name());
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs {
|
||||
output_names: if let Some(name) = names {
|
||||
vec![name]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetOutputsByModel { model } => {
|
||||
let names = self
|
||||
.space
|
||||
.outputs()
|
||||
.filter(|output| output.physical_properties().model == model)
|
||||
.map(|output| output.name())
|
||||
.collect::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs { output_names: names },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetOutputsByRes { res } => {
|
||||
let names = self
|
||||
.space
|
||||
.outputs()
|
||||
.filter_map(|output| {
|
||||
if let Some(mode) = output.current_mode() {
|
||||
if mode.size == (res.0 as i32, res.1 as i32).into() {
|
||||
Some(output.name())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs { output_names: names },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetOutputByFocus => {
|
||||
let names = self
|
||||
.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.map(|output| output.name())
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs { output_names: names },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetTagsByOutput { output_name } => {
|
||||
let output = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == output_name);
|
||||
if let Some(output) = output {
|
||||
let tag_ids = output.with_state(|state| {
|
||||
state.tags
|
||||
.iter()
|
||||
.map(|tag| tag.id())
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Tags { tag_ids }
|
||||
}).unwrap();
|
||||
}
|
||||
}
|
||||
Request::GetTagActive { tag_id } => {
|
||||
let tag = self
|
||||
.space
|
||||
.outputs()
|
||||
.flat_map(|op| {
|
||||
op.with_state(|state| state.tags.clone())
|
||||
})
|
||||
.find(|tag| tag.id() == tag_id);
|
||||
if let Some(tag) = tag {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::TagActive {
|
||||
active: tag.active()
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Request::GetTagName { tag_id } => {
|
||||
let tag = self
|
||||
.space
|
||||
.outputs()
|
||||
.flat_map(|op| {
|
||||
op.with_state(|state| state.tags.clone())
|
||||
})
|
||||
.find(|tag| tag.id() == tag_id);
|
||||
if let Some(tag) = tag {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::TagName {
|
||||
name: tag.name()
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
fn handle_request(&mut self, request_id: RequestId, request: Request) {
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
match request {
|
||||
Request::GetWindows => {
|
||||
let window_ids = self
|
||||
.windows
|
||||
.iter()
|
||||
.map(|win| win.with_state(|state| state.id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// FIXME: figure out what to do if error
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::Windows { window_ids },
|
||||
},
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
Request::GetWindowProps { window_id } => {
|
||||
let window = window_id.window(self);
|
||||
let size = window
|
||||
.as_ref()
|
||||
.map(|win| (win.geometry().size.w, win.geometry().size.h));
|
||||
let loc = window
|
||||
.as_ref()
|
||||
.and_then(|win| self.space.element_location(win))
|
||||
.map(|loc| (loc.x, loc.y));
|
||||
let (class, title) = window.as_ref().map_or((None, None), |win| {
|
||||
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
||||
.lock()
|
||||
.expect("failed to acquire lock");
|
||||
(lock.app_id.clone(), lock.title.clone())
|
||||
})
|
||||
});
|
||||
let floating = window
|
||||
.as_ref()
|
||||
.map(|win| win.with_state(|state| state.floating.is_floating()));
|
||||
let focused = window.as_ref().and_then(|win| {
|
||||
self.focus_state
|
||||
.current_focus() // TODO: actual focus
|
||||
.map(|foc_win| win == &foc_win)
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::WindowProps {
|
||||
size,
|
||||
loc,
|
||||
class,
|
||||
title,
|
||||
floating,
|
||||
focused,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputs => {
|
||||
let output_names = self
|
||||
.space
|
||||
.outputs()
|
||||
.map(|output| output.name())
|
||||
.collect::<Vec<_>>();
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::Outputs { output_names },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetOutputProps { output_name } => {
|
||||
let output = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name);
|
||||
let res = output.as_ref().and_then(|output| {
|
||||
output.current_mode().map(|mode| (mode.size.w, mode.size.h))
|
||||
});
|
||||
let refresh_rate = output
|
||||
.as_ref()
|
||||
.and_then(|output| output.current_mode().map(|mode| mode.refresh));
|
||||
let model = output
|
||||
.as_ref()
|
||||
.map(|output| output.physical_properties().model);
|
||||
let physical_size = output.as_ref().map(|output| {
|
||||
(
|
||||
output.physical_properties().size.w,
|
||||
output.physical_properties().size.h,
|
||||
)
|
||||
});
|
||||
let make = output
|
||||
.as_ref()
|
||||
.map(|output| output.physical_properties().make);
|
||||
let loc = output
|
||||
.as_ref()
|
||||
.map(|output| (output.current_location().x, output.current_location().y));
|
||||
let focused = self
|
||||
.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.and_then(|foc_op| output.map(|op| op == foc_op));
|
||||
let tag_ids = output.as_ref().map(|output| {
|
||||
output.with_state(|state| {
|
||||
state.tags.iter().map(|tag| tag.id()).collect::<Vec<_>>()
|
||||
})
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::OutputProps {
|
||||
make,
|
||||
model,
|
||||
loc,
|
||||
res,
|
||||
refresh_rate,
|
||||
physical_size,
|
||||
focused,
|
||||
tag_ids,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetTags => {
|
||||
let tag_ids = self
|
||||
.space
|
||||
.outputs()
|
||||
.flat_map(|op| op.with_state(|state| state.tags.clone()))
|
||||
.map(|tag| tag.id())
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!("GetTags: {:?}", tag_ids);
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::Tags { tag_ids },
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
Request::GetTagProps { tag_id } => {
|
||||
let tag = tag_id.tag(self);
|
||||
let output_name = tag
|
||||
.as_ref()
|
||||
.and_then(|tag| tag.output(self))
|
||||
.map(|output| output.name());
|
||||
let active = tag.as_ref().map(|tag| tag.active());
|
||||
let name = tag.as_ref().map(|tag| tag.name());
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id,
|
||||
response: RequestResponse::TagProps {
|
||||
active,
|
||||
name,
|
||||
output_name,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to send to client");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -657,9 +596,19 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
|
||||
pub fn re_layout(&mut self, output: &Output) {
|
||||
let windows = self.windows.iter().filter(|win| {
|
||||
win.with_state(|state| state.tags.iter().any(|tag| self.output_for_tag(tag).is_some_and(|op| &op == output)))
|
||||
}).cloned().collect::<Vec<_>>();
|
||||
let windows = self
|
||||
.windows
|
||||
.iter()
|
||||
.filter(|win| {
|
||||
win.with_state(|state| {
|
||||
state
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| tag.output(self).is_some_and(|op| &op == output))
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let (render, do_not_render) = output.with_state(|state| {
|
||||
let first_tag = state.focused_tags().next();
|
||||
if let Some(first_tag) = first_tag {
|
||||
|
@ -716,13 +665,15 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
/// Schedule something to be done when windows have finished committing and have become
|
||||
/// idle.
|
||||
pub fn schedule_on_commit<F, B: Backend>(data: &mut CalloopData<B>, windows: Vec<Window>, on_commit: F)
|
||||
where
|
||||
pub fn schedule_on_commit<F, B: Backend>(
|
||||
data: &mut CalloopData<B>,
|
||||
windows: Vec<Window>,
|
||||
on_commit: F,
|
||||
) where
|
||||
F: FnOnce(&mut CalloopData<B>) + 'static,
|
||||
{
|
||||
for window in windows.iter() {
|
||||
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle))
|
||||
{
|
||||
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) {
|
||||
data.state.loop_handle.insert_idle(|data| {
|
||||
schedule_on_commit(data, windows, on_commit);
|
||||
});
|
||||
|
|
18
src/tag.rs
18
src/tag.rs
|
@ -28,6 +28,14 @@ impl TagId {
|
|||
fn next() -> Self {
|
||||
Self(TAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
pub fn tag<B: Backend>(&self, state: &State<B>) -> Option<Tag> {
|
||||
state
|
||||
.space
|
||||
.outputs()
|
||||
.flat_map(|op| op.with_state(|state| state.tags.clone()))
|
||||
.find(|tag| &tag.id() == self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -88,13 +96,11 @@ impl Tag {
|
|||
layout: Layout::MasterStack, // TODO: get from config
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
pub fn output_for_tag(&self, tag: &Tag) -> Option<Output> {
|
||||
self.space
|
||||
pub fn output<B: Backend>(&self, state: &State<B>) -> Option<Output> {
|
||||
state
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == tag)))
|
||||
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == self)))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,18 +15,30 @@ use smithay::{
|
|||
utils::{Logical, Point, Serial, Size},
|
||||
};
|
||||
|
||||
use crate::{state::WithState, tag::Tag};
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
tag::Tag,
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct WindowId(u32);
|
||||
|
||||
// TODO: this probably doesn't need to be atomic
|
||||
static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
impl WindowId {
|
||||
pub fn next() -> Self {
|
||||
Self(WINDOW_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Get the window that has this WindowId.
|
||||
pub fn window<B: Backend>(&self, state: &State<B>) -> Option<Window> {
|
||||
state
|
||||
.windows
|
||||
.iter()
|
||||
.find(|win| win.with_state(|state| &state.id == self))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WindowState {
|
||||
|
|
Loading…
Reference in a new issue