atagen

monolith

public
Star0
Watch0

Commit 15eeea605449

tarball

atagen <boss@atagen.co>2026-10-19 08:38 UTC

8 files changed+107-45
outputs: size layout areas in logical pixels

Every output was laid out in physical pixels, so a scale could not be applied anywhere. An output's area is now its transformed mode size divided by its scale, set from the config on connect and on reload for DRM and nested, and nested pointer positions are divided by the scale to match.
Modified file: crates/config/default.toml+7 additions−7 deletions
@@ -39,13 +39,13 @@ urgent = "#ebcb8b"
3939 # Variable refresh rate on outputs that support it, off, on, or fullscreen.
4040 vrr = "fullscreen"
4141
42-# Per connector, or [output.nested] for the nested backend's window. transform,
43-# position and scale follow a reload, mode needs a replug. transform is normal,
44-# 90, 180, 270, flipped, flipped-90, flipped-180 or flipped-270. Outputs without
45-# a position line up left to right. position is in logical pixels, the size of
46-# the output divided by its scale. scale is 0.5 to 4.0 and fractional values
47-# are rounded to the nearest 1/120. X11 clients are drawn at scale 1 and
48-# stretched.
42+# Per connector. transform, position and scale follow a reload, mode needs a
43+# replug. transform is normal, 90, 180, 270, flipped, flipped-90, flipped-180 or
44+# flipped-270. Outputs without a position line up left to right. position is in
45+# logical pixels, the size of the output divided by its scale. scale is 0.5 to
46+# 4.0 and is rounded to the nearest 1/120. X11 clients are drawn at scale 1 and
47+# stretched. The nested backend's window is [output.nested], where only scale
48+# applies.
4949 # [output.DP-1]
5050 # transform = "90"
5151 # position = [2560, 0]
Modified file: crates/monolith/src/backend.rs+19 additions−1 deletions
@@ -3,9 +3,10 @@
33
44 use crate::drm::Drm;
55 use crate::nested::Host;
6-use monolith_layout::OutputId;
6+use monolith_layout::{OutputId, Rect};
77 use smithay::backend::drm::DrmNode;
88 use smithay::output::Output;
9+use smithay::utils::{Logical, Physical, Point, Size, Transform};
910 use smithay::wayland::drm_syncobj::DrmSyncobjState;
1011
1112 pub enum Backend {
@@ -62,3 +63,20 @@ impl Backend {
6263 self.drm_mut()?.syncobj.as_mut()
6364 }
6465 }
66+
67+/// An output's rectangle in the global logical space. A mode counts physical
68+/// pixels, and `scale` of them make one logical pixel.
69+pub fn logical_area(
70+ position: Point<i32, Logical>,
71+ mode: Size<i32, Physical>,
72+ transform: Transform,
73+ scale: f64,
74+) -> Rect {
75+ let size = transform.transform_size(mode);
76+ Rect::new(
77+ f64::from(position.x),
78+ f64::from(position.y),
79+ f64::from(size.w) / scale,
80+ f64::from(size.h) / scale,
81+ )
82+}
Modified file: crates/monolith/src/commands.rs+1 additions
@@ -244,6 +244,7 @@ impl State {
244244 drm.devices.reconfigure(&self.settings.config.input);
245245 }
246246 crate::drm::reconfigure_outputs(self);
247+ crate::nested::reconfigure(self);
247248 self.configure_idle();
248249 tracing::info!("config reloaded");
249250 Ok(())
Modified file: crates/monolith/src/drm.rs+20 additions−23 deletions
@@ -2,7 +2,7 @@
22 //! connectors, libinput for input, and one smithay DRM output per connector,
33 //! each rendered when its own vblank lands.
44
5-use crate::backend::Backend;
5+use crate::backend::{Backend, logical_area};
66 use crate::devices::Devices;
77 use crate::scene::Draw;
88 use crate::state::Services;
@@ -412,12 +412,13 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
412412 .fold(0.0, f64::max);
413413 let position = match config.position {
414414 Some([x, y]) => Point::from((x, y)),
415- None => Point::from((right as i32, 0)),
415+ None => Point::from((right.ceil() as i32, 0)),
416416 };
417+ let scale = config.scale();
417418 output.change_current_state(
418419 Some(wl_mode),
419420 Some(transform),
420- Some(Scale::Integer(1)),
421+ Some(Scale::Fractional(scale)),
421422 Some(position),
422423 );
423424 output.set_preferred(wl_mode);
@@ -451,20 +452,13 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
451452 let global = output.create_global::<State>(&core.display);
452453 space.map_output(&output, position);
453454 let (w, h) = mode.size();
454- let size = transform.transform_size(
455- smithay::utils::Size::<i32, smithay::utils::Logical>::from((i32::from(w), i32::from(h))),
456- );
457- let area = Rect::new(
458- f64::from(position.x),
459- f64::from(position.y),
460- f64::from(size.w),
461- f64::from(size.h),
462- );
455+ let area = logical_area(position, wl_mode.size, transform, scale);
463456 let id = layout.add_output(&name, area);
464457 tracing::info!(
465458 name,
466459 w,
467460 h,
461+ scale,
468462 refresh = mode.vrefresh(),
469463 ?transform,
470464 x = position.x,
@@ -565,30 +559,33 @@ pub fn reconfigure_outputs(state: &mut State) {
565559 continue;
566560 };
567561 let transform = transform(config.transform);
568- let size = transform.transform_size(mode.size);
562+ let scale = config.scale();
569563 let position: Point<i32, Logical> = match config.position {
570564 Some([x, y]) => (x, y).into(),
571565 None => (right, 0).into(),
572566 };
573- right = right.max(position.x + size.w);
574- if s.output.current_transform() == transform && s.output.current_location() == position {
567+ let area = logical_area(position, mode.size, transform, scale);
568+ right = right.max(area.right().ceil() as i32);
569+ if s.output.current_transform() == transform
570+ && s.output.current_location() == position
571+ && s.output.current_scale().fractional_scale() == scale
572+ {
575573 continue;
576574 }
577- s.output
578- .change_current_state(None, Some(transform), None, Some(position));
579- space.map_output(&s.output, position);
580- let area = Rect::new(
581- f64::from(position.x),
582- f64::from(position.y),
583- f64::from(size.w),
584- f64::from(size.h),
575+ s.output.change_current_state(
576+ None,
577+ Some(transform),
578+ Some(Scale::Fractional(scale)),
579+ Some(position),
585580 );
581+ space.map_output(&s.output, position);
586582 layout.set_output_area(s.id, area);
587583 s.redraw = true;
588584 changed = true;
589585 tracing::info!(
590586 name = s.output.name(),
591587 ?transform,
588+ scale,
592589 x = position.x,
593590 y = position.y,
594591 "output reconfigured"
Modified file: crates/monolith/src/lock.rs+6 additions−1 deletions
@@ -94,7 +94,12 @@ fn configure(surface: &LockSurface, output: &Output) {
9494 let Some(mode) = output.current_mode() else {
9595 return;
9696 };
97- let size = output.current_transform().transform_size(mode.size);
97+ let logical = mode
98+ .size
99+ .to_f64()
100+ .to_logical(output.current_scale().fractional_scale())
101+ .to_i32_round::<i32>();
102+ let size = output.current_transform().transform_size(logical);
98103 let size = (size.w as u32, size.h as u32);
99104 surface.with_pending_state(|s| s.size = Some(size.into()));
100105 surface.send_configure();
Modified file: crates/monolith/src/nested/input.rs+8 additions−1 deletions
@@ -51,7 +51,14 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
5151 surface_x,
5252 surface_y,
5353 ..
54- } => state.pointer_to(Point::from((surface_x, surface_y)), time),
54+ } => {
55+ let scale = state
56+ .backend
57+ .nested()
58+ .map_or(1.0, |h| h.output.current_scale().fractional_scale());
59+ let pos = Point::from((surface_x / scale, surface_y / scale));
60+ state.pointer_to(pos, time);
61+ }
5562 wl_pointer::Event::Button {
5663 button,
5764 state: WEnum::Value(pressed),
Modified file: crates/monolith/src/nested/mod.rs+42 additions−10 deletions
@@ -5,17 +5,17 @@
55
66 mod input;
77
8-use crate::backend::Backend;
8+use crate::backend::{Backend, logical_area};
99 use crate::state::Services;
1010 use crate::state::State;
1111 use calloop_wayland_source::WaylandSource;
12-use monolith_layout::{OutputId, Rect};
12+use monolith_layout::OutputId;
1313 use monolith_render_vk::VulkanAllocator;
1414 use smithay::backend::allocator::dmabuf::Dmabuf;
1515 use smithay::backend::allocator::{Format, Fourcc, Modifier, Slot, Swapchain};
1616 use smithay::backend::renderer::Bind;
1717 use smithay::backend::renderer::damage::OutputDamageTracker;
18-use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
18+use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};
1919 use smithay::reexports::calloop::EventLoop;
2020 use smithay::utils::{Logical, Size, Transform};
2121 use wayland_client::globals::{GlobalListContents, registry_queue_init};
@@ -122,20 +122,16 @@ pub fn init(
122122 size: DEFAULT_SIZE.into(),
123123 refresh: 60_000,
124124 };
125+ let scale = configured_scale(state);
125126 output.change_current_state(
126127 Some(mode),
127128 Some(Transform::Normal),
128- None,
129+ Some(Scale::Fractional(scale)),
129130 Some((0, 0).into()),
130131 );
131132 output.set_preferred(mode);
132133 state.wm.space.map_output(&output, (0, 0));
133- let area = Rect::new(
134- 0.0,
135- 0.0,
136- f64::from(DEFAULT_SIZE.0),
137- f64::from(DEFAULT_SIZE.1),
138- );
134+ let area = logical_area((0, 0).into(), mode.size, Transform::Normal, scale);
139135 let output_id = state.wm.layout.add_output("nested", area);
140136
141137 state.backend = Backend::Nested(Box::new(Host {
@@ -166,6 +162,42 @@ pub fn init(
166162 Ok(())
167163 }
168164
165+fn configured_scale(state: &State) -> f64 {
166+ state
167+ .settings
168+ .config
169+ .output
170+ .named
171+ .get("nested")
172+ .map_or(1.0, |c| c.scale())
173+}
174+
175+/// Applies a reloaded config's scale to the window's output.
176+pub fn reconfigure(state: &mut State) {
177+ let scale = configured_scale(state);
178+ let Some(host) = state.backend.nested() else {
179+ return;
180+ };
181+ if host.output.current_scale().fractional_scale() == scale {
182+ return;
183+ }
184+ let (o, size) = (host.output_id, host.size);
185+ host.output
186+ .change_current_state(None, None, Some(Scale::Fractional(scale)), None);
187+ let area = logical_area(
188+ (0, 0).into(),
189+ (size.w, size.h).into(),
190+ Transform::Normal,
191+ scale,
192+ );
193+ state.wm.layout.set_output_area(o, area);
194+ tracing::info!(scale, "output reconfigured");
195+ state.dirty.redraw = true;
196+ state.reserve_layers();
197+ state.reconfigure_lock();
198+ state.apply_layout();
199+}
200+
169201 /// Renders a frame if something changed and the host is ready for one.
170202 pub fn render(state: &mut State) {
171203 let State {
Modified file: crates/monolith/src/state.rs+4 additions−2 deletions
@@ -1,4 +1,5 @@
11 use crate::anim::Animator;
2+use crate::backend::logical_area;
23 use crate::commands::Binds;
34 use crate::focus::Focus;
45 use crate::globals::Protocols;
@@ -18,7 +19,7 @@ use smithay::reexports::calloop::{EventLoop, Interest, LoopSignal, Mode, PostAct
1819 use smithay::reexports::wayland_server::backend::{ClientData, ClientId, DisconnectReason};
1920 use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
2021 use smithay::reexports::wayland_server::{Display, DisplayHandle};
21-use smithay::utils::{Clock, Logical, Monotonic, Point, SERIAL_COUNTER, Size};
22+use smithay::utils::{Clock, Logical, Monotonic, Point, SERIAL_COUNTER, Size, Transform};
2223 use smithay::wayland::compositor::CompositorClientState;
2324 use smithay::wayland::shell::wlr_layer::Layer;
2425 use smithay::wayland::socket::ListeningSocketSource;
@@ -254,11 +255,12 @@ impl State {
254255 pub fn resize_output(&mut self, size: Size<i32, Logical>) {
255256 if let Some(host) = self.backend.nested() {
256257 let o = host.output_id;
257- let area = Rect::new(0.0, 0.0, f64::from(size.w), f64::from(size.h));
258258 let mode = smithay::output::Mode {
259259 size: (size.w, size.h).into(),
260260 refresh: 60_000,
261261 };
262+ let scale = host.output.current_scale().fractional_scale();
263+ let area = logical_area((0, 0).into(), mode.size, Transform::Normal, scale);
262264 host.output
263265 .change_current_state(Some(mode), None, None, None);
264266 self.wm.layout.set_output_area(o, area);