atagen

monolith

public
Star0
Watch0

Commit ea84a35c5001

tarball

atagen <boss@atagen.co>2026-10-18 02:53 UTC

4 files changed+71-6
monolith: apply output transform and position on reload

Neither needs a modeset, as DrmCompositor reads the transform from the Output every frame, so a restart or replug was never required for them. Outputs without a position are laid out again so a rotated one does not overlap its neighbours.
Modified file: crates/config/default.toml+3 additions−3 deletions
@@ -34,9 +34,9 @@ unfocused = "#3b4252"
3434 # Variable refresh rate on outputs that support it, off, on, or fullscreen.
3535 vrr = "fullscreen"
3636
37-# Per connector, read when the output connects. transform is normal, 90, 180,
38-# 270, flipped, flipped-90, flipped-180 or flipped-270. Outputs without a
39-# position line up left to right.
37+# Per connector. transform and position follow a reload, mode needs a replug.
38+# transform is normal, 90, 180, 270, flipped, flipped-90, flipped-180 or
39+# flipped-270. Outputs without a position line up left to right.
4040 # [output.DP-1]
4141 # transform = "90"
4242 # position = [2560, 0]
Modified file: crates/config/src/lib.rs+2 additions−2 deletions
@@ -86,8 +86,8 @@ pub struct Outputs {
8686 pub named: std::collections::BTreeMap<String, OutputConfig>,
8787 }
8888
89-/// Applied when the output connects. Changing them needs a restart or a
90-/// replug, since a new mode or rotation means a modeset.
89+/// Transform and position follow a reload. A new mode waits for a restart
90+/// or a replug, since it means a modeset.
9191 #[derive(Deserialize, Debug, Clone, PartialEq, Default)]
9292 #[serde(default, deny_unknown_fields)]
9393 pub struct OutputConfig {
Modified file: crates/monolith/src/commands.rs+1 additions
@@ -224,6 +224,7 @@ impl State {
224224 if keyboard_changed {
225225 self.reconfigure_keyboard();
226226 }
227+ crate::drm::reconfigure_outputs(self);
227228 tracing::info!("config reloaded");
228229 Ok(())
229230 }
Modified file: crates/monolith/src/drm.rs+65 additions−1 deletions
@@ -34,7 +34,7 @@ use smithay::reexports::drm::Device as _;
3434 use smithay::reexports::drm::control::{self, ModeTypeFlags, connector, crtc};
3535 use smithay::reexports::input::Libinput;
3636 use smithay::reexports::rustix::fs::OFlags;
37-use smithay::utils::{DeviceFd, Point, Transform};
37+use smithay::utils::{DeviceFd, Logical, Point, Transform};
3838 use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};
3939 use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
4040 use std::collections::HashMap;
@@ -438,6 +438,70 @@ fn transform(t: OutputTransform) -> Transform {
438438 }
439439 }
440440
441+/// Outputs without a configured position are laid out left to right in
442+/// their current order, so one rotating pushes its neighbours along.
443+pub fn reconfigure_outputs(state: &mut State) {
444+ let State {
445+ backend: Backend::Drm(drm),
446+ wm: Wm { layout, space, .. },
447+ settings,
448+ ..
449+ } = state
450+ else {
451+ return;
452+ };
453+ let mut surfaces: Vec<&mut Surface> = drm.surfaces.values_mut().collect();
454+ surfaces.sort_by_key(|s| s.output.current_location().x);
455+ let mut right = 0;
456+ let mut changed = false;
457+ for s in surfaces {
458+ let config = settings
459+ .config
460+ .output
461+ .named
462+ .get(&s.output.name())
463+ .cloned()
464+ .unwrap_or_default();
465+ let Some(mode) = s.output.current_mode() else {
466+ continue;
467+ };
468+ let transform = transform(config.transform);
469+ let size = transform.transform_size(mode.size);
470+ let position: Point<i32, Logical> = match config.position {
471+ Some([x, y]) => (x, y).into(),
472+ None => (right, 0).into(),
473+ };
474+ right = right.max(position.x + size.w);
475+ if s.output.current_transform() == transform && s.output.current_location() == position {
476+ continue;
477+ }
478+ s.output
479+ .change_current_state(None, Some(transform), None, Some(position));
480+ space.map_output(&s.output, position);
481+ let area = Rect::new(
482+ f64::from(position.x),
483+ f64::from(position.y),
484+ f64::from(size.w),
485+ f64::from(size.h),
486+ );
487+ layout.set_output_area(s.id, area);
488+ s.redraw = true;
489+ changed = true;
490+ tracing::info!(
491+ name = s.output.name(),
492+ ?transform,
493+ x = position.x,
494+ y = position.y,
495+ "output reconfigured"
496+ );
497+ }
498+ if changed {
499+ state.reserve_layers();
500+ state.reconfigure_lock();
501+ state.apply_layout();
502+ }
503+}
504+
441505 fn disconnect(state: &mut State, crtc: crtc::Handle) {
442506 let Some(drm) = state.backend.drm_mut() else {
443507 return;