Skip to content

Commit 669b0d4

Browse files
Ktrompflclaude
andcommitted
merge set-size and set-position
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 50cb149 commit 669b0d4

12 files changed

Lines changed: 364 additions & 188 deletions

File tree

jay-config/src/_private/client.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -607,18 +607,28 @@ impl ConfigClient {
607607
(width, height)
608608
}
609609

610-
pub fn set_window_size(&self, window: Window, width: i32, height: i32) {
611-
self.send(&ClientMessage::SetWindowSize {
610+
#[expect(clippy::too_many_arguments)]
611+
pub fn set_window_position(
612+
&self,
613+
window: Window,
614+
x1: Option<i32>,
615+
y1: Option<i32>,
616+
x2: Option<i32>,
617+
y2: Option<i32>,
618+
width: Option<i32>,
619+
height: Option<i32>,
620+
) {
621+
self.send(&ClientMessage::SetWindowPosition {
612622
window,
623+
x1,
624+
y1,
625+
x2,
626+
y2,
613627
width,
614628
height,
615629
});
616630
}
617631

618-
pub fn set_window_position(&self, window: Window, x: i32, y: i32) {
619-
self.send(&ClientMessage::SetWindowPosition { window, x, y });
620-
}
621-
622632
pub fn get_workspace_position(&self, workspace: Workspace) -> (i32, i32) {
623633
let res = self.send_with_response(&ClientMessage::GetWorkspacePosition { workspace });
624634
get_response!(res, (0, 0), GetWorkspacePosition { x, y });

jay-config/src/_private/ipc.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -984,15 +984,14 @@ pub enum ClientMessage<'a> {
984984
GetWindowSize {
985985
window: Window,
986986
},
987-
SetWindowSize {
988-
window: Window,
989-
width: i32,
990-
height: i32,
991-
},
992987
SetWindowPosition {
993988
window: Window,
994-
x: i32,
995-
y: i32,
989+
x1: Option<i32>,
990+
y1: Option<i32>,
991+
x2: Option<i32>,
992+
y2: Option<i32>,
993+
width: Option<i32>,
994+
height: Option<i32>,
996995
},
997996
GetWorkspacePosition {
998997
workspace: Workspace,

jay-config/src/input.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -713,16 +713,19 @@ impl Seat {
713713
self.window().resize(dx1, dy1, dx2, dy2);
714714
}
715715

716-
/// Resizes the focused window to an absolute size.
717-
pub fn set_size(self, width: i32, height: i32) {
718-
self.window().set_size(width, height);
719-
}
720-
721-
/// Sets the position of the focused window to an absolute value.
716+
/// Sets the position and/or size of the focused window.
722717
///
723-
/// This only has an effect if the window is floating.
724-
pub fn set_position(self, x: i32, y: i32) {
725-
self.window().set_position(x, y);
718+
/// See [`Window::set_position`](crate::window::Window::set_position) for details.
719+
pub fn set_position(
720+
self,
721+
x1: Option<i32>,
722+
y1: Option<i32>,
723+
x2: Option<i32>,
724+
y2: Option<i32>,
725+
width: Option<i32>,
726+
height: Option<i32>,
727+
) {
728+
self.window().set_position(x1, y1, x2, y2, width, height);
726729
}
727730

728731
/// Sets whether the cursor should automatically move to the center of a window

jay-config/src/window.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -252,19 +252,31 @@ impl Window {
252252
get!((0, 0)).get_window_size(self)
253253
}
254254

255-
/// Resizes the window to an absolute size.
256-
///
257-
/// This has the same effect as [`resize`](Self::resize) but takes the target size
258-
/// instead of a delta.
259-
pub fn set_size(self, width: i32, height: i32) {
260-
get!().set_window_size(self, width, height);
261-
}
262-
263-
/// Sets the position of the window in the global compositor space.
255+
/// Sets the position and/or size of the window.
264256
///
265257
/// This only has an effect if the window is floating.
266-
pub fn set_position(self, x: i32, y: i32) {
267-
get!().set_window_position(self, x, y);
258+
///
259+
/// Every argument that is `None` is left unconstrained by this call. Every argument that
260+
/// is `Some(_)` is constrained to that value.
261+
///
262+
/// Since `x2 = x1 + width` (and analogously for the y axis), not every combination of
263+
/// constraints is satisfiable. If `x1`, `x2`, and `width` are all constrained but not
264+
/// self-consistent (and analogously for `y1`, `y2`, `height`), this call has no effect.
265+
///
266+
/// If fewer than two of `x1`, `x2`, `width` are constrained (and analogously for `y1`,
267+
/// `y2`, `height`), the missing values default to preserving the window's current size,
268+
/// e.g. setting only `width` keeps `x1` fixed and moves `x2`, while setting only `x1` keeps
269+
/// the width fixed and moves `x2` along with it.
270+
pub fn set_position(
271+
self,
272+
x1: Option<i32>,
273+
y1: Option<i32>,
274+
x2: Option<i32>,
275+
y2: Option<i32>,
276+
width: Option<i32>,
277+
height: Option<i32>,
278+
) {
279+
get!().set_window_position(self, x1, y1, x2, y2, width, height);
268280
}
269281
}
270282

src/config/handler.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3046,21 +3046,24 @@ impl ConfigProxyHandler {
30463046
Ok(())
30473047
}
30483048

3049-
fn handle_set_window_size(
3049+
fn handle_set_window_position(
30503050
&self,
30513051
window: Window,
3052-
width: i32,
3053-
height: i32,
3052+
x1: Option<i32>,
3053+
y1: Option<i32>,
3054+
x2: Option<i32>,
3055+
y2: Option<i32>,
3056+
width: Option<i32>,
3057+
height: Option<i32>,
30543058
) -> Result<(), CphError> {
3055-
if width <= 0 || height <= 0 {
3056-
return Err(CphError::InvalidWindowSize(width, height));
3059+
let tl = self.get_window(window)?;
3060+
let pos = tl.node_absolute_position(LiveTL);
3061+
let (x1, x2) = resolve_geometry_axis(x1, x2, width, pos.x1(), pos.x2())?;
3062+
let (y1, y2) = resolve_geometry_axis(y1, y2, height, pos.y1(), pos.y2())?;
3063+
if x2 - x1 <= 0 || y2 - y1 <= 0 {
3064+
return Err(CphError::InvalidWindowSize(x2 - x1, y2 - y1));
30573065
}
3058-
self.get_window(window)?.tl_set_size(width, height);
3059-
Ok(())
3060-
}
3061-
3062-
fn handle_set_window_position(&self, window: Window, x: i32, y: i32) -> Result<(), CphError> {
3063-
self.get_window(window)?.tl_set_position(x, y);
3066+
tl.tl_set_geometry(x1, y1, x2, y2);
30643067
Ok(())
30653068
}
30663069

@@ -3995,15 +3998,16 @@ impl ConfigProxyHandler {
39953998
ClientMessage::GetWindowSize { window } => {
39963999
self.handle_get_window_size(window).wrn("get_window_size")?
39974000
}
3998-
ClientMessage::SetWindowSize {
4001+
ClientMessage::SetWindowPosition {
39994002
window,
4003+
x1,
4004+
y1,
4005+
x2,
4006+
y2,
40004007
width,
40014008
height,
40024009
} => self
4003-
.handle_set_window_size(window, width, height)
4004-
.wrn("set_window_size")?,
4005-
ClientMessage::SetWindowPosition { window, x, y } => self
4006-
.handle_set_window_position(window, x, y)
4010+
.handle_set_window_position(window, x1, y1, x2, y2, width, height)
40074011
.wrn("set_window_position")?,
40084012
ClientMessage::GetWorkspacePosition { workspace } => self
40094013
.handle_get_workspace_position(workspace)
@@ -4175,6 +4179,42 @@ impl ConfigProxyHandler {
41754179
}
41764180
}
41774181

4182+
/// Resolves the constraints on a single axis (x1/x2/width or y1/y2/height) of
4183+
/// [`ConfigProxyHandler::handle_set_window_position`] into a concrete `(c1, c2)` pair.
4184+
///
4185+
/// `c1`, `c2` and `size` are constrained to their contained value if they are `Some(_)` and
4186+
/// are otherwise unconstrained.
4187+
///
4188+
/// If two of the three are constrained, the third is derived from them. If only one is
4189+
/// constrained, the window keeps its current size (if `c1` or `c2` is constrained) or its
4190+
/// current position (if only `size` is constrained). If none are constrained, the axis is
4191+
/// left unchanged. If all three are constrained but not self-consistent, an error is returned.
4192+
fn resolve_geometry_axis(
4193+
c1: Option<i32>,
4194+
c2: Option<i32>,
4195+
size: Option<i32>,
4196+
cur_c1: i32,
4197+
cur_c2: i32,
4198+
) -> Result<(i32, i32), CphError> {
4199+
let cur_size = cur_c2.saturating_sub(cur_c1);
4200+
let res = match (c1, c2, size) {
4201+
(Some(c1), Some(c2), Some(size)) => {
4202+
if c2.saturating_sub(c1) != size {
4203+
return Err(CphError::InconsistentWindowGeometry);
4204+
}
4205+
(c1, c2)
4206+
}
4207+
(Some(c1), Some(c2), None) => (c1, c2),
4208+
(Some(c1), None, Some(size)) => (c1, c1.saturating_add(size)),
4209+
(None, Some(c2), Some(size)) => (c2.saturating_sub(size), c2),
4210+
(Some(c1), None, None) => (c1, c1.saturating_add(cur_size)),
4211+
(None, Some(c2), None) => (c2.saturating_sub(cur_size), c2),
4212+
(None, None, Some(size)) => (cur_c1, cur_c1.saturating_add(size)),
4213+
(None, None, None) => (cur_c1, cur_c2),
4214+
};
4215+
Ok(res)
4216+
}
4217+
41784218
#[derive(Debug, Error)]
41794219
enum CphError {
41804220
#[error("Tried to set an unknown accel profile: {}", (.0).0)]
@@ -4207,6 +4247,8 @@ enum CphError {
42074247
InvalidConnectorPosition(i32, i32),
42084248
#[error("{0}x{1} is not a valid window size")]
42094249
InvalidWindowSize(i32, i32),
4250+
#[error("The given x1/x2/width or y1/y2/height constraints are not self-consistent")]
4251+
InconsistentWindowGeometry,
42104252
#[error("Keymap {0:?} does not exist")]
42114253
KeymapDoesNotExist(Keymap),
42124254
#[error("Seat {0:?} does not exist")]

src/tree/toplevel.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,7 @@ pub trait ToplevelNode: ToplevelNodeBase {
7979
fn tl_mark_ancestor_fullscreen(&self, fullscreen: bool);
8080
fn tl_mark_fullscreen(&self, connector: Option<&Rc<ConnectorData>>);
8181
fn tl_resize(&self, dx1: i32, dy1: i32, dx2: i32, dy2: i32);
82-
fn tl_set_size(&self, width: i32, height: i32);
83-
fn tl_set_position(&self, x: i32, y: i32);
82+
fn tl_set_geometry(&self, x1: i32, y1: i32, x2: i32, y2: i32);
8483
}
8584

8685
impl<T: ToplevelNodeBase> ToplevelNode for T {
@@ -307,21 +306,11 @@ impl<T: ToplevelNodeBase> ToplevelNode for T {
307306
parent.cnode_resize_child(self, x1, y1, x2, y2);
308307
}
309308

310-
fn tl_set_size(&self, width: i32, height: i32) {
309+
fn tl_set_geometry(&self, x1: i32, y1: i32, x2: i32, y2: i32) {
311310
let Some(parent) = self.tl_data().parent.get() else {
312311
return;
313312
};
314-
let pos = self.node_absolute_position(LiveTL);
315-
let x2 = pos.x1().saturating_add(width);
316-
let y2 = pos.y1().saturating_add(height);
317-
parent.cnode_resize_child(self, None, None, Some(x2), Some(y2));
318-
}
319-
320-
fn tl_set_position(&self, x: i32, y: i32) {
321-
let Some(parent) = self.tl_data().parent.get() else {
322-
return;
323-
};
324-
parent.cnode_set_child_position(self, x, y);
313+
parent.cnode_resize_child(self, Some(x1), Some(y1), Some(x2), Some(y2));
325314
}
326315
}
327316

toml-config/src/config.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,17 +211,17 @@ pub enum Action {
211211
dx2: i32,
212212
dy2: i32,
213213
},
214-
SetSize {
215-
width: i32,
216-
height: i32,
217-
},
218-
SetPosition {
219-
x: i32,
220-
y: i32,
221-
},
222214
HideOverlay {
223215
ws: Rc<WorkspaceSlot>,
224216
},
217+
SetPosition {
218+
x1: Option<i32>,
219+
y1: Option<i32>,
220+
x2: Option<i32>,
221+
y2: Option<i32>,
222+
width: Option<i32>,
223+
height: Option<i32>,
224+
},
225225
}
226226

227227
#[derive(Debug)]

toml-config/src/config/parsers/action.rs

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ pub enum ShowWorkspaceError {
111111
FallbackOutputModeParser(FallbackOutputModeParserError),
112112
}
113113

114+
/// Extracts a field that should either be an integer or the string `"keep"`.
115+
///
116+
/// `"keep"` is represented as `None`, an integer `n` is represented as `Some(n)`.
117+
fn coordinate(
118+
name: &'static str,
119+
) -> impl for<'v, 'w> FnOnce(
120+
&mut Extractor<'v, 'w>,
121+
) -> Result<Spanned<Option<i32>>, Spanned<ExtractorError>> {
122+
move |extractor: &mut Extractor| {
123+
val(name)(extractor).and_then(|v| match v.value {
124+
Value::String(s) if s.as_str() == "keep" => Ok(None.spanned(v.span)),
125+
Value::Integer(i) => match i32::try_from(*i) {
126+
Ok(n) => Ok(Some(n).spanned(v.span)),
127+
Err(_) => Err(ExtractorError::I32.spanned(v.span)),
128+
},
129+
_ => Err(
130+
ExtractorError::Expected("an integer or \"keep\"", v.value.name())
131+
.spanned(v.span),
132+
),
133+
})
134+
}
135+
}
136+
114137
pub struct ActionParser<'a, 'b>(pub &'a Context<'b>);
115138

116139
impl ActionParser<'_, '_> {
@@ -601,19 +624,22 @@ impl ActionParser<'_, '_> {
601624
})
602625
}
603626

604-
fn parse_set_size(&mut self, ext: &mut Extractor<'_, '_>) -> ParseResult<Self> {
605-
let (width, height) = ext.extract((s32("width"), s32("height")))?;
606-
Ok(Action::SetSize {
607-
width: width.value,
608-
height: height.value,
609-
})
610-
}
611-
612627
fn parse_set_position(&mut self, ext: &mut Extractor<'_, '_>) -> ParseResult<Self> {
613-
let (x, y) = ext.extract((s32("x"), s32("y")))?;
628+
let (x1, y1, x2, y2, width, height) = ext.extract((
629+
opt(coordinate("x1")),
630+
opt(coordinate("y1")),
631+
opt(coordinate("x2")),
632+
opt(coordinate("y2")),
633+
opt(coordinate("width")),
634+
opt(coordinate("height")),
635+
))?;
614636
Ok(Action::SetPosition {
615-
x: x.value,
616-
y: y.value,
637+
x1: x1.despan().flatten(),
638+
y1: y1.despan().flatten(),
639+
x2: x2.despan().flatten(),
640+
y2: y2.despan().flatten(),
641+
width: width.despan().flatten(),
642+
height: height.despan().flatten(),
617643
})
618644
}
619645

@@ -690,7 +716,6 @@ impl Parser for ActionParser<'_, '_> {
690716
"create-virtual-output" => self.parse_create_virtual_output(&mut ext),
691717
"remove-virtual-output" => self.parse_remove_virtual_output(&mut ext),
692718
"resize" => self.parse_resize(&mut ext),
693-
"set-size" => self.parse_set_size(&mut ext),
694719
"set-position" => self.parse_set_position(&mut ext),
695720
"hide-overlay" => self.parse_hide_overlay(&mut ext),
696721
"show-overlay" => self.parse_show_overlay(&mut ext),

toml-config/src/lib.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -518,12 +518,14 @@ impl Action {
518518
Action::Resize { dx1, dy1, dx2, dy2 } => {
519519
window_or_seat!(s, s.resize(dx1, dy1, dx2, dy2))
520520
}
521-
Action::SetSize { width, height } => {
522-
window_or_seat!(s, s.set_size(width, height))
523-
}
524-
Action::SetPosition { x, y } => {
525-
window_or_seat!(s, s.set_position(x, y))
526-
}
521+
Action::SetPosition {
522+
x1,
523+
y1,
524+
x2,
525+
y2,
526+
width,
527+
height,
528+
} => window_or_seat!(s, s.set_position(x1, y1, x2, y2, width, height)),
527529
Action::HideOverlay { ws } => {
528530
let workspace = ws.ws.get();
529531
b.new(move || workspace.hide())

0 commit comments

Comments
 (0)