mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
commit
d813d429bf
22 changed files with 1044 additions and 419 deletions
28
README.md
28
README.md
|
@ -11,12 +11,24 @@
|
|||
A very, VERY WIP Smithay-based wayland compositor
|
||||
</div>
|
||||
|
||||
## Features
|
||||
- [x] Winit backend
|
||||
- [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
|
||||
- [ ] Server-side decorations
|
||||
- [ ] The other stuff Awesome has
|
||||
- [x] Is very cool :thumbsup:
|
||||
|
||||
## Info
|
||||
### Why Pinnacle?
|
||||
Well, I currently use [Awesome](https://github.com/awesomeWM/awesome). And I really like it! Unfortunately, Awesome doesn't exist for Wayland ([anymore](http://way-cooler.org/blog/2020/01/09/way-cooler-post-mortem.html)). There doesn't seem to be any Wayland compositor out there that has all of the following:
|
||||
- Tags for window management
|
||||
- Configurable in Lua (or any other programming language for that matter)
|
||||
- Has a bunch of batteries included (widget system, systray, etc)
|
||||
- Tags for window management
|
||||
- Configurable in Lua (or any other programming language for that matter)
|
||||
- Has a bunch of batteries included (widget system, systray, etc)
|
||||
|
||||
So, this is my attempt at making an Awesome-esque Wayland compositor.
|
||||
|
||||
|
@ -52,13 +64,13 @@ cargo run [--release] -- --<backend>
|
|||
|
||||
`backend` can be one of two values:
|
||||
|
||||
- `winit`: run Pinnacle as a window in your graphical environment
|
||||
- `udev`: run Pinnacle in a tty. NOTE: I tried running udev in Awesome and some things broke so uh, don't do that
|
||||
- `winit`: run Pinnacle as a window in your graphical environment
|
||||
- `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.
|
||||
|
||||
Pinnacle supports configuration through Lua (and hopefully more languages if I architect it correctly :crab:).
|
||||
Pinnacle supports configuration through Lua (and hopefully more languages if it's not too unwieldy :crab:).
|
||||
|
||||
Run Pinnacle with the `PINNACLE_CONFIG` environment variable set to the path of your config file. If not specified, Pinnacle will look for the following:
|
||||
```
|
||||
|
@ -93,7 +105,7 @@ Doc website soon:tm:
|
|||
## Controls
|
||||
The following controls are currently hardcoded:
|
||||
|
||||
- `Ctrl + Left Mouse`: Move a window
|
||||
- `Ctrl + Right Mouse`: Resize a window
|
||||
- `Ctrl + Left Mouse`: Move a window
|
||||
- `Ctrl + Right Mouse`: Resize a window
|
||||
|
||||
You can find the rest of the controls in the [`example_config`](api/lua/example_config.lua).
|
||||
|
|
|
@ -12,33 +12,108 @@ pcall(require, "luarocks.loader")
|
|||
-- Neovim users be like:
|
||||
require("pinnacle").setup(function(pinnacle)
|
||||
local input = pinnacle.input -- Key and mouse binds
|
||||
local client = pinnacle.client -- Window management
|
||||
local window = pinnacle.window -- Window management
|
||||
local process = pinnacle.process -- Process spawning
|
||||
local tag = pinnacle.tag -- Tag management
|
||||
|
||||
-- Every key supported by xkbcommon.
|
||||
-- Support for just putting in a string of a key is intended.
|
||||
local keys = input.keys
|
||||
|
||||
---@type Modifier
|
||||
local mod_key = "Ctrl" -- This is set to `Ctrl` instead of `Super` to not conflict with your WM/DE keybinds
|
||||
-- ^ Add type annotations for that sweet, sweet autocomplete
|
||||
|
||||
local terminal = "alacritty"
|
||||
|
||||
-- Keybinds ----------------------------------------------------------------------
|
||||
input.keybind({ "Ctrl", "Alt" }, keys.q, pinnacle.quit)
|
||||
input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)
|
||||
|
||||
input.keybind({ "Ctrl", "Alt" }, keys.c, client.close_window)
|
||||
input.keybind({ mod_key, "Alt" }, keys.c, window.close_window)
|
||||
|
||||
input.keybind({ "Ctrl", "Alt" }, keys.space, client.toggle_floating)
|
||||
input.keybind({ mod_key, "Alt" }, keys.space, window.toggle_floating)
|
||||
|
||||
input.keybind({ "Ctrl" }, keys.Return, function()
|
||||
process.spawn("alacritty", function(stdout, stderr, exit_code, exit_msg)
|
||||
-- do something with the output here; remember to check for nil!
|
||||
input.keybind({ mod_key }, keys.Return, function()
|
||||
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
|
||||
-- do something with the output here
|
||||
end)
|
||||
end)
|
||||
|
||||
input.keybind({ "Ctrl" }, keys.KEY_1, function()
|
||||
input.keybind({ mod_key }, keys.l, function()
|
||||
process.spawn("kitty")
|
||||
end)
|
||||
input.keybind({ "Ctrl" }, keys.KEY_2, function()
|
||||
input.keybind({ mod_key }, keys.k, function()
|
||||
process.spawn("foot")
|
||||
end)
|
||||
input.keybind({ "Ctrl" }, keys.KEY_3, function()
|
||||
input.keybind({ mod_key }, keys.j, function()
|
||||
process.spawn("nautilus")
|
||||
end)
|
||||
|
||||
-- Tags ---------------------------------------------------------------------------
|
||||
tag.add("1", "2", "3", "4", "5")
|
||||
tag.toggle("1")
|
||||
|
||||
input.keybind({ mod_key }, keys.KEY_1, function()
|
||||
tag.switch_to("1")
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_2, function()
|
||||
tag.switch_to("2")
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_3, function()
|
||||
tag.switch_to("3")
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_4, function()
|
||||
tag.switch_to("4")
|
||||
end)
|
||||
input.keybind({ mod_key }, keys.KEY_5, function()
|
||||
tag.switch_to("5")
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_1, function()
|
||||
tag.toggle("1")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_2, function()
|
||||
tag.toggle("2")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_3, function()
|
||||
tag.toggle("3")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_4, function()
|
||||
tag.toggle("4")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift" }, keys.KEY_5, function()
|
||||
tag.toggle("5")
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_1, function()
|
||||
window.get_focused():move_to_tag("1")
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_2, function()
|
||||
window.get_focused():move_to_tag("2")
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_3, function()
|
||||
window.get_focused():move_to_tag("3")
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_4, function()
|
||||
window.get_focused():move_to_tag("4")
|
||||
end)
|
||||
input.keybind({ mod_key, "Alt" }, keys.KEY_5, function()
|
||||
window.get_focused():move_to_tag("5")
|
||||
end)
|
||||
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_1, function()
|
||||
window.get_focused():toggle_tag("1")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_2, function()
|
||||
window.get_focused():toggle_tag("2")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_3, function()
|
||||
window.get_focused():toggle_tag("3")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_4, function()
|
||||
window.get_focused():toggle_tag("4")
|
||||
end)
|
||||
input.keybind({ mod_key, "Shift", "Alt" }, keys.KEY_5, function()
|
||||
window.get_focused():toggle_tag("5")
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -8,9 +8,9 @@ local input = {
|
|||
keys = require("keys"),
|
||||
}
|
||||
|
||||
---Set a keybind. If called on an already existing keybind, it gets replaced.
|
||||
---@param key Keys The key for the keybind. NOTE: uppercase and lowercase characters are considered different.
|
||||
---@param modifiers Modifiers[] Which modifiers need to be pressed for the keybind to trigger.
|
||||
---Set a keybind. If called with an already existing keybind, it gets replaced.
|
||||
---@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.
|
||||
function input.keybind(modifiers, key, action)
|
||||
table.insert(CallbackTable, action)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
--
|
||||
-- SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
---@alias Modifiers "Alt" | "Ctrl" | "Shift" | "Super"
|
||||
---@alias Modifier "Alt" | "Ctrl" | "Shift" | "Super"
|
||||
|
||||
---@enum Keys
|
||||
local M = {
|
||||
|
|
|
@ -9,11 +9,20 @@
|
|||
---@class _Msg
|
||||
---@field SetKeybind { key: Keys, modifiers: Modifiers[], callback_id: integer }
|
||||
---@field SetMousebind { button: integer }
|
||||
--Windows
|
||||
---@field CloseWindow { client_id: integer? }
|
||||
---@field ToggleFloating { client_id: integer? }
|
||||
---@field SetWindowSize { window_id: integer, size: { w: integer, h: integer } }
|
||||
---@field MoveWindowToTag { window_id: integer, tag_id: string }
|
||||
---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
|
||||
--
|
||||
---@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[] }
|
||||
|
||||
---@alias Msg _Msg | "Quit"
|
||||
|
||||
|
|
|
@ -51,9 +51,11 @@ local pinnacle = {
|
|||
---Key and mouse binds
|
||||
input = require("input"),
|
||||
---Window management
|
||||
client = require("client"),
|
||||
window = require("window"),
|
||||
---Process spawning
|
||||
process = require("process"),
|
||||
---Tag management
|
||||
tag = require("tag"),
|
||||
}
|
||||
|
||||
---Quit Pinnacle.
|
||||
|
|
60
api/lua/tag.lua
Normal file
60
api/lua/tag.lua
Normal file
|
@ -0,0 +1,60 @@
|
|||
-- 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
|
||||
|
||||
local tag = {}
|
||||
|
||||
---Add tags.
|
||||
---
|
||||
---If you need to add the strings in a table, use `tag.add_table` instead.
|
||||
---
|
||||
---# Example
|
||||
---
|
||||
---```lua
|
||||
---tag.add("1", "2", "3", "4", "5") -- Add tags with names 1-5
|
||||
---```
|
||||
---@param ... string The names of the new tags you want to add.
|
||||
function tag.add(...)
|
||||
local tags = table.pack(...)
|
||||
tags["n"] = nil
|
||||
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
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)
|
||||
SendMsg({
|
||||
AddTags = {
|
||||
tags = tags,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Toggle a tag's display.
|
||||
---@param name string The name of the tag.
|
||||
function tag.toggle(name)
|
||||
SendMsg({
|
||||
ToggleTag = {
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Switch to a tag, deactivating any other active tags.
|
||||
---@param name string The name of the tag.
|
||||
function tag.switch_to(name)
|
||||
SendMsg({
|
||||
SwitchToTag = {
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
return tag
|
|
@ -11,13 +11,13 @@
|
|||
---@field private size { w: integer, h: integer } The size of the window
|
||||
---@field private location { x: integer, y: integer } The location of the window
|
||||
---@field private floating boolean Whether the window is floating or not (tiled)
|
||||
local window = {}
|
||||
local win = {}
|
||||
|
||||
---@param props { id: integer, app_id: string?, title: string?, size: { w: integer, h: integer }, location: { x: integer, y: integer }, floating: boolean }
|
||||
---@return Window
|
||||
local function new_window(props)
|
||||
-- Copy functions over
|
||||
for k, v in pairs(window) do
|
||||
for k, v in pairs(win) do
|
||||
props[k] = v
|
||||
end
|
||||
|
||||
|
@ -26,7 +26,7 @@ end
|
|||
|
||||
---Set a window's size.
|
||||
---@param size { w: integer?, h: integer? }
|
||||
function window:set_size(size)
|
||||
function win:set_size(size)
|
||||
self.size = {
|
||||
w = size.w or self.size.w,
|
||||
h = size.h or self.size.h,
|
||||
|
@ -39,19 +39,41 @@ function window:set_size(size)
|
|||
})
|
||||
end
|
||||
|
||||
---Move a window to a tag, removing all other ones.
|
||||
---@param name string The name of the tag.
|
||||
function win:move_to_tag(name)
|
||||
SendMsg({
|
||||
MoveWindowToTag = {
|
||||
window_id = self.id,
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Toggle the specified tag for this window.
|
||||
---@param name string The name of the tag.
|
||||
function win:toggle_tag(name)
|
||||
SendMsg({
|
||||
ToggleTagOnWindow = {
|
||||
window_id = self.id,
|
||||
tag_id = name,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
---Get a window's size.
|
||||
---@return { w: integer, h: integer }
|
||||
function window:get_size()
|
||||
function win:get_size()
|
||||
return self.size
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------
|
||||
|
||||
local client = {}
|
||||
local window = {}
|
||||
|
||||
---Close a window.
|
||||
---@param client_id integer? The id of the window you want closed, or nil to close the currently focused window, if any.
|
||||
function client.close_window(client_id)
|
||||
function window.close_window(client_id)
|
||||
SendMsg({
|
||||
CloseWindow = {
|
||||
client_id = client_id,
|
||||
|
@ -61,7 +83,7 @@ end
|
|||
|
||||
---Toggle a window's floating status.
|
||||
---@param client_id integer? The id of the window you want to toggle, or nil to toggle the currently focused window, if any.
|
||||
function client.toggle_floating(client_id)
|
||||
function window.toggle_floating(client_id)
|
||||
SendMsg({
|
||||
ToggleFloating = {
|
||||
client_id = client_id,
|
||||
|
@ -69,39 +91,25 @@ function client.toggle_floating(client_id)
|
|||
})
|
||||
end
|
||||
|
||||
---Get a window.
|
||||
---@param identifier { app_id: string } | { title: string } | "focus" A table with either the key app_id or title, depending if you want to get the window via its app_id or title, OR the string "focus" to get the currently focused window.
|
||||
---@return Window
|
||||
function client.get_window(identifier)
|
||||
---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()
|
||||
if type(identifier) == "string" then
|
||||
SendRequest({
|
||||
GetWindowByFocus = {
|
||||
id = req_id,
|
||||
},
|
||||
})
|
||||
elseif identifier.app_id then
|
||||
SendRequest({
|
||||
GetWindowByAppId = {
|
||||
id = req_id,
|
||||
app_id = identifier.app_id,
|
||||
},
|
||||
})
|
||||
else
|
||||
SendRequest({
|
||||
GetWindowByTitle = {
|
||||
id = req_id,
|
||||
title = identifier.title,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
SendRequest({
|
||||
GetWindowByAppId = {
|
||||
id = req_id,
|
||||
app_id = app_id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local props = response.RequestResponse.response.Window.window
|
||||
|
||||
---@type Window
|
||||
local win = {
|
||||
local wind = {
|
||||
id = props.id,
|
||||
app_id = props.app_id or "",
|
||||
title = props.title or "",
|
||||
|
@ -116,12 +124,82 @@ function client.get_window(identifier)
|
|||
floating = props.floating,
|
||||
}
|
||||
|
||||
return new_window(win)
|
||||
return new_window(wind)
|
||||
end
|
||||
|
||||
---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,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local props = response.RequestResponse.response.Window.window
|
||||
|
||||
---@type Window
|
||||
local wind = {
|
||||
id = props.id,
|
||||
app_id = props.app_id or "",
|
||||
title = props.title or "",
|
||||
size = {
|
||||
w = props.size[1],
|
||||
h = props.size[2],
|
||||
},
|
||||
location = {
|
||||
x = props.location[1],
|
||||
y = props.location[2],
|
||||
},
|
||||
floating = props.floating,
|
||||
}
|
||||
|
||||
return new_window(wind)
|
||||
end
|
||||
|
||||
---Get the currently focused window.
|
||||
---@return Window
|
||||
function window.get_focused()
|
||||
local req_id = Requests:next()
|
||||
|
||||
SendRequest({
|
||||
GetWindowByFocus = {
|
||||
id = req_id,
|
||||
},
|
||||
})
|
||||
|
||||
local response = ReadMsg()
|
||||
|
||||
local props = response.RequestResponse.response.Window.window
|
||||
|
||||
---@type Window
|
||||
local wind = {
|
||||
id = props.id,
|
||||
app_id = props.app_id or "",
|
||||
title = props.title or "",
|
||||
size = {
|
||||
w = props.size[1],
|
||||
h = props.size[2],
|
||||
},
|
||||
location = {
|
||||
x = props.location[1],
|
||||
y = props.location[2],
|
||||
},
|
||||
floating = props.floating,
|
||||
}
|
||||
|
||||
return new_window(wind)
|
||||
end
|
||||
|
||||
---Get all windows.
|
||||
---@return Window[]
|
||||
function client.get_windows()
|
||||
function window.get_windows()
|
||||
SendRequest({
|
||||
GetAllWindows = {
|
||||
id = Requests:next(),
|
||||
|
@ -152,6 +230,4 @@ function client.get_windows()
|
|||
return windows
|
||||
end
|
||||
|
||||
-- local win = client.get_window("focus")
|
||||
|
||||
return client
|
||||
return window
|
|
@ -7,7 +7,10 @@
|
|||
// The MessagePack format for these is a one-element map where the element's key is the enum name and its
|
||||
// value is a map of the enum's values
|
||||
|
||||
use crate::window::{tag::Tag, window_state::WindowId, WindowProperties};
|
||||
use crate::{
|
||||
tag::TagId,
|
||||
window::{window_state::WindowId, WindowProperties},
|
||||
};
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
|
||||
pub struct CallbackId(pub u32);
|
||||
|
@ -33,16 +36,32 @@ pub enum Msg {
|
|||
#[serde(default)]
|
||||
client_id: Option<u32>,
|
||||
},
|
||||
MoveToTag {
|
||||
tag: Tag,
|
||||
},
|
||||
ToggleTag {
|
||||
tag: Tag,
|
||||
},
|
||||
SetWindowSize {
|
||||
window_id: WindowId,
|
||||
size: (i32, i32),
|
||||
},
|
||||
MoveWindowToTag {
|
||||
window_id: WindowId,
|
||||
tag_id: TagId,
|
||||
},
|
||||
ToggleTagOnWindow {
|
||||
window_id: WindowId,
|
||||
tag_id: TagId,
|
||||
},
|
||||
|
||||
// Tag management
|
||||
ToggleTag {
|
||||
tag_id: TagId,
|
||||
},
|
||||
SwitchToTag {
|
||||
tag_id: TagId,
|
||||
},
|
||||
AddTags {
|
||||
tags: Vec<TagId>,
|
||||
},
|
||||
RemoveTags {
|
||||
tags: Vec<TagId>,
|
||||
},
|
||||
|
||||
// Process management
|
||||
/// Spawn a program with an optional callback.
|
||||
|
|
|
@ -225,7 +225,13 @@ pub fn run_udev() -> Result<(), Box<dyn Error>> {
|
|||
pointer_images: Vec::new(),
|
||||
pointer_element: PointerElement::default(),
|
||||
};
|
||||
let mut state = State::init(
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
let mut state = State::<UdevData>::init(
|
||||
data,
|
||||
&mut display,
|
||||
event_loop.get_signal(),
|
||||
|
@ -236,6 +242,39 @@ pub fn run_udev() -> Result<(), Box<dyn Error>> {
|
|||
* Initialize the udev backend
|
||||
*/
|
||||
let udev_backend = UdevBackend::new(state.seat.name())?;
|
||||
|
||||
for (device_id, path) in udev_backend.device_list() {
|
||||
if let Err(err) = DrmNode::from_dev_id(device_id)
|
||||
.map_err(DeviceAddError::DrmNode)
|
||||
.and_then(|node| state.device_added(node, path))
|
||||
{
|
||||
tracing::error!("Skipping device {device_id}: {err}");
|
||||
}
|
||||
}
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(udev_backend, move |event, _, data| match event {
|
||||
UdevEvent::Added { device_id, path } => {
|
||||
if let Err(err) = DrmNode::from_dev_id(device_id)
|
||||
.map_err(DeviceAddError::DrmNode)
|
||||
.and_then(|node| data.state.device_added(node, &path))
|
||||
{
|
||||
tracing::error!("Skipping device {device_id}: {err}");
|
||||
}
|
||||
}
|
||||
UdevEvent::Changed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
data.state.device_changed(node)
|
||||
}
|
||||
}
|
||||
UdevEvent::Removed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
data.state.device_removed(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
/*
|
||||
* Initialize libinput backend
|
||||
*/
|
||||
|
@ -299,14 +338,6 @@ pub fn run_udev() -> Result<(), Box<dyn Error>> {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
for (device_id, path) in udev_backend.device_list() {
|
||||
if let Err(err) = DrmNode::from_dev_id(device_id)
|
||||
.map_err(DeviceAddError::DrmNode)
|
||||
.and_then(|node| state.device_added(node, path))
|
||||
{
|
||||
tracing::error!("Skipping device {device_id}: {err}");
|
||||
}
|
||||
}
|
||||
state.shm_state.update_formats(
|
||||
state
|
||||
.backend_data
|
||||
|
@ -422,30 +453,6 @@ pub fn run_udev() -> Result<(), Box<dyn Error>> {
|
|||
});
|
||||
});
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(udev_backend, move |event, _, data| match event {
|
||||
UdevEvent::Added { device_id, path } => {
|
||||
if let Err(err) = DrmNode::from_dev_id(device_id)
|
||||
.map_err(DeviceAddError::DrmNode)
|
||||
.and_then(|node| data.state.device_added(node, &path))
|
||||
{
|
||||
tracing::error!("Skipping device {device_id}: {err}");
|
||||
}
|
||||
}
|
||||
UdevEvent::Changed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
data.state.device_changed(node)
|
||||
}
|
||||
}
|
||||
UdevEvent::Removed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
data.state.device_removed(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(
|
||||
Some(Duration::from_millis(6)),
|
||||
&mut CalloopData { state, display },
|
||||
|
@ -829,6 +836,8 @@ impl State<UdevData> {
|
|||
);
|
||||
let global = output.create_global::<State<UdevData>>(&self.backend_data.display_handle);
|
||||
|
||||
self.focus_state.focused_output = Some(output.clone());
|
||||
|
||||
let x = self.space.outputs().fold(0, |acc, o| {
|
||||
acc + self.space.output_geometry(o).unwrap().size.w
|
||||
});
|
||||
|
|
|
@ -177,7 +177,7 @@ pub fn run_winit() -> Result<(), Box<dyn Error>> {
|
|||
}
|
||||
};
|
||||
|
||||
let mut state = State::init(
|
||||
let mut state = State::<WinitData>::init(
|
||||
WinitData {
|
||||
backend: winit_backend,
|
||||
damage_tracker: OutputDamageTracker::from_output(&output),
|
||||
|
@ -387,7 +387,9 @@ pub fn run_winit() -> Result<(), Box<dyn Error>> {
|
|||
event_loop.run(
|
||||
Some(Duration::from_millis(6)),
|
||||
&mut CalloopData { display, state },
|
||||
|_data| {},
|
||||
|_data| {
|
||||
// println!("{}", _data.state.space.elements().count());
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -17,6 +17,7 @@ impl FocusState {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
/// Get the currently focused window. If there is none, the previous focus is returned.
|
||||
pub fn current_focus(&mut self) -> Option<Window> {
|
||||
while let Some(window) = self.focus_stack.last() {
|
||||
if window.alive() {
|
||||
|
@ -27,6 +28,7 @@ impl FocusState {
|
|||
None
|
||||
}
|
||||
|
||||
/// Set the currently focused window.
|
||||
pub fn set_focus(&mut self, window: Window) {
|
||||
self.focus_stack.retain(|win| win != &window);
|
||||
self.focus_stack.push(window);
|
||||
|
|
|
@ -48,6 +48,7 @@ use smithay::{
|
|||
use crate::{
|
||||
backend::Backend,
|
||||
layout::Layout,
|
||||
output::OutputState,
|
||||
state::{ClientState, State},
|
||||
window::window_state::{WindowResizeState, WindowState},
|
||||
};
|
||||
|
@ -116,11 +117,9 @@ impl<B: Backend> CompositorHandler for State<B> {
|
|||
if let Some(window) = self.window_for_surface(surface) {
|
||||
WindowState::with_state(&window, |state| {
|
||||
if let WindowResizeState::WaitingForCommit(new_pos) = state.resize_state {
|
||||
// tracing::info!("Committing, new location");
|
||||
state.resize_state = WindowResizeState::Idle;
|
||||
self.space.map_element(window.clone(), new_pos, false);
|
||||
}
|
||||
// state.resize_state
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -224,6 +223,24 @@ 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![]
|
||||
};
|
||||
tracing::debug!("new window, tags are {:?}", state.tags);
|
||||
});
|
||||
|
||||
self.windows.push(window.clone());
|
||||
self.space.map_element(window.clone(), (0, 0), true);
|
||||
self.loop_handle.insert_idle(move |data| {
|
||||
data.state
|
||||
|
@ -236,6 +253,7 @@ 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| {
|
||||
|
@ -245,6 +263,8 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
}
|
||||
|
||||
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);
|
||||
|
@ -345,15 +365,15 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
}
|
||||
|
||||
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
||||
// TODO: add serial to WaitingForAck
|
||||
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 {
|
||||
match &configure {
|
||||
Configure::Toplevel(configure) => {
|
||||
// tracing::info!("acking before serial check");
|
||||
if configure.serial >= serial {
|
||||
// tracing::info!("acking, serial >=");
|
||||
tracing::debug!("acked configure, new loc is {:?}", new_loc);
|
||||
state.resize_state = WindowResizeState::WaitingForCommit(new_loc);
|
||||
}
|
||||
}
|
||||
|
@ -361,9 +381,31 @@ impl<B: Backend> XdgShellHandler for State<B> {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 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) {
|
||||
tracing::debug!("remapping window");
|
||||
WindowState::with_state(&window, |state| {
|
||||
if let WindowResizeState::WaitingForCommit(new_loc) = state.resize_state {
|
||||
self.space.map_element(window.clone(), new_loc, false);
|
||||
state.resize_state = WindowResizeState::Idle;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fn minimize_request(&mut self, surface: ToplevelSurface) {
|
||||
// if let Some(window) = self.window_for_surface(surface.wl_surface()) {
|
||||
// self.space.unmap_elem(&window);
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: impl the rest of the fns in XdgShellHandler
|
||||
}
|
||||
delegate_xdg_shell!(@<B: Backend> State<B>);
|
||||
|
|
16
src/input.rs
16
src/input.rs
|
@ -234,12 +234,23 @@ impl<B: Backend> State<B> {
|
|||
if modifiers.logo {
|
||||
modifier_mask.push(Modifiers::Super);
|
||||
}
|
||||
let raw_sym = if keysym.raw_syms().len() == 1 {
|
||||
keysym.raw_syms()[0]
|
||||
} else {
|
||||
keysyms::KEY_NoSymbol
|
||||
};
|
||||
if let Some(callback_id) = state
|
||||
.input_state
|
||||
.keybinds
|
||||
.get(&(modifier_mask.into(), keysym.modified_sym()))
|
||||
.get(&(modifier_mask.into(), raw_sym))
|
||||
{
|
||||
return FilterResult::Intercept(*callback_id);
|
||||
} else if modifiers.ctrl
|
||||
&& modifiers.shift
|
||||
&& modifiers.alt
|
||||
&& keysym.modified_sym() == keysyms::KEY_Escape
|
||||
{
|
||||
return FilterResult::Intercept(CallbackId(999999));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,6 +273,9 @@ impl<B: Backend> State<B> {
|
|||
self.move_mode = move_mode;
|
||||
|
||||
if let Some(callback_id) = action {
|
||||
if callback_id.0 == 999999 {
|
||||
self.loop_signal.stop();
|
||||
}
|
||||
if let Some(stream) = self.api_state.stream.as_ref() {
|
||||
if let Err(err) = crate::api::send_to_client(
|
||||
&mut stream.lock().expect("Could not lock stream mutex"),
|
||||
|
|
|
@ -44,6 +44,7 @@ impl Layout {
|
|||
};
|
||||
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| {
|
||||
|
@ -60,8 +61,10 @@ impl Layout {
|
|||
.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(),
|
||||
|
@ -72,6 +75,7 @@ impl Layout {
|
|||
return;
|
||||
}
|
||||
|
||||
tracing::debug!("layed out first window");
|
||||
let mut windows = windows.iter();
|
||||
let first_window = windows.next().unwrap();
|
||||
|
||||
|
@ -105,15 +109,6 @@ impl Layout {
|
|||
let x = output.current_location().x + output_size.w / 2;
|
||||
|
||||
for (i, win) in windows.enumerate() {
|
||||
// let (min_size, _max_size) = match win.wl_surface() {
|
||||
// Some(wl_surface) => compositor::with_states(&wl_surface, |states| {
|
||||
// let data = states.cached_state.current::<SurfaceCachedState>();
|
||||
// (data.min_size, data.max_size)
|
||||
// }),
|
||||
// None => ((0, 0).into(), (0, 0).into()),
|
||||
// };
|
||||
// let min_height =
|
||||
// i32::max(i32::max(0, win.geometry().loc.y.abs()) + 1, min_size.h);
|
||||
win.toplevel().with_pending_state(|state| {
|
||||
let mut new_size = output_size;
|
||||
new_size.w /= 2;
|
||||
|
|
|
@ -27,6 +27,7 @@ mod output;
|
|||
mod pointer;
|
||||
mod render;
|
||||
mod state;
|
||||
mod tag;
|
||||
mod window;
|
||||
mod xdg;
|
||||
|
||||
|
|
|
@ -4,15 +4,15 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
use smithay::output::Output;
|
||||
|
||||
use crate::window::tag::Tag;
|
||||
use crate::tag::TagId;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OutputState {
|
||||
focused_tags: Vec<Tag>,
|
||||
pub focused_tags: HashMap<TagId, bool>,
|
||||
}
|
||||
|
||||
impl OutputState {
|
||||
|
@ -22,7 +22,7 @@ impl OutputState {
|
|||
{
|
||||
output
|
||||
.user_data()
|
||||
.insert_if_missing(|| RefCell::<Self>::default);
|
||||
.insert_if_missing(RefCell::<Self>::default);
|
||||
|
||||
let state = output
|
||||
.user_data()
|
||||
|
|
817
src/state.rs
817
src/state.rs
|
@ -8,6 +8,7 @@ use std::{
|
|||
error::Error,
|
||||
ffi::OsString,
|
||||
os::{fd::AsRawFd, unix::net::UnixStream},
|
||||
path::Path,
|
||||
process::Stdio,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
@ -17,7 +18,11 @@ 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},
|
||||
};
|
||||
use calloop::futures::Scheduler;
|
||||
|
@ -84,297 +89,239 @@ 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,
|
||||
|
||||
pub cursor_status: CursorImageStatus,
|
||||
pub pointer_location: Point<f64, Logical>,
|
||||
pub windows: Vec<Window>,
|
||||
|
||||
pub async_scheduler: Scheduler<()>,
|
||||
}
|
||||
|
||||
impl<B: Backend> State<B> {
|
||||
/// 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: B,
|
||||
display: &mut Display<Self>,
|
||||
loop_signal: LoopSignal,
|
||||
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();
|
||||
pub fn handle_msg(&mut self, msg: Msg) {
|
||||
match msg {
|
||||
Msg::SetKeybind {
|
||||
key,
|
||||
modifiers,
|
||||
callback_id,
|
||||
} => {
|
||||
tracing::info!("set keybind: {:?}, {}", modifiers, key);
|
||||
self.input_state
|
||||
.keybinds
|
||||
.insert((modifiers.into(), key), callback_id);
|
||||
}
|
||||
Msg::SetMousebind { button } => todo!(),
|
||||
Msg::CloseWindow { client_id } => {
|
||||
// TODO: client_id
|
||||
tracing::info!("CloseWindow {:?}", client_id);
|
||||
if let Some(window) = self.focus_state.current_focus() {
|
||||
window.toplevel().send_close();
|
||||
}
|
||||
}
|
||||
Msg::ToggleFloating { client_id } => {
|
||||
// TODO: add client_ids
|
||||
if let Some(window) = self.focus_state.current_focus() {
|
||||
crate::window::toggle_floating(self, &window);
|
||||
}
|
||||
}
|
||||
|
||||
std::env::set_var("WAYLAND_DISPLAY", socket_name.clone());
|
||||
Msg::Spawn {
|
||||
command,
|
||||
callback_id,
|
||||
} => {
|
||||
self.handle_spawn(command, callback_id);
|
||||
}
|
||||
|
||||
// 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}");
|
||||
}
|
||||
Msg::SetWindowSize { window_id, size } => {
|
||||
let Some(window) = self.space.elements().find(|&win| {
|
||||
WindowState::with_state(win, |state| state.id == window_id)
|
||||
}) else { return; };
|
||||
|
||||
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");
|
||||
})?;
|
||||
// TODO: tiled vs floating
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(size.into());
|
||||
});
|
||||
window.toplevel().send_pending_configure();
|
||||
}
|
||||
Msg::MoveWindowToTag { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| WindowState::with_state(win, |state| state.id == window_id))
|
||||
{
|
||||
WindowState::with_state(window, |state| {
|
||||
state.tags = vec![tag_id.clone()];
|
||||
});
|
||||
}
|
||||
|
||||
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) => {
|
||||
// TODO: move this into its own function
|
||||
// TODO: no like seriously this is getting a bit unwieldy
|
||||
// TODO: no like rustfmt literally refuses to format the code below
|
||||
match msg {
|
||||
Msg::SetKeybind {
|
||||
key,
|
||||
modifiers,
|
||||
callback_id,
|
||||
} => {
|
||||
tracing::info!("set keybind: {:?}, {}", modifiers, key);
|
||||
data.state
|
||||
.input_state
|
||||
.keybinds
|
||||
.insert((modifiers.into(), key), callback_id);
|
||||
}
|
||||
Msg::SetMousebind { button } => todo!(),
|
||||
Msg::CloseWindow { client_id } => {
|
||||
// TODO: client_id
|
||||
tracing::info!("CloseWindow {:?}", client_id);
|
||||
if let Some(window) = data.state.focus_state.current_focus() {
|
||||
window.toplevel().send_close();
|
||||
self.re_layout();
|
||||
}
|
||||
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
||||
if let Some(window) = self
|
||||
.windows
|
||||
.iter()
|
||||
.find(|&win| WindowState::with_state(win, |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());
|
||||
}
|
||||
}
|
||||
Msg::ToggleFloating { client_id } => {
|
||||
// TODO: add client_ids
|
||||
if let Some(window) = data.state.focus_state.current_focus() {
|
||||
crate::window::toggle_floating(&mut data.state, &window);
|
||||
});
|
||||
|
||||
self.re_layout();
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Msg::Spawn {
|
||||
command,
|
||||
callback_id,
|
||||
} => {
|
||||
data.state.handle_spawn(command, callback_id);
|
||||
if let Some(active) = state.focused_tags.get_mut(&tag_id) {
|
||||
*active = true;
|
||||
} else {
|
||||
state.focused_tags.insert(tag_id.clone(), true);
|
||||
}
|
||||
Msg::MoveToTag { tag } => todo!(),
|
||||
Msg::ToggleTag { tag } => todo!(),
|
||||
tracing::debug!("focused tags: {:?}", state.focused_tags);
|
||||
});
|
||||
|
||||
Msg::SetWindowSize { window_id, size } => {
|
||||
let Some(window) = data.state.space.elements().find(|&win| {
|
||||
WindowState::with_state(win, |state| state.id == window_id)
|
||||
}) else { return; };
|
||||
self.re_layout();
|
||||
}
|
||||
Msg::AddTags { tags } => {
|
||||
self.tag_state.tags.extend(tags.into_iter().map(|tag| Tag {
|
||||
id: tag,
|
||||
windows: vec![],
|
||||
}));
|
||||
}
|
||||
Msg::RemoveTags { tags } => {
|
||||
self.tag_state.tags.retain(|tag| !tags.contains(&tag.id));
|
||||
}
|
||||
|
||||
// TODO: tiled vs floating
|
||||
window.toplevel().with_pending_state(|state| {
|
||||
state.size = Some(size.into());
|
||||
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) =
|
||||
compositor::with_states(current_focus.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData doesn't exist")
|
||||
.lock()
|
||||
.expect("Couldn't lock XdgToplevelSurfaceData");
|
||||
(lock.app_id.clone(), lock.title.clone())
|
||||
});
|
||||
window.toplevel().send_pending_configure();
|
||||
}
|
||||
|
||||
Msg::Quit => {
|
||||
data.state.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) = data.state.focus_state.current_focus() else { return; };
|
||||
let (app_id, title) = compositor::with_states(
|
||||
current_focus.toplevel().wl_surface(),
|
||||
|states| {
|
||||
let lock = states.
|
||||
data_map
|
||||
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) =
|
||||
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||
let lock = states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData doesn't exist")
|
||||
.lock()
|
||||
.expect("Couldn't lock XdgToplevelSurfaceData");
|
||||
(lock.app_id.clone(), lock.title.clone())
|
||||
}
|
||||
);
|
||||
let (window_id, floating) = WindowState::with_state(¤t_focus, |state| {
|
||||
});
|
||||
let (window_id, floating) = WindowState::with_state(win, |state| {
|
||||
(state.id, state.floating.is_floating())
|
||||
});
|
||||
// TODO: unwrap
|
||||
let location = data.state.space.element_location(¤t_focus).unwrap();
|
||||
let props = WindowProperties {
|
||||
let location = self
|
||||
.space
|
||||
.element_location(win)
|
||||
.expect("Window location doesn't exist");
|
||||
WindowProperties {
|
||||
id: window_id,
|
||||
app_id,
|
||||
title,
|
||||
size: current_focus.geometry().size.into(),
|
||||
size: win.geometry().size.into(),
|
||||
location: location.into(),
|
||||
floating,
|
||||
};
|
||||
let stream = data.state.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");
|
||||
}
|
||||
})
|
||||
.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,
|
||||
},
|
||||
},
|
||||
Request::GetAllWindows { id } => {
|
||||
let window_props = data.state.space.elements().map(|win| {
|
||||
|
||||
let (app_id, title) = compositor::with_states(
|
||||
win.toplevel().wl_surface(),
|
||||
|states| {
|
||||
let lock = states.
|
||||
data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.expect("XdgToplevelSurfaceData doesn't exist")
|
||||
.lock()
|
||||
.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 = data.state.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 = data.state.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 },
|
||||
}
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
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");
|
||||
}
|
||||
})?;
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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 lua_cpath = std::env::var("LUA_CPATH").expect("Lua is not installed!");
|
||||
let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}");
|
||||
|
||||
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");
|
||||
|
||||
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(),
|
||||
|
||||
seat,
|
||||
|
||||
move_mode: false,
|
||||
socket_name: socket_name.to_string_lossy().to_string(),
|
||||
|
||||
popup_manager: PopupManager::default(),
|
||||
|
||||
async_scheduler: sched,
|
||||
})
|
||||
)
|
||||
.expect("Couldn't send to client");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_spawn(&self, command: Vec<String>, callback_id: Option<CallbackId>) {
|
||||
|
@ -412,11 +359,15 @@ impl<B: Backend> State<B> {
|
|||
return;
|
||||
};
|
||||
|
||||
// TODO: find a way to make this hellish code look better, deal with unwraps
|
||||
if let Some(callback_id) = callback_id {
|
||||
let stdout = child.stdout.take();
|
||||
let stderr = child.stderr.take();
|
||||
let stream_out = self.api_state.stream.as_ref().expect("Stream doesn't exist").clone();
|
||||
let stream_out = self
|
||||
.api_state
|
||||
.stream
|
||||
.as_ref()
|
||||
.expect("Stream doesn't exist")
|
||||
.clone();
|
||||
let stream_err = stream_out.clone();
|
||||
let stream_exit = stream_out.clone();
|
||||
|
||||
|
@ -447,7 +398,7 @@ impl<B: Backend> State<B> {
|
|||
Err(err) => {
|
||||
tracing::warn!("child read err: {err}");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -483,7 +434,7 @@ impl<B: Backend> State<B> {
|
|||
Err(err) => {
|
||||
tracing::warn!("child read err: {err}");
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -520,6 +471,350 @@ 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");
|
||||
}
|
||||
})?;
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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 lua_cpath = std::env::var("LUA_CPATH").expect("Lua is not installed!");
|
||||
let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}");
|
||||
|
||||
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 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![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl State<UdevData> {
|
||||
pub fn init(
|
||||
backend_data: UdevData,
|
||||
display: &mut Display<Self>,
|
||||
loop_signal: LoopSignal,
|
||||
loop_handle: LoopHandle<'static, CalloopData<UdevData>>,
|
||||
) -> 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>();
|
||||
|
||||
// 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");
|
||||
}
|
||||
})?;
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
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 lua_cpath = std::env::var("LUA_CPATH").expect("Lua is not installed!");
|
||||
let new_lua_cpath = format!("{local_lua_path}/lib/?.so;{lua_cpath}");
|
||||
|
||||
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 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)?;
|
||||
|
||||
loop_handle.insert_idle(|data| {
|
||||
data.state
|
||||
.loop_handle
|
||||
.insert_source(rx_channel, |msg, _, data| match msg {
|
||||
Event::Msg(msg) => data.state.handle_msg(msg),
|
||||
Event::Closed => todo!(),
|
||||
})
|
||||
.unwrap(); // TODO: unwrap
|
||||
});
|
||||
|
||||
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![],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CalloopData<B: Backend> {
|
||||
|
@ -535,8 +830,6 @@ impl ClientData for ClientState {
|
|||
fn initialized(&self, _client_id: ClientId) {}
|
||||
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
||||
|
||||
// fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
|
28
src/tag.rs
Normal file
28
src/tag.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
// 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;
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TagId(String);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tag {
|
||||
pub id: TagId,
|
||||
pub windows: Vec<Window>,
|
||||
// TODO: layout
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TagState {
|
||||
pub tags: Vec<Tag>,
|
||||
}
|
||||
|
||||
impl TagState {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@ use crate::{
|
|||
|
||||
use self::window_state::{Float, WindowId, WindowState};
|
||||
|
||||
pub mod tag;
|
||||
pub mod window_state;
|
||||
|
||||
// TODO: maybe get rid of this and move the fn into resize_surface state because it's the only user
|
||||
|
@ -51,6 +50,12 @@ impl<B: Backend> State<B> {
|
|||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| s == *surface).unwrap_or(false))
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
self.windows
|
||||
.iter()
|
||||
.find(|&win| win.toplevel().wl_surface() == surface)
|
||||
.cloned()
|
||||
})
|
||||
}
|
||||
|
||||
/// Swap the positions and sizes of two windows.
|
||||
|
|
|
@ -1,28 +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;
|
||||
|
||||
use crate::{backend::Backend, state::State};
|
||||
|
||||
use super::window_state::WindowState;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Tag(String);
|
||||
|
||||
impl Tag {
|
||||
/// Returns all windows that have this tag.
|
||||
pub fn windows<B: Backend>(&self, state: &State<B>) -> Vec<Window> {
|
||||
state
|
||||
.space
|
||||
.elements()
|
||||
.filter(|&window| {
|
||||
WindowState::with_state(window, |win_state| win_state.tags.contains(self))
|
||||
})
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
|
@ -14,11 +14,12 @@ use smithay::{
|
|||
utils::{Logical, Point, Serial, Size},
|
||||
};
|
||||
|
||||
use super::tag::Tag;
|
||||
use crate::tag::{Tag, TagId, TagState};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct WindowId(u32);
|
||||
|
||||
// TODO: this probably doesn't need to be atomic
|
||||
static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
impl WindowId {
|
||||
|
@ -35,7 +36,16 @@ 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<Tag>,
|
||||
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()
|
||||
}
|
||||
|
||||
/// The state of a window's resize operation.
|
||||
|
@ -116,12 +126,11 @@ impl WindowState {
|
|||
.user_data()
|
||||
.insert_if_missing(RefCell::<Self>::default);
|
||||
|
||||
let mut state = window
|
||||
let state = window
|
||||
.user_data()
|
||||
.get::<RefCell<Self>>()
|
||||
.expect("This should never happen")
|
||||
.borrow_mut();
|
||||
func(&mut state)
|
||||
.expect("This should never happen");
|
||||
func(&mut state.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue