mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
Add more window methods to API
This commit is contained in:
parent
85284f72ad
commit
f2b54be2fc
9 changed files with 574 additions and 266 deletions
|
@ -41,7 +41,12 @@ require("pinnacle").setup(function(pinnacle)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
input.keybind({ mod_key, "Alt" }, keys.space, window.toggle_floating)
|
input.keybind({ mod_key, "Alt" }, keys.space, function()
|
||||||
|
local win = window.get_focused()
|
||||||
|
if win ~= nil then
|
||||||
|
win:toggle_floating()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
input.keybind({ mod_key }, keys.Return, function()
|
input.keybind({ mod_key }, keys.Return, function()
|
||||||
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
|
process.spawn(terminal, function(stdout, stderr, exit_code, exit_msg)
|
||||||
|
@ -59,6 +64,18 @@ require("pinnacle").setup(function(pinnacle)
|
||||||
process.spawn("nautilus")
|
process.spawn("nautilus")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- Just testing stuff
|
||||||
|
input.keybind({ mod_key }, keys.h, function()
|
||||||
|
local win = window.get_focused()
|
||||||
|
if win ~= nil then
|
||||||
|
print("loc: " .. (win:loc() and win:loc().x or "nil") .. ", " .. (win:loc() and win:loc().y or "nil"))
|
||||||
|
print("size: " .. (win:size() and win:size().w or "nil") .. ", " .. (win:size() and win:size().h or "nil"))
|
||||||
|
print("class: " .. (win:class() or "nil"))
|
||||||
|
print("title: " .. (win:title() or "nil"))
|
||||||
|
print("float: " .. tostring(win:floating()))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
-- Tags ---------------------------------------------------------------------------
|
-- Tags ---------------------------------------------------------------------------
|
||||||
|
|
||||||
output.connect_for_all(function(op)
|
output.connect_for_all(function(op)
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
--Windows
|
--Windows
|
||||||
---@field CloseWindow { window_id: integer }
|
---@field CloseWindow { window_id: integer }
|
||||||
---@field ToggleFloating { window_id: integer }
|
---@field ToggleFloating { window_id: integer }
|
||||||
---@field SetWindowSize { window_id: integer, size: integer[] }
|
---@field SetWindowSize { window_id: integer, width: integer?, height: integer? }
|
||||||
---@field MoveWindowToTag { window_id: integer, tag_id: string }
|
---@field MoveWindowToTag { window_id: integer, tag_id: string }
|
||||||
---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
|
---@field ToggleTagOnWindow { window_id: integer, tag_id: string }
|
||||||
--
|
--
|
||||||
|
@ -35,6 +35,11 @@
|
||||||
--Windows
|
--Windows
|
||||||
---@field GetWindowByAppId { app_id: string }
|
---@field GetWindowByAppId { app_id: string }
|
||||||
---@field GetWindowByTitle { title: string }
|
---@field GetWindowByTitle { title: string }
|
||||||
|
---@field GetWindowSize { window_id: WindowId }
|
||||||
|
---@field GetWindowLocation { window_id: WindowId }
|
||||||
|
---@field GetWindowFLoating { window_id: WindowId }
|
||||||
|
---@field GetWindowClass { window_id: WindowId }
|
||||||
|
---@field GetWindowTitle { window_id: WindowId }
|
||||||
--Outputs
|
--Outputs
|
||||||
---@field GetOutputByName { output_name: OutputName }
|
---@field GetOutputByName { output_name: OutputName }
|
||||||
---@field GetOutputsByModel { model: string }
|
---@field GetOutputsByModel { model: string }
|
||||||
|
@ -58,9 +63,18 @@
|
||||||
---@alias OutputName string
|
---@alias OutputName string
|
||||||
|
|
||||||
---@class RequestResponse
|
---@class RequestResponse
|
||||||
|
--Windows
|
||||||
---@field Window { window_id: WindowId|nil }
|
---@field Window { window_id: WindowId|nil }
|
||||||
---@field Windows { window_ids: WindowId[] }
|
---@field Windows { window_ids: WindowId[] }
|
||||||
|
---@field WindowSize { size: (integer[])? }
|
||||||
|
---@field WindowLocation { loc: (integer[])? }
|
||||||
|
---@field WindowClass { class: string? }
|
||||||
|
---@field WindowTitle { title: string? }
|
||||||
|
---@field WindowFloating { floating: boolean? }
|
||||||
|
--Outputs
|
||||||
|
---@field Output { output_name: OutputName? }
|
||||||
---@field Outputs { output_names: OutputName[] }
|
---@field Outputs { output_names: OutputName[] }
|
||||||
|
--Tags
|
||||||
---@field Tags { tag_ids: TagId[] }
|
---@field Tags { tag_ids: TagId[] }
|
||||||
---@field TagActive { active: boolean }
|
---@field TagActive { active: boolean }
|
||||||
---@field TagName { name: string }
|
---@field TagName { name: string }
|
||||||
|
|
|
@ -54,7 +54,7 @@ local output = {}
|
||||||
---print(monitor.name) -- should print `DP-1`
|
---print(monitor.name) -- should print `DP-1`
|
||||||
---```
|
---```
|
||||||
---@param name string The name of the output.
|
---@param name string The name of the output.
|
||||||
---@return Output|nil
|
---@return Output|nil output The output, or nil if none have the provided name.
|
||||||
function output.get_by_name(name)
|
function output.get_by_name(name)
|
||||||
SendRequest({
|
SendRequest({
|
||||||
GetOutputByName = {
|
GetOutputByName = {
|
||||||
|
@ -64,10 +64,10 @@ function output.get_by_name(name)
|
||||||
|
|
||||||
local response = ReadMsg()
|
local response = ReadMsg()
|
||||||
|
|
||||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
local output_name = response.RequestResponse.response.Output.output_name
|
||||||
|
|
||||||
if output_names[1] ~= nil then
|
if output_name ~= nil then
|
||||||
return new_output({ name = output_names[1] })
|
return new_output({ name = output_name })
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
@ -78,7 +78,7 @@ end
|
||||||
---Get outputs by their model.
|
---Get outputs by their model.
|
||||||
---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays.
|
---This is something like "DELL E2416H" or whatever gibberish monitor manufacturers call their displays.
|
||||||
---@param model string The model of the output(s).
|
---@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.
|
---@return Output[] outputs All outputs with this model.
|
||||||
function output.get_by_model(model)
|
function output.get_by_model(model)
|
||||||
SendRequest({
|
SendRequest({
|
||||||
GetOutputsByModel = {
|
GetOutputsByModel = {
|
||||||
|
@ -103,7 +103,7 @@ end
|
||||||
---
|
---
|
||||||
---@param width integer The width of the outputs, in pixels.
|
---@param width integer The width of the outputs, in pixels.
|
||||||
---@param height integer The height 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.
|
---@return Output[] outputs All outputs with this resolution.
|
||||||
function output.get_by_res(width, height)
|
function output.get_by_res(width, height)
|
||||||
SendRequest({
|
SendRequest({
|
||||||
GetOutputsByRes = {
|
GetOutputsByRes = {
|
||||||
|
@ -149,10 +149,10 @@ function output.get_focused()
|
||||||
|
|
||||||
local response = ReadMsg()
|
local response = ReadMsg()
|
||||||
|
|
||||||
local output_names = response.RequestResponse.response.Outputs.output_names
|
local output_name = response.RequestResponse.response.Output.output_name
|
||||||
|
|
||||||
if output_names[1] ~= nil then
|
if output_name ~= nil then
|
||||||
return new_output({ name = output_names[1] })
|
return new_output({ name = output_name })
|
||||||
else
|
else
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -86,6 +86,7 @@ function pinnacle.setup(config_func)
|
||||||
function SendMsg(data)
|
function SendMsg(data)
|
||||||
local encoded = msgpack.encode(data)
|
local encoded = msgpack.encode(data)
|
||||||
assert(encoded)
|
assert(encoded)
|
||||||
|
-- print(encoded)
|
||||||
local len = encoded:len()
|
local len = encoded:len()
|
||||||
socket.send(socket_fd, string.pack("=I4", len))
|
socket.send(socket_fd, string.pack("=I4", len))
|
||||||
socket.send(socket_fd, encoded)
|
socket.send(socket_fd, encoded)
|
||||||
|
|
|
@ -6,11 +6,6 @@
|
||||||
|
|
||||||
---@class Window
|
---@class Window
|
||||||
---@field private id integer The internal id of this window
|
---@field private id integer The internal id of this window
|
||||||
---@field private app_id string? The equivalent of an X11 window's class
|
|
||||||
---@field private title string? The window's title
|
|
||||||
---@field private size { w: integer, h: integer } The size of the window
|
|
||||||
---@field private location { x: integer, y: integer } The location of the window
|
|
||||||
---@field private floating boolean Whether the window is floating or not (tiled)
|
|
||||||
local win = {}
|
local win = {}
|
||||||
|
|
||||||
---@param props Window
|
---@param props Window
|
||||||
|
@ -25,21 +20,32 @@ local function new_window(props)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Set a window's size.
|
---Set a window's size.
|
||||||
|
---
|
||||||
|
---### Examples
|
||||||
|
---```lua
|
||||||
|
---window.get_focused():set_size({ w = 500, h = 500 }) -- make the window square and 500 pixels wide/tall
|
||||||
|
---window.get_focused():set_size({ h = 300 }) -- keep the window's width but make it 300 pixels tall
|
||||||
|
---window.get_focused():set_size({}) -- do absolutely nothing useful
|
||||||
|
---```
|
||||||
---@param size { w: integer?, h: integer? }
|
---@param size { w: integer?, h: integer? }
|
||||||
function win:set_size(size)
|
function win:set_size(size)
|
||||||
self.size = {
|
|
||||||
w = size.w or self.size.w,
|
|
||||||
h = size.h or self.size.h,
|
|
||||||
}
|
|
||||||
SendMsg({
|
SendMsg({
|
||||||
SetWindowSize = {
|
SetWindowSize = {
|
||||||
window_id = self.id,
|
window_id = self.id,
|
||||||
size = { self.size.w, self.size.h },
|
width = size.w,
|
||||||
|
height = size.h,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
---Move a window to a tag, removing all other ones.
|
---Move a window to a tag, removing all other ones.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
----- With the focused window on tags 1, 2, 3, and 4...
|
||||||
|
---window.get_focused():move_to_tag("5")
|
||||||
|
----- ...will make the window only appear on tag 5.
|
||||||
|
---```
|
||||||
---@param name string The name of the tag.
|
---@param name string The name of the tag.
|
||||||
function win:move_to_tag(name)
|
function win:move_to_tag(name)
|
||||||
SendMsg({
|
SendMsg({
|
||||||
|
@ -51,6 +57,15 @@ function win:move_to_tag(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Toggle the specified tag for this window.
|
---Toggle the specified tag for this window.
|
||||||
|
---
|
||||||
|
---Note: toggling off all tags currently makes a window not response to layouting.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
----- With the focused window only on tag 1...
|
||||||
|
---window.get_focused():toggle_tag("2")
|
||||||
|
----- ...will also make the window appear on tag 2.
|
||||||
|
---```
|
||||||
---@param name string The name of the tag.
|
---@param name string The name of the tag.
|
||||||
function win:toggle_tag(name)
|
function win:toggle_tag(name)
|
||||||
SendMsg({
|
SendMsg({
|
||||||
|
@ -62,6 +77,14 @@ function win:toggle_tag(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
---Close this window.
|
---Close this window.
|
||||||
|
---
|
||||||
|
---This only sends a close *event* to the window and is the same as just clicking the X button in the titlebar.
|
||||||
|
---This will trigger save prompts in applications like GIMP.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
---window.get_focused():close() -- close the currently focused window
|
||||||
|
---```
|
||||||
function win:close()
|
function win:close()
|
||||||
SendMsg({
|
SendMsg({
|
||||||
CloseWindow = {
|
CloseWindow = {
|
||||||
|
@ -71,6 +94,11 @@ function win:close()
|
||||||
end
|
end
|
||||||
|
|
||||||
---Toggle this window's floating status.
|
---Toggle this window's floating status.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
---window.get_focused():toggle_floating() -- toggles the focused window between tiled and floating
|
||||||
|
---```
|
||||||
function win:toggle_floating()
|
function win:toggle_floating()
|
||||||
SendMsg({
|
SendMsg({
|
||||||
ToggleFloating = {
|
ToggleFloating = {
|
||||||
|
@ -80,9 +108,127 @@ function win:toggle_floating()
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get a window's size.
|
---Get a window's size.
|
||||||
---@return { w: integer, h: integer }
|
---
|
||||||
function win:get_size()
|
---### Example
|
||||||
return self.size
|
---```lua
|
||||||
|
----- With a 4K monitor, given a focused fullscreen window...
|
||||||
|
---local size = window.get_focused():size()
|
||||||
|
----- ...should have size equal to `{ w = 3840, h = 2160 }`.
|
||||||
|
---```
|
||||||
|
---@return { w: integer, h: integer }|nil size The size of the window, or nil if it doesn't exist.
|
||||||
|
function win:size()
|
||||||
|
SendRequest({
|
||||||
|
GetWindowSize = {
|
||||||
|
window_id = self.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local response = ReadMsg()
|
||||||
|
local size = response.RequestResponse.response.WindowSize.size
|
||||||
|
if size == nil then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
w = size[1],
|
||||||
|
h = size[2],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get this window's location in the global space.
|
||||||
|
---
|
||||||
|
---Think of your monitors as being laid out on a big sheet.
|
||||||
|
---The top left of the sheet if you trim it down is (0, 0).
|
||||||
|
---The location of this window is relative to that point.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
----- With two 1080p monitors side by side and set up as such,
|
||||||
|
----- if a window is fullscreen on the right one...
|
||||||
|
---local loc = that_window:loc()
|
||||||
|
----- ...should have loc equal to `{ x = 1920, y = 0 }`.
|
||||||
|
---```
|
||||||
|
---@return { x: integer, y: integer }|nil loc The location of the window, or nil if it's not on-screen or alive.
|
||||||
|
function win:loc()
|
||||||
|
SendRequest({
|
||||||
|
GetWindowLocation = {
|
||||||
|
window_id = self.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local response = ReadMsg()
|
||||||
|
local loc = response.RequestResponse.response.WindowLocation.loc
|
||||||
|
if loc == nil then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
x = loc[1],
|
||||||
|
y = loc[2],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get this window's class. This is usually the name of the application.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
----- With Alacritty focused...
|
||||||
|
---print(window.get_focused():class())
|
||||||
|
----- ...should print "Alacritty".
|
||||||
|
---```
|
||||||
|
---@return string|nil class This window's class, or nil if it doesn't exist.
|
||||||
|
function win:class()
|
||||||
|
SendRequest({
|
||||||
|
GetWindowClass = {
|
||||||
|
window_id = self.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local response = ReadMsg()
|
||||||
|
local class = response.RequestResponse.response.WindowClass.class
|
||||||
|
return class
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get this window's title.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
----- With Alacritty focused...
|
||||||
|
---print(window.get_focused():title())
|
||||||
|
----- ...should print the directory Alacritty is in or what it's running (what's in its title bar).
|
||||||
|
---```
|
||||||
|
---@return string|nil title This window's title, or nil if it doesn't exist.
|
||||||
|
function win:title()
|
||||||
|
SendRequest({
|
||||||
|
GetWindowTitle = {
|
||||||
|
window_id = self.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local response = ReadMsg()
|
||||||
|
local title = response.RequestResponse.response.WindowTitle.title
|
||||||
|
return title
|
||||||
|
end
|
||||||
|
|
||||||
|
---Get this window's floating status.
|
||||||
|
---
|
||||||
|
---### Example
|
||||||
|
---```lua
|
||||||
|
----- With Alacritty focused and floating...
|
||||||
|
---print(tostring(window.get_focused():floating()))
|
||||||
|
----- ...should print "true".
|
||||||
|
---```
|
||||||
|
---@return boolean|nil floating `true` if it's floating, `false` if it's tiled, or nil if it doesn't exist.
|
||||||
|
function win:floating()
|
||||||
|
SendRequest({
|
||||||
|
GetWindowFloating = {
|
||||||
|
window_id = self.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
local response = ReadMsg()
|
||||||
|
local floating = response.RequestResponse.response.WindowFloating.floating
|
||||||
|
return floating
|
||||||
end
|
end
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
-------------------------------------------------------------------
|
||||||
|
|
|
@ -33,7 +33,10 @@ pub enum Msg {
|
||||||
},
|
},
|
||||||
SetWindowSize {
|
SetWindowSize {
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
size: (i32, i32),
|
#[serde(default)]
|
||||||
|
width: Option<i32>,
|
||||||
|
#[serde(default)]
|
||||||
|
height: Option<i32>,
|
||||||
},
|
},
|
||||||
MoveWindowToTag {
|
MoveWindowToTag {
|
||||||
window_id: WindowId,
|
window_id: WindowId,
|
||||||
|
@ -96,14 +99,22 @@ pub struct RequestId(pub u32);
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
/// Messages that require a server response, usually to provide some data.
|
/// Messages that require a server response, usually to provide some data.
|
||||||
pub enum Request {
|
pub enum Request {
|
||||||
|
// Windows
|
||||||
GetWindowByAppId { app_id: String },
|
GetWindowByAppId { app_id: String },
|
||||||
GetWindowByTitle { title: String },
|
GetWindowByTitle { title: String },
|
||||||
GetWindowByFocus,
|
GetWindowByFocus,
|
||||||
GetAllWindows,
|
GetAllWindows,
|
||||||
|
GetWindowSize { window_id: WindowId },
|
||||||
|
GetWindowLocation { window_id: WindowId },
|
||||||
|
GetWindowFloating { window_id: WindowId },
|
||||||
|
GetWindowClass { window_id: WindowId },
|
||||||
|
GetWindowTitle { window_id: WindowId },
|
||||||
|
// Outputs
|
||||||
GetOutputByName { output_name: String },
|
GetOutputByName { output_name: String },
|
||||||
GetOutputsByModel { model: String },
|
GetOutputsByModel { model: String },
|
||||||
GetOutputsByRes { res: (u32, u32) },
|
GetOutputsByRes { res: (u32, u32) },
|
||||||
GetOutputByFocus,
|
GetOutputByFocus,
|
||||||
|
// Tags
|
||||||
GetTagsByOutput { output_name: String },
|
GetTagsByOutput { output_name: String },
|
||||||
GetTagActive { tag_id: TagId },
|
GetTagActive { tag_id: TagId },
|
||||||
GetTagName { tag_id: TagId },
|
GetTagName { tag_id: TagId },
|
||||||
|
@ -187,6 +198,12 @@ pub enum Args {
|
||||||
pub enum RequestResponse {
|
pub enum RequestResponse {
|
||||||
Window { window_id: Option<WindowId> },
|
Window { window_id: Option<WindowId> },
|
||||||
Windows { window_ids: Vec<WindowId> },
|
Windows { window_ids: Vec<WindowId> },
|
||||||
|
WindowSize { size: Option<(i32, i32)> },
|
||||||
|
WindowLocation { loc: Option<(i32, i32)> },
|
||||||
|
WindowClass { class: Option<String> },
|
||||||
|
WindowTitle { title: Option<String> },
|
||||||
|
WindowFloating { floating: Option<bool> },
|
||||||
|
Output { output_name: Option<String> },
|
||||||
Outputs { output_names: Vec<String> },
|
Outputs { output_names: Vec<String> },
|
||||||
Tags { tag_ids: Vec<TagId> },
|
Tags { tag_ids: Vec<TagId> },
|
||||||
TagActive { active: bool },
|
TagActive { active: bool },
|
||||||
|
|
|
@ -159,7 +159,8 @@ impl Layout {
|
||||||
state.size.expect("size should have been set")
|
state.size.expect("size should have been set")
|
||||||
});
|
});
|
||||||
let win1_loc = win1.with_state(|state| {
|
let win1_loc = win1.with_state(|state| {
|
||||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
let WindowResizeState::Requested(_, loc) =
|
||||||
|
state.resize_state else { unreachable!() };
|
||||||
loc
|
loc
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -271,7 +272,8 @@ impl Layout {
|
||||||
state.size.expect("size should have been set")
|
state.size.expect("size should have been set")
|
||||||
});
|
});
|
||||||
let win1_loc = win1.with_state(|state| {
|
let win1_loc = win1.with_state(|state| {
|
||||||
let WindowResizeState::Requested(_, loc) = state.resize_state else { unreachable!() };
|
let WindowResizeState::Requested(_, loc) =
|
||||||
|
state.resize_state else { unreachable!() };
|
||||||
loc
|
loc
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
573
src/state.rs
573
src/state.rs
|
@ -55,7 +55,7 @@ use smithay::{
|
||||||
dmabuf::DmabufFeedback,
|
dmabuf::DmabufFeedback,
|
||||||
fractional_scale::FractionalScaleManagerState,
|
fractional_scale::FractionalScaleManagerState,
|
||||||
output::OutputManagerState,
|
output::OutputManagerState,
|
||||||
shell::xdg::XdgShellState,
|
shell::xdg::{XdgShellState, XdgToplevelSurfaceData},
|
||||||
shm::ShmState,
|
shm::ShmState,
|
||||||
socket::ListeningSocketSource,
|
socket::ListeningSocketSource,
|
||||||
viewporter::ViewporterState,
|
viewporter::ViewporterState,
|
||||||
|
@ -119,20 +119,12 @@ impl<B: Backend> State<B> {
|
||||||
}
|
}
|
||||||
Msg::SetMousebind { button: _ } => todo!(),
|
Msg::SetMousebind { button: _ } => todo!(),
|
||||||
Msg::CloseWindow { window_id } => {
|
Msg::CloseWindow { window_id } => {
|
||||||
if let Some(window) = self
|
if let Some(window) = window_id.window(self) {
|
||||||
.windows
|
|
||||||
.iter()
|
|
||||||
.find(|win| win.with_state(|state| state.id == window_id))
|
|
||||||
{
|
|
||||||
window.toplevel().send_close();
|
window.toplevel().send_close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::ToggleFloating { window_id } => {
|
Msg::ToggleFloating { window_id } => {
|
||||||
if let Some(window) = self
|
if let Some(window) = window_id.window(self) {
|
||||||
.windows
|
|
||||||
.iter()
|
|
||||||
.find(|win| win.with_state(|state| state.id == window_id)).cloned()
|
|
||||||
{
|
|
||||||
crate::window::toggle_floating(self, &window);
|
crate::window::toggle_floating(self, &window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,23 +136,30 @@ impl<B: Backend> State<B> {
|
||||||
self.handle_spawn(command, callback_id);
|
self.handle_spawn(command, callback_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::SetWindowSize { window_id, size } => {
|
Msg::SetWindowSize {
|
||||||
let Some(window) = self.space.elements().find(|&win| {
|
window_id,
|
||||||
win.with_state( |state| state.id == window_id)
|
width,
|
||||||
}) else { return; };
|
height,
|
||||||
|
} => {
|
||||||
|
let Some(window) = window_id.window(self) else { return };
|
||||||
|
|
||||||
// TODO: tiled vs floating
|
// TODO: tiled vs floating
|
||||||
|
let window_size = window.geometry().size;
|
||||||
window.toplevel().with_pending_state(|state| {
|
window.toplevel().with_pending_state(|state| {
|
||||||
state.size = Some(size.into());
|
// INFO: calling window.geometry() in with_pending_state
|
||||||
|
// | will hang the compositor
|
||||||
|
state.size = Some(
|
||||||
|
(
|
||||||
|
width.unwrap_or(window_size.w),
|
||||||
|
height.unwrap_or(window_size.h),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
window.toplevel().send_pending_configure();
|
window.toplevel().send_pending_configure();
|
||||||
}
|
}
|
||||||
Msg::MoveWindowToTag { window_id, tag_id } => {
|
Msg::MoveWindowToTag { window_id, tag_id } => {
|
||||||
if let Some(window) = self
|
if let Some(window) = window_id.window(self) {
|
||||||
.windows
|
|
||||||
.iter()
|
|
||||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
|
||||||
{
|
|
||||||
window.with_state(|state| {
|
window.with_state(|state| {
|
||||||
self.focus_state
|
self.focus_state
|
||||||
.focused_output
|
.focused_output
|
||||||
|
@ -173,17 +172,12 @@ impl<B: Backend> State<B> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
let output = self.focus_state.focused_output.clone().unwrap();
|
||||||
|
self.re_layout(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = self.focus_state.focused_output.clone().unwrap();
|
|
||||||
self.re_layout(&output);
|
|
||||||
}
|
}
|
||||||
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
Msg::ToggleTagOnWindow { window_id, tag_id } => {
|
||||||
if let Some(window) = self
|
if let Some(window) = window_id.window(self) {
|
||||||
.windows
|
|
||||||
.iter()
|
|
||||||
.find(|&win| win.with_state(|state| state.id == window_id))
|
|
||||||
{
|
|
||||||
window.with_state(|state| {
|
window.with_state(|state| {
|
||||||
self.focus_state
|
self.focus_state
|
||||||
.focused_output
|
.focused_output
|
||||||
|
@ -205,14 +199,21 @@ impl<B: Backend> State<B> {
|
||||||
self.re_layout(&output);
|
self.re_layout(&output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::ToggleTag { output_name, tag_name } => {
|
Msg::ToggleTag {
|
||||||
|
output_name,
|
||||||
|
tag_name,
|
||||||
|
} => {
|
||||||
tracing::debug!("ToggleTag");
|
tracing::debug!("ToggleTag");
|
||||||
|
|
||||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
let output = self
|
||||||
|
.space
|
||||||
|
.outputs()
|
||||||
|
.find(|op| op.name() == output_name)
|
||||||
|
.cloned();
|
||||||
if let Some(output) = output {
|
if let Some(output) = output {
|
||||||
|
|
||||||
output.with_state(|state| {
|
output.with_state(|state| {
|
||||||
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) {
|
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name)
|
||||||
|
{
|
||||||
tracing::debug!("Setting tag {tag:?} to {}", !tag.active());
|
tracing::debug!("Setting tag {tag:?} to {}", !tag.active());
|
||||||
tag.set_active(!tag.active());
|
tag.set_active(!tag.active());
|
||||||
}
|
}
|
||||||
|
@ -220,10 +221,16 @@ impl<B: Backend> State<B> {
|
||||||
self.re_layout(&output);
|
self.re_layout(&output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::SwitchToTag { output_name, tag_name } => {
|
Msg::SwitchToTag {
|
||||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
output_name,
|
||||||
|
tag_name,
|
||||||
|
} => {
|
||||||
|
let output = self
|
||||||
|
.space
|
||||||
|
.outputs()
|
||||||
|
.find(|op| op.name() == output_name)
|
||||||
|
.cloned();
|
||||||
if let Some(output) = output {
|
if let Some(output) = output {
|
||||||
|
|
||||||
output.with_state(|state| {
|
output.with_state(|state| {
|
||||||
if !state.tags.iter().any(|tag| tag.name() == tag_name) {
|
if !state.tags.iter().any(|tag| tag.name() == tag_name) {
|
||||||
// TODO: notify error
|
// TODO: notify error
|
||||||
|
@ -233,7 +240,11 @@ impl<B: Backend> State<B> {
|
||||||
tag.set_active(false);
|
tag.set_active(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) else {
|
let Some(tag) = state
|
||||||
|
.tags
|
||||||
|
.iter_mut()
|
||||||
|
.find(|tag| tag.name() == tag_name)
|
||||||
|
else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
tag.set_active(true);
|
tag.set_active(true);
|
||||||
|
@ -252,21 +263,25 @@ impl<B: Backend> State<B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: add output
|
// TODO: add output
|
||||||
Msg::AddTags { output_name, tag_names } => {
|
Msg::AddTags {
|
||||||
|
output_name,
|
||||||
|
tag_names,
|
||||||
|
} => {
|
||||||
if let Some(output) = self
|
if let Some(output) = self
|
||||||
.space
|
.space
|
||||||
.outputs()
|
.outputs()
|
||||||
.find(|output| output.name() == output_name)
|
.find(|output| output.name() == output_name)
|
||||||
{
|
{
|
||||||
output.with_state(|state| {
|
output.with_state(|state| {
|
||||||
state
|
state.tags.extend(tag_names.iter().cloned().map(Tag::new));
|
||||||
.tags
|
|
||||||
.extend(tag_names.iter().cloned().map(Tag::new));
|
|
||||||
tracing::debug!("tags added, are now {:?}", state.tags);
|
tracing::debug!("tags added, are now {:?}", state.tags);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::RemoveTags { output_name, tag_names } => {
|
Msg::RemoveTags {
|
||||||
|
output_name,
|
||||||
|
tag_names,
|
||||||
|
} => {
|
||||||
if let Some(output) = self
|
if let Some(output) = self
|
||||||
.space
|
.space
|
||||||
.outputs()
|
.outputs()
|
||||||
|
@ -277,12 +292,20 @@ impl<B: Backend> State<B> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Msg::SetLayout { output_name, tag_name, layout } => {
|
Msg::SetLayout {
|
||||||
let output = self.space.outputs().find(|op| op.name() == output_name).cloned();
|
output_name,
|
||||||
|
tag_name,
|
||||||
|
layout,
|
||||||
|
} => {
|
||||||
|
let output = self
|
||||||
|
.space
|
||||||
|
.outputs()
|
||||||
|
.find(|op| op.name() == output_name)
|
||||||
|
.cloned();
|
||||||
if let Some(output) = output {
|
if let Some(output) = output {
|
||||||
|
|
||||||
output.with_state(|state| {
|
output.with_state(|state| {
|
||||||
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name) {
|
if let Some(tag) = state.tags.iter_mut().find(|tag| tag.name() == tag_name)
|
||||||
|
{
|
||||||
tag.set_layout(layout);
|
tag.set_layout(layout);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -317,194 +340,258 @@ impl<B: Backend> State<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Msg::Request(request) => {
|
Msg::Request(request) => {
|
||||||
let stream = self
|
self.handle_request(request);
|
||||||
.api_state
|
}
|
||||||
.stream
|
}
|
||||||
.as_ref()
|
}
|
||||||
.expect("Stream doesn't exist");
|
|
||||||
let mut stream = stream.lock().expect("Couldn't lock stream");
|
|
||||||
match request {
|
|
||||||
Request::GetWindowByAppId { app_id: _ } => todo!(),
|
|
||||||
Request::GetWindowByTitle { title: _ } => todo!(),
|
|
||||||
Request::GetWindowByFocus => {
|
|
||||||
match self.focus_state.current_focus() {
|
|
||||||
Some(current_focus) => {
|
|
||||||
let window_id =
|
|
||||||
current_focus.with_state(|state| state.id);
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Window { window_id: Some(window_id) },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("Send to client failed");
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Window { window_id: None },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.expect("Send to client failed");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Request::GetAllWindows => {
|
|
||||||
let window_ids = self
|
|
||||||
.windows
|
|
||||||
.iter()
|
|
||||||
.map(|win| {
|
|
||||||
win.with_state(|state| state.id)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// FIXME: figure out what to do if error
|
fn handle_request(&mut self, request: Request) {
|
||||||
crate::api::send_to_client(
|
let stream = self
|
||||||
&mut stream,
|
.api_state
|
||||||
&OutgoingMsg::RequestResponse {
|
.stream
|
||||||
response: RequestResponse::Windows {
|
.as_ref()
|
||||||
window_ids,
|
.expect("Stream doesn't exist");
|
||||||
},
|
let mut stream = stream.lock().expect("Couldn't lock stream");
|
||||||
|
match request {
|
||||||
|
Request::GetWindowByAppId { app_id: _ } => todo!(),
|
||||||
|
Request::GetWindowByTitle { title: _ } => todo!(),
|
||||||
|
Request::GetWindowByFocus => match self.focus_state.current_focus() {
|
||||||
|
Some(current_focus) => {
|
||||||
|
let window_id = current_focus.with_state(|state| state.id);
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Window {
|
||||||
|
window_id: Some(window_id),
|
||||||
},
|
},
|
||||||
)
|
},
|
||||||
.expect("Couldn't send to client");
|
)
|
||||||
}
|
.expect("Send to client failed");
|
||||||
Request::GetOutputByName { output_name } => {
|
}
|
||||||
// TODO: name better
|
None => {
|
||||||
let names = self
|
crate::api::send_to_client(
|
||||||
.space
|
&mut stream,
|
||||||
.outputs()
|
&OutgoingMsg::RequestResponse {
|
||||||
.find(|output| output.name() == output_name)
|
response: RequestResponse::Window { window_id: None },
|
||||||
.map(|output| output.name());
|
},
|
||||||
crate::api::send_to_client(
|
)
|
||||||
&mut stream,
|
.expect("Send to client failed");
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Outputs {
|
|
||||||
output_names: if let Some(name) = names {
|
|
||||||
vec![name]
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Request::GetOutputsByModel { model } => {
|
|
||||||
let names = self
|
|
||||||
.space
|
|
||||||
.outputs()
|
|
||||||
.filter(|output| output.physical_properties().model == model)
|
|
||||||
.map(|output| output.name())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Outputs { output_names: names },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Request::GetOutputsByRes { res } => {
|
|
||||||
let names = self
|
|
||||||
.space
|
|
||||||
.outputs()
|
|
||||||
.filter_map(|output| {
|
|
||||||
if let Some(mode) = output.current_mode() {
|
|
||||||
if mode.size == (res.0 as i32, res.1 as i32).into() {
|
|
||||||
Some(output.name())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Outputs { output_names: names },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Request::GetOutputByFocus => {
|
|
||||||
let names = self
|
|
||||||
.focus_state
|
|
||||||
.focused_output
|
|
||||||
.as_ref()
|
|
||||||
.map(|output| output.name())
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Outputs { output_names: names },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Request::GetTagsByOutput { output_name } => {
|
|
||||||
let output = self
|
|
||||||
.space
|
|
||||||
.outputs()
|
|
||||||
.find(|op| op.name() == output_name);
|
|
||||||
if let Some(output) = output {
|
|
||||||
let tag_ids = output.with_state(|state| {
|
|
||||||
state.tags
|
|
||||||
.iter()
|
|
||||||
.map(|tag| tag.id())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::Tags { tag_ids }
|
|
||||||
}).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Request::GetTagActive { tag_id } => {
|
|
||||||
let tag = self
|
|
||||||
.space
|
|
||||||
.outputs()
|
|
||||||
.flat_map(|op| {
|
|
||||||
op.with_state(|state| state.tags.clone())
|
|
||||||
})
|
|
||||||
.find(|tag| tag.id() == tag_id);
|
|
||||||
if let Some(tag) = tag {
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::TagActive {
|
|
||||||
active: tag.active()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Request::GetTagName { tag_id } => {
|
|
||||||
let tag = self
|
|
||||||
.space
|
|
||||||
.outputs()
|
|
||||||
.flat_map(|op| {
|
|
||||||
op.with_state(|state| state.tags.clone())
|
|
||||||
})
|
|
||||||
.find(|tag| tag.id() == tag_id);
|
|
||||||
if let Some(tag) = tag {
|
|
||||||
crate::api::send_to_client(
|
|
||||||
&mut stream,
|
|
||||||
&OutgoingMsg::RequestResponse {
|
|
||||||
response: RequestResponse::TagName {
|
|
||||||
name: tag.name()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Request::GetAllWindows => {
|
||||||
|
let window_ids = self
|
||||||
|
.windows
|
||||||
|
.iter()
|
||||||
|
.map(|win| win.with_state(|state| state.id))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// FIXME: figure out what to do if error
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Windows { window_ids },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("Couldn't send to client");
|
||||||
|
}
|
||||||
|
Request::GetWindowSize { window_id } => {
|
||||||
|
let size = window_id
|
||||||
|
.window(self)
|
||||||
|
.map(|win| (win.geometry().size.w, win.geometry().size.h));
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::WindowSize { size },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetWindowLocation { window_id } => {
|
||||||
|
let loc = window_id
|
||||||
|
.window(self)
|
||||||
|
.and_then(|win| self.space.element_location(&win))
|
||||||
|
.map(|loc| (loc.x, loc.y));
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::WindowLocation { loc },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetWindowClass { window_id } => {
|
||||||
|
let class = window_id.window(self).and_then(|win| {
|
||||||
|
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||||
|
let lock = states
|
||||||
|
.data_map
|
||||||
|
.get::<XdgToplevelSurfaceData>()
|
||||||
|
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
||||||
|
.lock()
|
||||||
|
.expect("failed to acquire lock");
|
||||||
|
lock.app_id.clone()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::WindowClass { class },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetWindowTitle { window_id } => {
|
||||||
|
let title = window_id.window(self).and_then(|win| {
|
||||||
|
compositor::with_states(win.toplevel().wl_surface(), |states| {
|
||||||
|
let lock = states
|
||||||
|
.data_map
|
||||||
|
.get::<XdgToplevelSurfaceData>()
|
||||||
|
.expect("XdgToplevelSurfaceData wasn't in surface's data map")
|
||||||
|
.lock()
|
||||||
|
.expect("failed to acquire lock");
|
||||||
|
lock.title.clone()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::WindowTitle { title },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetWindowFloating { window_id } => {
|
||||||
|
let floating = window_id
|
||||||
|
.window(self)
|
||||||
|
.map(|win| win.with_state(|state| state.floating.is_floating()));
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::WindowFloating { floating },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetOutputByName { output_name } => {
|
||||||
|
// TODO: name better
|
||||||
|
let name = self
|
||||||
|
.space
|
||||||
|
.outputs()
|
||||||
|
.find(|output| output.name() == output_name)
|
||||||
|
.map(|output| output.name());
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Output { output_name: name },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetOutputsByModel { model } => {
|
||||||
|
let names = self
|
||||||
|
.space
|
||||||
|
.outputs()
|
||||||
|
.filter(|output| output.physical_properties().model == model)
|
||||||
|
.map(|output| output.name())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Outputs {
|
||||||
|
output_names: names,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetOutputsByRes { res } => {
|
||||||
|
let names = self
|
||||||
|
.space
|
||||||
|
.outputs()
|
||||||
|
.filter_map(|output| {
|
||||||
|
if let Some(mode) = output.current_mode() {
|
||||||
|
if mode.size == (res.0 as i32, res.1 as i32).into() {
|
||||||
|
Some(output.name())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Outputs {
|
||||||
|
output_names: names,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetOutputByFocus => {
|
||||||
|
let name = self
|
||||||
|
.focus_state
|
||||||
|
.focused_output
|
||||||
|
.as_ref()
|
||||||
|
.map(|output| output.name());
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Output { output_name: name },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
Request::GetTagsByOutput { output_name } => {
|
||||||
|
let output = self.space.outputs().find(|op| op.name() == output_name);
|
||||||
|
if let Some(output) = output {
|
||||||
|
let tag_ids = output.with_state(|state| {
|
||||||
|
state.tags.iter().map(|tag| tag.id()).collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
crate::api::send_to_client(
|
||||||
|
&mut stream,
|
||||||
|
&OutgoingMsg::RequestResponse {
|
||||||
|
response: RequestResponse::Tags { tag_ids },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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() },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to send to client");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,9 +744,19 @@ impl<B: Backend> State<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn re_layout(&mut self, output: &Output) {
|
pub fn re_layout(&mut self, output: &Output) {
|
||||||
let windows = self.windows.iter().filter(|win| {
|
let windows = self
|
||||||
win.with_state(|state| state.tags.iter().any(|tag| self.output_for_tag(tag).is_some_and(|op| &op == output)))
|
.windows
|
||||||
}).cloned().collect::<Vec<_>>();
|
.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 (render, do_not_render) = output.with_state(|state| {
|
||||||
let first_tag = state.focused_tags().next();
|
let first_tag = state.focused_tags().next();
|
||||||
if let Some(first_tag) = first_tag {
|
if let Some(first_tag) = first_tag {
|
||||||
|
@ -716,13 +813,15 @@ impl<B: Backend> State<B> {
|
||||||
}
|
}
|
||||||
/// Schedule something to be done when windows have finished committing and have become
|
/// Schedule something to be done when windows have finished committing and have become
|
||||||
/// idle.
|
/// idle.
|
||||||
pub fn schedule_on_commit<F, B: Backend>(data: &mut CalloopData<B>, windows: Vec<Window>, on_commit: F)
|
pub fn schedule_on_commit<F, B: Backend>(
|
||||||
where
|
data: &mut CalloopData<B>,
|
||||||
|
windows: Vec<Window>,
|
||||||
|
on_commit: F,
|
||||||
|
) where
|
||||||
F: FnOnce(&mut CalloopData<B>) + 'static,
|
F: FnOnce(&mut CalloopData<B>) + 'static,
|
||||||
{
|
{
|
||||||
for window in windows.iter() {
|
for window in windows.iter() {
|
||||||
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle))
|
if window.with_state(|state| !matches!(state.resize_state, WindowResizeState::Idle)) {
|
||||||
{
|
|
||||||
data.state.loop_handle.insert_idle(|data| {
|
data.state.loop_handle.insert_idle(|data| {
|
||||||
schedule_on_commit(data, windows, on_commit);
|
schedule_on_commit(data, windows, on_commit);
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,18 +15,30 @@ use smithay::{
|
||||||
utils::{Logical, Point, Serial, Size},
|
utils::{Logical, Point, Serial, Size},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{state::WithState, tag::Tag};
|
use crate::{
|
||||||
|
backend::Backend,
|
||||||
|
state::{State, WithState},
|
||||||
|
tag::Tag,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Hash, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WindowId(u32);
|
pub struct WindowId(u32);
|
||||||
|
|
||||||
// TODO: this probably doesn't need to be atomic
|
|
||||||
static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
static WINDOW_ID_COUNTER: AtomicU32 = AtomicU32::new(0);
|
||||||
|
|
||||||
impl WindowId {
|
impl WindowId {
|
||||||
pub fn next() -> Self {
|
pub fn next() -> Self {
|
||||||
Self(WINDOW_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
Self(WINDOW_ID_COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the window that has this WindowId.
|
||||||
|
pub fn window<B: Backend>(&self, state: &State<B>) -> Option<Window> {
|
||||||
|
state
|
||||||
|
.windows
|
||||||
|
.iter()
|
||||||
|
.find(|win| win.with_state(|state| &state.id == self))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WindowState {
|
pub struct WindowState {
|
||||||
|
|
Loading…
Reference in a new issue