wayland: Add support for wlr_output_configuration

This commit is contained in:
Victoria Brekenfeld 2022-04-13 22:59:14 +02:00
parent 1519942a63
commit edbfcfa2e5
8 changed files with 543 additions and 257 deletions

62
Cargo.lock generated
View file

@ -349,9 +349,9 @@ checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "darling"
version = "0.13.2"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e92cb285610dd935f60ee8b4d62dd1988bd12b7ea50579bd6a138201525318e"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
@ -359,9 +359,9 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.13.2"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c29e95ab498b18131ea460b2c0baa18cbf041231d122b0b7bfebef8c8e88989"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
@ -373,9 +373,9 @@ dependencies = [
[[package]]
name = "darling_macro"
version = "0.13.2"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b21dd6b221dd547528bd6fb15f1a3b7ab03b9a06f76bff288a8c629bcfbe7f0e"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
@ -715,9 +715,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
version = "0.3.56"
version = "0.3.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
dependencies = [
"wasm-bindgen",
]
@ -736,9 +736,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.121"
version = "0.2.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
[[package]]
name = "libloading"
@ -1087,18 +1087,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.36"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58"
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
dependencies = [
"proc-macro2",
]
@ -1311,7 +1311,7 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
[[package]]
name = "smithay"
version = "0.3.0"
source = "git+https://github.com/pop-os/smithay?branch=main#d8bf081cb5d2c6c666daaf238e2a6360ca963716"
source = "git+https://github.com/pop-os/smithay?branch=main#a9aea2275645cc4b2fc290103cc2072f9a2bfbb1"
dependencies = [
"appendlist",
"bitflags",
@ -1350,9 +1350,9 @@ dependencies = [
[[package]]
name = "smithay-client-toolkit"
version = "0.15.3"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1325f292209cee78d5035530932422a30aa4c8fda1a16593ac083c1de211e68a"
checksum = "8a28f16a97fa0e8ce563b2774d1e732dd5d4025d2772c5dba0a41a0f90a29da3"
dependencies = [
"bitflags",
"calloop",
@ -1395,9 +1395,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.90"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704df27628939572cd88d33f171cd6f896f4eaca85252c6e0a72d8d8287ee86f"
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
dependencies = [
"proc-macro2",
"quote",
@ -1534,9 +1534,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.79"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@ -1544,9 +1544,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.79"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
dependencies = [
"bumpalo",
"lazy_static",
@ -1559,9 +1559,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.79"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1569,9 +1569,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.79"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
dependencies = [
"proc-macro2",
"quote",
@ -1582,9 +1582,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.79"
version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
[[package]]
name = "wayland-client"
@ -1692,9 +1692,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.56"
version = "0.3.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
dependencies = [
"js-sys",
"wasm-bindgen",

View file

@ -5,9 +5,9 @@ use crate::state::Fps;
use crate::{
backend::render,
config::OutputConfig,
config::{Config, OutputConfig},
shell::Shell,
state::{BackendData, Common, State},
utils::GlobalDrop,
};
use anyhow::{Context, Result};
@ -32,7 +32,7 @@ use smithay::{
drm::control::{connector, crtc, Device as ControlDevice, ModeTypeFlags},
input::Libinput,
nix::{fcntl::OFlag, sys::stat::dev_t},
wayland_server::{protocol::wl_output, Display},
wayland_server::protocol::wl_output,
},
utils::signaling::{Linkable, Signaler},
wayland::output::{Mode as OutputMode, Output, PhysicalProperties},
@ -73,9 +73,9 @@ pub struct Device {
}
pub struct Surface {
surface: GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>,
surface: Option<GbmBufferedSurface<Rc<RefCell<GbmDevice<SessionFd>>>, SessionFd>>,
connector: connector::Handle,
output: Output,
_global: GlobalDrop<wl_output::WlOutput>,
last_submit: Option<DrmEventTime>,
refresh_rate: u32,
vrr: bool,
@ -241,8 +241,8 @@ impl State {
DrmEvent::VBlank(crtc) => {
if let Some(device) = state.backend.kms().devices.get_mut(&drm_node) {
if let Some(surface) = device.surfaces.get_mut(&crtc) {
match surface.surface.frame_submitted() {
Ok(_) => {
match surface.surface.as_mut().map(|x| x.frame_submitted()) {
Some(Ok(_)) => {
surface.last_submit = metadata.take().map(|data| data.time);
surface.pending = false;
state
@ -254,7 +254,10 @@ impl State {
state.common.start_time.elapsed().as_millis() as u32
);
}
Err(err) => slog_scope::warn!("Failed to submit frame: {}", err),
Some(Err(err)) => {
slog_scope::warn!("Failed to submit frame: {}", err)
}
None => {} // got disabled
};
}
}
@ -293,23 +296,30 @@ impl State {
};
let outputs = device.enumerate_surfaces()?.added; // There are no removed outputs on newly added devices
let mut wl_outputs = Vec::new();
for (crtc, conn) in outputs {
match device.setup_surface(
&drm_node,
crtc,
conn,
self.backend.kms().signaler.clone(),
&mut self.common.display.borrow_mut(),
&mut self.common.event_loop_handle,
) {
Ok(output) => self.common.shell.map_output(
&output,
&mut self.backend,
&mut self.common.config,
),
Ok(output) => {
self.common.shell.map_output(
&output,
&mut self.backend,
&mut self.common.config,
);
wl_outputs.push(output);
}
Err(err) => slog_scope::warn!("Failed to initialize output: {}", err),
};
}
self.common.output_conf.add_heads(wl_outputs.iter());
self.common
.output_conf
.update(&mut *self.common.display.borrow_mut());
self.backend.kms().devices.insert(drm_node, device);
Ok(())
@ -336,7 +346,6 @@ impl State {
crtc,
conn,
signaler.clone(),
&mut self.common.display.borrow_mut(),
&mut self.common.event_loop_handle,
) {
Ok(output) => {
@ -347,16 +356,21 @@ impl State {
}
}
self.common.output_conf.remove_heads(outputs_removed.iter());
for output in outputs_removed.into_iter() {
self.common
.shell
.unmap_output(&output, &mut self.backend, &self.common.config);
.unmap_output(&output, &mut self.backend, &mut self.common.config);
}
self.common.output_conf.add_heads(outputs_added.iter());
for output in outputs_added.into_iter() {
self.common
.shell
.map_output(&output, &mut self.backend, &mut self.common.config)
}
self.common
.output_conf
.update(&mut self.common.display.borrow_mut());
Ok(())
}
@ -377,11 +391,15 @@ impl State {
self.common.event_loop_handle.remove(socket.token);
}
}
self.common.output_conf.remove_heads(outputs_removed.iter());
for output in outputs_removed.into_iter() {
self.common
.shell
.unmap_output(&output, &mut self.backend, &self.common.config);
.unmap_output(&output, &mut self.backend, &mut self.common.config);
}
self.common
.output_conf
.update(&mut *self.common.display.borrow_mut());
Ok(())
}
@ -402,29 +420,18 @@ impl Device {
let surfaces = self
.surfaces
.iter()
.map(|(c, s)| (*c, s.surface.current_connectors().into_iter().next()))
.collect::<HashMap<crtc::Handle, Option<connector::Handle>>>();
.map(|(c, s)| (*c, s.connector))
.collect::<HashMap<crtc::Handle, connector::Handle>>();
let added = config
.iter()
.filter(|(conn, crtc)| {
surfaces
.get(&crtc)
.map(|c| c.as_ref() != Some(*conn))
.unwrap_or(true)
})
.filter(|(conn, crtc)| surfaces.get(&crtc).map(|c| c != *conn).unwrap_or(true))
.map(|(conn, crtc)| (crtc, conn))
.map(|(crtc, conn)| (*crtc, *conn))
.collect::<Vec<_>>();
let removed = surfaces
.iter()
.filter(|(crtc, conn)| {
if let Some(conn) = conn {
config.get(conn).map(|c| c != *crtc).unwrap_or(true)
} else {
true
}
})
.filter(|(crtc, conn)| config.get(conn).map(|c| c != *crtc).unwrap_or(true))
.map(|(crtc, _)| *crtc)
.collect::<Vec<_>>();
@ -437,7 +444,6 @@ impl Device {
crtc: crtc::Handle,
conn: connector::Handle,
signaler: Signaler<Signal>,
display: &mut Display,
loop_handle: &mut LoopHandle<'static, State>,
) -> Result<Output> {
let drm = &mut *self.drm.as_source_mut();
@ -446,10 +452,14 @@ impl Device {
let vrr = drm_helpers::set_vrr(drm, crtc, conn, true).unwrap_or(false);
let interface = drm_helpers::interface_name(drm, conn)?;
let edid_info = drm_helpers::edid_info(drm, conn)?;
let mode = crtc_info.mode().unwrap_or_else(||
conn_info.modes().iter().find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
.copied().unwrap_or(conn_info.modes()[0])
);
let mode = crtc_info.mode().unwrap_or_else(|| {
conn_info
.modes()
.iter()
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
.copied()
.unwrap_or(conn_info.modes()[0])
});
let refresh_rate = drm_helpers::calculate_refresh_rate(mode);
let mut surface = drm.create_surface(crtc, mode, &[conn])?;
surface.link(signaler);
@ -464,8 +474,7 @@ impl Device {
refresh: refresh_rate as i32,
};
let (phys_w, phys_h) = conn_info.size().unwrap_or((0, 0));
let (output, output_global) = Output::new(
display,
let output = Output::new(
interface,
PhysicalProperties {
size: (phys_w as i32, phys_h as i32).into(),
@ -476,7 +485,15 @@ impl Device {
},
None,
);
output.set_preferred(output_mode.clone());
for mode in conn_info.modes() {
let refresh_rate = drm_helpers::calculate_refresh_rate(*mode);
let mode = OutputMode {
size: (mode.size().0 as i32, mode.size().1 as i32).into(),
refresh: refresh_rate as i32,
};
output.add_mode(mode);
}
output.set_preferred(output_mode);
output.change_current_state(
Some(output_mode),
// TODO: Readout property for monitor rotation
@ -486,10 +503,7 @@ impl Device {
);
output.user_data().insert_if_missing(|| {
RefCell::new(OutputConfig {
mode: (
(output_mode.size.w, output_mode.size.h),
Some(format!("{}x{}@{}", mode.size().0, mode.size().1, refresh_rate)),
),
mode: ((output_mode.size.w, output_mode.size.h), Some(refresh_rate)),
vrr,
..Default::default()
})
@ -519,8 +533,8 @@ impl Device {
let data = Surface {
output: output.clone(),
_global: output_global.into(),
surface: target,
surface: Some(target),
connector: conn,
vrr,
refresh_rate,
last_submit: None,
@ -545,6 +559,10 @@ impl Surface {
target_node: &DrmNode,
state: &mut Common,
) -> Result<()> {
if self.surface.is_none() {
return Ok(());
}
let nodes = state
.shell
.active_space(&self.output)
@ -578,8 +596,8 @@ impl Surface {
let mut renderer = api.renderer(render_node, &target_node).unwrap();
let (buffer, age) = self
.surface
let surface = self.surface.as_mut().unwrap();
let (buffer, age) = surface
.next_buffer()
.with_context(|| "Failed to allocate buffer")?;
@ -598,12 +616,12 @@ impl Surface {
&mut self.fps,
) {
Ok(_) => {
self.surface
surface
.queue_buffer()
.with_context(|| "Failed to submit buffer for display")?;
}
Err(err) => {
self.surface.reset_buffers();
surface.reset_buffers();
anyhow::bail!("Rendering failed: {}", err);
}
};
@ -615,67 +633,107 @@ impl KmsState {
pub fn apply_config_for_output(
&mut self,
output: &Output,
) -> Result<(), Box<dyn std::error::Error>> {
if let Some(device) = self
config: &mut Config,
shell: &mut Shell,
test_only: bool,
) -> Result<(), anyhow::Error> {
let recreated = if let Some(device) = self
.devices
.values_mut()
.find(|dev| dev.surfaces.values().any(|s| s.output == *output))
{
let mut surface = device
let (crtc, mut surface) = device
.surfaces
.values_mut()
.find(|s| s.output == *output)
.iter_mut()
.find(|(_, s)| s.output == *output)
.unwrap();
let config = output
let output_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
let conn_info = device.drm.as_source_ref().get_connector(
surface
.surface
.current_connectors()
.into_iter()
.next()
.unwrap_or_else(
|| surface.surface
.pending_connectors()
.into_iter()
.next()
.unwrap()
),
)?;
let mode = conn_info
.modes()
.iter()
.find(|mode| {
let refresh_rate = drm_helpers::calculate_refresh_rate(**mode);
format!("{}x{}@{}", mode.size().0, mode.size().1, refresh_rate)
== *config.mode.1.as_ref().unwrap()
})
.ok_or(anyhow::anyhow!("Unknown mode"))?;
surface.surface.use_mode(*mode).unwrap();
if !output_config.enabled {
if !test_only {
if surface.surface.take().is_some() {
// just drop it
shell.disable_output(output, config);
}
}
false
} else {
let drm = &mut *device.drm.as_source_mut();
let conn = surface.connector;
let conn_info = drm.get_connector(conn)?;
let mode = conn_info
.modes()
.iter()
.min_by_key(|mode| {
let refresh_rate = drm_helpers::calculate_refresh_rate(**mode);
(output_config.mode.1.unwrap() as i32 - refresh_rate as i32).abs()
})
.ok_or(anyhow::anyhow!("Unknown mode"))?;
if config.vrr != surface.vrr {
surface.vrr = drm_helpers::set_vrr(
&*device.drm.as_source_ref(),
surface.surface.crtc(),
conn_info.handle(),
config.vrr,
)?;
if !test_only {
if let Some(gbm_surface) = surface.surface.as_mut() {
if output_config.vrr != surface.vrr {
surface.vrr = drm_helpers::set_vrr(
drm,
*crtc,
conn_info.handle(),
output_config.vrr,
)?;
}
gbm_surface.use_mode(*mode).unwrap();
false
} else {
surface.vrr = drm_helpers::set_vrr(drm, *crtc, conn, output_config.vrr)
.unwrap_or(false);
surface.refresh_rate = drm_helpers::calculate_refresh_rate(*mode);
let mut drm_surface = drm.create_surface(*crtc, *mode, &[conn])?;
drm_surface.link(self.signaler.clone());
let target = GbmBufferedSurface::new(
drm_surface,
device.allocator.clone(),
device.formats.clone(),
None,
)
.with_context(|| {
format!(
"Failed to initialize Gbm surface for {}",
drm_helpers::interface_name(drm, conn)
.unwrap_or_else(|_| String::from("Unknown"))
)
})?;
surface.surface = Some(target);
shell.enable_output(output, config);
true
}
} else {
false
}
}
} else {
false
};
if recreated {
self.schedule_render(output);
}
Ok(())
}
pub fn schedule_render(&mut self, output: &Output) {
if let Some((device, surface)) = self
if let Some((device, crtc, surface)) = self
.devices
.iter_mut()
.flat_map(|(node, d)| d.surfaces.values_mut().map(move |s| (node, s)))
.find(|(_, s)| s.output == *output)
.flat_map(|(node, d)| d.surfaces.iter_mut().map(move |(c, s)| (node, c, s)))
.find(|(_, _, s)| s.output == *output)
{
if surface.surface.is_none() {
return;
}
if !surface.pending {
surface.pending = true;
let duration = surface
@ -688,7 +746,7 @@ impl KmsState {
DrmEventTime::Realtime(time) => time.duration_since(SystemTime::now()).ok(),
})
.unwrap_or(Duration::ZERO); // + Duration::from_secs_f64((1.0 / surface.refresh_rate as f64) - 20.0);
let data = (*device, surface.surface.crtc());
let data = (*device, *crtc);
if surface.vrr {
surface.render_timer.add_timeout(Duration::ZERO, data);
} else {

View file

@ -72,6 +72,32 @@ impl WinitState {
Ok(())
}
pub fn apply_config_for_output(
&mut self,
output: &Output,
test_only: bool,
) -> Result<(), anyhow::Error> {
// TODO: if we ever have multiple winit outputs, don't ignore config.enabled
// reset size
let size = self.backend.borrow().window_size();
let mut config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
if dbg!(config.mode.0) != dbg!((size.physical_size.w as i32, size.physical_size.h as i32)) {
if !test_only {
config.mode = (
(size.physical_size.w as i32, size.physical_size.h as i32),
None,
);
}
Err(anyhow::anyhow!("Cannot set window size"))
} else {
Ok(())
}
}
}
pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Result<()> {
@ -93,7 +119,7 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
size: (size.physical_size.w as i32, size.physical_size.h as i32).into(),
refresh: 60_000,
};
let (output, _global) = Output::new(&mut *state.common.display.borrow_mut(), name, props, None);
let output = Output::new(name, props, None);
//let _global = global.into();
output.change_current_state(
Some(mode),
@ -146,7 +172,7 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
state.common.shell.unmap_output(
&output,
&mut state.backend,
&state.common.config,
&mut state.common.config,
);
if let Some(token) = token.take() {
event_loop_handle.remove(token);
@ -163,6 +189,11 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
#[cfg(feature = "debug")]
fps: Fps::default(),
});
state.common.output_conf.add_heads(std::iter::once(&output));
state
.common
.output_conf
.update(&mut *state.common.display.borrow_mut());
state
.common
.shell
@ -224,10 +255,22 @@ impl State {
size,
refresh: 60_000,
};
{
let mut config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
config.mode.0 = size.into();
}
output.delete_mode(output.current_mode().unwrap());
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
output.change_current_state(Some(mode), None, None, None);
layer_map_for_output(output).arrange();
self.common
.output_conf
.update(&mut *self.common.display.borrow_mut());
render_ping.ping();
}
WinitEvent::Refresh => render_ping.ping(),

View file

@ -5,7 +5,6 @@ use crate::{
config::OutputConfig,
input::{set_active_output, Devices},
state::{BackendData, Common, State},
utils::GlobalDrop,
};
use anyhow::{Context, Result};
use smithay::{
@ -20,10 +19,7 @@ use smithay::{
reexports::{
calloop::{ping, EventLoop, LoopHandle},
gbm::{Device as GbmDevice, FdWrapper},
wayland_server::{
protocol::wl_output::{Subpixel, WlOutput},
Display,
},
wayland_server::{protocol::wl_output::Subpixel, Display},
},
wayland::{
dmabuf::init_dmabuf_global,
@ -48,11 +44,7 @@ pub struct X11State {
}
impl X11State {
pub fn add_window(
&mut self,
display: &mut Display,
handle: LoopHandle<'_, State>,
) -> Result<Output> {
pub fn add_window(&mut self, handle: LoopHandle<'_, State>) -> Result<Output> {
let window = WindowBuilder::new()
.title("COSMIC")
.build(&self.handle)
@ -83,8 +75,7 @@ impl X11State {
size: (size.w as i32, size.h as i32).into(),
refresh: 60_000,
};
let (output, global) = Output::new(display, name, props, None);
let _global = global.into();
let output = Output::new(name, props, None);
output.change_current_state(Some(mode), None, None, Some((0, 0).into()));
output.set_preferred(mode);
output.user_data().insert_if_missing(|| {
@ -125,7 +116,6 @@ impl X11State {
pending: true,
#[cfg(feature = "debug")]
fps: Fps::default(),
_global,
});
// schedule first render
@ -141,6 +131,35 @@ impl X11State {
}
}
}
pub fn apply_config_for_output(
&mut self,
output: &Output,
test_only: bool,
) -> Result<(), anyhow::Error> {
// TODO: if we ever have multiple winit outputs, don't ignore config.enabled
// reset size
let size = self
.surfaces
.iter()
.find(|s| s.output == *output)
.unwrap()
.window
.size();
let mut config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
if config.mode.0 != (size.w as i32, size.h as i32) {
if !test_only {
config.mode = ((size.w as i32, size.h as i32), None);
}
Err(anyhow::anyhow!("Cannot set window size"))
} else {
Ok(())
}
}
}
pub struct Surface {
@ -152,7 +171,6 @@ pub struct Surface {
pending: bool,
#[cfg(feature = "debug")]
fps: Fps,
_global: GlobalDrop<WlOutput>,
}
impl Surface {
@ -234,8 +252,13 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
let output = state
.backend
.x11()
.add_window(&mut *state.common.display.borrow_mut(), event_loop.handle())
.add_window(event_loop.handle())
.with_context(|| "Failed to create wl_output")?;
state.common.output_conf.add_heads(std::iter::once(&output));
state
.common
.output_conf
.update(&mut *state.common.display.borrow_mut());
state
.common
.shell
@ -266,7 +289,7 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
state.common.shell.unmap_output(
&output,
&mut state.backend,
&state.common.config,
&mut state.common.config,
);
}
}
@ -287,10 +310,23 @@ pub fn init_backend(event_loop: &mut EventLoop<State>, state: &mut State) -> Res
.find(|s| s.window.id() == window_id)
{
let output = &surface.output;
{
let mut config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
config.mode.0 = size.into();
}
output.delete_mode(output.current_mode().unwrap());
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
layer_map_for_output(output).arrange();
state
.common
.output_conf
.update(&mut *state.common.display.borrow_mut());
surface.dirty = true;
if !surface.pending {
surface.render.ping();

View file

@ -52,14 +52,20 @@ impl From<Output> for OutputInfo {
}
}
fn default_enabled() -> bool {
true
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub struct OutputConfig {
pub mode: ((i32, i32), Option<String>),
pub mode: ((i32, i32), Option<u32>),
pub vrr: bool,
pub scale: f64,
#[serde(with = "TransformDef")]
pub transform: Transform,
pub position: (i32, i32),
#[serde(default = "default_enabled")]
pub enabled: bool,
}
impl Default for OutputConfig {
@ -70,6 +76,7 @@ impl Default for OutputConfig {
scale: 1.0,
transform: Transform::Normal,
position: (0, 0),
enabled: true,
}
}
}
@ -79,14 +86,8 @@ impl OutputConfig {
self.mode.0.into()
}
pub fn mode_refresh(&self) -> i32 {
self.mode
.1
.as_deref()
.and_then(|x| x.split("@").nth(1))
.and_then(|x| str::parse::<f64>(x).ok())
.map(|x| x.round() as i32)
.unwrap_or(60)
pub fn mode_refresh(&self) -> u32 {
self.mode.1.unwrap_or(60_000)
}
}

View file

@ -143,11 +143,7 @@ impl FloatingLayout {
Default::default()
}
fn map_window(
space: &mut Space,
window: &Window,
output: &Output,
) {
fn map_window(space: &mut Space, window: &Window, output: &Output) {
let win_geo = window.bbox();
let layers = layer_map_for_output(&output);
let geometry = layers.non_exclusive_zone();

View file

@ -92,43 +92,14 @@ impl Shell {
}
}
fn apply_config(
output: &Output,
backend: &mut BackendData,
) -> Result<(), Box<dyn std::error::Error>> {
backend.apply_config_for_output(output)?;
let final_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
let mode = Some(OutputMode {
size: final_config.mode_size(),
refresh: final_config.mode_refresh(),
})
.filter(|m| match output.current_mode() {
None => true,
Some(c_m) => m.size != c_m.size || m.refresh != c_m.refresh,
});
let transform =
Some(final_config.transform.into()).filter(|x| *x != output.current_transform());
let scale = Some(final_config.scale.ceil() as i32).filter(|x| *x != output.current_scale());
let location =
Some(final_config.position.into()).filter(|x| *x != output.current_location());
output.change_current_state(mode, transform, scale, location);
Ok(())
}
fn refresh_config(&mut self, backend: &mut BackendData, config: &Config) -> bool {
fn refresh_config(&mut self, backend: &mut BackendData, config: &mut Config) -> bool {
let mut infos = self
.outputs()
.cloned()
.map(Into::<crate::config::OutputInfo>::into)
.collect::<Vec<_>>();
infos.sort();
if let Some(configs) = config.dynamic_conf.outputs().config.get(&infos) {
if let Some(configs) = config.dynamic_conf.outputs().config.get(&infos).cloned() {
let mut reset = false;
let known_good_configs = self
.outputs
@ -143,18 +114,20 @@ impl Shell {
})
.collect::<Vec<_>>();
for (name, config) in infos
.iter()
.map(|o| &o.connector)
.zip(configs.into_iter().cloned())
for (name, output_config) in infos.iter().map(|o| &o.connector).zip(configs.into_iter())
{
let output = self.outputs.iter().find(|o| &o.name() == name).unwrap();
let output = self
.outputs
.iter()
.find(|o| &o.name() == name)
.unwrap()
.clone();
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = config;
if let Err(err) = Self::apply_config(output, backend) {
.borrow_mut() = output_config;
if let Err(err) = backend.apply_config_for_output(&output, false, config, self) {
slog_scope::warn!(
"Failed to set new config for output {}: {}",
output.name(),
@ -166,13 +139,19 @@ impl Shell {
}
if reset {
for (output, config) in self.outputs.iter().zip(known_good_configs.into_iter()) {
for (output, output_config) in self
.outputs
.clone()
.into_iter()
.zip(known_good_configs.into_iter())
{
*output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut() = config;
if let Err(err) = Self::apply_config(output, backend) {
.borrow_mut() = output_config;
if let Err(err) = backend.apply_config_for_output(&output, false, config, self)
{
slog_scope::error!(
"Failed to reset config for output {}: {}",
output.name(),
@ -212,6 +191,30 @@ impl Shell {
}
}
pub fn save_config(&self, config: &mut Config) {
let mut infos = self
.outputs()
.cloned()
.map(|o| {
(
Into::<crate::config::OutputInfo>::into(o.clone()),
o.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone(),
)
})
.collect::<Vec<(OutputInfo, OutputConfig)>>();
infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
let (infos, configs) = infos.into_iter().unzip();
config
.dynamic_conf
.outputs_mut()
.config
.insert(infos, configs);
}
fn assign_next_free_output<'a>(
spaces: &'a mut [Workspace],
output: &Output,
@ -233,9 +236,49 @@ impl Shell {
workspace
}
fn add_output(&mut self, output: &Output) {
let config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
match self.mode {
Mode::OutputBound => {
let workspace = Self::assign_next_free_output(&mut self.spaces, output);
workspace.space.map_output(output, config.scale, (0, 0));
}
Mode::Global { active } => {
let workspace = &mut self.spaces[active];
workspace
.space
.map_output(output, config.scale, config.position);
}
}
}
fn remove_output(&mut self, output: &Output) {
match self.mode {
Mode::OutputBound => {
if let Some(idx) = output
.user_data()
.get::<ActiveWorkspace>()
.and_then(|a| a.get())
{
self.spaces[idx].space.unmap_output(output);
self.outputs.retain(|o| o != output);
}
}
Mode::Global { active } => {
self.spaces[active].space.unmap_output(output);
self.outputs.retain(|o| o != output);
// TODO move windows and outputs farther on the right / or load save config for remaining monitors
}
}
}
pub fn map_output(&mut self, output: &Output, backend: &mut BackendData, config: &mut Config) {
self.outputs.push(output.clone());
if !self.refresh_config(backend, config) {
let new_pos_x = self
.outputs()
@ -267,64 +310,32 @@ impl Shell {
.position = new_pos;
output.change_current_state(None, None, None, Some(new_pos.into()));
match self.mode {
Mode::OutputBound => {
let workspace = Self::assign_next_free_output(&mut self.spaces, output);
workspace.space.map_output(output, 1.0, (0, 0));
}
Mode::Global { active } => {
// just put new outputs on the right of the previous ones.
// in the future we will only need that as a fallback and need to read saved configurations here
let workspace = &mut self.spaces[active];
workspace.space.map_output(output, 1.0, new_pos);
}
}
let mut infos = self
.outputs()
.cloned()
.map(|o| {
(
Into::<crate::config::OutputInfo>::into(o.clone()),
o.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow()
.clone(),
)
})
.collect::<Vec<(OutputInfo, OutputConfig)>>();
infos.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b));
let (infos, configs) = infos.into_iter().unzip();
config
.dynamic_conf
.outputs_mut()
.config
.insert(infos, configs);
self.add_output(output);
self.save_config(config);
}
}
pub fn unmap_output(&mut self, output: &Output, backend: &mut BackendData, config: &Config) {
match self.mode {
Mode::OutputBound => {
if let Some(idx) = output
.user_data()
.get::<ActiveWorkspace>()
.and_then(|a| a.get())
{
self.spaces[idx].space.unmap_output(output);
self.outputs.retain(|o| o != output);
}
}
Mode::Global { active } => {
self.spaces[active].space.unmap_output(output);
self.outputs.retain(|o| o != output);
// TODO move windows and outputs farther on the right / or load save config for remaining monitors
}
}
pub fn unmap_output(
&mut self,
output: &Output,
backend: &mut BackendData,
config: &mut Config,
) {
self.remove_output(output);
self.refresh_config(backend, config);
}
pub fn enable_output(&mut self, output: &Output, config: &mut Config) {
self.outputs.push(output.clone());
self.add_output(output);
self.save_config(config);
}
pub fn disable_output(&mut self, output: &Output, config: &mut Config) {
self.remove_output(output);
self.save_config(config);
}
pub fn output_size(&self, output: &Output) -> Size<i32, Logical> {
let workspace = self.active_space(output);
workspace

View file

@ -2,7 +2,7 @@
use crate::{
backend::{kms::KmsState, winit::WinitState, x11::X11State},
config::Config,
config::{Config, OutputConfig},
logger::LogState,
shell::{init_shell, Shell},
};
@ -13,7 +13,13 @@ use smithay::{
},
wayland::{
data_device::{default_action_chooser, init_data_device, DataDeviceEvent},
output::{xdg::init_xdg_output_manager, Output},
output::{
wlr_configuration::{
self, init_wlr_output_configuration, ConfigurationManager, ModeConfiguration,
},
xdg::init_xdg_output_manager,
Mode as OutputMode, Output,
},
seat::Seat,
shell::xdg::ToplevelSurface,
shm::init_shm_global,
@ -42,6 +48,7 @@ pub struct Common {
pub socket: OsString,
pub event_loop_handle: LoopHandle<'static, State>,
pub output_conf: ConfigurationManager,
pub shell: Shell,
pub pending_toplevels: Vec<ToplevelSurface>,
pub dirty_flag: Arc<AtomicBool>,
@ -108,14 +115,45 @@ impl BackendData {
pub fn apply_config_for_output(
&mut self,
output: &Output,
) -> Result<(), Box<dyn std::error::Error>> {
match self {
BackendData::Kms(ref mut state) => state.apply_config_for_output(output),
_ => {
// TODO: reset the mode for nested backends, because we have no control over it
Ok(())
test_only: bool,
config: &mut Config,
shell: &mut Shell,
) -> Result<(), anyhow::Error> {
let result = match self {
BackendData::Kms(ref mut state) => {
state.apply_config_for_output(output, config, shell, test_only)
}
BackendData::Winit(ref mut state) => state.apply_config_for_output(output, test_only),
BackendData::X11(ref mut state) => state.apply_config_for_output(output, test_only),
_ => unreachable!("No backend set when applying output config"),
};
if result.is_ok() {
// apply to Output
let final_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow();
let mode = Some(OutputMode {
size: final_config.mode_size(),
refresh: final_config.mode_refresh() as i32,
})
.filter(|m| match output.current_mode() {
None => true,
Some(c_m) => m.size != c_m.size || m.refresh != c_m.refresh,
});
let transform =
Some(final_config.transform.into()).filter(|x| *x != output.current_transform());
let scale =
Some(final_config.scale.ceil() as i32).filter(|x| *x != output.current_scale());
let location =
Some(final_config.position.into()).filter(|x| *x != output.current_location());
output.change_current_state(mode, transform, scale, location);
shell.save_config(config);
}
result
}
pub fn schedule_render(&mut self, output: &Output) {
@ -176,6 +214,108 @@ impl State {
default_action_chooser,
None,
);
let (output_conf, _) = init_wlr_output_configuration(
&mut display,
|_| true,
|conf, test_only, mut ddata| {
let state = ddata.get::<State>().unwrap();
if conf.iter().all(|(_, conf)| conf.is_none()) {
return false; // we don't allow the user to accidentally disable all their outputs
}
let mut backups = Vec::new();
for (output, conf) in &conf {
{
let mut current_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
backups.push((output, current_config.clone()));
if let Some(conf) = conf {
match conf.mode {
Some(ModeConfiguration::Mode(mode)) => {
current_config.mode =
((mode.size.w, mode.size.h), Some(mode.refresh as u32));
}
Some(ModeConfiguration::Custom { size, refresh }) => {
current_config.mode =
((size.w, size.h), refresh.map(|x| x as u32));
}
_ => {}
}
if let Some(scale) = conf.scale {
current_config.scale = scale;
}
if let Some(transform) = conf.transform {
current_config.transform = transform;
}
if let Some(position) = conf.position {
current_config.position = position.into();
}
current_config.enabled = true;
} else {
current_config.enabled = false;
}
}
if let Err(err) = state.backend.apply_config_for_output(
output,
test_only,
&mut state.common.config,
&mut state.common.shell,
) {
slog_scope::warn!(
"Failed to apply config to {}: {}. Resetting",
output.name(),
err
);
for (output, backup) in backups {
{
let mut current_config = output
.user_data()
.get::<RefCell<OutputConfig>>()
.unwrap()
.borrow_mut();
*current_config = backup;
}
if !test_only {
if let Err(err) = state.backend.apply_config_for_output(
output,
false,
&mut state.common.config,
&mut state.common.shell,
) {
slog_scope::error!(
"Failed to reset output config for {}: {}",
output.name(),
err
);
}
}
}
return false;
}
}
for output in conf.iter().filter(|(_, c)| c.is_some()).map(|(o, _)| o) {
wlr_configuration::enable_head(output);
}
for output in conf.iter().filter(|(_, c)| c.is_none()).map(|(o, _)| o) {
wlr_configuration::disable_head(output);
}
state.common.event_loop_handle.insert_idle(move |state| {
state
.common
.output_conf
.update(&mut *state.common.display.borrow_mut());
});
true
},
None,
);
#[cfg(not(feature = "debug"))]
let dirty_flag = Arc::new(AtomicBool::new(false));
@ -189,6 +329,7 @@ impl State {
socket,
event_loop_handle: handle,
output_conf,
shell,
pending_toplevels: Vec::new(),
dirty_flag,