Commit 845a622a162f
tarball11 files changed+152-81
@@ -0,0 +1,64 @@
1
+//! Where frames go and input comes from. The set is closed: bare metal and2
+//! a window on another compositor, and later a headless one for tests.3
+4
+use crate::drm::Drm;5
+use crate::nested::Host;6
+use monolith_layout::OutputId;7
+use smithay::backend::drm::DrmNode;8
+use smithay::output::Output;9
+use smithay::wayland::drm_syncobj::DrmSyncobjState;10
+11
+pub enum Backend {12
+ /// Before `drm::init` or `nested::init` has run.13
+ Starting,14
+ Drm(Box<Drm>),15
+ Nested(Box<Host>),16
+}17
+18
+impl Backend {19
+ pub fn drm(&self) -> Option<&Drm> {20
+ match self {21
+ Backend::Drm(d) => Some(d),22
+ _ => None,23
+ }24
+ }25
+26
+ pub fn drm_mut(&mut self) -> Option<&mut Drm> {27
+ match self {28
+ Backend::Drm(d) => Some(d),29
+ _ => None,30
+ }31
+ }32
+33
+ pub fn nested(&self) -> Option<&Host> {34
+ match self {35
+ Backend::Nested(h) => Some(h),36
+ _ => None,37
+ }38
+ }39
+40
+ pub fn nested_mut(&mut self) -> Option<&mut Host> {41
+ match self {42
+ Backend::Nested(h) => Some(h),43
+ _ => None,44
+ }45
+ }46
+47
+ /// The smithay output behind a layout output.48
+ pub fn output(&self, o: OutputId) -> Option<Output> {49
+ match self {50
+ Backend::Drm(d) => d.output(o),51
+ Backend::Nested(h) => (h.output_id == o).then(|| h.output.clone()),52
+ Backend::Starting => None,53
+ }54
+ }55
+56
+ /// The node clients allocate dmabufs on, known on bare metal.57
+ pub fn render_node(&self) -> Option<DrmNode> {58
+ self.drm().map(|d| d.render_node)59
+ }60
+61
+ pub fn syncobj_mut(&mut self) -> Option<&mut DrmSyncobjState> {62
+ self.drm_mut()?.syncobj.as_mut()63
+ }64
+}@@ -163,7 +163,7 @@ impl State {
163163
}164164
165165
fn constraints(&self, size: (i32, i32)) -> BufferConstraints {166
- let dma = self.render_node.map(|node| {166
+ let dma = self.backend.render_node().map(|node| {167167
let mut formats: Vec<(Fourcc, Vec<_>)> = Vec::new();168168
for f in self.renderer.render_formats().iter() {169169
match formats.iter_mut().find(|(c, _)| *c == f.code) {@@ -302,7 +302,7 @@ impl State {
302302
fn output_named(&self, name: &str) -> Option<(OutputId, Output)> {303303
self.layout304304
.outputs()305
- .filter_map(|(o, _)| Some((o, self.smithay_output(o)?)))305
+ .filter_map(|(o, _)| Some((o, self.backend.output(o)?)))306306
.find(|(_, out)| out.name() == name)307307
}308308
}@@ -176,7 +176,7 @@ impl State {
176176
if let Some(sock) = &self.services.ipc.path {177177
cmd.env("MONOLITH_SOCKET", sock);178178
}179
- if self.drm.is_some() {179
+ if self.backend.drm().is_some() {180180
cmd.env("XDG_CURRENT_DESKTOP", "monolith")181181
.env("XDG_SESSION_TYPE", "wayland");182182
}@@ -2,6 +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;56
use crate::scene::Draw;67
use crate::state::Services;78
use crate::state::{State, now_ns};@@ -54,6 +55,8 @@ pub struct Drm {
5455
libinput: Libinput,5556
handle: LoopHandle<'static, State>,5657
node: DrmNode,58
+ pub render_node: DrmNode,59
+ pub syncobj: Option<DrmSyncobjState>,5760
manager: Manager,5861
scanner: DrmScanner,5962
nvidia: bool,@@ -193,20 +196,20 @@ pub fn init(
193196
.get_driver()194197
.map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))195198
.unwrap_or(false);196
- if supports_syncobj_eventfd(&fd) {197
- state.syncobj = Some(DrmSyncobjState::new::<State>(199
+ let syncobj = if supports_syncobj_eventfd(&fd) {200
+ Some(DrmSyncobjState::new::<State>(198201
&state.core.display,199202
fd.clone(),200
- ));203
+ ))201204
} else {202205
tracing::info!("no syncobj eventfd support, clients stay on implicit sync");203
- }206
+ None207
+ };204208
let gbm = GbmDevice::new(fd)?;205209
let render_node = node206210
.node_with_type(NodeType::Render)207211
.and_then(Result::ok)208212
.unwrap_or(node);209
- state.render_node = Some(render_node);210213
let allocator = GbmAllocator::new(211214
gbm.clone(),212215
GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,@@ -236,13 +239,13 @@ pub fn init(
236239
)?;237240
handle.insert_source(notifier, |event, _, state| match event {238241
SessionEvent::PauseSession => {239
- if let Some(drm) = state.drm.as_mut() {242
+ if let Some(drm) = state.backend.drm_mut() {240243
drm.libinput.suspend();241244
drm.manager.pause();242245
}243246
}244247
SessionEvent::ActivateSession => {245
- if let Some(drm) = state.drm.as_mut() {248
+ if let Some(drm) = state.backend.drm_mut() {246249
if drm.libinput.resume().is_err() {247250
tracing::warn!("libinput did not resume");248251
}@@ -265,19 +268,21 @@ pub fn init(
265268
handle.insert_source(udev, move |event, _, state| {266269
if let UdevEvent::Changed { device_id } = event267270
&& state268
- .drm269
- .as_ref()271
+ .backend272
+ .drm()270273
.is_some_and(|d| d.node.dev_id() == device_id)271274
{272275
scan(state);273276
}274277
})?;275278
276
- state.drm = Some(Drm {279
+ state.backend = Backend::Drm(Box::new(Drm {277280
session,278281
libinput,279282
handle,280283
node,284
+ render_node,285
+ syncobj,281286
manager,282287
scanner: DrmScanner::new(),283288
nvidia,@@ -287,14 +292,14 @@ pub fn init(
287292
FrameFlags::DEFAULT288293
},289294
surfaces: HashMap::new(),290
- });295
+ }));291296
scan(state);292297
Ok(())293298
}294299
295300
/// Brings outputs in line with what is plugged in.296301
fn scan(state: &mut State) {297
- let Some(drm) = state.drm.as_mut() else {302
+ let Some(drm) = state.backend.drm_mut() else {298303
return;299304
};300305
let events: Vec<DrmScanEvent> = match drm.scanner.scan_connectors(drm.manager.device()) {@@ -335,7 +340,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
335340
return;336341
};337342
let State {338
- drm: Some(drm),343
+ backend: Backend::Drm(drm),339344
renderer,340345
layout,341346
space,@@ -431,7 +436,7 @@ fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
431436
}432437
433438
fn disconnect(state: &mut State, crtc: crtc::Handle) {434
- let Some(drm) = state.drm.as_mut() else {439
+ let Some(drm) = state.backend.drm_mut() else {435440
return;436441
};437442
let Some(surface) = drm.surfaces.remove(&crtc) else {@@ -445,16 +450,21 @@ fn disconnect(state: &mut State, crtc: crtc::Handle) {
445450
state.layout.remove_output(surface.id);446451
}447452
448
-/// The smithay output for a layout output, for protocols that need one.449
-pub fn output_for(drm: &Drm, id: OutputId) -> Option<Output> {450
- drm.surfaces451
- .values()452
- .find(|s| s.id == id)453
- .map(|s| s.output.clone())453
+impl Drm {454
+ pub fn output(&self, id: OutputId) -> Option<Output> {455
+ self.surfaces456
+ .values()457
+ .find(|s| s.id == id)458
+ .map(|s| s.output.clone())459
+ }454460
}455461
456462
fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMetadata>) {457
- let Some(surface) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) else {463
+ let Some(surface) = state464
+ .backend465
+ .drm_mut()466
+ .and_then(|d| d.surfaces.get_mut(&crtc))467
+ else {458468
return;459469
};460470
if let Err(e) = surface.drm.frame_submitted() {@@ -464,7 +474,11 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
464474
if let Err(e) = state.renderer.absorb_submit_stall() {465475
tracing::warn!("absorbing a submit stall failed: {e}");466476
}467
- let Some(surface) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) else {477
+ let Some(surface) = state478
+ .backend479
+ .drm_mut()480
+ .and_then(|d| d.surfaces.get_mut(&crtc))481
+ else {468482
return;469483
};470484
if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {@@ -506,7 +520,7 @@ fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMe
506520
/// Starts a render at the last moment that still makes the next reachable507521
/// vblank, so commits arriving until then make it into the frame.508522
fn schedule(state: &mut State, crtc: crtc::Handle) {509
- let Some(drm) = state.drm.as_mut() else {523
+ let Some(drm) = state.backend.drm_mut() else {510524
return;511525
};512526
let Some(surface) = drm.surfaces.get_mut(&crtc) else {@@ -526,7 +540,11 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
526540
}527541
let wait = Timer::from_duration(Duration::from_nanos(CURSOR_FRAME_NS - since_flip));528542
let token = drm.handle.insert_source(wait, move |_, _, state| {529
- if let Some(s) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) {543
+ if let Some(s) = state544
+ .backend545
+ .drm_mut()546
+ .and_then(|d| d.surfaces.get_mut(&crtc))547
+ {530548
s.scheduled = None;531549
}532550
draw(state, crtc, now_ns());@@ -542,7 +560,7 @@ fn schedule(state: &mut State, crtc: crtc::Handle) {
542560
surface.target_ns = Some(vblank);543561
let wait = Timer::from_duration(Duration::from_nanos(start.saturating_sub(now)));544562
let token = drm.handle.insert_source(wait, move |_, _, state| {545
- let target = state.drm.as_mut().and_then(|d| {563
+ let target = state.backend.drm_mut().and_then(|d| {546564
let s = d.surfaces.get_mut(&crtc)?;547565
s.scheduled = None;548566
s.target_ns@@ -563,7 +581,7 @@ fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
563581
564582
/// Renders every output that has something new and no frame in flight.565583
pub fn render(state: &mut State) {566
- let Some(drm) = state.drm.as_mut() else {584
+ let Some(drm) = state.backend.drm_mut() else {567585
return;568586
};569587
let cursor = std::mem::take(&mut state.cursor_moved);@@ -588,7 +606,7 @@ pub fn render(state: &mut State) {
588606
#[profiling::function]589607
fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {590608
let State {591
- drm: Some(drm),609
+ backend: Backend::Drm(drm),592610
renderer,593611
layout,594612
windows,@@ -274,7 +274,7 @@ smithay::delegate_dispatch2!(State);
274274
275275
impl DrmSyncobjHandler for State {276276
fn drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState> {277
- self.syncobj.as_mut()277
+ self.backend.syncobj_mut()278278
}279279
}280280
@@ -121,7 +121,7 @@ impl State {
121121
}122122
}123123
Some(Some(Action::Vt(vt))) => {124
- if let Some(drm) = self.drm.as_mut()124
+ if let Some(drm) = self.backend.drm_mut()125125
&& let Err(e) = drm.session.change_vt(vt)126126
{127127
tracing::warn!("switching to VT {vt} failed: {e}");@@ -112,7 +112,7 @@ impl State {
112112
let outputs: Vec<OutputId> = self.layout.outputs().map(|(o, _)| o).collect();113113
let mut changed = false;114114
for o in outputs {115
- let Some(output) = self.smithay_output(o) else {115
+ let Some(output) = self.backend.output(o) else {116116
continue;117117
};118118
let Some(area) = self.layout.output(o).map(|out| out.area) else {@@ -1,5 +1,6 @@
11
mod activation;22
mod anim;3
+mod backend;34
mod capture;45
mod commands;56
mod cursor;@@ -5,10 +5,11 @@
55
66
mod input;77
8
+use crate::backend::Backend;89
use crate::state::Services;910
use crate::state::State;1011
use calloop_wayland_source::WaylandSource;11
-use monolith_layout::Rect;12
+use monolith_layout::{OutputId, Rect};1213
use monolith_render_vk::VulkanAllocator;1314
use smithay::backend::allocator::dmabuf::Dmabuf;1415
use smithay::backend::allocator::{Format, Fourcc, Modifier, Slot, Swapchain};@@ -35,6 +36,7 @@ const DEFAULT_SIZE: (i32, i32) = (1280, 800);
3536
const CLOCK_MONOTONIC: u32 = 1;3637
3738
pub struct Host {39
+ pub output_id: OutputId,3840
pub size: Size<i32, Logical>,3941
pub output: Output,4042
conn: Connection,@@ -137,9 +139,10 @@ pub fn init(
137139
f64::from(DEFAULT_SIZE.0),138140
f64::from(DEFAULT_SIZE.1),139141
);140
- state.output_id = Some(state.layout.add_output("nested", area));142
+ let output_id = state.layout.add_output("nested", area);141143
142
- state.host = Some(Host {144
+ state.backend = Backend::Nested(Box::new(Host {145
+ output_id,143146
size: DEFAULT_SIZE.into(),144147
damage: OutputDamageTracker::from_output(&output),145148
output,@@ -159,7 +162,7 @@ pub fn init(
159162
monotonic: false,160163
last_present_ns: None,161164
refresh_ns: 16_666_667,162
- });165
+ }));163166
queue.roundtrip(state)?;164167
queue.roundtrip(state)?;165168
WaylandSource::new(conn, queue).insert(event_loop.handle())?;@@ -169,7 +172,7 @@ pub fn init(
169172
/// Renders a frame if something changed and the host is ready for one.170173
pub fn render(state: &mut State) {171174
let State {172
- host,175
+ backend,173176
renderer,174177
space,175178
needs_redraw,@@ -179,7 +182,6 @@ pub fn render(state: &mut State) {
179182
anim,180183
borders,181184
settings,182
- output_id,183185
services:184186
Services {185187
lock,@@ -189,7 +191,9 @@ pub fn render(state: &mut State) {
189191
},190192
..191193
} = state;192
- let Some(host) = host.as_mut() else { return };194
+ let Some(host) = backend.nested_mut() else {195
+ return;196
+ };193197
if !host.configured || host.frame_pending || !*needs_redraw {194198
return;195199
}@@ -230,7 +234,7 @@ pub fn render(state: &mut State) {
230234
return;231235
}232236
};233
- let Some(o) = *output_id else { return };237
+ let o = host.output_id;234238
let elements = crate::lock::elements(lock, renderer, &host.output).unwrap_or_else(|| {235239
crate::scene::build(236240
layout,@@ -407,7 +411,7 @@ impl Dispatch<xdg_surface::XdgSurface, ()> for State {
407411
) {408412
if let xdg_surface::Event::Configure { serial } = event {409413
surface.ack_configure(serial);410
- if let Some(host) = state.host.as_mut() {414
+ if let Some(host) = state.backend.nested_mut() {411415
host.configured = true;412416
}413417
state.needs_redraw = true;@@ -427,7 +431,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for State {
427431
match event {428432
xdg_toplevel::Event::Configure { width, height, .. } if width > 0 && height > 0 => {429433
let size = Size::from((width, height));430
- if let Some(host) = state.host.as_mut()434
+ if let Some(host) = state.backend.nested_mut()431435
&& host.size != size432436
{433437
host.pending_size = Some(size);@@ -455,7 +459,7 @@ impl Dispatch<zwp_linux_dmabuf_v1::ZwpLinuxDmabufV1, ()> for State {
455459
modifier_hi,456460
modifier_lo,457461
} = event458
- && let (Ok(code), Some(host)) = (Fourcc::try_from(format), state.host.as_mut())462
+ && let (Ok(code), Some(host)) = (Fourcc::try_from(format), state.backend.nested_mut())459463
{460464
let modifier = Modifier::from((u64::from(modifier_hi) << 32) | u64::from(modifier_lo));461465
host.formats.push(Format { code, modifier });@@ -473,7 +477,7 @@ impl Dispatch<wl_buffer::WlBuffer, ()> for State {
473477
_: &QueueHandle<Self>,474478
) {475479
if let wl_buffer::Event::Release = event476
- && let Some(host) = state.host.as_mut()480
+ && let Some(host) = state.backend.nested_mut()477481
{478482
host.in_flight.retain(|(b, _)| b != buffer);479483
}@@ -490,7 +494,7 @@ impl Dispatch<wl_callback::WlCallback, ()> for State {
490494
_: &QueueHandle<Self>,491495
) {492496
if let wl_callback::Event::Done { .. } = event493
- && let Some(host) = state.host.as_mut()497
+ && let Some(host) = state.backend.nested_mut()494498
{495499
host.frame_pending = false;496500
}@@ -513,7 +517,7 @@ impl Dispatch<wp_presentation::WpPresentation, ()> for State {
513517
_: &QueueHandle<Self>,514518
) {515519
if let wp_presentation::Event::ClockId { clk_id } = event516
- && let Some(host) = state.host.as_mut()520
+ && let Some(host) = state.backend.nested_mut()517521
{518522
host.monotonic = clk_id == CLOCK_MONOTONIC;519523
}@@ -539,7 +543,7 @@ impl Dispatch<wp_presentation_feedback::WpPresentationFeedback, ()> for State {
539543
else {540544
return;541545
};542
- let Some(host) = state.host.as_mut() else {546
+ let Some(host) = state.backend.nested_mut() else {543547
return;544548
};545549
let secs = (u64::from(tv_sec_hi) << 32) | u64::from(tv_sec_lo);@@ -2,7 +2,6 @@ use crate::anim::{Animator, Ghost};
22
use crate::commands::Binds;33
use crate::focus::Focus;44
use crate::globals::Protocols;5
-use crate::nested::Host;65
use monolith_config::Config;76
use monolith_layout::{Layout, OutputId, Rect, WindowId};87
use monolith_protocols::workspace::{Group, Snapshot, Workspace};@@ -42,9 +41,7 @@ pub struct State {
4241
pub renderer: VulkanRenderer,4342
pub space: Space<Window>,4443
pub popups: PopupManager,45
- pub host: Option<Host>,46
- pub drm: Option<crate::drm::Drm>,47
- pub syncobj: Option<smithay::wayland::drm_syncobj::DrmSyncobjState>,44
+ pub backend: crate::backend::Backend,4845
pub cursor: crate::cursor::Cursor,4946
pub cursor_status: CursorImageStatus,5047
pub needs_redraw: bool,@@ -56,7 +53,6 @@ pub struct State {
5653
/// Toplevels that have not committed yet. Rules need their app_id and5754
/// title, which clients set after creating the toplevel.5855
pub unmapped: Vec<Window>,59
- pub output_id: Option<OutputId>,6056
pub suppressed: Vec<Keycode>,6157
pub move_inverted: bool,6258
pub drag: Option<crate::input::Drag>,@@ -74,8 +70,6 @@ pub struct State {
7470
pub placed_by_rule: HashSet<WindowId>,7571
/// A layer surface given the keyboard by a click, like a launcher.7672
pub layer_focus: Option<WlSurface>,77
- /// The render node clients get dmabufs for, known on bare metal.78
- pub render_node: Option<smithay::backend::drm::DrmNode>,7973
pub seat_state: SeatState<State>,8074
pub seat: Seat<State>,8175
}@@ -173,9 +167,7 @@ impl State {
173167
renderer,174168
space: Space::default(),175169
popups: PopupManager::default(),176
- host: None,177
- drm: None,178
- syncobj: None,170
+ backend: crate::backend::Backend::Starting,179171
cursor: crate::cursor::Cursor::load(),180172
cursor_status: CursorImageStatus::default_named(),181173
needs_redraw: true,@@ -183,7 +175,6 @@ impl State {
183175
layout,184176
windows: HashMap::new(),185177
unmapped: Vec::new(),186
- output_id: None,187178
suppressed: Vec::new(),188179
move_inverted: false,189180
drag: None,@@ -204,7 +195,6 @@ impl State {
204195
awaiting: HashSet::new(),205196
placed_by_rule: HashSet::new(),206197
layer_focus: None,207
- render_node: None,208198
seat_state,209199
seat,210200
})@@ -214,8 +204,11 @@ impl State {
214204
pub fn after_dispatch(&mut self) {215205
self.space.refresh();216206
self.popups.cleanup();217
- crate::nested::render(self);218
- crate::drm::render(self);207
+ match self.backend {208
+ crate::backend::Backend::Drm(_) => crate::drm::render(self),209
+ crate::backend::Backend::Nested(_) => crate::nested::render(self),210
+ crate::backend::Backend::Starting => {}211
+ }219212
self.confirm_lock();220213
self.complete_captures();221214
crate::ipc::broadcast(self);@@ -225,17 +218,16 @@ impl State {
225218
}226219
227220
pub fn resize_output(&mut self, size: Size<i32, Logical>) {228
- if let Some(o) = self.output_id {221
+ if let Some(host) = self.backend.nested() {222
+ let o = host.output_id;229223
let area = Rect::new(0.0, 0.0, f64::from(size.w), f64::from(size.h));224
+ let mode = smithay::output::Mode {225
+ size: (size.w, size.h).into(),226
+ refresh: 60_000,227
+ };228
+ host.output229
+ .change_current_state(Some(mode), None, None, None);230230
self.layout.set_output_area(o, area);231
- if let Some(host) = &self.host {232
- let mode = smithay::output::Mode {233
- size: (size.w, size.h).into(),234
- refresh: 60_000,235
- };236
- host.output237
- .change_current_state(Some(mode), None, None, None);238
- }239231
self.reserve_layers();240232
self.reconfigure_lock();241233
self.apply_layout();@@ -541,14 +533,6 @@ impl State {
541533
}542534
}543535
544
- /// The smithay output behind a layout output, on whichever backend runs.545
- pub fn smithay_output(&self, o: OutputId) -> Option<smithay::output::Output> {546
- if let Some(host) = self.host.as_ref().filter(|_| Some(o) == self.output_id) {547
- return Some(host.output.clone());548
- }549
- self.drm.as_ref().and_then(|d| crate::drm::output_for(d, o))550
- }551
-552536
fn publish_workspaces(&mut self) {553537
if !self.protocols.workspaces.has_clients() {554538
return;@@ -562,7 +546,7 @@ impl State {
562546
let groups = l563547
.outputs()564548
.filter_map(|(o, _)| {565
- let output = self.smithay_output(o)?;549
+ let output = self.backend.output(o)?;566550
// Bars read a tag's existence as having windows, as with567551
// workspaces that come and go, so empty unviewed tags are left out.568552
let workspaces = l@@ -132,7 +132,7 @@ impl State {
132132
app_id,133133
outputs: w134134
.and_then(|w| w.output)135
- .and_then(|o| self.smithay_output(o))135
+ .and_then(|o| self.backend.output(o))136136
.into_iter()137137
.collect(),138138
activated: focused == Some(id),