mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Add output configuration & integer scaling support
This commit is contained in:
@@ -27,6 +27,14 @@ input {
|
||||
}
|
||||
}
|
||||
|
||||
// You can configure outputs by their name, which you can find with wayland-info(1).
|
||||
// The built-in laptop monitor is usually called "eDP-1".
|
||||
// Remember to uncommend the node by removing "/-"!
|
||||
/-output "eDP-1" {
|
||||
// Scale is a floating-point number, but at the moment only integer values work.
|
||||
scale 2.0
|
||||
}
|
||||
|
||||
binds {
|
||||
// Keys consist of modifiers separated by + signs, followed by an XKB key name
|
||||
// in the end. To find an XKB name for a particular key, you may use a program
|
||||
|
||||
+14
-2
@@ -21,7 +21,7 @@ use smithay::backend::session::libseat::LibSeatSession;
|
||||
use smithay::backend::session::{Event as SessionEvent, Session};
|
||||
use smithay::backend::udev::{self, UdevBackend, UdevEvent};
|
||||
use smithay::desktop::utils::OutputPresentationFeedback;
|
||||
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
||||
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel, Scale};
|
||||
use smithay::reexports::calloop::{Dispatcher, LoopHandle, RegistrationToken};
|
||||
use smithay::reexports::drm::control::{
|
||||
connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags,
|
||||
@@ -500,6 +500,15 @@ impl Tty {
|
||||
);
|
||||
debug!("connecting connector: {output_name}");
|
||||
|
||||
let config = self
|
||||
.config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name == output_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let device = self.output_device.as_mut().unwrap();
|
||||
|
||||
let mut mode = connector.modes().get(0);
|
||||
@@ -534,6 +543,9 @@ impl Tty {
|
||||
.map(|info| (info.manufacturer, info.model))
|
||||
.unwrap_or_else(|| ("Unknown".into(), "Unknown".into()));
|
||||
|
||||
let scale = config.scale.clamp(0.1, 10.);
|
||||
let scale = scale.max(1.).round() as i32;
|
||||
|
||||
let output = Output::new(
|
||||
output_name.clone(),
|
||||
PhysicalProperties {
|
||||
@@ -544,7 +556,7 @@ impl Tty {
|
||||
},
|
||||
);
|
||||
let wl_mode = Mode::from(*mode);
|
||||
output.change_current_state(Some(wl_mode), None, None, Some((0, 0).into()));
|
||||
output.change_current_state(Some(wl_mode), None, Some(Scale::Integer(scale)), None);
|
||||
output.set_preferred(wl_mode);
|
||||
|
||||
output.user_data().insert_if_missing(|| TtyOutputState {
|
||||
|
||||
+12
-2
@@ -8,7 +8,7 @@ use smithay::backend::renderer::damage::OutputDamageTracker;
|
||||
use smithay::backend::renderer::gles::GlesRenderer;
|
||||
use smithay::backend::renderer::{DebugFlags, Renderer};
|
||||
use smithay::backend::winit::{self, WinitError, WinitEvent, WinitGraphicsBackend};
|
||||
use smithay::output::{Mode, Output, PhysicalProperties, Subpixel};
|
||||
use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};
|
||||
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
|
||||
use smithay::reexports::calloop::LoopHandle;
|
||||
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
|
||||
@@ -38,6 +38,16 @@ impl Winit {
|
||||
.with_title("niri");
|
||||
let (backend, mut winit_event_loop) = winit::init_from_builder(builder).unwrap();
|
||||
|
||||
let output_config = config
|
||||
.borrow()
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|o| o.name == "winit")
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let scale = output_config.scale.clamp(0.1, 10.);
|
||||
let scale = scale.max(1.).round() as i32;
|
||||
|
||||
let mode = Mode {
|
||||
size: backend.window_size().physical_size,
|
||||
refresh: 60_000,
|
||||
@@ -55,8 +65,8 @@ impl Winit {
|
||||
output.change_current_state(
|
||||
Some(mode),
|
||||
Some(Transform::Flipped180),
|
||||
Some(Scale::Integer(scale)),
|
||||
None,
|
||||
Some((0, 0).into()),
|
||||
);
|
||||
output.set_preferred(mode);
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ use smithay::input::keyboard::Keysym;
|
||||
pub struct Config {
|
||||
#[knuffel(child, default)]
|
||||
pub input: Input,
|
||||
#[knuffel(children(name = "output"))]
|
||||
pub outputs: Vec<Output>,
|
||||
#[knuffel(child, default)]
|
||||
pub binds: Binds,
|
||||
#[knuffel(child, default)]
|
||||
@@ -62,6 +64,23 @@ pub struct Touchpad {
|
||||
pub accel_speed: f64,
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
|
||||
pub struct Output {
|
||||
#[knuffel(argument)]
|
||||
pub name: String,
|
||||
#[knuffel(child, unwrap(argument), default = 1.)]
|
||||
pub scale: f64,
|
||||
}
|
||||
|
||||
impl Default for Output {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
scale: 1.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(knuffel::Decode, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Binds(#[knuffel(children)] pub Vec<Bind>);
|
||||
|
||||
@@ -263,6 +282,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
output "eDP-1" {
|
||||
scale 2.0
|
||||
}
|
||||
|
||||
binds {
|
||||
Mod+T { spawn "alacritty"; }
|
||||
Mod+Q { close-window; }
|
||||
@@ -293,6 +316,10 @@ mod tests {
|
||||
accel_speed: 0.2,
|
||||
},
|
||||
},
|
||||
outputs: vec![Output {
|
||||
name: "eDP-1".to_owned(),
|
||||
scale: 2.,
|
||||
}],
|
||||
binds: Binds(vec![
|
||||
Bind {
|
||||
key: Key {
|
||||
|
||||
+16
-7
@@ -1231,6 +1231,7 @@ impl Monitor<Window> {
|
||||
&self,
|
||||
renderer: &mut GlesRenderer,
|
||||
) -> Vec<MonitorRenderElement<GlesRenderer>> {
|
||||
let output_scale = Scale::from(self.output.current_scale().fractional_scale());
|
||||
let output_transform = self.output.current_transform();
|
||||
let output_mode = self.output.current_mode().unwrap();
|
||||
let output_size = output_transform.transform_size(output_mode.size);
|
||||
@@ -1251,7 +1252,7 @@ impl Monitor<Window> {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
CropRenderElement::from_element(
|
||||
elem,
|
||||
1.,
|
||||
output_scale,
|
||||
Rectangle::from_loc_and_size((0, 0), output_size),
|
||||
)?,
|
||||
(0, -offset),
|
||||
@@ -1262,7 +1263,7 @@ impl Monitor<Window> {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
CropRenderElement::from_element(
|
||||
elem,
|
||||
1.,
|
||||
output_scale,
|
||||
Rectangle::from_loc_and_size((0, 0), output_size),
|
||||
)?,
|
||||
(0, -offset + output_size.h),
|
||||
@@ -1279,7 +1280,7 @@ impl Monitor<Window> {
|
||||
Some(RelocateRenderElement::from_element(
|
||||
CropRenderElement::from_element(
|
||||
elem,
|
||||
1.,
|
||||
output_scale,
|
||||
Rectangle::from_loc_and_size((0, 0), output_size),
|
||||
)?,
|
||||
(0, 0),
|
||||
@@ -1823,6 +1824,14 @@ impl Workspace<Window> {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// FIXME: workspaces should probably cache their last used scale so they can be correctly
|
||||
// rendered even with no outputs connected.
|
||||
let output_scale = self
|
||||
.output
|
||||
.as_ref()
|
||||
.map(|o| Scale::from(o.current_scale().fractional_scale()))
|
||||
.unwrap_or(Scale::from(1.));
|
||||
|
||||
let mut rv = vec![];
|
||||
let view_pos = self.view_pos();
|
||||
|
||||
@@ -1840,8 +1849,8 @@ impl Workspace<Window> {
|
||||
}
|
||||
rv.extend(active_win.render_elements(
|
||||
renderer,
|
||||
win_pos.to_physical(1),
|
||||
Scale::from(1.),
|
||||
win_pos.to_physical_precise_round(output_scale),
|
||||
output_scale,
|
||||
1.,
|
||||
));
|
||||
|
||||
@@ -1862,8 +1871,8 @@ impl Workspace<Window> {
|
||||
|
||||
rv.extend(win.render_elements(
|
||||
renderer,
|
||||
win_pos.to_physical(1),
|
||||
Scale::from(1.),
|
||||
win_pos.to_physical_precise_round(output_scale),
|
||||
output_scale,
|
||||
1.,
|
||||
));
|
||||
}
|
||||
|
||||
+24
-13
@@ -799,6 +799,7 @@ impl Niri {
|
||||
renderer: &mut GlesRenderer,
|
||||
output: &Output,
|
||||
) -> Vec<OutputRenderElements<GlesRenderer>> {
|
||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let output_pos = self.global_space.output_geometry(output).unwrap().loc;
|
||||
let pointer_pos = self.seat.get_pointer().unwrap().current_location() - output_pos.to_f64();
|
||||
|
||||
@@ -825,7 +826,7 @@ impl Niri {
|
||||
} else {
|
||||
default_hotspot
|
||||
};
|
||||
let pointer_pos = (pointer_pos - hotspot.to_f64()).to_physical_precise_round(1.);
|
||||
let pointer_pos = (pointer_pos - hotspot.to_f64()).to_physical_precise_round(output_scale);
|
||||
|
||||
let mut pointer_elements = match &self.cursor_image {
|
||||
CursorImageStatus::Hidden => vec![],
|
||||
@@ -843,7 +844,7 @@ impl Niri {
|
||||
renderer,
|
||||
surface,
|
||||
pointer_pos,
|
||||
1.,
|
||||
output_scale,
|
||||
1.,
|
||||
Kind::Cursor,
|
||||
),
|
||||
@@ -854,7 +855,7 @@ impl Niri {
|
||||
renderer,
|
||||
dnd_icon,
|
||||
pointer_pos,
|
||||
1.,
|
||||
output_scale,
|
||||
1.,
|
||||
Kind::Unspecified,
|
||||
));
|
||||
@@ -871,6 +872,8 @@ impl Niri {
|
||||
) -> Vec<OutputRenderElements<GlesRenderer>> {
|
||||
let _span = tracy_client::span!("Niri::render");
|
||||
|
||||
let output_scale = Scale::from(output.current_scale().fractional_scale());
|
||||
|
||||
// Get monitor elements.
|
||||
let mon = self.monitor_set.monitor_for_output(output).unwrap();
|
||||
let monitor_elements = mon.render_elements(renderer);
|
||||
@@ -901,8 +904,8 @@ impl Niri {
|
||||
surface
|
||||
.render_elements(
|
||||
renderer,
|
||||
loc.to_physical_precise_round(1.),
|
||||
Scale::from(1.),
|
||||
loc.to_physical_precise_round(output_scale),
|
||||
output_scale,
|
||||
1.,
|
||||
)
|
||||
.into_iter()
|
||||
@@ -926,8 +929,8 @@ impl Niri {
|
||||
surface
|
||||
.render_elements(
|
||||
renderer,
|
||||
loc.to_physical_precise_round(1.),
|
||||
Scale::from(1.),
|
||||
loc.to_physical_precise_round(output_scale),
|
||||
output_scale,
|
||||
1.,
|
||||
)
|
||||
.into_iter()
|
||||
@@ -1084,6 +1087,7 @@ impl Niri {
|
||||
let _span = tracy_client::span!("Niri::send_for_screen_cast");
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
|
||||
for cast in &mut self.casts {
|
||||
if !cast.is_active.get() {
|
||||
@@ -1119,7 +1123,7 @@ impl Niri {
|
||||
let dmabuf = cast.dmabufs.borrow()[&fd].clone();
|
||||
|
||||
// FIXME: Hidden / embedded / metadata cursor
|
||||
render_to_dmabuf(backend.renderer(), dmabuf, size, elements).unwrap();
|
||||
render_to_dmabuf(backend.renderer(), dmabuf, size, scale, elements).unwrap();
|
||||
|
||||
let maxsize = data.as_raw().maxsize;
|
||||
let chunk = data.chunk_mut();
|
||||
@@ -1139,8 +1143,9 @@ impl Niri {
|
||||
let _span = tracy_client::span!("Niri::screenshot");
|
||||
|
||||
let size = output.current_mode().unwrap().size;
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let elements = self.render(renderer, output, true);
|
||||
let pixels = render_to_vec(renderer, size, &elements)?;
|
||||
let pixels = render_to_vec(renderer, size, scale, &elements)?;
|
||||
|
||||
let path = make_screenshot_path().context("error making screenshot path")?;
|
||||
debug!("saving screenshot to {path:?}");
|
||||
@@ -1174,6 +1179,7 @@ impl Niri {
|
||||
let outputs: Vec<_> = self.global_space.outputs().cloned().collect();
|
||||
for output in outputs {
|
||||
let geom = self.global_space.output_geometry(&output).unwrap();
|
||||
// FIXME: this does not work when outputs can have non-1 scale.
|
||||
let geom = geom.to_physical(1);
|
||||
|
||||
size.w = max(size.w, geom.loc.x + geom.size.w);
|
||||
@@ -1185,7 +1191,8 @@ impl Niri {
|
||||
}));
|
||||
}
|
||||
|
||||
let pixels = render_to_vec(renderer, size, &elements)?;
|
||||
// FIXME: scale.
|
||||
let pixels = render_to_vec(renderer, size, Scale::from(1.), &elements)?;
|
||||
|
||||
let path = make_screenshot_path().context("error making screenshot path")?;
|
||||
debug!("saving screenshot to {path:?}");
|
||||
@@ -1232,6 +1239,7 @@ impl ClientData for ClientState {
|
||||
fn render_and_download(
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
elements: &[impl RenderElement<GlesRenderer>],
|
||||
) -> anyhow::Result<GlesMapping> {
|
||||
let _span = tracy_client::span!("render_and_download");
|
||||
@@ -1255,7 +1263,7 @@ fn render_and_download(
|
||||
|
||||
for element in elements.iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(Scale::from(1.));
|
||||
let dst = element.geometry(scale);
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[output_rect])
|
||||
.context("error drawing element")?;
|
||||
@@ -1273,11 +1281,13 @@ fn render_and_download(
|
||||
fn render_to_vec(
|
||||
renderer: &mut GlesRenderer,
|
||||
size: Size<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
elements: &[impl RenderElement<GlesRenderer>],
|
||||
) -> anyhow::Result<Vec<u8>> {
|
||||
let _span = tracy_client::span!("render_to_vec");
|
||||
|
||||
let mapping = render_and_download(renderer, size, elements).context("error rendering")?;
|
||||
let mapping =
|
||||
render_and_download(renderer, size, scale, elements).context("error rendering")?;
|
||||
let copy = renderer
|
||||
.map_texture(&mapping)
|
||||
.context("error mapping texture")?;
|
||||
@@ -1288,6 +1298,7 @@ fn render_to_dmabuf(
|
||||
renderer: &mut GlesRenderer,
|
||||
dmabuf: Dmabuf,
|
||||
size: Size<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
elements: &[OutputRenderElements<GlesRenderer>],
|
||||
) -> anyhow::Result<()> {
|
||||
let _span = tracy_client::span!("render_to_dmabuf");
|
||||
@@ -1305,7 +1316,7 @@ fn render_to_dmabuf(
|
||||
|
||||
for element in elements.iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(Scale::from(1.));
|
||||
let dst = element.geometry(scale);
|
||||
element
|
||||
.draw(&mut frame, src, dst, &[output_rect])
|
||||
.context("error drawing element")?;
|
||||
|
||||
Reference in New Issue
Block a user