Merge pull request #41 from Ottatop/dev

Add output location setting
This commit is contained in:
Ottatop 2023-08-04 17:07:25 -05:00 committed by GitHub
commit 1f53eb92a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 415 additions and 48 deletions

View file

@ -121,6 +121,28 @@ function output_module.tags(op) end
---@see Output.add_tags
function output_module.add_tags(op, ...) end
---Set the specified output's location.
---
---@usage
----- Assuming DP-1 is 2560x1440 and DP-2 is 1920x1080...
---local dp1 = output.get_by_name("DP-1")
---local dp2 = output.get_by_name("DP-2")
---
----- Place DP-2 to the left of DP-1, top borders aligned
---output.set_loc(dp1, { x = 1920, y = 0 })
---output.set_loc(dp2, { x = 0, y = 0 })
---
----- Do the same as above, with a different origin
---output.set_loc(dp1, { x = 0, y = 0 })
---output.set_loc(dp2, { x = -1920, y = 0 })
---
----- Place DP-2 to the right of DP-1, bottom borders aligned
---output.set_loc(dp1, { x = 0, y = 0 })
---output.set_loc(dp2, { x = 2560, y = 1440 - 1080 })
---@tparam Output op
---@tparam table loc A table of the form `{ x: integer?, y: integer? }`
function output_module.set_loc(op, loc) end
----------------------------------------------------------
---The output object.
@ -176,3 +198,86 @@ function output:physical_size() end
---@treturn boolean|nil
---@see OutputModule.focused
function output:focused() end
---Set this output's location.
---
---@usage
--- -- Assuming DP-1 is 2560x1440 and DP-2 is 1920x1080...
---local dp1 = output.get_by_name("DP-1")
---local dp2 = output.get_by_name("DP-2")
---
--- -- Place DP-2 to the left of DP-1, top borders aligned
---dp1:set_loc({ x = 1920, y = 0 })
---dp2:set_loc({ x = 0, y = 0 })
---
--- -- Do the same as above, with a different origin
---dp1:set_loc({ x = 0, y = 0 })
---dp2:set_loc({ x = -1920, y = 0 })
---
--- -- Place DP-2 to the right of DP-1, bottom borders aligned
---dp1:set_loc({ x = 0, y = 0 })
---dp2:set_loc({ x = 2560, y = 1440 - 1080 })
---@tparam table loc A table of the form `{ x: integer?, y: integer? }`
function output:set_loc(loc) end
---Set this output's location to the right of `op`.
---
--- top center bottom
--- ┌────────┬──────┐ ┌────────┐ ┌────────┐
--- │op │self │ │op ├──────┐ │op │
--- │ ├──────┘ │ │self │ │ ├──────┐
--- │ │ │ ├──────┘ │ │self │
--- └────────┘ └────────┘ └────────┴──────┘
---
---This will fail if `op` is an invalid output.
---@tparam Output op
---@tparam[opt="top"] string alignment One of `top`, `center`, or `bottom`. This is how you want to align the `self` output.
---@see Output.set_loc
function output:set_loc_right_of(op, alignment) end
---Set this output's location to the left of `op`.
---
--- top center bottom
--- ┌──────┬────────┐ ┌────────┐ ┌────────┐
--- │self │op │ ┌──────┤op │ │op │
--- └──────┤ │ │self │ │ ┌──────┤ │
--- │ │ └──────┤ │ │self │ │
--- └────────┘ └────────┘ └──────┴────────┘
---
---This will fail if `op` is an invalid output.
---@tparam Output op
---@tparam[opt="top"] string alignment One of `top`, `center`, or `bottom`. This is how you want to align the `self` output.
---@see Output.set_loc
function output:set_loc_left_of(op, alignment) end
---Set this output's location to the top of `op`.
---
--- left center right
--- ┌──────┐ ┌──────┐ ┌──────┐
--- │self │ │self │ │self │
--- ├──────┴─┐ ┌┴──────┴┐ ┌─┴──────┤
--- │op │ │op │ │op │
--- │ │ │ │ │ │
--- └────────┘ └────────┘ └────────┘
---
---This will fail if `op` is an invalid output.
---@tparam Output op
---@tparam[opt="left"] string alignment One of `left`, `center`, or `right`. This is how you want to align the `self` output.
---@see Output.set_loc
function output:set_loc_top_of(op, alignment) end
---Set this output's location to the bottom of `op`.
---
--- ┌────────┐ ┌────────┐ ┌────────┐
--- │op │ │op │ │op │
--- │ │ │ │ │ │
--- ├──────┬─┘ └┬──────┬┘ └─┬──────┤
--- │self │ │self │ │self │
--- └──────┘ └──────┘ └──────┘
--- left center right
---
---This will fail if `op` is an invalid output.
---@tparam Output op
---@tparam[opt="left"] string alignment One of `left`, `center`, or `right`. This is how you want to align the `self` output.
---@see Output.set_loc
function output:set_loc_bottom_of(op, alignment) end

View file

@ -4,15 +4,6 @@
---@module TagModule
local tag_module = {}
---@alias Layout
---| "MasterStack" # One master window on the left with all other windows stacked to the right.
---| "Dwindle" # Windows split in half towards the bottom right corner.
---| "Spiral" # Windows split in half in a spiral.
---| "CornerTopLeft" # One main corner window in the top left with a column of windows on the right and a row on the bottom.
---| "CornerTopRight" # One main corner window in the top right with a column of windows on the left and a row on the bottom.
---| "CornerBottomLeft" # One main corner window in the bottom left with a column of windows on the right and a row on the top.
---| "CornerBottomRight" # One main corner window in the bottom right with a column of windows on the left and a row on the top.
---Add tags to the specified output.
---
---@usage

View file

@ -27,6 +27,15 @@ require("pinnacle").setup(function(pinnacle)
local terminal = "alacritty"
-- Outputs -----------------------------------------------------------------------
-- You can set your own monitor layout as I have done below for my monitors.
-- local lg = output.get_by_name("DP-2") --[[@as Output]]
-- local dell = output.get_by_name("DP-3") --[[@as Output]]
--
-- dell:set_loc_left_of(lg, "bottom")
-- Keybinds ----------------------------------------------------------------------
input.keybind({ mod_key, "Alt" }, keys.q, pinnacle.quit)

View file

@ -21,6 +21,7 @@
---@field SetLayout { tag_id: TagId, layout: Layout }?
--Outputs
---@field ConnectForAllOutputs { callback_id: integer }?
---@field SetOutputLocation { output_name: OutputName, x: integer?, y: integer? }?
---@alias Msg _Msg | "Quit"

View file

@ -93,6 +93,203 @@ function output:focused()
return output_module.focused(self)
end
---Set this output's location.
---
---### Examples
---```lua
----- Assuming DP-1 is 2560x1440 and DP-2 is 1920x1080...
---local dp1 = output.get_by_name("DP-1")
---local dp2 = output.get_by_name("DP-2")
---
----- Place DP-2 to the left of DP-1, top borders aligned
---dp1:set_loc({ x = 1920, y = 0 })
---dp2:set_loc({ x = 0, y = 0 })
---
----- Do the same as above, with a different origin
---dp1:set_loc({ x = 0, y = 0 })
---dp2:set_loc({ x = -1920, y = 0 })
---
----- Place DP-2 to the right of DP-1, bottom borders aligned
---dp1:set_loc({ x = 0, y = 0 })
---dp2:set_loc({ x = 2560, y = 1440 - 1080 })
---```
---@param loc { x: integer?, y: integer? }
function output:set_loc(loc)
output_module.set_loc(self, loc)
end
-- TODO: move this into own file or something ---------------------------------------------
---@alias AlignmentVertical
---| "top" Align the tops of the outputs
---| "center" Center the outputs vertically
---| "bottom" Align the bottoms of the outputs
---@alias AlignmentHorizontal
---| "left" Align the left edges of the outputs
---| "center" Center the outputs vertically
---| "right" Align the right edges of the outputs
---@param op1 Output
---@param op2 Output
---@param left_or_right "left" | "right"
---@param alignment AlignmentVertical? How you want to align the `self` output. Defaults to `top`.
local function set_loc_horizontal(op1, op2, left_or_right, alignment)
local alignment = alignment or "top"
local self_loc = op1:loc()
local self_res = op1:res()
local other_loc = op2:loc()
local other_res = op2:res()
if
self_loc == nil
or self_res == nil
or other_loc == nil
or other_res == nil
then
return
end
---@type integer
local x
if left_or_right == "left" then
x = other_loc.x - self_res.w
else
x = other_loc.x + other_res.w
end
if alignment == "top" then
output_module.set_loc(op1, { x = x, y = other_loc.y })
elseif alignment == "center" then
output_module.set_loc(
op1,
{ x = x, y = other_loc.y + (other_res.h - self_res.h) // 2 }
)
elseif alignment == "bottom" then
output_module.set_loc(
op1,
{ x = x, y = other_loc.y + (other_res.h - self_res.h) }
)
end
end
---Set this output's location to the right of the specified output.
---
---```
--- top center bottom
--- ┌────────┬──────┐ ┌────────┐ ┌────────┐
--- │op │self │ │op ├──────┐ │op │
--- │ ├──────┘ │ │self │ │ ├──────┐
--- │ │ │ ├──────┘ │ │self │
--- └────────┘ └────────┘ └────────┴──────┘
---```
---This will fail if `op` is an invalid output.
---@param op Output
---@param alignment AlignmentVertical? How you want to align the `self` output. Defaults to `top`.
---@see Output.set_loc if you need more granular control
function output:set_loc_right_of(op, alignment)
set_loc_horizontal(self, op, "right", alignment)
end
---Set this output's location to the left of the specified output.
---
---```
--- top center bottom
--- ┌──────┬────────┐ ┌────────┐ ┌────────┐
--- │self │op │ ┌──────┤op │ │op │
--- └──────┤ │ │self │ │ ┌──────┤ │
--- │ │ └──────┤ │ │self │ │
--- └────────┘ └────────┘ └──────┴────────┘
---```
---This will fail if `op` is an invalid output.
---@param op Output
---@param alignment AlignmentVertical? How you want to align the `self` output. Defaults to `top`.
---@see Output.set_loc if you need more granular control
function output:set_loc_left_of(op, alignment)
set_loc_horizontal(self, op, "left", alignment)
end
---@param op1 Output
---@param op2 Output
---@param top_or_bottom "top" | "bottom"
---@param alignment AlignmentHorizontal? How you want to align the `self` output. Defaults to `top`.
local function set_loc_vertical(op1, op2, top_or_bottom, alignment)
local alignment = alignment or "left"
local self_loc = op1:loc()
local self_res = op1:res()
local other_loc = op2:loc()
local other_res = op2:res()
if
self_loc == nil
or self_res == nil
or other_loc == nil
or other_res == nil
then
return
end
---@type integer
local y
if top_or_bottom == "top" then
y = other_loc.y - self_res.h
else
y = other_loc.y + other_res.h
end
if alignment == "left" then
output_module.set_loc(op1, { x = other_loc.x, y = y })
elseif alignment == "center" then
output_module.set_loc(
op1,
{ x = other_loc.x + (other_res.w - self_res.w) // 2, y = y }
)
elseif alignment == "right" then
output_module.set_loc(
op1,
{ x = other_loc.x + (other_res.w - self_res.w), y = y }
)
end
end
---Set this output's location to the top of the specified output.
---
---```
--- left center right
--- ┌──────┐ ┌──────┐ ┌──────┐
--- │self │ │self │ │self │
--- ├──────┴─┐ ┌┴──────┴┐ ┌─┴──────┤
--- │op │ │op │ │op │
--- │ │ │ │ │ │
--- └────────┘ └────────┘ └────────┘
---```
---This will fail if `op` is an invalid output.
---@param op Output
---@param alignment AlignmentHorizontal? How you want to align the `self` output. Defaults to `left`.
---@see Output.set_loc if you need more granular control
function output:set_loc_top_of(op, alignment)
set_loc_vertical(self, op, "top", alignment)
end
---Set this output's location to the bottom of the specified output.
---
---```
--- ┌────────┐ ┌────────┐ ┌────────┐
--- │op │ │op │ │op │
--- │ │ │ │ │ │
--- ├──────┬─┘ └┬──────┬┘ └─┬──────┤
--- │self │ │self │ │self │
--- └──────┘ └──────┘ └──────┘
--- left center right
---```
---This will fail if `op` is an invalid output.
---@param op Output
---@param alignment AlignmentHorizontal? How you want to align the `self` output. Defaults to `left`.
---@see Output.set_loc if you need more granular control
function output:set_loc_bottom_of(op, alignment)
set_loc_vertical(self, op, "bottom", alignment)
end
------------------------------------------------------
---Get an output by its name.
@ -104,7 +301,7 @@ end
---### Example
---```lua
---local monitor = output.get_by_name("DP-1")
---print(monitor.name) -- should print `DP-1`
---print(monitor:name()) -- should print `DP-1`
---```
---@param name string The name of the output.
---@return Output|nil output The output, or nil if none have the provided name.
@ -240,6 +437,8 @@ function output_module.get_for_tag(tag)
end
end
---------Fully-qualified functions
---Get the specified output's make.
---@param op Output
---@return string|nil
@ -369,4 +568,36 @@ function output_module.add_tags(op, ...)
require("tag").add(op, ...)
end
---Set the specified output's location.
---
---### Examples
---```lua
----- Assuming DP-1 is 2560x1440 and DP-2 is 1920x1080...
---local dp1 = output.get_by_name("DP-1")
---local dp2 = output.get_by_name("DP-2")
---
----- Place DP-2 to the left of DP-1, top borders aligned
---output.set_loc(dp1, { x = 1920, y = 0 })
---output.set_loc(dp2, { x = 0, y = 0 })
---
----- Do the same as above, with a different origin
---output.set_loc(dp1, { x = 0, y = 0 })
---output.set_loc(dp2, { x = -1920, y = 0 })
---
----- Place DP-2 to the right of DP-1, bottom borders aligned
---output.set_loc(dp1, { x = 0, y = 0 })
---output.set_loc(dp2, { x = 2560, y = 1440 - 1080 })
---```
---@param op Output
---@param loc { x: integer?, y: integer? }
function output_module.set_loc(op, loc)
SendMsg({
SetOutputLocation = {
output_name = op:name(),
x = loc.x,
y = loc.y,
},
})
end
return output_module

View file

@ -66,10 +66,15 @@ require("pinnacle").setup(function(pinnacle)
-- Just testing stuff
input.keybind({ mod_key }, keys.h, function()
local win = window.get_focused()
if win ~= nil then
win:set_size({ w = 500, h = 500 })
end
local dp2 = output.get_by_name("DP-2")
local dp3 = output.get_by_name("DP-3")
dp2:set_loc_bottom_of(dp3, "right")
-- local win = window.get_focused()
-- if win ~= nil then
-- win:set_size({ w = 500, h = 500 })
-- end
-- local wins = window.get_all()
-- for _, win in pairs(wins) do

View file

@ -3,7 +3,7 @@
// 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::{layout::Layout, tag::TagId, window::window_state::WindowId};
use crate::{layout::Layout, output::OutputName, tag::TagId, window::window_state::WindowId};
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy)]
pub struct CallbackId(pub u32);
@ -68,6 +68,13 @@ pub enum Msg {
ConnectForAllOutputs {
callback_id: CallbackId,
},
SetOutputLocation {
output_name: OutputName,
#[serde(default)]
x: Option<i32>,
#[serde(default)]
y: Option<i32>,
},
// Process management
/// Spawn a program with an optional callback.

View file

@ -440,6 +440,7 @@ impl State<UdevData> {
// InputEvent::DeviceRemoved { device } => todo!(),
InputEvent::Keyboard { event } => self.keyboard::<B>(event),
InputEvent::PointerMotion { event } => self.pointer_motion::<B>(event),
// currently does not seem to use absolute
InputEvent::PointerMotionAbsolute { event } => self.pointer_motion_absolute::<B>(event),
InputEvent::PointerButton { event } => self.pointer_button::<B>(event),
InputEvent::PointerAxis { event } => self.pointer_axis::<B>(event),
@ -557,38 +558,26 @@ impl State<UdevData> {
}
let (pos_x, pos_y) = pos.into();
let max_x = self.space.outputs().fold(0, |acc, o| {
acc + self
.space
.output_geometry(o)
.expect("Output geometry doesn't exist")
.size
.w
});
let clamped_x = pos_x.clamp(0.0, max_x as f64);
let max_y = self
.space
.outputs()
.find(|o| {
let geo = self
.space
.output_geometry(o)
.expect("Output geometry doesn't exist");
geo.contains((clamped_x as i32, 0))
})
.map(|o| {
self.space
.output_geometry(o)
.expect("Output geometry doesn't exist")
.size
.h
});
if let Some(max_y) = max_y {
let clamped_y = pos_y.clamp(0.0, max_y as f64);
(clamped_x, clamped_y).into()
} else {
(clamped_x, pos_y).into()
}
let nearest_points = self.space.outputs().map(|op| {
let size = self
.space
.output_geometry(op)
.expect("called output_geometry on unmapped output")
.size;
let loc = op.current_location();
let pos_x = pos_x.clamp(loc.x as f64, (loc.x + size.w) as f64);
let pos_y = pos_y.clamp(loc.y as f64, (loc.y + size.h) as f64);
(pos_x, pos_y)
});
let nearest_point = nearest_points.min_by(|(x1, y1), (x2, y2)| {
f64::total_cmp(
&((pos_x - x1).powi(2) + (pos_y - y1).powi(2)).sqrt(),
&((pos_x - x2).powi(2) + (pos_y - y2).powi(2)).sqrt(),
)
});
nearest_point.map(|point| point.into()).unwrap_or(pos)
}
}

View file

@ -4,11 +4,26 @@ use std::cell::RefCell;
use smithay::output::Output;
use crate::{state::WithState, tag::Tag};
use crate::{
backend::Backend,
state::{State, WithState},
tag::Tag,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
pub struct OutputName(pub String);
impl OutputName {
/// Get the output with this name.
pub fn output<B: Backend>(&self, state: &State<B>) -> Option<Output> {
state
.space
.outputs()
.find(|output| output.name() == self.0)
.cloned()
}
}
#[derive(Default)]
pub struct OutputState {
pub tags: Vec<Tag>,

View file

@ -272,6 +272,20 @@ impl<B: Backend> State<B> {
}
self.output_callback_ids.push(callback_id);
}
Msg::SetOutputLocation { output_name, x, y } => {
let Some(output) = output_name.output(self) else { return };
let mut loc = output.current_location();
if let Some(x) = x {
loc.x = x;
}
if let Some(y) = y {
loc.y = y;
}
output.change_current_state(None, None, None, Some(loc));
self.space.map_output(&output, loc);
tracing::debug!("mapping output {} to {loc:?}", output.name());
self.re_layout(&output);
}
Msg::Quit => {
self.loop_signal.stop();