mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
commit
9b46a113d6
29 changed files with 2074 additions and 819 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Get dependencies
|
||||
run: sudo apt install libwayland-dev libxkbcommon-dev libudev-dev libinput-dev libgbm-dev libseat-dev libsystemd-dev
|
||||
run: sudo apt update && sudo apt install libwayland-dev libxkbcommon-dev libudev-dev libinput-dev libgbm-dev libseat-dev libsystemd-dev
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests that don't exist
|
||||
|
|
|
@ -20,6 +20,7 @@ rmp-serde = { version = "1.1.1" }
|
|||
calloop = { version = "0.10.1", features = ["executor", "futures-io"] }
|
||||
futures-lite = { version = "1.13.0" }
|
||||
async-process = { version = "1.7.0" }
|
||||
itertools = { version = "0.11.0" }
|
||||
|
||||
[features]
|
||||
default = ["egl", "winit", "udev"]
|
||||
|
|
35
README.md
35
README.md
|
@ -1,5 +1,3 @@
|
|||
Cool stuff happens on the dev branch sometimes, check it out!
|
||||
|
||||
# <div align="center">Pinnacle</div>
|
||||
<div align="center">
|
||||
<picture>
|
||||
|
@ -18,10 +16,15 @@ Cool stuff happens on the dev branch sometimes, check it out!
|
|||
- [x] Udev backend
|
||||
- This is currently just a copy of Anvil's udev backend.
|
||||
- [x] Basic tags
|
||||
- Tags are currently very jank on the udev backend with multiple monitors. If you're checking udev out, I suggest unplugging all but one monitor or just using the winit backend until I flesh out the tag system.
|
||||
- [ ] Widget system
|
||||
- [ ] Layout system
|
||||
- [x] Left master stack, corner, dwindle, spiral layouts
|
||||
- [ ] Other three master stack directions, floating, magnifier, maximized, and fullscreen layouts
|
||||
- [ ] Resizable layouts
|
||||
- [ ] XWayland support
|
||||
- [ ] Layer-shell support
|
||||
- [ ] Server-side decorations
|
||||
- [ ] Animations and blur and all that pizazz
|
||||
- [ ] Widget system
|
||||
- [ ] The other stuff Awesome has
|
||||
- [x] Is very cool :thumbsup:
|
||||
|
||||
|
@ -36,15 +39,18 @@ So, this is my attempt at making an Awesome-esque Wayland compositor.
|
|||
|
||||
## Dependencies
|
||||
You'll need the following packages, as specified by [Smithay](https://github.com/Smithay/smithay):
|
||||
```
|
||||
libwayland
|
||||
libxkbcommon
|
||||
libudev
|
||||
libinput
|
||||
libgdm
|
||||
libseat
|
||||
```
|
||||
Package names will vary across distros. TODO: list those names.
|
||||
`libwayland libxkbcommon libudev libinput libgdm libseat`
|
||||
- Arch:
|
||||
```
|
||||
sudo pacman -S wayland libxkbcommon systemd-libs libinput libgdm seatd
|
||||
```
|
||||
- Debian:
|
||||
```
|
||||
sudo apt install libwayland-dev libxkbcommon-dev libudev-dev libinput-dev libgdm-dev libseat-dev
|
||||
```
|
||||
- TODO: other distros.
|
||||
|
||||
You'll also need Lua 5.4 for configuration.
|
||||
|
||||
## Building
|
||||
Build the project with:
|
||||
|
@ -70,7 +76,7 @@ cargo run [--release] -- --<backend>
|
|||
- `udev`: run Pinnacle in a tty. NOTE: I tried running udev in Awesome and some things broke so uh, don't do that
|
||||
|
||||
## Configuration
|
||||
Please note: this is VERY WIP and has basically no options yet.
|
||||
Please note: this is VERY WIP and has few options.
|
||||
|
||||
Pinnacle supports configuration through Lua (and hopefully more languages if it's not too unwieldy :crab:).
|
||||
|
||||
|
@ -109,5 +115,6 @@ The following controls are currently hardcoded:
|
|||
|
||||
- `Ctrl + Left Mouse`: Move a window
|
||||
- `Ctrl + Right Mouse`: Resize a window
|
||||
- `Ctrl + Alt + Shift + Esc`: Kill Pinnacle. This is for when the compositor inevitably locks up because I did a dumb thing :thumbsup:
|
||||
|
||||
You can find the rest of the controls in the [`example_config`](api/lua/example_config.lua).
|
||||
|
|
|
@ -11,10 +11,11 @@ pcall(require, "luarocks.loader")
|
|||
|
||||
-- Neovim users be like:
|
||||
require("pinnacle").setup(function(pinnacle)
|
||||
local input = pinnacle.input -- Key and mouse binds
|
||||
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 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.
|
||||
|
@ -27,6 +28,7 @@ require("pinnacle").setup(function(pinnacle)
|
|||
local terminal = "alacritty"
|
||||
|
||||
-- Keybinds ----------------------------------------------------------------------
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.c, window.close_window)
|
||||
|
@ -50,8 +52,65 @@ require("pinnacle").setup(function(pinnacle)
|
|||
end)
|
||||
|
||||
-- Tags ---------------------------------------------------------------------------
|
||||
tag.add("1", "2", "3", "4", "5")
|
||||
tag.toggle("1")
|
||||
|
||||
output.connect_for_all(function(op)
|
||||
op:add_tags("1", "2", "3", "4", "5")
|
||||
-- Same as tag.add(op, "1", "2", "3", "4", "5")
|
||||
tag.toggle("1", op)
|
||||
end)
|
||||
|
||||
---@type Layout[]
|
||||
local layouts = {
|
||||
"MasterStack",
|
||||
"Dwindle",
|
||||
"Spiral",
|
||||
"CornerTopLeft",
|
||||
"CornerTopRight",
|
||||
"CornerBottomLeft",
|
||||
"CornerBottomRight",
|
||||
}
|
||||
local indices = {}
|
||||
|
||||
-- Layout cycling
|
||||
-- Yes, this is very complicated and yes, I'll cook up a way to make it less complicated.
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
tag.switch_to("1")
|
||||
|
|
|
@ -9,9 +9,19 @@ local input = {
|
|||
}
|
||||
|
||||
---Set a keybind. If called with an already existing keybind, it gets replaced.
|
||||
---
|
||||
---# Example
|
||||
---
|
||||
---```lua
|
||||
----- The following sets Super + Return to open Alacritty
|
||||
---
|
||||
---input.keybind({ "Super" }, input.keys.Return, function()
|
||||
--- process.spawn("Alacritty")
|
||||
---end)
|
||||
---```
|
||||
---@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 run.
|
||||
---@param action fun() What to do.
|
||||
function input.keybind(modifiers, key, action)
|
||||
table.insert(CallbackTable, action)
|
||||
SendMsg({
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super"
|
||||
|
||||
---@enum Keys
|
||||
local M = {
|
||||
local keys = {
|
||||
NoSymbol = 0x00000000,
|
||||
|
||||
VoidSymbol = 0x00ffffff,
|
||||
|
@ -4321,4 +4321,4 @@ local M = {
|
|||
block = 0x100000fc,
|
||||
}
|
||||
|
||||
return M
|
||||
return keys
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
---@meta _
|
||||
|
||||
---@class _Msg
|
||||
---@field SetKeybind { key: Keys, modifiers: Modifiers[], callback_id: integer }
|
||||
---@field SetKeybind { key: Keys, modifiers: Modifier[], callback_id: integer }
|
||||
---@field SetMousebind { button: integer }
|
||||
--Windows
|
||||
---@field CloseWindow { client_id: integer? }
|
||||
|
@ -19,27 +19,47 @@
|
|||
---@field Spawn { command: string[], callback_id: integer? }
|
||||
---@field Request Request
|
||||
--Tags
|
||||
---@field ToggleTag { tag_id: string }
|
||||
---@field SwitchToTag { tag_id: string }
|
||||
---@field AddTags { tags: string[] }
|
||||
---@field RemoveTags { tags: string[] }
|
||||
---@field ToggleTag { output_name: string, tag_name: string }
|
||||
---@field SwitchToTag { output_name: string, tag_name: string }
|
||||
---@field AddTags { output_name: string, tags: string[] }
|
||||
---@field RemoveTags { output_name: string, tags: string[] }
|
||||
---@field SetLayout { output_name: string, tag_name: string, layout: Layout }
|
||||
--Outputs
|
||||
---@field ConnectForAllOutputs { callback_id: integer }
|
||||
|
||||
---@alias Msg _Msg | "Quit"
|
||||
|
||||
---@class Request
|
||||
---@field GetWindowByFocus { id: integer }
|
||||
---@field GetAllWindows { id: integer }
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
---@class _Request
|
||||
--Windows
|
||||
---@field GetWindowByAppId { app_id: string }
|
||||
---@field GetWindowByTitle { title: string }
|
||||
--Outputs
|
||||
---@field GetOutputByName { name: string }
|
||||
---@field GetOutputsByModel { model: string }
|
||||
---@field GetOutputsByRes { res: integer[] }
|
||||
---@field GetTagsByOutput { output: string }
|
||||
---@field GetTagActive { tag_id: integer }
|
||||
---@field GetTagName { tag_id: integer }
|
||||
|
||||
---@alias Request _Request | "GetWindowByFocus" | "GetAllWindows" | "GetOutputByFocus"
|
||||
|
||||
---@class IncomingMsg
|
||||
---@field CallCallback { callback_id: integer, args: Args }
|
||||
---@field RequestResponse { request_id: integer, response: RequestResponse }
|
||||
---@field RequestResponse { response: RequestResponse }
|
||||
|
||||
---@class Args
|
||||
---@field Spawn { stdout: string?, stderr: string?, exit_code: integer?, exit_msg: string? }
|
||||
---@field ConnectForAllOutputs { output_name: string }
|
||||
|
||||
---@class RequestResponse
|
||||
---@field Window { window: WindowProperties }
|
||||
---@field GetAllWindows { windows: WindowProperties[] }
|
||||
---@field Outputs { names: string[] }
|
||||
---@field Tags { tags: TagProperties[] }
|
||||
---@field TagActive { active: boolean }
|
||||
---@field TagName { name: string }
|
||||
|
||||
---@class WindowProperties
|
||||
---@field id integer
|
||||
|
@ -48,3 +68,6 @@
|
|||
---@field size integer[] A two element int array, \[1\] = w, \[2\] = h
|
||||
---@field location integer[] A two element int array, \[1\] = x, \[2\] = y
|
||||
---@field floating boolean
|
||||
|
||||
---@class TagProperties
|
||||
---@field id integer
|
||||
|
|
184
api/lua/output.lua
Normal file
184
api/lua/output.lua
Normal file
|
@ -0,0 +1,184 @@
|
|||
-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
-- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
-- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
---@class Output A display.
|
||||
---@field name string The name of this output (or rather, of its connector).
|
||||
local op = {}
|
||||
|
||||
---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
|
||||
---@return Output
|
||||
local function new_output(props)
|
||||
-- Copy functions over
|
||||
for k, v in pairs(op) do
|
||||
props[k] = v
|
||||
end
|
||||
|
||||
return props
|
||||
end
|
||||
|
||||
------------------------------------------------------
|
||||
|
||||
local output = {}
|
||||
|
||||
---Get an output by its name.
|
||||
---
|
||||
---"Name" in this sense does not mean its model or manufacturer;
|
||||
---rather, "name" is the name of the connector the output is connected to.
|
||||
---This should be something like "HDMI-A-0", "eDP-1", or similar.
|
||||
---
|
||||
---# Examples
|
||||
---```lua
|
||||
---local monitor = output.get_by_name("DP-1")
|
||||
---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 = {
|
||||
name = name,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local names = response.RequestResponse.response.Outputs.names
|
||||
|
||||
if names[1] ~= nil then
|
||||
return new_output({ name = names[1] })
|
||||
else
|
||||
return nil
|
||||
end
|
||||
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.
|
||||
---
|
||||
---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()
|
||||
|
||||
local names = response.RequestResponse.response.Outputs.names
|
||||
|
||||
---@type Output
|
||||
local outputs = {}
|
||||
for _, v in pairs(names) do
|
||||
table.insert(outputs, new_output({ name = v }))
|
||||
end
|
||||
|
||||
return outputs
|
||||
end
|
||||
|
||||
---Get outputs by their resolution.
|
||||
---
|
||||
---@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()
|
||||
|
||||
local names = response.RequestResponse.response.Outputs.names
|
||||
|
||||
---@type Output
|
||||
local outputs = {}
|
||||
for _, v in pairs(names) do
|
||||
table.insert(outputs, new_output({ name = v }))
|
||||
end
|
||||
|
||||
return outputs
|
||||
end
|
||||
|
||||
---Get the currently focused output. This is currently implemented as the one with the cursor on it.
|
||||
---
|
||||
---This function may return nil, which means you may get a warning if you try to use it without checking for nil.
|
||||
---Usually this function will not be nil unless you unplug all monitors, so instead of checking,
|
||||
---you can ignore the warning by either forcing the type to be non-nil with an inline comment:
|
||||
---```lua
|
||||
---local op = output.get_focused() --[[@as Output]]
|
||||
---```
|
||||
---or by disabling nil check warnings for the line:
|
||||
---```lua
|
||||
---local op = output.get_focused()
|
||||
------@diagnostic disable-next-line:need-check-nil
|
||||
---local tags_on_output = op:tags()
|
||||
---```
|
||||
---Type checking done by Lua LS isn't perfect.
|
||||
---Note that directly using the result of this function inline will *not* raise a warning, so be careful.
|
||||
---```lua
|
||||
---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()
|
||||
SendMsg({
|
||||
Request = "GetOutputByFocus",
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local names = response.RequestResponse.response.Outputs.names
|
||||
|
||||
if names[1] ~= nil then
|
||||
return new_output({ name = names[1] })
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
---Connect a function to be run on all current and future outputs.
|
||||
---
|
||||
---When called, `connect_for_all` will immediately run `func` with all currently connected outputs.
|
||||
---If a new output is connected, `func` will also be called with it.
|
||||
---
|
||||
---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)
|
||||
---@param args Args
|
||||
table.insert(CallbackTable, function(args)
|
||||
local args = args.ConnectForAllOutputs
|
||||
func(new_output({ name = args.output_name }))
|
||||
end)
|
||||
SendMsg({
|
||||
ConnectForAllOutputs = {
|
||||
callback_id = #CallbackTable,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
return output
|
|
@ -19,6 +19,7 @@ local function read_exact(socket_fd, count)
|
|||
local len_to_read = count
|
||||
local data = ""
|
||||
while len_to_read > 0 do
|
||||
-- print("need to read " .. tostring(len_to_read) .. " bytes")
|
||||
local bytes, err_msg, errnum = socket.recv(socket_fd, len_to_read)
|
||||
|
||||
if bytes == nil then
|
||||
|
@ -56,6 +57,8 @@ local pinnacle = {
|
|||
process = require("process"),
|
||||
---Tag management
|
||||
tag = require("tag"),
|
||||
---Output management
|
||||
output = require("output"),
|
||||
}
|
||||
|
||||
---Quit Pinnacle.
|
||||
|
@ -103,9 +106,11 @@ function pinnacle.setup(config_func)
|
|||
|
||||
---@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 tb = msgpack.decode(msg_bytes)
|
||||
|
@ -114,15 +119,6 @@ function pinnacle.setup(config_func)
|
|||
return tb
|
||||
end
|
||||
|
||||
Requests = {
|
||||
id = 1,
|
||||
}
|
||||
function Requests:next()
|
||||
local id = self.id
|
||||
self.id = self.id + 1
|
||||
return id
|
||||
end
|
||||
|
||||
config_func(pinnacle)
|
||||
|
||||
while true do
|
||||
|
|
1
api/lua/stylua.toml
Normal file
1
api/lua/stylua.toml
Normal file
|
@ -0,0 +1 @@
|
|||
indent_type = "Spaces"
|
216
api/lua/tag.lua
216
api/lua/tag.lua
|
@ -6,55 +6,235 @@
|
|||
|
||||
local tag = {}
|
||||
|
||||
---@alias Layout
|
||||
---| "MasterStack" # One master window on the left with all other windows stacked to the right.
|
||||
---| "Dwindle" # Windows split in half towards the bottom right corner.
|
||||
---| "Spiral" # Windows split in half in a spiral.
|
||||
---| "CornerTopLeft" # One main corner window in the top left with a column of windows on the right and a row on the bottom.
|
||||
---| "CornerTopRight" # One main corner window in the top right with a column of windows on the left and a row on the bottom.
|
||||
---| "CornerBottomLeft" # One main corner window in the bottom left with a column of windows on the right and a row on the top.
|
||||
---| "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 = {}
|
||||
|
||||
---@param props Tag
|
||||
---@return Tag
|
||||
local function new_tag(props)
|
||||
-- Copy functions over
|
||||
for k, v in pairs(tg) do
|
||||
props[k] = v
|
||||
end
|
||||
|
||||
return props
|
||||
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
|
||||
end
|
||||
|
||||
function tg:name()
|
||||
SendRequest({
|
||||
GetTagName = {
|
||||
tag_id = self.id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
local name = response.RequestResponse.response.TagName.name
|
||||
return name
|
||||
end
|
||||
|
||||
---Set this tag's layout.
|
||||
---@param layout Layout
|
||||
function tg:set_layout(layout) -- TODO: output param
|
||||
tag.set_layout(self:name(), layout)
|
||||
end
|
||||
|
||||
-----------------------------------------------------------
|
||||
|
||||
---Add tags.
|
||||
---
|
||||
---If you need to add the strings in a table, use `tag.add_table` instead.
|
||||
---If you need to add the names as a table, use `tag.add_table` instead.
|
||||
---
|
||||
---# Example
|
||||
---
|
||||
---```lua
|
||||
---tag.add("1", "2", "3", "4", "5") -- Add tags with names 1-5
|
||||
---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
|
||||
---end
|
||||
---```
|
||||
---@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(...)
|
||||
function tag.add(output, ...)
|
||||
local tags = table.pack(...)
|
||||
tags["n"] = nil
|
||||
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
output_name = output.name,
|
||||
tags = tags,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Like `tag.add`, but with a table of strings instead.
|
||||
---@param tags string[] The names of the new tags you want to add, as a table.
|
||||
function tag.add_table(tags)
|
||||
---
|
||||
---# 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 = {
|
||||
tags = tags,
|
||||
output_name = output.name,
|
||||
tags = names,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Toggle a tag's display.
|
||||
---Toggle a tag on the specified output. If `output` isn't specified, toggle it on the currently focused output instead.
|
||||
---
|
||||
---# Example
|
||||
---
|
||||
---```lua
|
||||
----- Assuming all tags are toggled off...
|
||||
---local op = output.get_by_name("DP-1")
|
||||
---tag.toggle("1", op)
|
||||
---tag.toggle("2", op)
|
||||
----- will cause windows on both tags 1 and 2 to be displayed at the same time.
|
||||
---```
|
||||
---@param name string The name of the tag.
|
||||
function tag.toggle(name)
|
||||
SendMsg({
|
||||
ToggleTag = {
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
---@param output Output? The output.
|
||||
function tag.toggle(name, output)
|
||||
if output ~= nil then
|
||||
SendMsg({
|
||||
ToggleTag = {
|
||||
output_name = output.name,
|
||||
tag_name = name,
|
||||
},
|
||||
})
|
||||
else
|
||||
local op = require("output").get_focused()
|
||||
if op ~= nil then
|
||||
SendMsg({
|
||||
ToggleTag = {
|
||||
output_name = op.name,
|
||||
tag_name = name,
|
||||
},
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Switch to a tag, deactivating any other active tags.
|
||||
---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.
|
||||
---
|
||||
---This is used to replicate what a traditional workspace is on some other Wayland compositors.
|
||||
---
|
||||
---# Example
|
||||
---
|
||||
---```lua
|
||||
---tag.switch_to("3") -- Switches to and displays *only* windows on tag 3
|
||||
---```
|
||||
---@param name string The name of the tag.
|
||||
function tag.switch_to(name)
|
||||
SendMsg({
|
||||
SwitchToTag = {
|
||||
tag_id = name,
|
||||
---@param output Output? The output.
|
||||
function tag.switch_to(name, output)
|
||||
if output ~= nil then
|
||||
SendMsg({
|
||||
SwitchToTag = {
|
||||
output_name = output.name,
|
||||
tag_name = name,
|
||||
},
|
||||
})
|
||||
else
|
||||
local op = require("output").get_focused()
|
||||
if op ~= nil then
|
||||
SendMsg({
|
||||
SwitchToTag = {
|
||||
output_name = op.name,
|
||||
tag_name = name,
|
||||
},
|
||||
})
|
||||
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.
|
||||
---@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
|
||||
SendMsg({
|
||||
SetLayout = {
|
||||
output_name = output.name,
|
||||
tag_name = name,
|
||||
layout = layout,
|
||||
},
|
||||
})
|
||||
else
|
||||
local op = require("output").get_focused()
|
||||
if op ~= nil then
|
||||
SendMsg({
|
||||
SetLayout = {
|
||||
output_name = op.name,
|
||||
tag_name = name,
|
||||
layout = layout,
|
||||
},
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---Get all tags on the specified output.
|
||||
---
|
||||
---You can also use `output_obj:tags()`, which delegates to this function:
|
||||
---```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())
|
||||
---```
|
||||
---@param output Output
|
||||
---@return Tag[]
|
||||
function tag.get_on_output(output)
|
||||
SendRequest({
|
||||
GetTagsByOutput = {
|
||||
output = output.name,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local tag_props = response.RequestResponse.response.Tags.tags
|
||||
|
||||
---@type Tag[]
|
||||
local tags = {}
|
||||
|
||||
for _, prop in pairs(tag_props) do
|
||||
table.insert(tags, new_tag({ id = prop.id }))
|
||||
end
|
||||
|
||||
return tags
|
||||
end
|
||||
|
||||
return tag
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
---@field private floating boolean Whether the window is floating or not (tiled)
|
||||
local win = {}
|
||||
|
||||
---@param props { id: integer, app_id: string?, title: string?, size: { w: integer, h: integer }, location: { x: integer, y: integer }, floating: boolean }
|
||||
---@param props Window
|
||||
---@return Window
|
||||
local function new_window(props)
|
||||
-- Copy functions over
|
||||
|
@ -91,15 +91,14 @@ function window.toggle_floating(client_id)
|
|||
})
|
||||
end
|
||||
|
||||
---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 window -- TODO: nil
|
||||
function window.get_by_app_id(app_id)
|
||||
local req_id = Requests:next()
|
||||
|
||||
SendRequest({
|
||||
GetWindowByAppId = {
|
||||
id = req_id,
|
||||
app_id = app_id,
|
||||
},
|
||||
})
|
||||
|
@ -127,15 +126,14 @@ function window.get_by_app_id(app_id)
|
|||
return new_window(wind)
|
||||
end
|
||||
|
||||
---TODO: This function is not implemented yet.
|
||||
---
|
||||
---Get a window by its title.
|
||||
---@param title string The window's title.
|
||||
---@return Window
|
||||
function window.get_by_title(title)
|
||||
local req_id = Requests:next()
|
||||
|
||||
SendRequest({
|
||||
GetWindowByTitle = {
|
||||
id = req_id,
|
||||
title = title,
|
||||
},
|
||||
})
|
||||
|
@ -166,13 +164,7 @@ end
|
|||
---Get the currently focused window.
|
||||
---@return Window
|
||||
function window.get_focused()
|
||||
local req_id = Requests:next()
|
||||
|
||||
SendRequest({
|
||||
GetWindowByFocus = {
|
||||
id = req_id,
|
||||
},
|
||||
})
|
||||
SendRequest("GetWindowByFocus")
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
|
@ -199,12 +191,8 @@ end
|
|||
|
||||
---Get all windows.
|
||||
---@return Window[]
|
||||
function window.get_windows()
|
||||
SendRequest({
|
||||
GetAllWindows = {
|
||||
id = Requests:next(),
|
||||
},
|
||||
})
|
||||
function window.get_all()
|
||||
SendRequest("GetAllWindows")
|
||||
|
||||
-- INFO: these read synchronously so this should always work IF the server works correctly
|
||||
|
||||
|
|
|
@ -112,6 +112,7 @@ pub fn send_to_client(
|
|||
stream: &mut UnixStream,
|
||||
msg: &OutgoingMsg,
|
||||
) -> Result<(), rmp_serde::encode::Error> {
|
||||
// tracing::debug!("Sending {msg:?}");
|
||||
let msg = rmp_serde::to_vec_named(msg)?;
|
||||
let msg_len = msg.len() as u32;
|
||||
let bytes = msg_len.to_ne_bytes();
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
// value is a map of the enum's values
|
||||
|
||||
use crate::{
|
||||
tag::TagId,
|
||||
layout::Layout,
|
||||
tag::{TagId, TagProperties},
|
||||
window::{window_state::WindowId, WindowProperties},
|
||||
};
|
||||
|
||||
|
@ -20,7 +21,7 @@ pub enum Msg {
|
|||
// Input
|
||||
SetKeybind {
|
||||
key: u32,
|
||||
modifiers: Vec<Modifiers>,
|
||||
modifiers: Vec<Modifier>,
|
||||
callback_id: CallbackId,
|
||||
},
|
||||
SetMousebind {
|
||||
|
@ -42,25 +43,41 @@ pub enum Msg {
|
|||
},
|
||||
MoveWindowToTag {
|
||||
window_id: WindowId,
|
||||
tag_id: TagId,
|
||||
tag_id: String,
|
||||
},
|
||||
ToggleTagOnWindow {
|
||||
window_id: WindowId,
|
||||
tag_id: TagId,
|
||||
tag_id: String,
|
||||
},
|
||||
|
||||
// Tag management
|
||||
ToggleTag {
|
||||
tag_id: TagId,
|
||||
output_name: String,
|
||||
tag_name: String,
|
||||
},
|
||||
SwitchToTag {
|
||||
tag_id: TagId,
|
||||
output_name: String,
|
||||
tag_name: String,
|
||||
},
|
||||
AddTags {
|
||||
tags: Vec<TagId>,
|
||||
/// The name of the output you want these tags on.
|
||||
output_name: String,
|
||||
tags: Vec<String>,
|
||||
},
|
||||
RemoveTags {
|
||||
tags: Vec<TagId>,
|
||||
/// The name of the output you want these tags removed from.
|
||||
output_name: String,
|
||||
tags: Vec<String>,
|
||||
},
|
||||
SetLayout {
|
||||
output_name: String,
|
||||
tag_name: String,
|
||||
layout: Layout,
|
||||
},
|
||||
|
||||
// Output management
|
||||
ConnectForAllOutputs {
|
||||
callback_id: CallbackId,
|
||||
},
|
||||
|
||||
// Process management
|
||||
|
@ -81,28 +98,36 @@ pub enum Msg {
|
|||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct RequestId(pub 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 { id: RequestId, app_id: String },
|
||||
GetWindowByTitle { id: RequestId, title: String },
|
||||
GetWindowByFocus { id: RequestId },
|
||||
GetAllWindows { id: RequestId },
|
||||
GetWindowByAppId { app_id: String },
|
||||
GetWindowByTitle { title: String },
|
||||
GetWindowByFocus,
|
||||
GetAllWindows,
|
||||
GetOutputByName { name: String },
|
||||
GetOutputsByModel { model: String },
|
||||
GetOutputsByRes { res: (u32, u32) },
|
||||
GetOutputByFocus,
|
||||
GetTagsByOutput { output: String },
|
||||
GetTagActive { tag_id: TagId },
|
||||
GetTagName { tag_id: TagId },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Modifiers {
|
||||
pub enum Modifier {
|
||||
Shift = 0b0000_0001,
|
||||
Ctrl = 0b0000_0010,
|
||||
Alt = 0b0000_0100,
|
||||
Super = 0b0000_1000,
|
||||
}
|
||||
|
||||
/// A bitmask of [Modifiers] for the purpose of hashing.
|
||||
/// A bitmask of [`Modifier`]s for the purpose of hashing.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct ModifierMask(u8);
|
||||
|
||||
impl<T: IntoIterator<Item = Modifiers>> From<T> for ModifierMask {
|
||||
impl<T: IntoIterator<Item = Modifier>> From<T> for ModifierMask {
|
||||
fn from(value: T) -> Self {
|
||||
let value = value.into_iter();
|
||||
let mut mask: u8 = 0b0000_0000;
|
||||
|
@ -114,19 +139,19 @@ impl<T: IntoIterator<Item = Modifiers>> From<T> for ModifierMask {
|
|||
}
|
||||
|
||||
impl ModifierMask {
|
||||
pub fn values(self) -> Vec<Modifiers> {
|
||||
let mut res = Vec::<Modifiers>::new();
|
||||
if self.0 & Modifiers::Shift as u8 == Modifiers::Shift as u8 {
|
||||
res.push(Modifiers::Shift);
|
||||
pub fn values(self) -> Vec<Modifier> {
|
||||
let mut res = Vec::<Modifier>::new();
|
||||
if self.0 & Modifier::Shift as u8 == Modifier::Shift as u8 {
|
||||
res.push(Modifier::Shift);
|
||||
}
|
||||
if self.0 & Modifiers::Ctrl as u8 == Modifiers::Ctrl as u8 {
|
||||
res.push(Modifiers::Ctrl);
|
||||
if self.0 & Modifier::Ctrl as u8 == Modifier::Ctrl as u8 {
|
||||
res.push(Modifier::Ctrl);
|
||||
}
|
||||
if self.0 & Modifiers::Alt as u8 == Modifiers::Alt as u8 {
|
||||
res.push(Modifiers::Alt);
|
||||
if self.0 & Modifier::Alt as u8 == Modifier::Alt as u8 {
|
||||
res.push(Modifier::Alt);
|
||||
}
|
||||
if self.0 & Modifiers::Super as u8 == Modifiers::Super as u8 {
|
||||
res.push(Modifiers::Super);
|
||||
if self.0 & Modifier::Super as u8 == Modifier::Super as u8 {
|
||||
res.push(Modifier::Super);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
@ -141,7 +166,6 @@ pub enum OutgoingMsg {
|
|||
args: Option<Args>,
|
||||
},
|
||||
RequestResponse {
|
||||
request_id: RequestId,
|
||||
response: RequestResponse,
|
||||
},
|
||||
}
|
||||
|
@ -159,10 +183,17 @@ pub enum Args {
|
|||
#[serde(default)]
|
||||
exit_msg: Option<String>,
|
||||
},
|
||||
ConnectForAllOutputs {
|
||||
output_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum RequestResponse {
|
||||
Window { window: WindowProperties },
|
||||
GetAllWindows { windows: Vec<WindowProperties> },
|
||||
Outputs { names: Vec<String> },
|
||||
Tags { tags: Vec<TagProperties> },
|
||||
TagActive { active: bool },
|
||||
TagName { name: String },
|
||||
}
|
||||
|
|
|
@ -97,6 +97,7 @@ use smithay_drm_extras::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
api::msg::{Args, OutgoingMsg},
|
||||
render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements},
|
||||
state::{take_presentation_feedback, CalloopData, State, SurfaceDmabufFeedback},
|
||||
};
|
||||
|
@ -226,11 +227,6 @@ pub fn run_udev() -> Result<(), Box<dyn Error>> {
|
|||
pointer_element: PointerElement::default(),
|
||||
};
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
let mut state = State::<UdevData>::init(
|
||||
data,
|
||||
&mut display,
|
||||
|
@ -852,6 +848,32 @@ impl State<UdevData> {
|
|||
device_id: node,
|
||||
});
|
||||
|
||||
// Run any connected callbacks
|
||||
{
|
||||
let clone = output.clone();
|
||||
self.loop_handle.insert_idle(move |data| {
|
||||
let stream = data
|
||||
.state
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
for callback_id in data.state.output_callback_ids.iter() {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::CallCallback {
|
||||
callback_id: *callback_id,
|
||||
args: Some(Args::ConnectForAllOutputs {
|
||||
output_name: clone.name(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let allocator = GbmAllocator::new(
|
||||
device.gbm.clone(),
|
||||
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
|
||||
|
|
|
@ -47,7 +47,6 @@ use smithay::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
layout::{Direction, Layout},
|
||||
render::{pointer::PointerElement, CustomRenderElements, OutputRenderElements},
|
||||
state::{CalloopData, State},
|
||||
};
|
||||
|
@ -220,11 +219,7 @@ pub fn run_winit() -> Result<(), Box<dyn Error>> {
|
|||
None,
|
||||
None,
|
||||
);
|
||||
Layout::master_stack(
|
||||
state,
|
||||
state.space.elements().cloned().collect(),
|
||||
Direction::Left,
|
||||
);
|
||||
state.re_layout(&output);
|
||||
}
|
||||
WinitEvent::Focus(_) => {}
|
||||
WinitEvent::Input(input_evt) => {
|
||||
|
|
|
@ -19,7 +19,11 @@ use smithay::{
|
|||
utils::{IsAlive, Logical, Point, Rectangle},
|
||||
};
|
||||
|
||||
use crate::{backend::Backend, state::State, window::window_state::WindowState};
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
window::window_state::WindowResizeState,
|
||||
};
|
||||
|
||||
pub struct MoveSurfaceGrab<S: SeatHandler> {
|
||||
pub start_data: GrabStartData<S>,
|
||||
|
@ -47,7 +51,7 @@ impl<B: Backend> PointerGrab<State<B>> for MoveSurfaceGrab<State<B>> {
|
|||
// tracing::info!("window geo is: {:?}", self.window.geometry());
|
||||
// tracing::info!("loc is: {:?}", data.space.element_location(&self.window));
|
||||
|
||||
let tiled = WindowState::with_state(&self.window, |state| state.floating.is_tiled());
|
||||
let tiled = self.window.with_state(|state| state.floating.is_tiled());
|
||||
|
||||
if tiled {
|
||||
// INFO: this is being used instead of space.element_under(event.location) because that
|
||||
|
@ -72,10 +76,16 @@ impl<B: Backend> PointerGrab<State<B>> for MoveSurfaceGrab<State<B>> {
|
|||
return;
|
||||
}
|
||||
|
||||
let window_under_floating =
|
||||
WindowState::with_state(&window_under, |state| state.floating.is_floating());
|
||||
let is_floating = window_under.with_state(|state| state.floating.is_floating());
|
||||
|
||||
if window_under_floating {
|
||||
if is_floating {
|
||||
return;
|
||||
}
|
||||
|
||||
let has_pending_resize = window_under
|
||||
.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle));
|
||||
|
||||
if has_pending_resize {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -86,14 +96,6 @@ impl<B: Backend> PointerGrab<State<B>> for MoveSurfaceGrab<State<B>> {
|
|||
let new_loc = self.initial_window_loc.to_f64() + delta;
|
||||
data.space
|
||||
.map_element(self.window.clone(), new_loc.to_i32_round(), true);
|
||||
// let loc = data
|
||||
// .space
|
||||
// .element_location(&self.window)
|
||||
// .unwrap_or((0, 0).into());
|
||||
// tracing::info!("new loc from element_location: {}, {}", loc.x, loc.y);
|
||||
// let geo = self.window.geometry();
|
||||
// tracing::info!("geo loc: {}, {}", geo.loc.x, geo.loc.y);
|
||||
// tracing::info!("geo size: {}, {}", geo.size.w, geo.size.h);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,10 @@ use smithay::{
|
|||
wayland::{compositor, seat::WaylandFocus, shell::xdg::SurfaceCachedState},
|
||||
};
|
||||
|
||||
use crate::{backend::Backend, state::State, window::SurfaceState};
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
};
|
||||
|
||||
pub struct ResizeSurfaceGrab<S: SeatHandler> {
|
||||
start_data: GrabStartData<S>,
|
||||
|
@ -40,8 +43,8 @@ impl<S: SeatHandler> ResizeSurfaceGrab<S> {
|
|||
initial_window_rect: Rectangle<i32, Logical>,
|
||||
button_used: u32,
|
||||
) -> Self {
|
||||
ResizeSurfaceState::with_state(window.toplevel().wl_surface(), |state| {
|
||||
*state = ResizeSurfaceState::Resizing {
|
||||
window.toplevel().wl_surface().with_state(|state| {
|
||||
state.resize_state = ResizeSurfaceState::Resizing {
|
||||
edges,
|
||||
initial_window_rect,
|
||||
};
|
||||
|
@ -169,8 +172,8 @@ impl<B: Backend> PointerGrab<State<B>> for ResizeSurfaceGrab<State<B>> {
|
|||
|
||||
toplevel_surface.send_pending_configure();
|
||||
|
||||
ResizeSurfaceState::with_state(toplevel_surface.wl_surface(), |state| {
|
||||
*state = ResizeSurfaceState::WaitingForLastCommit {
|
||||
toplevel_surface.wl_surface().with_state(|state| {
|
||||
state.resize_state = ResizeSurfaceState::WaitingForLastCommit {
|
||||
edges: self.edges,
|
||||
initial_window_rect: self.initial_window_rect,
|
||||
};
|
||||
|
@ -193,7 +196,7 @@ impl<B: Backend> PointerGrab<State<B>> for ResizeSurfaceGrab<State<B>> {
|
|||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
||||
enum ResizeSurfaceState {
|
||||
pub enum ResizeSurfaceState {
|
||||
#[default]
|
||||
Idle,
|
||||
Resizing {
|
||||
|
@ -225,15 +228,14 @@ impl ResizeSurfaceState {
|
|||
}
|
||||
}
|
||||
|
||||
impl SurfaceState for ResizeSurfaceState {}
|
||||
|
||||
pub fn handle_commit<B: Backend>(state: &mut State<B>, surface: &WlSurface) -> Option<()> {
|
||||
let window = state.window_for_surface(surface)?;
|
||||
let mut window_loc = state.space.element_location(&window)?;
|
||||
let geometry = window.geometry();
|
||||
|
||||
let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with_state(surface, |state| {
|
||||
let new_loc: Point<Option<i32>, Logical> = surface.with_state(|state| {
|
||||
state
|
||||
.resize_state
|
||||
.commit()
|
||||
.map(|(edges, initial_window_rect)| {
|
||||
let mut new_x: Option<i32> = None;
|
||||
|
|
188
src/handlers.rs
188
src/handlers.rs
|
@ -4,14 +4,16 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::utils,
|
||||
delegate_compositor, delegate_data_device, delegate_fractional_scale, delegate_output,
|
||||
delegate_presentation, delegate_relative_pointer, delegate_seat, delegate_shm,
|
||||
delegate_viewporter, delegate_xdg_shell,
|
||||
desktop::{
|
||||
find_popup_root_surface, PopupKeyboardGrab, PopupKind, PopupPointerGrab,
|
||||
PopupUngrabStrategy, Window,
|
||||
find_popup_root_surface, utils::surface_primary_scanout_output, PopupKeyboardGrab,
|
||||
PopupKind, PopupPointerGrab, PopupUngrabStrategy, Window,
|
||||
},
|
||||
input::{
|
||||
pointer::{CursorImageStatus, Focus},
|
||||
|
@ -19,7 +21,7 @@ use smithay::{
|
|||
},
|
||||
reexports::{
|
||||
calloop::Interest,
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge},
|
||||
wayland_server::{
|
||||
protocol::{wl_buffer::WlBuffer, wl_seat::WlSeat, wl_surface::WlSurface},
|
||||
Client, Resource,
|
||||
|
@ -47,10 +49,8 @@ use smithay::{
|
|||
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
layout::Layout,
|
||||
output::OutputState,
|
||||
state::{ClientState, State},
|
||||
window::window_state::{WindowResizeState, WindowState},
|
||||
state::{ClientState, State, WithState},
|
||||
window::{window_state::WindowResizeState, WindowBlocker, BLOCKER_COUNTER},
|
||||
};
|
||||
|
||||
impl<B: Backend> BufferHandler for State<B> {
|
||||
|
@ -96,7 +96,7 @@ impl<B: Backend> CompositorHandler for State<B> {
|
|||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
tracing::debug!("commit");
|
||||
// tracing::debug!("commit");
|
||||
|
||||
utils::on_commit_buffer_handler::<Self>(surface);
|
||||
|
||||
|
@ -117,13 +117,19 @@ impl<B: Backend> CompositorHandler for State<B> {
|
|||
crate::grab::resize_grab::handle_commit(self, surface);
|
||||
|
||||
if let Some(window) = self.window_for_surface(surface) {
|
||||
WindowState::with_state(&window, |state| {
|
||||
if let WindowResizeState::WaitingForCommit(new_pos) = state.resize_state {
|
||||
window.with_state(|state| {
|
||||
if let WindowResizeState::Acknowledged(new_pos) = state.resize_state {
|
||||
state.resize_state = WindowResizeState::Idle;
|
||||
self.space.map_element(window.clone(), new_pos, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
// let states = self
|
||||
// .windows
|
||||
// .iter()
|
||||
// .map(|win| win.with_state(|state| state.resize_state.clone()))
|
||||
// .collect::<Vec<_>>();
|
||||
// tracing::debug!("states: {states:?}");
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
|
@ -225,25 +231,94 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
let window = Window::new(surface);
|
||||
|
||||
WindowState::with_state(&window, |state| {
|
||||
state.tags = if let Some(focused_output) = &self.focus_state.focused_output {
|
||||
OutputState::with(focused_output, |state| {
|
||||
state
|
||||
.focused_tags
|
||||
.iter()
|
||||
.filter_map(|(id, active)| active.then_some(id.clone()))
|
||||
.collect()
|
||||
})
|
||||
} else if let Some(first_tag) = self.tag_state.tags.first() {
|
||||
vec![first_tag.id.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
window.toplevel().with_pending_state(|tl_state| {
|
||||
tl_state.states.set(xdg_toplevel::State::TiledTop);
|
||||
tl_state.states.set(xdg_toplevel::State::TiledBottom);
|
||||
tl_state.states.set(xdg_toplevel::State::TiledLeft);
|
||||
tl_state.states.set(xdg_toplevel::State::TiledRight);
|
||||
});
|
||||
window.with_state(|state| {
|
||||
state.tags = match (
|
||||
&self.focus_state.focused_output,
|
||||
self.space.outputs().next(),
|
||||
) {
|
||||
(Some(output), _) | (None, Some(output)) => output.with_state(|state| {
|
||||
let output_tags = state.focused_tags().cloned().collect::<Vec<_>>();
|
||||
if !output_tags.is_empty() {
|
||||
output_tags
|
||||
} else if let Some(first_tag) = state.tags.first() {
|
||||
vec![first_tag.clone()]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}),
|
||||
(None, None) => vec![],
|
||||
};
|
||||
|
||||
tracing::debug!("new window, tags are {:?}", state.tags);
|
||||
});
|
||||
|
||||
let windows_on_output = self
|
||||
.windows
|
||||
.iter()
|
||||
.filter(|win| {
|
||||
win.with_state(|state| {
|
||||
self.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.with_state(|op_state| {
|
||||
op_state
|
||||
.tags
|
||||
.iter()
|
||||
.any(|tag| state.tags.iter().any(|tg| tg == tag))
|
||||
})
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.windows.push(window.clone());
|
||||
self.space.map_element(window.clone(), (0, 0), true);
|
||||
// self.space.map_element(window.clone(), (0, 0), true);
|
||||
if let Some(focused_output) = self.focus_state.focused_output.clone() {
|
||||
focused_output.with_state(|state| {
|
||||
let first_tag = state.focused_tags().next();
|
||||
if let Some(first_tag) = first_tag {
|
||||
first_tag.layout().layout(
|
||||
self.windows.clone(),
|
||||
state.focused_tags().cloned().collect(),
|
||||
&self.space,
|
||||
&focused_output,
|
||||
);
|
||||
}
|
||||
});
|
||||
BLOCKER_COUNTER.store(1, std::sync::atomic::Ordering::SeqCst);
|
||||
tracing::debug!(
|
||||
"blocker {}",
|
||||
BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst)
|
||||
);
|
||||
for win in windows_on_output.iter() {
|
||||
compositor::add_blocker(win.toplevel().wl_surface(), WindowBlocker);
|
||||
}
|
||||
let clone = window.clone();
|
||||
self.loop_handle.insert_idle(|data| {
|
||||
crate::state::schedule_on_commit(data, vec![clone], move |data| {
|
||||
BLOCKER_COUNTER.store(0, std::sync::atomic::Ordering::SeqCst);
|
||||
tracing::debug!(
|
||||
"blocker {}",
|
||||
BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst)
|
||||
);
|
||||
for client in windows_on_output
|
||||
.iter()
|
||||
.filter_map(|win| win.toplevel().wl_surface().client())
|
||||
{
|
||||
data.state
|
||||
.client_compositor_state(&client)
|
||||
.blocker_cleared(&mut data.state, &data.display.handle())
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
self.loop_handle.insert_idle(move |data| {
|
||||
data.state
|
||||
.seat
|
||||
|
@ -255,21 +330,28 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
SERIAL_COUNTER.next_serial(),
|
||||
);
|
||||
});
|
||||
|
||||
let windows: Vec<Window> = self.space.elements().cloned().collect();
|
||||
|
||||
self.loop_handle.insert_idle(|data| {
|
||||
tracing::debug!("Layout master_stack");
|
||||
Layout::master_stack(&mut data.state, windows, crate::layout::Direction::Left);
|
||||
});
|
||||
}
|
||||
|
||||
fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
|
||||
tracing::debug!("toplevel destroyed");
|
||||
self.windows.retain(|window| window.toplevel() != &surface);
|
||||
let mut windows: Vec<Window> = self.space.elements().cloned().collect();
|
||||
windows.retain(|window| window.toplevel() != &surface);
|
||||
Layout::master_stack(self, windows, crate::layout::Direction::Left);
|
||||
if let Some(focused_output) = self.focus_state.focused_output.as_ref() {
|
||||
focused_output.with_state(|state| {
|
||||
let first_tag = state.focused_tags().next();
|
||||
if let Some(first_tag) = first_tag {
|
||||
first_tag.layout().layout(
|
||||
self.windows.clone(),
|
||||
state.focused_tags().cloned().collect(),
|
||||
&self.space,
|
||||
focused_output,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// let mut windows: Vec<Window> = self.space.elements().cloned().collect();
|
||||
// windows.retain(|window| window.toplevel() != &surface);
|
||||
// Layouts::master_stack(self, windows, crate::layout::Direction::Left);
|
||||
let focus = self
|
||||
.focus_state
|
||||
.current_focus()
|
||||
|
@ -367,40 +449,30 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
}
|
||||
|
||||
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
||||
tracing::debug!("start of ack_configure");
|
||||
if let Some(window) = self.window_for_surface(&surface) {
|
||||
tracing::debug!("found window for surface");
|
||||
WindowState::with_state(&window, |state| {
|
||||
if let WindowResizeState::WaitingForAck(serial, new_loc) = state.resize_state {
|
||||
window.with_state(|state| {
|
||||
if let WindowResizeState::Requested(serial, new_loc) = state.resize_state {
|
||||
match &configure {
|
||||
Configure::Toplevel(configure) => {
|
||||
if configure.serial >= serial {
|
||||
tracing::debug!("acked configure, new loc is {:?}", new_loc);
|
||||
state.resize_state = WindowResizeState::WaitingForCommit(new_loc);
|
||||
// tracing::debug!("acked configure, new loc is {:?}", new_loc);
|
||||
state.resize_state = WindowResizeState::Acknowledged(new_loc);
|
||||
if let Some(focused_output) =
|
||||
self.focus_state.focused_output.clone()
|
||||
{
|
||||
window.send_frame(
|
||||
&focused_output,
|
||||
self.clock.now(),
|
||||
Some(Duration::ZERO),
|
||||
surface_primary_scanout_output,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Configure::Popup(_) => todo!(),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// HACK: If a window is currently going through something that generates a bunch of
|
||||
// | commits, like an animation, unmapping it while it's doing that has a chance
|
||||
// | to cause any send_configures to not trigger a commit. I'm not sure if this is because of
|
||||
// | the way I've implemented things or if it's something else. Because of me
|
||||
// | mapping the element in commit, this means that the window won't reappear on a tag
|
||||
// | change. The code below is a workaround until I can figure it out.
|
||||
if !self.space.elements().any(|win| win == &window) {
|
||||
WindowState::with_state(&window, |state| {
|
||||
if let WindowResizeState::WaitingForCommit(new_loc) = state.resize_state {
|
||||
tracing::debug!("remapping window");
|
||||
let win = window.clone();
|
||||
self.loop_handle.insert_idle(move |data| {
|
||||
data.state.space.map_element(win, new_loc, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
12
src/input.rs
12
src/input.rs
|
@ -6,7 +6,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::api::msg::{CallbackId, ModifierMask, Modifiers, OutgoingMsg};
|
||||
use crate::api::msg::{CallbackId, Modifier, ModifierMask, OutgoingMsg};
|
||||
use smithay::{
|
||||
backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
|
||||
|
@ -221,18 +221,18 @@ impl<B: Backend> State<B> {
|
|||
time,
|
||||
|state, modifiers, keysym| {
|
||||
if press_state == KeyState::Pressed {
|
||||
let mut modifier_mask = Vec::<Modifiers>::new();
|
||||
let mut modifier_mask = Vec::<Modifier>::new();
|
||||
if modifiers.alt {
|
||||
modifier_mask.push(Modifiers::Alt);
|
||||
modifier_mask.push(Modifier::Alt);
|
||||
}
|
||||
if modifiers.shift {
|
||||
modifier_mask.push(Modifiers::Shift);
|
||||
modifier_mask.push(Modifier::Shift);
|
||||
}
|
||||
if modifiers.ctrl {
|
||||
modifier_mask.push(Modifiers::Ctrl);
|
||||
modifier_mask.push(Modifier::Ctrl);
|
||||
}
|
||||
if modifiers.logo {
|
||||
modifier_mask.push(Modifiers::Super);
|
||||
modifier_mask.push(Modifier::Super);
|
||||
}
|
||||
let raw_sym = if keysym.raw_syms().len() == 1 {
|
||||
keysym.raw_syms()[0]
|
||||
|
|
624
src/layout.rs
624
src/layout.rs
|
@ -4,14 +4,622 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod automatic;
|
||||
pub mod manual;
|
||||
use itertools::{Either, Itertools};
|
||||
use smithay::{
|
||||
desktop::{Space, Window},
|
||||
output::Output,
|
||||
utils::{Logical, Size},
|
||||
};
|
||||
|
||||
pub struct Layout;
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
tag::Tag,
|
||||
window::window_state::WindowResizeState,
|
||||
};
|
||||
|
||||
pub enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
// TODO: couple this with the layouts
|
||||
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Layout {
|
||||
MasterStack,
|
||||
Dwindle,
|
||||
Spiral,
|
||||
CornerTopLeft,
|
||||
CornerTopRight,
|
||||
CornerBottomLeft,
|
||||
CornerBottomRight,
|
||||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn layout(
|
||||
&self,
|
||||
windows: Vec<Window>,
|
||||
tags: Vec<Tag>,
|
||||
space: &Space<Window>,
|
||||
output: &Output,
|
||||
) {
|
||||
let windows = filter_windows(&windows, tags);
|
||||
|
||||
let Some(output_geo) = space.output_geometry(output) else {
|
||||
tracing::error!("could not get output geometry");
|
||||
return;
|
||||
};
|
||||
|
||||
let output_loc = output.current_location();
|
||||
|
||||
match self {
|
||||
Layout::MasterStack => {
|
||||
let master = windows.first();
|
||||
let stack = windows.iter().skip(1);
|
||||
|
||||
let Some(master) = master else { return };
|
||||
|
||||
let stack_count = stack.clone().count();
|
||||
|
||||
if stack_count == 0 {
|
||||
// one window
|
||||
master.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(output_geo.size);
|
||||
});
|
||||
|
||||
master.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
master.toplevel().send_configure(),
|
||||
(output_loc.x, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
let new_master_size: Size<i32, Logical> =
|
||||
(output_geo.size.w / 2, output_geo.size.h).into();
|
||||
master.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(new_master_size);
|
||||
});
|
||||
master.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
master.toplevel().send_configure(),
|
||||
(output_loc.x, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
|
||||
let stack_count = stack_count;
|
||||
|
||||
let height = output_geo.size.h as f32 / stack_count as f32;
|
||||
let mut y_s = vec![];
|
||||
for i in 0..stack_count {
|
||||
y_s.push((i as f32 * height).round() as i32);
|
||||
}
|
||||
let heights = y_s
|
||||
.windows(2)
|
||||
.map(|pair| pair[1] - pair[0])
|
||||
.chain(vec![output_geo.size.h - y_s.last().expect("vec was empty")])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, win) in stack.enumerate() {
|
||||
win.toplevel().with_pending_state(|state| {
|
||||
// INFO: Some windows crash the compositor if they become too short in height,
|
||||
// | so they're limited to a minimum of 40 pixels as a workaround.
|
||||
state.size =
|
||||
Some((output_geo.size.w / 2, i32::max(heights[i], 40)).into());
|
||||
});
|
||||
|
||||
win.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win.toplevel().send_configure(),
|
||||
(output_geo.size.w / 2 + output_loc.x, y_s[i] + output_loc.y)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Layout::Dwindle => {
|
||||
let mut iter = windows.windows(2).peekable();
|
||||
|
||||
if iter.peek().is_none() {
|
||||
if let Some(window) = windows.first() {
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(output_geo.size);
|
||||
});
|
||||
|
||||
window.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
window.toplevel().send_configure(),
|
||||
(output_loc.x, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (i, wins) in iter.enumerate() {
|
||||
let win1 = &wins[0];
|
||||
let win2 = &wins[1];
|
||||
|
||||
enum Slice {
|
||||
Right,
|
||||
Below,
|
||||
}
|
||||
|
||||
let slice = if i % 2 == 0 {
|
||||
Slice::Right
|
||||
} else {
|
||||
Slice::Below
|
||||
};
|
||||
|
||||
if i == 0 {
|
||||
win1.toplevel()
|
||||
.with_pending_state(|state| state.size = Some(output_geo.size));
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
output_loc,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let win1_size = win1.toplevel().with_pending_state(|state| {
|
||||
state.size.expect("size should have been set")
|
||||
});
|
||||
let win1_loc = win1.with_state(|state| {
|
||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
||||
loc
|
||||
});
|
||||
|
||||
match slice {
|
||||
Slice::Right => {
|
||||
let width_partition = win1_size.w / 2;
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(win1_size.w - width_partition, i32::max(win1_size.h, 40))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
win1_loc,
|
||||
);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size =
|
||||
Some((width_partition, i32::max(win1_size.h, 40)).into());
|
||||
});
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win2.toplevel().send_configure(),
|
||||
(win1_loc.x + (win1_size.w - width_partition), win1_loc.y)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
Slice::Below => {
|
||||
let height_partition = win1_size.h / 2;
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(win1_size.w, i32::max(win1_size.h - height_partition, 40))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
win1_loc,
|
||||
);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size =
|
||||
Some((win1_size.w, i32::max(height_partition, 40)).into());
|
||||
});
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win2.toplevel().send_configure(),
|
||||
(win1_loc.x, win1_loc.y + (win1_size.h - height_partition))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Layout::Spiral => {
|
||||
let mut iter = windows.windows(2).peekable();
|
||||
|
||||
if iter.peek().is_none() {
|
||||
if let Some(window) = windows.first() {
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(output_geo.size);
|
||||
});
|
||||
|
||||
window.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
window.toplevel().send_configure(),
|
||||
(output_loc.x, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
for (i, wins) in iter.enumerate() {
|
||||
let win1 = &wins[0];
|
||||
let win2 = &wins[1];
|
||||
|
||||
enum Slice {
|
||||
Above,
|
||||
Below,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
let slice = match i % 4 {
|
||||
0 => Slice::Right,
|
||||
1 => Slice::Below,
|
||||
2 => Slice::Left,
|
||||
3 => Slice::Above,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
if i == 0 {
|
||||
win1.toplevel()
|
||||
.with_pending_state(|state| state.size = Some(output_geo.size));
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
output_loc,
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
let win1_size = win1.toplevel().with_pending_state(|state| {
|
||||
state.size.expect("size should have been set")
|
||||
});
|
||||
let win1_loc = win1.with_state(|state| {
|
||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
||||
loc
|
||||
});
|
||||
|
||||
match slice {
|
||||
Slice::Above => {
|
||||
let height_partition = win1_size.h / 2;
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(win1_size.w, i32::max(win1_size.h - height_partition, 40))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
(win1_loc.x, win1_loc.y + height_partition).into(),
|
||||
);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size =
|
||||
Some((win1_size.w, i32::max(height_partition, 40)).into());
|
||||
});
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win2.toplevel().send_configure(),
|
||||
win1_loc,
|
||||
);
|
||||
});
|
||||
}
|
||||
Slice::Below => {
|
||||
let height_partition = win1_size.h / 2;
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(win1_size.w, win1_size.h - i32::max(height_partition, 40))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
win1_loc,
|
||||
);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size =
|
||||
Some((win1_size.w, i32::max(height_partition, 40)).into());
|
||||
});
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win2.toplevel().send_configure(),
|
||||
(win1_loc.x, win1_loc.y + (win1_size.h - height_partition))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
Slice::Left => {
|
||||
let width_partition = win1_size.w / 2;
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(win1_size.w - width_partition, i32::max(win1_size.h, 40))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
(win1_loc.x + width_partition, win1_loc.y).into(),
|
||||
);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size =
|
||||
Some((width_partition, i32::max(win1_size.h, 40)).into());
|
||||
});
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win2.toplevel().send_configure(),
|
||||
win1_loc,
|
||||
);
|
||||
});
|
||||
}
|
||||
Slice::Right => {
|
||||
let width_partition = win1_size.w / 2;
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(win1_size.w - width_partition, i32::max(win1_size.h, 40))
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win1.toplevel().send_configure(),
|
||||
win1_loc,
|
||||
);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size =
|
||||
Some((width_partition, i32::max(win1_size.h, 40)).into());
|
||||
});
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win2.toplevel().send_configure(),
|
||||
(win1_loc.x + (win1_size.w - width_partition), win1_loc.y)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
layout @ (Layout::CornerTopLeft
|
||||
| Layout::CornerTopRight
|
||||
| Layout::CornerBottomLeft
|
||||
| Layout::CornerBottomRight) => match windows.len() {
|
||||
0 => (),
|
||||
1 => {
|
||||
windows[0].toplevel().with_pending_state(|state| {
|
||||
state.size = Some(output_geo.size);
|
||||
});
|
||||
|
||||
windows[0].with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
windows[0].toplevel().send_configure(),
|
||||
(output_loc.x, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
2 => {
|
||||
windows[0].toplevel().with_pending_state(|state| {
|
||||
state.size = Some((output_geo.size.w / 2, output_geo.size.h).into());
|
||||
});
|
||||
windows[0].with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
windows[0].toplevel().send_configure(),
|
||||
(output_loc.x, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
windows[1].toplevel().with_pending_state(|state| {
|
||||
state.size = Some((output_geo.size.w / 2, output_geo.size.h).into());
|
||||
});
|
||||
windows[1].with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
windows[1].toplevel().send_configure(),
|
||||
(output_loc.x + output_geo.size.w / 2, output_loc.y).into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
let mut windows = windows.into_iter();
|
||||
let Some(corner) = windows.next() else { unreachable!() };
|
||||
let (horiz_stack, vert_stack): (Vec<Window>, Vec<Window>) =
|
||||
windows.enumerate().partition_map(|(i, win)| {
|
||||
if i % 2 == 0 {
|
||||
Either::Left(win)
|
||||
} else {
|
||||
Either::Right(win)
|
||||
}
|
||||
});
|
||||
|
||||
let div_factor = 2;
|
||||
|
||||
corner.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(
|
||||
(
|
||||
output_geo.size.w / div_factor,
|
||||
output_geo.size.h / div_factor,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
corner.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
corner.toplevel().send_configure(),
|
||||
match layout {
|
||||
Layout::CornerTopLeft => (output_loc.x, output_loc.y),
|
||||
Layout::CornerTopRight => (
|
||||
output_loc.x + output_geo.size.w
|
||||
- output_geo.size.w / div_factor,
|
||||
output_loc.y,
|
||||
),
|
||||
Layout::CornerBottomLeft => (
|
||||
output_loc.x,
|
||||
output_loc.y + output_geo.size.h
|
||||
- output_geo.size.h / div_factor,
|
||||
),
|
||||
Layout::CornerBottomRight => (
|
||||
output_loc.x + output_geo.size.w
|
||||
- output_geo.size.w / div_factor,
|
||||
output_loc.y + output_geo.size.h
|
||||
- output_geo.size.h / div_factor,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
|
||||
let vert_stack_count = vert_stack.len();
|
||||
|
||||
let height = output_geo.size.h as f32 / vert_stack_count as f32;
|
||||
let mut y_s = vec![];
|
||||
for i in 0..vert_stack_count {
|
||||
y_s.push((i as f32 * height).round() as i32);
|
||||
}
|
||||
let heights = y_s
|
||||
.windows(2)
|
||||
.map(|pair| pair[1] - pair[0])
|
||||
.chain(vec![output_geo.size.h - y_s.last().expect("vec was empty")])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, win) in vert_stack.iter().enumerate() {
|
||||
win.toplevel().with_pending_state(|state| {
|
||||
// INFO: Some windows crash the compositor if they become too short in height,
|
||||
// | so they're limited to a minimum of 40 pixels as a workaround.
|
||||
state.size =
|
||||
Some((output_geo.size.w / 2, i32::max(heights[i], 40)).into());
|
||||
});
|
||||
|
||||
win.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win.toplevel().send_configure(),
|
||||
(
|
||||
match layout {
|
||||
Layout::CornerTopLeft | Layout::CornerBottomLeft => {
|
||||
output_geo.size.w / 2 + output_loc.x
|
||||
}
|
||||
Layout::CornerTopRight | Layout::CornerBottomRight => {
|
||||
output_loc.x
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
y_s[i] + output_loc.y,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
let horiz_stack_count = horiz_stack.len();
|
||||
|
||||
let width = output_geo.size.w as f32 / 2.0 / horiz_stack_count as f32;
|
||||
let mut x_s = vec![];
|
||||
for i in 0..horiz_stack_count {
|
||||
x_s.push((i as f32 * width).round() as i32);
|
||||
}
|
||||
let widths = x_s
|
||||
.windows(2)
|
||||
.map(|pair| pair[1] - pair[0])
|
||||
.chain(vec![
|
||||
output_geo.size.w / 2 - x_s.last().expect("vec was empty"),
|
||||
])
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for (i, win) in horiz_stack.iter().enumerate() {
|
||||
win.toplevel().with_pending_state(|state| {
|
||||
// INFO: Some windows crash the compositor if they become too short in height,
|
||||
// | so they're limited to a minimum of 40 pixels as a workaround.
|
||||
state.size =
|
||||
Some((i32::max(widths[i], 1), output_geo.size.h / 2).into());
|
||||
});
|
||||
|
||||
win.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(
|
||||
win.toplevel().send_configure(),
|
||||
match layout {
|
||||
Layout::CornerTopLeft => (
|
||||
x_s[i] + output_loc.x,
|
||||
output_loc.y + output_geo.size.h / 2,
|
||||
),
|
||||
Layout::CornerTopRight => (
|
||||
x_s[i] + output_loc.x + output_geo.size.w / 2,
|
||||
output_loc.y + output_geo.size.h / 2,
|
||||
),
|
||||
Layout::CornerBottomLeft => {
|
||||
(x_s[i] + output_loc.x, output_loc.y)
|
||||
}
|
||||
Layout::CornerBottomRight => (
|
||||
x_s[i] + output_loc.x + output_geo.size.w / 2,
|
||||
output_loc.y,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_windows(windows: &[Window], tags: Vec<Tag>) -> Vec<Window> {
|
||||
windows
|
||||
.iter()
|
||||
.filter(|window| {
|
||||
window.with_state(|state| {
|
||||
state.floating.is_tiled() && {
|
||||
for tag in state.tags.iter() {
|
||||
if tags.iter().any(|tg| tg == tag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
pub fn swap_window_positions(&mut self, win1: &Window, win2: &Window) {
|
||||
// FIXME: moving the mouse quickly will break swapping
|
||||
|
||||
let win1_loc = self.space.element_location(win1).unwrap(); // TODO: handle unwraps
|
||||
let win2_loc = self.space.element_location(win2).unwrap();
|
||||
let win1_geo = win1.geometry();
|
||||
let win2_geo = win2.geometry();
|
||||
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(win2_geo.size);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(win1_geo.size);
|
||||
});
|
||||
|
||||
let serial = win1.toplevel().send_configure();
|
||||
win1.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(serial, win2_loc);
|
||||
});
|
||||
|
||||
let serial = win2.toplevel().send_configure();
|
||||
win2.with_state(|state| {
|
||||
state.resize_state = WindowResizeState::Requested(serial, win1_loc);
|
||||
});
|
||||
|
||||
let mut elems = self
|
||||
.windows
|
||||
.iter_mut()
|
||||
.filter(|win| *win == win1 || *win == win2);
|
||||
|
||||
let (first, second) = (elems.next(), elems.next());
|
||||
|
||||
if let Some(first) = first {
|
||||
if let Some(second) = second {
|
||||
std::mem::swap(first, second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use smithay::{
|
||||
desktop::Window,
|
||||
wayland::{compositor, shell::xdg::XdgToplevelSurfaceData},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
state::State,
|
||||
window::window_state::{WindowResizeState, WindowState},
|
||||
};
|
||||
|
||||
use super::{Direction, Layout};
|
||||
|
||||
impl Layout {
|
||||
pub fn master_stack<B: Backend>(
|
||||
state: &mut State<B>,
|
||||
mut windows: Vec<Window>,
|
||||
side: Direction,
|
||||
) {
|
||||
windows.retain(|win| WindowState::with_state(win, |state| state.floating.is_tiled()));
|
||||
match side {
|
||||
Direction::Left => {
|
||||
let window_count = windows.len();
|
||||
if window_count == 0 {
|
||||
return;
|
||||
}
|
||||
// TODO: change focused_output to be not an option
|
||||
let Some(output) = state
|
||||
.focus_state
|
||||
.focused_output
|
||||
.as_ref()
|
||||
.or_else(|| state.space.outputs().next())
|
||||
else {
|
||||
tracing::warn!("no connected outputs");
|
||||
return;
|
||||
// TODO: no idea what happens if you spawn a window while no monitors are
|
||||
// | connected, figure that out
|
||||
};
|
||||
let output_size = state.space.output_geometry(output).unwrap().size;
|
||||
if window_count == 1 {
|
||||
tracing::debug!("Laying out only window");
|
||||
let window = windows[0].clone();
|
||||
|
||||
window.toplevel().with_pending_state(|tl_state| {
|
||||
tl_state.size = Some(state.space.output_geometry(output).unwrap().size);
|
||||
tracing::debug!("only size is {:?}", tl_state.size);
|
||||
});
|
||||
|
||||
let initial_configure_sent =
|
||||
compositor::with_states(window.toplevel().wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
tracing::debug!("initial configure sent is {initial_configure_sent}");
|
||||
if initial_configure_sent {
|
||||
WindowState::with_state(&window, |state| {
|
||||
tracing::debug!("sending configure");
|
||||
state.resize_state = WindowResizeState::WaitingForAck(
|
||||
window.toplevel().send_configure(),
|
||||
output.current_location(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
tracing::debug!("layed out first window");
|
||||
let mut windows = windows.iter();
|
||||
let first_window = windows.next().unwrap();
|
||||
|
||||
first_window.toplevel().with_pending_state(|tl_state| {
|
||||
let mut size = state.space.output_geometry(output).unwrap().size;
|
||||
size.w /= 2;
|
||||
tl_state.size = Some(size);
|
||||
tracing::debug!("first size is {:?}", tl_state.size);
|
||||
});
|
||||
|
||||
let initial_configure_sent =
|
||||
compositor::with_states(first_window.toplevel().wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
if initial_configure_sent {
|
||||
WindowState::with_state(first_window, |state| {
|
||||
tracing::debug!("sending resize state");
|
||||
state.resize_state = WindowResizeState::WaitingForAck(
|
||||
first_window.toplevel().send_configure(),
|
||||
output.current_location(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
let window_count = windows.len() as i32;
|
||||
let height = output_size.h / window_count;
|
||||
let x = output.current_location().x + output_size.w / 2;
|
||||
|
||||
for (i, win) in windows.enumerate() {
|
||||
win.toplevel().with_pending_state(|state| {
|
||||
let mut new_size = output_size;
|
||||
new_size.w /= 2;
|
||||
new_size.w = new_size.w.clamp(1, i32::MAX);
|
||||
new_size.h /= window_count;
|
||||
// INFO: The newest window won't have its geometry.loc set until after here and I don't know
|
||||
// | why, so this is hardcoded to 40. I don't anticipate people using
|
||||
// | windows that are that short, so figuring it out is low priority.
|
||||
// | Kitty specifically will crash the compositor if it's resized such
|
||||
// | that the bottom border goes above the bottom of the title bar if
|
||||
// | this is set too low.
|
||||
new_size.h = new_size.h.clamp(40, i32::MAX);
|
||||
state.size = Some(new_size);
|
||||
tracing::debug!("size is {:?}", state.size);
|
||||
});
|
||||
|
||||
let mut new_loc = output.current_location();
|
||||
new_loc.x = x;
|
||||
new_loc.y = (i as i32) * height;
|
||||
|
||||
let initial_configure_sent =
|
||||
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
if initial_configure_sent {
|
||||
WindowState::with_state(win, |state| {
|
||||
state.resize_state = WindowResizeState::WaitingForAck(
|
||||
win.toplevel().send_configure(),
|
||||
new_loc,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Direction::Right => todo!(),
|
||||
Direction::Top => todo!(),
|
||||
Direction::Bottom => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
//
|
||||
// love how i'm licensing this empty file
|
|
@ -4,31 +4,41 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
use std::cell::RefCell;
|
||||
|
||||
use smithay::output::Output;
|
||||
|
||||
use crate::tag::TagId;
|
||||
use crate::{state::WithState, tag::Tag};
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||
pub struct OutputName(pub String);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OutputState {
|
||||
pub focused_tags: HashMap<TagId, bool>,
|
||||
pub tags: Vec<Tag>,
|
||||
}
|
||||
|
||||
impl OutputState {
|
||||
pub fn with<F, T>(output: &Output, mut func: F) -> T
|
||||
where
|
||||
F: FnMut(&mut Self) -> T,
|
||||
{
|
||||
output
|
||||
.user_data()
|
||||
.insert_if_missing(RefCell::<Self>::default);
|
||||
impl WithState for Output {
|
||||
type State = OutputState;
|
||||
|
||||
let state = output
|
||||
fn with_state<F, T>(&self, mut func: F) -> T
|
||||
where
|
||||
F: FnMut(&mut Self::State) -> T,
|
||||
{
|
||||
self.user_data()
|
||||
.insert_if_missing(RefCell::<Self::State>::default);
|
||||
|
||||
let state = self
|
||||
.user_data()
|
||||
.get::<RefCell<Self>>()
|
||||
.expect("RefCell doesn't exist in data map (This should NEVER happen. If you see this, something oofed big-time.)");
|
||||
.get::<RefCell<Self::State>>()
|
||||
.expect("RefCell not in data map");
|
||||
|
||||
func(&mut state.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputState {
|
||||
pub fn focused_tags(&self) -> impl Iterator<Item = &Tag> {
|
||||
self.tags.iter().filter(|tag| tag.active())
|
||||
}
|
||||
}
|
||||
|
|
771
src/state.rs
771
src/state.rs
|
@ -5,6 +5,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
error::Error,
|
||||
ffi::OsString,
|
||||
os::{fd::AsRawFd, unix::net::UnixStream},
|
||||
|
@ -18,12 +19,10 @@ use crate::{
|
|||
msg::{Args, CallbackId, Msg, OutgoingMsg, Request, RequestResponse},
|
||||
PinnacleSocketSource,
|
||||
},
|
||||
backend::{udev::UdevData, winit::WinitData},
|
||||
focus::FocusState,
|
||||
layout::Layout,
|
||||
output::OutputState,
|
||||
tag::{Tag, TagState},
|
||||
window::{window_state::WindowState, WindowProperties},
|
||||
grab::resize_grab::ResizeSurfaceState,
|
||||
tag::{Tag, TagProperties},
|
||||
window::{window_state::WindowResizeState, WindowProperties},
|
||||
};
|
||||
use calloop::futures::Scheduler;
|
||||
use futures_lite::AsyncBufReadExt;
|
||||
|
@ -45,6 +44,7 @@ use smithay::{
|
|||
},
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_surface::WlSurface,
|
||||
Display,
|
||||
},
|
||||
},
|
||||
|
@ -89,7 +89,6 @@ pub struct State<B: Backend> {
|
|||
pub input_state: InputState,
|
||||
pub api_state: ApiState,
|
||||
pub focus_state: FocusState,
|
||||
pub tag_state: TagState,
|
||||
|
||||
pub popup_manager: PopupManager,
|
||||
|
||||
|
@ -98,10 +97,15 @@ pub struct State<B: Backend> {
|
|||
pub windows: Vec<Window>,
|
||||
|
||||
pub async_scheduler: Scheduler<()>,
|
||||
|
||||
// TODO: move into own struct
|
||||
// | basically just clean this mess up
|
||||
pub output_callback_ids: Vec<CallbackId>,
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
pub fn handle_msg(&mut self, msg: Msg) {
|
||||
// tracing::debug!("Got {msg:?}");
|
||||
match msg {
|
||||
Msg::SetKeybind {
|
||||
key,
|
||||
|
@ -137,7 +141,7 @@ impl<B: Backend> State<B> {
|
|||
|
||||
Msg::SetWindowSize { window_id, size } => {
|
||||
let Some(window) = self.space.elements().find(|&win| {
|
||||
WindowState::with_state(win, |state| state.id == window_id)
|
||||
win.with_state( |state| state.id == window_id)
|
||||
}) else { return; };
|
||||
|
||||
// TODO: tiled vs floating
|
||||
|
@ -150,87 +154,176 @@ impl<B: Backend> State<B> {
|
|||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| WindowState::with_state(win, |state| state.id == window_id))
|
||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
WindowState::with_state(window, |state| {
|
||||
state.tags = vec![tag_id.clone()];
|
||||
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()];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
self.re_layout();
|
||||
let output = self.focus_state.focused_output.clone().unwrap();
|
||||
self.re_layout(&output);
|
||||
}
|
||||
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| WindowState::with_state(win, |state| state.id == window_id))
|
||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
||||
{
|
||||
WindowState::with_state(window, |state| {
|
||||
if state.tags.contains(&tag_id) {
|
||||
state.tags.retain(|id| id != &tag_id);
|
||||
} else {
|
||||
state.tags.push(tag_id.clone());
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
self.re_layout();
|
||||
let output = self.focus_state.focused_output.clone().unwrap();
|
||||
self.re_layout(&output);
|
||||
}
|
||||
}
|
||||
Msg::ToggleTag { tag_id } => {
|
||||
OutputState::with(
|
||||
self.focus_state.focused_output.as_ref().unwrap(), // TODO: handle error
|
||||
|state| match state.focused_tags.get_mut(&tag_id) {
|
||||
Some(id) => {
|
||||
*id = !*id;
|
||||
tracing::debug!(
|
||||
"toggled tag {tag_id:?} {}",
|
||||
if *id { "on" } else { "off" }
|
||||
);
|
||||
}
|
||||
None => {
|
||||
state.focused_tags.insert(tag_id.clone(), true);
|
||||
tracing::debug!("toggled tag {tag_id:?} on");
|
||||
}
|
||||
},
|
||||
);
|
||||
Msg::ToggleTag { output_name, tag_name } => {
|
||||
tracing::debug!("ToggleTag");
|
||||
|
||||
self.re_layout();
|
||||
}
|
||||
Msg::SwitchToTag { tag_id } => {
|
||||
OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| {
|
||||
for (_, active) in state.focused_tags.iter_mut() {
|
||||
*active = false;
|
||||
}
|
||||
if let Some(active) = state.focused_tags.get_mut(&tag_id) {
|
||||
*active = true;
|
||||
} else {
|
||||
state.focused_tags.insert(tag_id.clone(), true);
|
||||
}
|
||||
tracing::debug!("focused tags: {:?}", state.focused_tags);
|
||||
});
|
||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
||||
if let Some(output) = output {
|
||||
|
||||
self.re_layout();
|
||||
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);
|
||||
}
|
||||
}
|
||||
Msg::AddTags { tags } => {
|
||||
self.tag_state.tags.extend(tags.into_iter().map(|tag| Tag {
|
||||
id: tag,
|
||||
windows: vec![],
|
||||
}));
|
||||
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::RemoveTags { tags } => {
|
||||
self.tag_state.tags.retain(|tag| !tags.contains(&tag.id));
|
||||
// TODO: add output
|
||||
Msg::AddTags { output_name, tags } => {
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
{
|
||||
output.with_state(|state| {
|
||||
state
|
||||
.tags
|
||||
.extend(tags.iter().cloned().map(Tag::new));
|
||||
tracing::debug!("tags added, are now {:?}", state.tags);
|
||||
});
|
||||
}
|
||||
}
|
||||
Msg::RemoveTags { output_name, tags } => {
|
||||
if let Some(output) = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == output_name)
|
||||
{
|
||||
output.with_state(|state| {
|
||||
state.tags.retain(|tag| !tags.contains(&tag.name()));
|
||||
});
|
||||
}
|
||||
}
|
||||
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::ConnectForAllOutputs { callback_id } => {
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
for output in self.space.outputs() {
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::CallCallback {
|
||||
callback_id,
|
||||
args: Some(Args::ConnectForAllOutputs {
|
||||
output_name: output.name(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
self.output_callback_ids.push(callback_id);
|
||||
}
|
||||
|
||||
Msg::Quit => {
|
||||
self.loop_signal.stop();
|
||||
}
|
||||
|
||||
Msg::Request(request) => match request {
|
||||
Request::GetWindowByAppId { id, app_id } => todo!(),
|
||||
Request::GetWindowByTitle { id, title } => todo!(),
|
||||
Request::GetWindowByFocus { id } => {
|
||||
let Some(current_focus) = self.focus_state.current_focus() else { return; };
|
||||
let (app_id, title) =
|
||||
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 => {
|
||||
let Some(current_focus) = self.focus_state.current_focus() else { return; };
|
||||
let (app_id, title) =
|
||||
compositor::with_states(current_focus.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
|
@ -240,40 +333,32 @@ impl<B: Backend> State<B> {
|
|||
.expect("Couldn't lock XdgToplevelSurfaceData");
|
||||
(lock.app_id.clone(), lock.title.clone())
|
||||
});
|
||||
let (window_id, floating) = WindowState::with_state(¤t_focus, |state| {
|
||||
(state.id, state.floating.is_floating())
|
||||
});
|
||||
// TODO: unwrap
|
||||
let location = self.space.element_location(¤t_focus).unwrap();
|
||||
let props = WindowProperties {
|
||||
id: window_id,
|
||||
app_id,
|
||||
title,
|
||||
size: current_focus.geometry().size.into(),
|
||||
location: location.into(),
|
||||
floating,
|
||||
};
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id: id,
|
||||
response: RequestResponse::Window { window: props },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
Request::GetAllWindows { id } => {
|
||||
let window_props = self
|
||||
.space
|
||||
.elements()
|
||||
.map(|win| {
|
||||
let (app_id, title) =
|
||||
let (window_id, floating) =
|
||||
current_focus.with_state(|state| (state.id, state.floating.is_floating()));
|
||||
// TODO: unwrap
|
||||
let location = self.space.element_location(¤t_focus).unwrap();
|
||||
let props = WindowProperties {
|
||||
id: window_id,
|
||||
app_id,
|
||||
title,
|
||||
size: current_focus.geometry().size.into(),
|
||||
location: location.into(),
|
||||
floating,
|
||||
};
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Window { window: props },
|
||||
},
|
||||
)
|
||||
.expect("Send to client failed");
|
||||
}
|
||||
Request::GetAllWindows => {
|
||||
let window_props = self
|
||||
.space
|
||||
.elements()
|
||||
.map(|win| {
|
||||
let (app_id, title) =
|
||||
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
|
@ -283,42 +368,168 @@ impl<B: Backend> State<B> {
|
|||
.expect("Couldn't lock XdgToplevelSurfaceData");
|
||||
(lock.app_id.clone(), lock.title.clone())
|
||||
});
|
||||
let (window_id, floating) = WindowState::with_state(win, |state| {
|
||||
(state.id, state.floating.is_floating())
|
||||
});
|
||||
// TODO: unwrap
|
||||
let location = self
|
||||
.space
|
||||
.element_location(win)
|
||||
.expect("Window location doesn't exist");
|
||||
WindowProperties {
|
||||
id: window_id,
|
||||
app_id,
|
||||
title,
|
||||
size: win.geometry().size.into(),
|
||||
location: location.into(),
|
||||
floating,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let (window_id, floating) =
|
||||
win.with_state(|state| (state.id, state.floating.is_floating()));
|
||||
// TODO: unwrap
|
||||
let location = self
|
||||
.space
|
||||
.element_location(win)
|
||||
.expect("Window location doesn't exist");
|
||||
WindowProperties {
|
||||
id: window_id,
|
||||
app_id,
|
||||
title,
|
||||
size: win.geometry().size.into(),
|
||||
location: location.into(),
|
||||
floating,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// FIXME: figure out what to do if error
|
||||
let stream = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist");
|
||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
request_id: id,
|
||||
response: RequestResponse::GetAllWindows {
|
||||
windows: window_props,
|
||||
// FIXME: figure out what to do if error
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::GetAllWindows {
|
||||
windows: window_props,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
Request::GetOutputByName { name } => {
|
||||
// TODO: name better
|
||||
let names = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|output| output.name() == name)
|
||||
.map(|output| output.name());
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Outputs {
|
||||
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 { 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 { 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 { names },
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Request::GetTagsByOutput { output } => {
|
||||
let output = self
|
||||
.space
|
||||
.outputs()
|
||||
.find(|op| op.name() == output);
|
||||
if let Some(output) = output {
|
||||
let tag_props = output.with_state(|state| {
|
||||
state.tags
|
||||
.iter()
|
||||
.map(|tag| TagProperties { id: tag.id() })
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
crate::api::send_to_client(
|
||||
&mut stream,
|
||||
&OutgoingMsg::RequestResponse {
|
||||
response: RequestResponse::Tags { tags: tag_props }
|
||||
}).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();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -472,203 +683,95 @@ impl<B: Backend> State<B> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn re_layout(&mut self) {
|
||||
let windows =
|
||||
OutputState::with(self.focus_state.focused_output.as_ref().unwrap(), |state| {
|
||||
for window in self.space.elements().cloned().collect::<Vec<_>>() {
|
||||
let should_render = WindowState::with_state(&window, |win_state| {
|
||||
for tag_id in win_state.tags.iter() {
|
||||
if *state.focused_tags.get(tag_id).unwrap_or(&false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
});
|
||||
if !should_render {
|
||||
self.space.unmap_elem(&window);
|
||||
}
|
||||
}
|
||||
|
||||
self.windows
|
||||
.iter()
|
||||
.filter(|&win| {
|
||||
WindowState::with_state(win, |win_state| {
|
||||
for tag_id in win_state.tags.iter() {
|
||||
if *state.focused_tags.get(tag_id).unwrap_or(&false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
tracing::debug!("Laying out {} windows", windows.len());
|
||||
|
||||
Layout::master_stack(self, windows, crate::layout::Direction::Left);
|
||||
}
|
||||
}
|
||||
|
||||
impl State<WinitData> {
|
||||
/// Create the main [`State`].
|
||||
///
|
||||
/// This will set the WAYLAND_DISPLAY environment variable, insert Wayland necessary sources
|
||||
/// into the event loop, and run an implementation of the config API (currently Lua).
|
||||
pub fn init(
|
||||
backend_data: WinitData,
|
||||
display: &mut Display<Self>,
|
||||
loop_signal: LoopSignal,
|
||||
loop_handle: LoopHandle<'static, CalloopData<WinitData>>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let socket = ListeningSocketSource::new_auto()?;
|
||||
let socket_name = socket.socket_name().to_os_string();
|
||||
|
||||
std::env::set_var("WAYLAND_DISPLAY", socket_name.clone());
|
||||
|
||||
// Opening a new process will use up a few file descriptors, around 10 for Alacritty, for
|
||||
// example. Because of this, opening up only around 100 processes would exhaust the file
|
||||
// descriptor limit on my system (Arch btw) and cause a "Too many open files" crash.
|
||||
//
|
||||
// To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical
|
||||
// environment, I *think* this is ok.
|
||||
if let Err(err) = smithay::reexports::nix::sys::resource::setrlimit(
|
||||
smithay::reexports::nix::sys::resource::Resource::RLIMIT_NOFILE,
|
||||
65536,
|
||||
65536 * 2,
|
||||
) {
|
||||
tracing::error!("Could not raise fd limit: errno {err}");
|
||||
}
|
||||
|
||||
loop_handle.insert_source(socket, |stream, _metadata, data| {
|
||||
data.display
|
||||
.handle()
|
||||
.insert_client(stream, Arc::new(ClientState::default()))
|
||||
.expect("Could not insert client into loop handle");
|
||||
})?;
|
||||
|
||||
loop_handle.insert_source(
|
||||
Generic::new(
|
||||
display.backend().poll_fd().as_raw_fd(),
|
||||
Interest::READ,
|
||||
Mode::Level,
|
||||
),
|
||||
|_readiness, _metadata, data| {
|
||||
data.display.dispatch_clients(&mut data.state)?;
|
||||
Ok(PostAction::Continue)
|
||||
},
|
||||
)?;
|
||||
|
||||
let (tx_channel, rx_channel) = calloop::channel::channel::<Msg>();
|
||||
loop_handle.insert_source(rx_channel, |msg, _, data| match msg {
|
||||
Event::Msg(msg) => data.state.handle_msg(msg),
|
||||
Event::Closed => todo!(),
|
||||
})?;
|
||||
|
||||
// We want to replace the client if a new one pops up
|
||||
// TODO: there should only ever be one client working at a time, and creating a new client
|
||||
// | when one is already running should be impossible.
|
||||
// INFO: this source try_clone()s the stream
|
||||
loop_handle.insert_source(PinnacleSocketSource::new(tx_channel)?, |stream, _, data| {
|
||||
if let Some(old_stream) = data
|
||||
.state
|
||||
.api_state
|
||||
.stream
|
||||
.replace(Arc::new(Mutex::new(stream)))
|
||||
{
|
||||
old_stream
|
||||
.lock()
|
||||
.expect("Couldn't lock old stream")
|
||||
.shutdown(std::net::Shutdown::Both)
|
||||
.expect("Couldn't shutdown old stream");
|
||||
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 (render, do_not_render) = output.with_state(|state| {
|
||||
let first_tag = state.focused_tags().next();
|
||||
if let Some(first_tag) = first_tag {
|
||||
first_tag.layout().layout(
|
||||
self.windows.clone(),
|
||||
state.focused_tags().cloned().collect(),
|
||||
&self.space,
|
||||
output,
|
||||
);
|
||||
}
|
||||
})?;
|
||||
|
||||
let (executor, sched) =
|
||||
calloop::futures::executor::<()>().expect("Couldn't create executor");
|
||||
loop_handle.insert_source(executor, |_, _, _| {})?;
|
||||
|
||||
// TODO: move all this into the lua api
|
||||
let config_path = std::env::var("PINNACLE_CONFIG").unwrap_or_else(|_| {
|
||||
let mut default_path =
|
||||
std::env::var("XDG_CONFIG_HOME").unwrap_or("~/.config".to_string());
|
||||
default_path.push_str("/pinnacle/init.lua");
|
||||
default_path
|
||||
windows.iter().cloned().partition::<Vec<_>, _>(|win| {
|
||||
win.with_state(|win_state| {
|
||||
if win_state.floating.is_floating() {
|
||||
return true;
|
||||
}
|
||||
for tag in win_state.tags.iter() {
|
||||
if state.focused_tags().any(|tg| tg == tag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
if Path::new(&config_path).exists() {
|
||||
let lua_path = std::env::var("LUA_PATH").expect("Lua is not installed!");
|
||||
let mut local_lua_path = std::env::current_dir()
|
||||
.expect("Couldn't get current dir")
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
local_lua_path.push_str("/api/lua"); // TODO: get from crate root and do dynamically
|
||||
let new_lua_path =
|
||||
format!("{local_lua_path}/?.lua;{local_lua_path}/?/init.lua;{local_lua_path}/lib/?.lua;{local_lua_path}/lib/?/init.lua;{lua_path}");
|
||||
let clone = render.clone();
|
||||
self.loop_handle.insert_idle(|data| {
|
||||
schedule_on_commit(data, clone, |dt| {
|
||||
for win in do_not_render {
|
||||
dt.state.space.unmap_elem(&win);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let lua_cpath = std::env::var("LUA_CPATH").expect("Lua is not installed!");
|
||||
let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}");
|
||||
// let blocker = WindowBlockerAll::new(render.clone());
|
||||
// blocker.insert_into_loop(self);
|
||||
// for win in render.iter() {
|
||||
// compositor::add_blocker(win.toplevel().wl_surface(), blocker.clone());
|
||||
// }
|
||||
|
||||
std::process::Command::new("lua5.4")
|
||||
.arg(config_path)
|
||||
.env("LUA_PATH", new_lua_path)
|
||||
.env("LUA_CPATH", new_lua_cpath)
|
||||
.spawn()
|
||||
.expect("Could not start config process");
|
||||
} else {
|
||||
tracing::error!("Could not find {}", config_path);
|
||||
}
|
||||
// let (blocker, source) = WindowBlocker::block_all::<B>(render.clone());
|
||||
// for win in render.iter() {
|
||||
// compositor::add_blocker(win.toplevel().wl_surface(), blocker.clone());
|
||||
// }
|
||||
// self.loop_handle.insert_idle(move |data| source(render.clone(), data));
|
||||
|
||||
let display_handle = display.handle();
|
||||
let mut seat_state = SeatState::new();
|
||||
let mut seat = seat_state.new_wl_seat(&display_handle, backend_data.seat_name());
|
||||
seat.add_pointer();
|
||||
seat.add_keyboard(XkbConfig::default(), 200, 25)?;
|
||||
|
||||
Ok(Self {
|
||||
backend_data,
|
||||
loop_signal,
|
||||
loop_handle,
|
||||
clock: Clock::<Monotonic>::new()?,
|
||||
compositor_state: CompositorState::new::<Self>(&display_handle),
|
||||
data_device_state: DataDeviceState::new::<Self>(&display_handle),
|
||||
seat_state,
|
||||
pointer_location: (0.0, 0.0).into(),
|
||||
shm_state: ShmState::new::<Self>(&display_handle, vec![]),
|
||||
space: Space::<Window>::default(),
|
||||
cursor_status: CursorImageStatus::Default,
|
||||
output_manager_state: OutputManagerState::new_with_xdg_output::<Self>(&display_handle),
|
||||
xdg_shell_state: XdgShellState::new::<Self>(&display_handle),
|
||||
viewporter_state: ViewporterState::new::<Self>(&display_handle),
|
||||
fractional_scale_manager_state: FractionalScaleManagerState::new::<Self>(
|
||||
&display_handle,
|
||||
),
|
||||
input_state: InputState::new(),
|
||||
api_state: ApiState::new(),
|
||||
focus_state: FocusState::new(),
|
||||
tag_state: TagState::new(),
|
||||
|
||||
seat,
|
||||
|
||||
move_mode: false,
|
||||
socket_name: socket_name.to_string_lossy().to_string(),
|
||||
|
||||
popup_manager: PopupManager::default(),
|
||||
|
||||
async_scheduler: sched,
|
||||
|
||||
windows: vec![],
|
||||
})
|
||||
// let (blocker, source) = WindowBlocker::new::<B>(render.clone());
|
||||
// for win in render.iter() {
|
||||
// compositor::add_blocker(win.toplevel().wl_surface(), blocker.clone());
|
||||
// }
|
||||
// self.loop_handle.insert_idle(move |data| source(render.clone(), render.clone(), data));
|
||||
}
|
||||
}
|
||||
/// 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
|
||||
F: FnOnce(&mut CalloopData<B>) + 'static,
|
||||
{
|
||||
// tracing::debug!("scheduling on_commit");
|
||||
// tracing::debug!("win len is {}", windows.len());
|
||||
for window in windows.iter() {
|
||||
window.with_state(|state| {
|
||||
// tracing::debug!("win state is {:?}", state.resize_state);
|
||||
});
|
||||
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle))
|
||||
{
|
||||
// tracing::debug!("some windows not idle");
|
||||
data.state.loop_handle.insert_idle(|data| {
|
||||
schedule_on_commit(data, windows, on_commit);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
impl State<UdevData> {
|
||||
on_commit(data);
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
pub fn init(
|
||||
backend_data: UdevData,
|
||||
backend_data: B,
|
||||
display: &mut Display<Self>,
|
||||
loop_signal: LoopSignal,
|
||||
loop_handle: LoopHandle<'static, CalloopData<UdevData>>,
|
||||
loop_handle: LoopHandle<'static, CalloopData<B>>,
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
let socket = ListeningSocketSource::new_auto()?;
|
||||
let socket_name = socket.socket_name().to_os_string();
|
||||
|
@ -801,7 +904,6 @@ impl State<UdevData> {
|
|||
input_state: InputState::new(),
|
||||
api_state: ApiState::new(),
|
||||
focus_state: FocusState::new(),
|
||||
tag_state: TagState::new(),
|
||||
|
||||
seat,
|
||||
|
||||
|
@ -813,6 +915,7 @@ impl State<UdevData> {
|
|||
async_scheduler: sched,
|
||||
|
||||
windows: vec![],
|
||||
output_callback_ids: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -874,6 +977,7 @@ pub fn take_presentation_feedback(
|
|||
/// State containing the config API's stream.
|
||||
#[derive(Default)]
|
||||
pub struct ApiState {
|
||||
// TODO: this may not need to be in an arc mutex because of the move to async
|
||||
pub stream: Option<Arc<Mutex<UnixStream>>>,
|
||||
}
|
||||
|
||||
|
@ -882,3 +986,36 @@ impl ApiState {
|
|||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WithState {
|
||||
type State: Default;
|
||||
fn with_state<F, T>(&self, func: F) -> T
|
||||
where
|
||||
F: FnMut(&mut Self::State) -> T;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WlSurfaceState {
|
||||
pub resize_state: ResizeSurfaceState,
|
||||
}
|
||||
|
||||
impl WithState for WlSurface {
|
||||
type State = WlSurfaceState;
|
||||
|
||||
fn with_state<F, T>(&self, mut func: F) -> T
|
||||
where
|
||||
F: FnMut(&mut Self::State) -> T,
|
||||
{
|
||||
compositor::with_states(self, |states| {
|
||||
states
|
||||
.data_map
|
||||
.insert_if_missing(RefCell::<Self::State>::default);
|
||||
let state = states
|
||||
.data_map
|
||||
.get::<RefCell<Self::State>>()
|
||||
.expect("This should never happen");
|
||||
|
||||
func(&mut state.borrow_mut())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
109
src/tag.rs
109
src/tag.rs
|
@ -4,25 +4,102 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use smithay::desktop::Window;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
hash::Hash,
|
||||
rc::Rc,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TagId(String);
|
||||
use smithay::output::Output;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tag {
|
||||
pub id: TagId,
|
||||
pub windows: Vec<Window>,
|
||||
// TODO: layout
|
||||
}
|
||||
use crate::{
|
||||
backend::Backend,
|
||||
layout::Layout,
|
||||
state::{State, WithState},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TagState {
|
||||
pub tags: Vec<Tag>,
|
||||
}
|
||||
static TAG_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
impl TagState {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TagId(u32);
|
||||
|
||||
impl TagId {
|
||||
fn next() -> Self {
|
||||
Self(TAG_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TagInner {
|
||||
/// The internal id of this tag.
|
||||
id: TagId,
|
||||
/// The name of this tag.
|
||||
name: String,
|
||||
/// Whether this tag is active or not.
|
||||
active: bool,
|
||||
/// What layout this tag has.
|
||||
layout: Layout,
|
||||
}
|
||||
|
||||
impl PartialEq for TagInner {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for TagInner {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Tag(Rc<RefCell<TagInner>>);
|
||||
|
||||
impl Tag {
|
||||
pub fn id(&self) -> TagId {
|
||||
self.0.borrow().id
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
self.0.borrow().name.clone()
|
||||
}
|
||||
|
||||
pub fn active(&self) -> bool {
|
||||
self.0.borrow().active
|
||||
}
|
||||
|
||||
pub fn set_active(&self, active: bool) {
|
||||
self.0.borrow_mut().active = active;
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.0.borrow().layout
|
||||
}
|
||||
|
||||
pub fn set_layout(&self, layout: Layout) {
|
||||
self.0.borrow_mut().layout = layout;
|
||||
}
|
||||
}
|
||||
|
||||
impl Tag {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self(Rc::new(RefCell::new(TagInner {
|
||||
id: TagId::next(),
|
||||
name,
|
||||
active: false,
|
||||
layout: Layout::MasterStack, // TODO: get from config
|
||||
})))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TagProperties {
|
||||
pub id: TagId,
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
pub fn output_for_tag(&self, tag: &Tag) -> Option<Output> {
|
||||
self.space
|
||||
.outputs()
|
||||
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == tag)))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
|
135
src/window.rs
135
src/window.rs
|
@ -4,45 +4,29 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
use smithay::{
|
||||
desktop::Window,
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
wayland::{compositor, seat::WaylandFocus},
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||
wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
wayland::{
|
||||
compositor::{Blocker, BlockerState},
|
||||
seat::WaylandFocus,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
backend::Backend, layout::Layout, state::State, window::window_state::WindowResizeState,
|
||||
backend::Backend,
|
||||
state::{State, WithState},
|
||||
};
|
||||
|
||||
use self::window_state::{Float, WindowId, WindowState};
|
||||
use self::window_state::{Float, WindowId};
|
||||
|
||||
pub mod window_state;
|
||||
|
||||
// TODO: maybe get rid of this and move the fn into resize_surface state because it's the only user
|
||||
pub trait SurfaceState: Default + 'static {
|
||||
/// Access the [`SurfaceState`] associated with a [`WlSurface`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if you use it within itself due to the use of a [`RefCell`].
|
||||
fn with_state<F, T>(wl_surface: &WlSurface, function: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Self) -> T,
|
||||
{
|
||||
compositor::with_states(wl_surface, |states| {
|
||||
states.data_map.insert_if_missing(RefCell::<Self>::default);
|
||||
let state = states
|
||||
.data_map
|
||||
.get::<RefCell<Self>>()
|
||||
.expect("This should never happen");
|
||||
|
||||
function(&mut state.borrow_mut())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
/// Returns the [Window] associated with a given [WlSurface].
|
||||
pub fn window_for_surface(&self, surface: &WlSurface) -> Option<Window> {
|
||||
|
@ -57,43 +41,11 @@ impl<B: Backend> State<B> {
|
|||
.cloned()
|
||||
})
|
||||
}
|
||||
|
||||
/// Swap the positions and sizes of two windows.
|
||||
pub fn swap_window_positions(&mut self, win1: &Window, win2: &Window) {
|
||||
// FIXME: moving the mouse quickly will break swapping
|
||||
|
||||
let win1_loc = self.space.element_location(win1).unwrap(); // TODO: handle unwraps
|
||||
let win2_loc = self.space.element_location(win2).unwrap();
|
||||
let win1_geo = win1.geometry();
|
||||
let win2_geo = win2.geometry();
|
||||
// tracing::info!("win1: {:?}, {:?}", win1_loc, win1_geo);
|
||||
// tracing::info!("win2: {:?}, {:?}", win2_loc, win2_geo);
|
||||
|
||||
win1.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(win2_geo.size);
|
||||
});
|
||||
win2.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(win1_geo.size);
|
||||
});
|
||||
|
||||
let serial = win1.toplevel().send_configure();
|
||||
WindowState::with_state(win1, |state| {
|
||||
state.resize_state = WindowResizeState::WaitingForAck(serial, win2_loc);
|
||||
});
|
||||
|
||||
let serial = win2.toplevel().send_configure();
|
||||
WindowState::with_state(win2, |state| {
|
||||
state.resize_state = WindowResizeState::WaitingForAck(serial, win1_loc);
|
||||
});
|
||||
|
||||
// self.space.map_element(win1.clone(), win2_loc, false);
|
||||
// self.space.map_element(win2.clone(), win1_loc, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle a window's floating status.
|
||||
pub fn toggle_floating<B: Backend>(state: &mut State<B>, window: &Window) {
|
||||
WindowState::with_state(window, |window_state| {
|
||||
window.with_state(|window_state| {
|
||||
match window_state.floating {
|
||||
Float::Tiled(prev_loc_and_size) => {
|
||||
if let Some((prev_loc, prev_size)) = prev_loc_and_size {
|
||||
|
@ -103,10 +55,17 @@ pub fn toggle_floating<B: Backend>(state: &mut State<B>, window: &Window) {
|
|||
|
||||
window.toplevel().send_pending_configure();
|
||||
|
||||
state.space.map_element(window.clone(), prev_loc, false); // TODO: should it activate?
|
||||
state.space.map_element(window.clone(), prev_loc, false);
|
||||
// TODO: should it activate?
|
||||
}
|
||||
|
||||
window_state.floating = Float::Floating;
|
||||
window.toplevel().with_pending_state(|tl_state| {
|
||||
tl_state.states.unset(xdg_toplevel::State::TiledTop);
|
||||
tl_state.states.unset(xdg_toplevel::State::TiledBottom);
|
||||
tl_state.states.unset(xdg_toplevel::State::TiledLeft);
|
||||
tl_state.states.unset(xdg_toplevel::State::TiledRight);
|
||||
});
|
||||
}
|
||||
Float::Floating => {
|
||||
window_state.floating = Float::Tiled(Some((
|
||||
|
@ -115,13 +74,46 @@ pub fn toggle_floating<B: Backend>(state: &mut State<B>, window: &Window) {
|
|||
state.space.element_location(window).unwrap(),
|
||||
window.geometry().size,
|
||||
)));
|
||||
window.toplevel().with_pending_state(|tl_state| {
|
||||
tl_state.states.set(xdg_toplevel::State::TiledTop);
|
||||
tl_state.states.set(xdg_toplevel::State::TiledBottom);
|
||||
tl_state.states.set(xdg_toplevel::State::TiledLeft);
|
||||
tl_state.states.set(xdg_toplevel::State::TiledRight);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let windows = state.space.elements().cloned().collect::<Vec<_>>();
|
||||
Layout::master_stack(state, windows, crate::layout::Direction::Left);
|
||||
state.space.raise_element(window, true);
|
||||
let output = state.focus_state.focused_output.clone().unwrap();
|
||||
state.re_layout(&output);
|
||||
|
||||
let render = output.with_state(|op_state| {
|
||||
state
|
||||
.windows
|
||||
.iter()
|
||||
.cloned()
|
||||
.filter(|win| {
|
||||
win.with_state(|win_state| {
|
||||
if win_state.floating.is_floating() {
|
||||
return true;
|
||||
}
|
||||
for tag in win_state.tags.iter() {
|
||||
if op_state.focused_tags().any(|tg| tg == tag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let clone = window.clone();
|
||||
state.loop_handle.insert_idle(move |data| {
|
||||
crate::state::schedule_on_commit(data, render, move |dt| {
|
||||
dt.state.space.raise_element(&clone, true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -135,3 +127,16 @@ pub struct WindowProperties {
|
|||
pub location: (i32, i32),
|
||||
pub floating: bool,
|
||||
}
|
||||
|
||||
pub struct WindowBlocker;
|
||||
pub static BLOCKER_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
impl Blocker for WindowBlocker {
|
||||
fn state(&self) -> BlockerState {
|
||||
if BLOCKER_COUNTER.load(std::sync::atomic::Ordering::SeqCst) > 0 {
|
||||
BlockerState::Pending
|
||||
} else {
|
||||
BlockerState::Released
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
|
@ -14,9 +15,9 @@ use smithay::{
|
|||
utils::{Logical, Point, Serial, Size},
|
||||
};
|
||||
|
||||
use crate::tag::{Tag, TagId, TagState};
|
||||
use crate::{state::WithState, tag::Tag};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct WindowId(u32);
|
||||
|
||||
// TODO: this probably doesn't need to be atomic
|
||||
|
@ -36,16 +37,7 @@ pub struct WindowState {
|
|||
/// The window's resize state. See [WindowResizeState] for more.
|
||||
pub resize_state: WindowResizeState,
|
||||
/// What tags the window is currently on.
|
||||
pub tags: Vec<TagId>,
|
||||
}
|
||||
|
||||
/// Returns a vec of references to all the tags the window is on.
|
||||
pub fn tags<'a>(tag_state: &'a TagState, window: &Window) -> Vec<&'a Tag> {
|
||||
tag_state
|
||||
.tags
|
||||
.iter()
|
||||
.filter(|&tag| WindowState::with_state(window, |state| state.tags.contains(&tag.id)))
|
||||
.collect()
|
||||
pub tags: Vec<Tag>,
|
||||
}
|
||||
|
||||
/// The state of a window's resize operation.
|
||||
|
@ -55,36 +47,46 @@ pub fn tags<'a>(tag_state: &'a TagState, window: &Window) -> Vec<&'a Tag> {
|
|||
/// sending a configure event. However, the client will probably not acknowledge the configure
|
||||
/// until *after* the window has moved, causing flickering.
|
||||
///
|
||||
/// To solve this, we need to create two additional steps: [`WaitingForAck`] and [`WaitingForCommit`].
|
||||
/// To solve this, we need to create two additional steps: [`Requested`] and [`Acknowledged`].
|
||||
/// If we need to change a window's location when we change its size, instead of
|
||||
/// calling `map_element()`, we change the window's [`WindowState`] and set
|
||||
/// its [`resize_state`] to `WaitingForAck` with the new position we want.
|
||||
/// its [`resize_state`] to `Requested` with the new position we want.
|
||||
///
|
||||
/// When the client acks the configure, we can move the state to `WaitingForCommit` in
|
||||
/// When the client acks the configure, we can move the state to `Acknowledged` in
|
||||
/// [`XdgShellHandler.ack_configure()`]. Finally, in [`CompositorHandler.commit()`], we set the
|
||||
/// state back to [`Idle`] and map the window.
|
||||
///
|
||||
/// [`space.map_element()`]: smithay::desktop::space::Space#method.map_element
|
||||
/// [`with_pending_state()`]: smithay::wayland::shell::xdg::ToplevelSurface#method.with_pending_state
|
||||
/// [`Idle`]: WindowResizeState::Idle
|
||||
/// [`WaitingForAck`]: WindowResizeState::WaitingForAck
|
||||
/// [`WaitingForCommit`]: WindowResizeState::WaitingForCommit
|
||||
/// [`Requested`]: WindowResizeState::Requested
|
||||
/// [`Acknowledged`]: WindowResizeState::Acknowledged
|
||||
/// [`resize_state`]: WindowState#structfield.resize_state
|
||||
/// [`XdgShellHandler.ack_configure()`]: smithay::wayland::shell::xdg::XdgShellHandler#method.ack_configure
|
||||
/// [`CompositorHandler.commit()`]: smithay::wayland::compositor::CompositorHandler#tymethod.commit
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub enum WindowResizeState {
|
||||
/// The window doesn't need to be moved.
|
||||
#[default]
|
||||
Idle,
|
||||
/// The window has received a configure request with a new size. The desired location and the
|
||||
/// configure request's serial should be provided here.
|
||||
WaitingForAck(Serial, Point<i32, Logical>),
|
||||
Requested(Serial, Point<i32, Logical>),
|
||||
/// The client has received the configure request and has successfully changed its size. It's
|
||||
/// now safe to move the window in [`CompositorHandler.commit()`] without flickering.
|
||||
///
|
||||
/// [`CompositorHandler.commit()`]: smithay::wayland::compositor::CompositorHandler#tymethod.commit
|
||||
WaitingForCommit(Point<i32, Logical>),
|
||||
Acknowledged(Point<i32, Logical>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for WindowResizeState {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Idle => write!(f, "Idle"),
|
||||
Self::Requested(_arg0, _arg1) => write!(f, "Requested"),
|
||||
Self::Acknowledged(_arg0) => write!(f, "Acknowledged"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Float {
|
||||
|
@ -116,20 +118,23 @@ impl WindowState {
|
|||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a [Window]'s state, optionally returning something.
|
||||
pub fn with_state<F, T>(window: &Window, mut func: F) -> T
|
||||
impl WithState for Window {
|
||||
type State = WindowState;
|
||||
|
||||
fn with_state<F, T>(&self, mut func: F) -> T
|
||||
where
|
||||
F: FnMut(&mut Self) -> T,
|
||||
F: FnMut(&mut Self::State) -> T,
|
||||
{
|
||||
window
|
||||
.user_data()
|
||||
.insert_if_missing(RefCell::<Self>::default);
|
||||
self.user_data()
|
||||
.insert_if_missing(RefCell::<Self::State>::default);
|
||||
|
||||
let state = window
|
||||
let state = self
|
||||
.user_data()
|
||||
.get::<RefCell<Self>>()
|
||||
.expect("This should never happen");
|
||||
.get::<RefCell<Self::State>>()
|
||||
.expect("RefCell not in data map");
|
||||
|
||||
func(&mut state.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use smithay::{
|
|||
use crate::{
|
||||
backend::Backend,
|
||||
grab::{move_grab::MoveSurfaceGrab, resize_grab::ResizeSurfaceGrab},
|
||||
state::State,
|
||||
state::{State, WithState},
|
||||
};
|
||||
|
||||
pub fn move_request<B: Backend>(
|
||||
|
@ -94,6 +94,9 @@ pub fn resize_request<B: Backend>(
|
|||
if let Some(start_data) = crate::pointer::pointer_grab_start_data(&pointer, wl_surface, serial)
|
||||
{
|
||||
let window = state.window_for_surface(wl_surface).unwrap();
|
||||
if window.with_state(|state| state.floating.is_tiled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let initial_window_loc = state.space.element_location(&window).unwrap();
|
||||
let initial_window_size = window.geometry().size;
|
||||
|
@ -124,13 +127,16 @@ pub fn resize_request_force<B: Backend>(
|
|||
edges: xdg_toplevel::ResizeEdge,
|
||||
button_used: u32,
|
||||
) {
|
||||
println!("resize_request_force started with edges {:?}", edges);
|
||||
let wl_surface = surface.wl_surface();
|
||||
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
let window = state.window_for_surface(wl_surface).unwrap();
|
||||
|
||||
if window.with_state(|state| state.floating.is_tiled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let initial_window_loc = state.space.element_location(&window).unwrap();
|
||||
let initial_window_size = window.geometry().size;
|
||||
|
||||
|
|
Loading…
Reference in a new issue