2023-08-13 12:46:53 +04:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2023-08-09 11:03:38 +04:00
|
|
|
use std::os::fd::FromRawFd;
|
2023-08-13 12:46:53 +04:00
|
|
|
use std::path::{Path, PathBuf};
|
2023-09-08 17:54:02 +04:00
|
|
|
use std::sync::{Mutex, Arc};
|
2023-08-14 15:53:24 +04:00
|
|
|
use std::time::Duration;
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-03 07:31:44 +04:00
|
|
|
use anyhow::{anyhow, Context};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
|
|
|
|
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay::backend::allocator::{Format as DrmFormat, Fourcc};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::backend::drm::compositor::DrmCompositor;
|
2023-08-14 15:53:24 +04:00
|
|
|
use smithay::backend::drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::backend::egl::{EGLContext, EGLDisplay};
|
|
|
|
|
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
|
2023-08-10 10:26:11 +04:00
|
|
|
use smithay::backend::renderer::gles::{GlesRenderer, GlesTexture};
|
2023-09-04 10:24:23 +04:00
|
|
|
use smithay::backend::renderer::{Bind, DebugFlags, ImportDma, ImportEgl};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::backend::session::libseat::LibSeatSession;
|
|
|
|
|
use smithay::backend::session::{Event as SessionEvent, Session};
|
|
|
|
|
use smithay::backend::udev::{self, UdevBackend, UdevEvent};
|
2023-08-16 10:59:34 +04:00
|
|
|
use smithay::desktop::utils::OutputPresentationFeedback;
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::output::{Mode, Output, OutputModeSource, PhysicalProperties, Subpixel};
|
2023-08-21 11:52:52 +04:00
|
|
|
use smithay::reexports::calloop::{Dispatcher, LoopHandle, RegistrationToken};
|
2023-08-14 15:53:24 +04:00
|
|
|
use smithay::reexports::drm::control::{
|
|
|
|
|
connector, crtc, Mode as DrmMode, ModeFlags, ModeTypeFlags,
|
|
|
|
|
};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::reexports::input::Libinput;
|
|
|
|
|
use smithay::reexports::nix::fcntl::OFlag;
|
|
|
|
|
use smithay::reexports::nix::libc::dev_t;
|
2023-09-03 15:15:55 +04:00
|
|
|
use smithay::reexports::wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags;
|
2023-08-16 10:59:34 +04:00
|
|
|
use smithay::reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback;
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay::utils::DeviceFd;
|
2023-09-04 10:24:23 +04:00
|
|
|
use smithay::wayland::dmabuf::{DmabufFeedbackBuilder, DmabufGlobal, DmabufState, DmabufFeedback};
|
2023-08-13 12:46:53 +04:00
|
|
|
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
|
2023-08-09 11:03:38 +04:00
|
|
|
use smithay_drm_extras::edid::EdidInfo;
|
|
|
|
|
|
2023-09-04 10:24:23 +04:00
|
|
|
use crate::niri::{OutputRenderElements, State};
|
2023-09-04 08:02:24 +04:00
|
|
|
use crate::utils::get_monotonic_time;
|
2023-09-03 14:10:02 +04:00
|
|
|
use crate::{LoopData, Niri};
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-09 14:20:59 +04:00
|
|
|
const BACKGROUND_COLOR: [f32; 4] = [0.1, 0.1, 0.1, 1.];
|
2023-08-09 11:03:38 +04:00
|
|
|
const SUPPORTED_COLOR_FORMATS: &[Fourcc] = &[Fourcc::Argb8888, Fourcc::Abgr8888];
|
|
|
|
|
|
|
|
|
|
pub struct Tty {
|
|
|
|
|
session: LibSeatSession,
|
2023-09-03 14:10:02 +04:00
|
|
|
udev_dispatcher: Dispatcher<'static, UdevBackend, LoopData>,
|
2023-08-09 11:03:38 +04:00
|
|
|
primary_gpu_path: PathBuf,
|
|
|
|
|
output_device: Option<OutputDevice>,
|
2023-09-08 17:54:02 +04:00
|
|
|
connectors: Arc<Mutex<HashMap<String, Output>>>,
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
type GbmDrmCompositor = DrmCompositor<
|
|
|
|
|
GbmAllocator<DrmDeviceFd>,
|
|
|
|
|
GbmDevice<DrmDeviceFd>,
|
|
|
|
|
OutputPresentationFeedback,
|
|
|
|
|
DrmDeviceFd,
|
|
|
|
|
>;
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
struct OutputDevice {
|
|
|
|
|
id: dev_t,
|
|
|
|
|
token: RegistrationToken,
|
|
|
|
|
drm: DrmDevice,
|
2023-08-13 12:46:53 +04:00
|
|
|
gbm: GbmDevice<DrmDeviceFd>,
|
2023-08-09 11:03:38 +04:00
|
|
|
gles: GlesRenderer,
|
2023-08-13 12:46:53 +04:00
|
|
|
formats: HashSet<DrmFormat>,
|
|
|
|
|
drm_scanner: DrmScanner,
|
2023-09-04 07:49:50 +04:00
|
|
|
surfaces: HashMap<crtc::Handle, Surface>,
|
2023-09-03 14:22:04 +04:00
|
|
|
dmabuf_state: DmabufState,
|
2023-09-04 10:24:23 +04:00
|
|
|
dmabuf_global: DmabufGlobal,
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
struct TtyOutputState {
|
|
|
|
|
device_id: dev_t,
|
|
|
|
|
crtc: crtc::Handle,
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-04 07:49:50 +04:00
|
|
|
struct Surface {
|
2023-09-04 08:01:50 +04:00
|
|
|
name: String,
|
2023-09-04 07:49:50 +04:00
|
|
|
compositor: GbmDrmCompositor,
|
|
|
|
|
dmabuf_feedback: DmabufFeedback,
|
2023-09-04 08:57:11 +04:00
|
|
|
/// Tracy frame that goes from vblank to vblank.
|
|
|
|
|
vblank_frame: Option<tracy_client::Frame>,
|
2023-09-14 09:19:20 +04:00
|
|
|
/// Frame name for the VBlank frame.
|
2023-09-04 08:57:11 +04:00
|
|
|
vblank_frame_name: tracy_client::FrameName,
|
2023-09-14 09:19:20 +04:00
|
|
|
/// Plot name for the VBlank dispatch offset plot.
|
|
|
|
|
vblank_plot_name: tracy_client::PlotName,
|
2023-09-04 07:49:50 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
impl Tty {
|
2023-09-03 14:10:02 +04:00
|
|
|
pub fn new(event_loop: LoopHandle<'static, LoopData>) -> Self {
|
2023-08-09 11:03:38 +04:00
|
|
|
let (session, notifier) = LibSeatSession::new().unwrap();
|
|
|
|
|
let seat_name = session.seat();
|
|
|
|
|
|
2023-08-21 11:52:52 +04:00
|
|
|
let udev_backend = UdevBackend::new(session.seat()).unwrap();
|
2023-09-03 14:10:02 +04:00
|
|
|
let udev_dispatcher =
|
|
|
|
|
Dispatcher::new(udev_backend, move |event, _, data: &mut LoopData| {
|
|
|
|
|
let tty = data.state.backend.tty();
|
|
|
|
|
let niri = &mut data.state.niri;
|
2023-08-21 11:52:52 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
match event {
|
|
|
|
|
UdevEvent::Added { device_id, path } => {
|
|
|
|
|
if !tty.session.is_active() {
|
|
|
|
|
debug!("skipping UdevEvent::Added as session is inactive");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-21 11:52:52 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
if let Err(err) = tty.device_added(device_id, &path, niri) {
|
|
|
|
|
warn!("error adding device: {err:?}");
|
|
|
|
|
}
|
2023-08-21 11:52:52 +04:00
|
|
|
}
|
2023-09-03 14:10:02 +04:00
|
|
|
UdevEvent::Changed { device_id } => {
|
|
|
|
|
if !tty.session.is_active() {
|
|
|
|
|
debug!("skipping UdevEvent::Changed as session is inactive");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-21 11:52:52 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
tty.device_changed(device_id, niri)
|
2023-08-21 11:52:52 +04:00
|
|
|
}
|
2023-09-03 14:10:02 +04:00
|
|
|
UdevEvent::Removed { device_id } => {
|
|
|
|
|
if !tty.session.is_active() {
|
|
|
|
|
debug!("skipping UdevEvent::Removed as session is inactive");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-09-03 13:25:43 +04:00
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
tty.device_removed(device_id, niri)
|
|
|
|
|
}
|
2023-08-21 11:52:52 +04:00
|
|
|
}
|
2023-09-03 14:10:02 +04:00
|
|
|
});
|
2023-08-21 11:52:52 +04:00
|
|
|
event_loop
|
|
|
|
|
.register_dispatcher(udev_dispatcher.clone())
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
let mut libinput = Libinput::new_with_udev(LibinputSessionInterface::from(session.clone()));
|
|
|
|
|
libinput.udev_assign_seat(&seat_name).unwrap();
|
|
|
|
|
|
|
|
|
|
let input_backend = LibinputInputBackend::new(libinput.clone());
|
|
|
|
|
event_loop
|
2023-08-15 18:17:26 +04:00
|
|
|
.insert_source(input_backend, |mut event, _, data| {
|
2023-09-03 14:10:02 +04:00
|
|
|
data.state.process_libinput_event(&mut event);
|
|
|
|
|
data.state.process_input_event(event);
|
2023-08-09 11:03:38 +04:00
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-08-21 11:52:52 +04:00
|
|
|
let udev_dispatcher_c = udev_dispatcher.clone();
|
2023-08-09 11:03:38 +04:00
|
|
|
event_loop
|
|
|
|
|
.insert_source(notifier, move |event, _, data| {
|
2023-09-03 14:10:02 +04:00
|
|
|
let tty = data.state.backend.tty();
|
|
|
|
|
let niri = &mut data.state.niri;
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
match event {
|
|
|
|
|
SessionEvent::PauseSession => {
|
2023-08-09 14:19:22 +04:00
|
|
|
debug!("pausing session");
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
libinput.suspend();
|
|
|
|
|
|
|
|
|
|
if let Some(output_device) = &tty.output_device {
|
|
|
|
|
output_device.drm.pause();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SessionEvent::ActivateSession => {
|
2023-08-09 14:19:22 +04:00
|
|
|
debug!("resuming session");
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
if libinput.resume().is_err() {
|
|
|
|
|
error!("error resuming libinput");
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-09 14:19:22 +04:00
|
|
|
if let Some(output_device) = &mut tty.output_device {
|
2023-08-21 11:52:52 +04:00
|
|
|
// We had an output device, check if it's been removed.
|
|
|
|
|
let output_device_id = output_device.id;
|
|
|
|
|
if !udev_dispatcher_c
|
|
|
|
|
.as_source_ref()
|
|
|
|
|
.device_list()
|
|
|
|
|
.any(|(device_id, _)| device_id == output_device_id)
|
|
|
|
|
{
|
|
|
|
|
// The output device, if we had any, has been removed.
|
|
|
|
|
tty.device_removed(output_device_id, niri);
|
|
|
|
|
} else {
|
|
|
|
|
// It hasn't been removed, update its state as usual.
|
|
|
|
|
output_device.drm.activate();
|
|
|
|
|
|
2023-08-21 12:45:58 +04:00
|
|
|
// HACK: force reset the connectors to make resuming work across
|
|
|
|
|
// sleep.
|
|
|
|
|
let output_device = tty.output_device.as_mut().unwrap();
|
|
|
|
|
let crtcs: Vec<_> = output_device
|
|
|
|
|
.drm_scanner
|
|
|
|
|
.crtcs()
|
|
|
|
|
.map(|(conn, crtc)| (conn.clone(), crtc))
|
|
|
|
|
.collect();
|
|
|
|
|
for (conn, crtc) in crtcs {
|
|
|
|
|
tty.connector_disconnected(niri, conn, crtc);
|
|
|
|
|
}
|
2023-08-21 11:52:52 +04:00
|
|
|
|
|
|
|
|
let output_device = tty.output_device.as_mut().unwrap();
|
2023-08-21 12:45:58 +04:00
|
|
|
let _ = output_device
|
|
|
|
|
.drm_scanner
|
|
|
|
|
.scan_connectors(&output_device.drm);
|
|
|
|
|
let crtcs: Vec<_> = output_device
|
|
|
|
|
.drm_scanner
|
|
|
|
|
.crtcs()
|
|
|
|
|
.map(|(conn, crtc)| (conn.clone(), crtc))
|
|
|
|
|
.collect();
|
|
|
|
|
for (conn, crtc) in crtcs {
|
|
|
|
|
if let Err(err) = tty.connector_connected(niri, conn, crtc) {
|
|
|
|
|
warn!("error connecting connector: {err:?}");
|
2023-08-21 11:52:52 +04:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-09 14:19:22 +04:00
|
|
|
|
2023-08-21 12:45:58 +04:00
|
|
|
// // Refresh the connectors.
|
|
|
|
|
// tty.device_changed(output_device_id, niri);
|
2023-08-27 10:37:30 +04:00
|
|
|
|
2023-08-21 12:45:58 +04:00
|
|
|
// // Refresh the state on unchanged connectors.
|
2023-08-27 10:37:30 +04:00
|
|
|
// let output_device = tty.output_device.as_mut().unwrap();
|
2023-08-21 12:45:58 +04:00
|
|
|
// for drm_compositor in output_device.surfaces.values_mut() {
|
|
|
|
|
// if let Err(err) = drm_compositor.surface().reset_state() {
|
|
|
|
|
// warn!("error resetting DRM surface state: {err}");
|
|
|
|
|
// }
|
|
|
|
|
// drm_compositor.reset_buffers();
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// niri.queue_redraw_all();
|
2023-08-21 11:52:52 +04:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// We didn't have an output device, check if it's been added.
|
|
|
|
|
for (device_id, path) in udev_dispatcher_c.as_source_ref().device_list()
|
|
|
|
|
{
|
|
|
|
|
if let Err(err) = tty.device_added(device_id, path, niri) {
|
|
|
|
|
warn!("error adding device: {err:?}");
|
2023-08-13 12:46:53 +04:00
|
|
|
}
|
2023-08-09 14:19:22 +04:00
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
|
|
let primary_gpu_path = udev::primary_gpu(&seat_name).unwrap().unwrap();
|
|
|
|
|
|
|
|
|
|
Self {
|
|
|
|
|
session,
|
2023-08-21 11:52:52 +04:00
|
|
|
udev_dispatcher,
|
2023-08-09 11:03:38 +04:00
|
|
|
primary_gpu_path,
|
|
|
|
|
output_device: None,
|
2023-09-08 17:54:02 +04:00
|
|
|
connectors: Arc::new(Mutex::new(HashMap::new())),
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn init(&mut self, niri: &mut Niri) {
|
2023-08-21 11:52:52 +04:00
|
|
|
for (device_id, path) in self.udev_dispatcher.clone().as_source_ref().device_list() {
|
2023-08-13 12:46:53 +04:00
|
|
|
if let Err(err) = self.device_added(device_id, path, niri) {
|
2023-08-09 11:03:38 +04:00
|
|
|
warn!("error adding device: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn device_added(
|
|
|
|
|
&mut self,
|
|
|
|
|
device_id: dev_t,
|
2023-08-13 12:46:53 +04:00
|
|
|
path: &Path,
|
2023-08-09 11:03:38 +04:00
|
|
|
niri: &mut Niri,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
if path != self.primary_gpu_path {
|
|
|
|
|
debug!("skipping non-primary device {path:?}");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug!("adding device {path:?}");
|
|
|
|
|
assert!(self.output_device.is_none());
|
|
|
|
|
|
|
|
|
|
let open_flags = OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK;
|
2023-08-13 12:46:53 +04:00
|
|
|
let fd = self.session.open(path, open_flags)?;
|
2023-08-09 11:03:38 +04:00
|
|
|
let device_fd = unsafe { DrmDeviceFd::new(DeviceFd::from_raw_fd(fd)) };
|
|
|
|
|
|
|
|
|
|
let (drm, drm_notifier) = DrmDevice::new(device_fd.clone(), true)?;
|
|
|
|
|
let gbm = GbmDevice::new(device_fd)?;
|
|
|
|
|
|
|
|
|
|
let display = EGLDisplay::new(gbm.clone())?;
|
|
|
|
|
let egl_context = EGLContext::new(&display)?;
|
|
|
|
|
|
2023-09-04 09:55:53 +04:00
|
|
|
// let capabilities = unsafe { GlesRenderer::supported_capabilities(&egl_context) }?
|
|
|
|
|
// .into_iter()
|
|
|
|
|
// .filter(|c| *c != Capability::ColorTransformations);
|
|
|
|
|
// let mut gles = unsafe { GlesRenderer::with_capabilities(egl_context, capabilities)? };
|
2023-08-09 11:03:38 +04:00
|
|
|
let mut gles = unsafe { GlesRenderer::new(egl_context)? };
|
|
|
|
|
gles.bind_wl_display(&niri.display_handle)?;
|
|
|
|
|
|
|
|
|
|
let token = niri
|
|
|
|
|
.event_loop
|
|
|
|
|
.insert_source(drm_notifier, move |event, metadata, data| {
|
2023-09-03 14:10:02 +04:00
|
|
|
let tty = data.state.backend.tty();
|
2023-08-09 11:03:38 +04:00
|
|
|
match event {
|
2023-08-13 12:46:53 +04:00
|
|
|
DrmEvent::VBlank(crtc) => {
|
2023-09-04 08:02:24 +04:00
|
|
|
let now = get_monotonic_time();
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let device = tty.output_device.as_mut().unwrap();
|
2023-09-04 08:02:24 +04:00
|
|
|
let surface = device.surfaces.get_mut(&crtc).unwrap();
|
|
|
|
|
let name = &surface.name;
|
|
|
|
|
trace!("vblank on {name} {metadata:?}");
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-04 08:57:11 +04:00
|
|
|
drop(surface.vblank_frame.take()); // Drop the old one first.
|
|
|
|
|
let vblank_frame = tracy_client::Client::running()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.non_continuous_frame(surface.vblank_frame_name);
|
|
|
|
|
surface.vblank_frame = Some(vblank_frame);
|
|
|
|
|
|
2023-08-14 15:53:24 +04:00
|
|
|
let presentation_time = match metadata.as_mut().unwrap().time {
|
|
|
|
|
DrmEventTime::Monotonic(time) => time,
|
|
|
|
|
DrmEventTime::Realtime(_) => {
|
|
|
|
|
// Not supported.
|
|
|
|
|
|
|
|
|
|
// This value will be ignored in the frame clock code.
|
|
|
|
|
Duration::ZERO
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-09-04 08:02:24 +04:00
|
|
|
let message = if presentation_time.is_zero() {
|
|
|
|
|
format!("vblank on {name}, presentation time unknown")
|
|
|
|
|
} else if presentation_time > now {
|
|
|
|
|
let diff = presentation_time - now;
|
2023-09-14 09:19:20 +04:00
|
|
|
tracy_client::Client::running()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.plot(surface.vblank_plot_name, -diff.as_secs_f64() * 1000.);
|
2023-09-04 08:02:24 +04:00
|
|
|
format!("vblank on {name}, presentation is {diff:?} later")
|
|
|
|
|
} else {
|
|
|
|
|
let diff = now - presentation_time;
|
2023-09-14 09:19:20 +04:00
|
|
|
tracy_client::Client::running()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.plot(surface.vblank_plot_name, diff.as_secs_f64() * 1000.);
|
2023-09-04 08:02:24 +04:00
|
|
|
format!("vblank on {name}, presentation was {diff:?} ago")
|
|
|
|
|
};
|
|
|
|
|
tracy_client::Client::running()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.message(&message, 0);
|
|
|
|
|
|
2023-09-04 14:26:55 +04:00
|
|
|
let output = data
|
|
|
|
|
.state
|
|
|
|
|
.niri
|
|
|
|
|
.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.find(|output| {
|
|
|
|
|
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
|
|
|
|
|
tty_state.device_id == device.id && tty_state.crtc == crtc
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.clone();
|
|
|
|
|
let output_state = data.state.niri.output_state.get_mut(&output).unwrap();
|
|
|
|
|
|
2023-08-16 10:59:34 +04:00
|
|
|
// Mark the last frame as submitted.
|
2023-09-04 08:02:24 +04:00
|
|
|
match surface.compositor.frame_submitted() {
|
2023-08-16 10:59:34 +04:00
|
|
|
Ok(Some(mut feedback)) => {
|
2023-09-04 14:26:55 +04:00
|
|
|
let refresh = output_state
|
|
|
|
|
.frame_clock
|
2023-09-04 15:09:58 +04:00
|
|
|
.refresh_interval()
|
|
|
|
|
.unwrap_or(Duration::ZERO);
|
2023-08-16 10:59:34 +04:00
|
|
|
// FIXME: ideally should be monotonically increasing for a surface.
|
|
|
|
|
let seq = metadata.as_ref().unwrap().sequence as u64;
|
|
|
|
|
let flags = wp_presentation_feedback::Kind::Vsync
|
|
|
|
|
| wp_presentation_feedback::Kind::HwClock
|
|
|
|
|
| wp_presentation_feedback::Kind::HwCompletion;
|
|
|
|
|
|
|
|
|
|
feedback.presented::<_, smithay::utils::Monotonic>(
|
|
|
|
|
presentation_time,
|
|
|
|
|
refresh,
|
|
|
|
|
seq,
|
|
|
|
|
flags,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Ok(None) => (),
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("error marking frame as submitted: {err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-14 15:53:24 +04:00
|
|
|
output_state.waiting_for_vblank = false;
|
|
|
|
|
output_state.frame_clock.presented(presentation_time);
|
2023-09-03 14:10:02 +04:00
|
|
|
data.state.niri.queue_redraw(output);
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
DrmEvent::Error(error) => error!("DRM error: {error}"),
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let formats = Bind::<Dmabuf>::supported_formats(&gles).unwrap_or_default();
|
|
|
|
|
|
2023-09-04 10:24:23 +04:00
|
|
|
let mut dmabuf_state = DmabufState::new();
|
|
|
|
|
let default_feedback = DmabufFeedbackBuilder::new(device_id, gles.dmabuf_formats())
|
|
|
|
|
.build()
|
|
|
|
|
.unwrap();
|
|
|
|
|
let dmabuf_global = dmabuf_state
|
|
|
|
|
.create_global_with_default_feedback::<State>(&niri.display_handle, &default_feedback);
|
2023-09-03 14:22:04 +04:00
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
self.output_device = Some(OutputDevice {
|
|
|
|
|
id: device_id,
|
|
|
|
|
token,
|
|
|
|
|
drm,
|
2023-08-13 12:46:53 +04:00
|
|
|
gbm,
|
2023-08-09 11:03:38 +04:00
|
|
|
gles,
|
2023-08-13 12:46:53 +04:00
|
|
|
formats,
|
|
|
|
|
drm_scanner: DrmScanner::new(),
|
|
|
|
|
surfaces: HashMap::new(),
|
2023-09-03 14:22:04 +04:00
|
|
|
dmabuf_state,
|
2023-09-04 10:24:23 +04:00
|
|
|
dmabuf_global,
|
2023-08-09 11:03:38 +04:00
|
|
|
});
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
self.device_changed(device_id, niri);
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn device_changed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
2023-08-13 12:46:53 +04:00
|
|
|
let Some(device) = &mut self.output_device else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
if device.id != device_id {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
debug!("output device changed");
|
|
|
|
|
|
|
|
|
|
for event in device.drm_scanner.scan_connectors(&device.drm) {
|
|
|
|
|
match event {
|
|
|
|
|
DrmScanEvent::Connected {
|
|
|
|
|
connector,
|
|
|
|
|
crtc: Some(crtc),
|
|
|
|
|
} => {
|
|
|
|
|
if let Err(err) = self.connector_connected(niri, connector, crtc) {
|
|
|
|
|
warn!("error connecting connector: {err:?}");
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
DrmScanEvent::Disconnected {
|
|
|
|
|
connector,
|
|
|
|
|
crtc: Some(crtc),
|
|
|
|
|
} => self.connector_disconnected(niri, connector, crtc),
|
|
|
|
|
_ => (),
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn device_removed(&mut self, device_id: dev_t, niri: &mut Niri) {
|
2023-08-21 12:45:58 +04:00
|
|
|
let Some(device) = &mut self.output_device else {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
};
|
2023-08-21 12:45:58 +04:00
|
|
|
if device_id != device.id {
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let crtcs: Vec<_> = device
|
|
|
|
|
.drm_scanner
|
|
|
|
|
.crtcs()
|
|
|
|
|
.map(|(info, crtc)| (info.clone(), crtc))
|
|
|
|
|
.collect();
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
for (connector, crtc) in crtcs {
|
|
|
|
|
self.connector_disconnected(niri, connector, crtc);
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-09-03 07:32:23 +04:00
|
|
|
let mut device = self.output_device.take().unwrap();
|
2023-09-04 10:24:23 +04:00
|
|
|
device
|
|
|
|
|
.dmabuf_state
|
|
|
|
|
.destroy_global::<State>(&niri.display_handle, device.dmabuf_global);
|
2023-09-03 07:32:23 +04:00
|
|
|
device.gles.unbind_wl_display();
|
2023-09-03 14:22:04 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
niri.event_loop.remove(device.token);
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
fn connector_connected(
|
2023-08-09 11:03:38 +04:00
|
|
|
&mut self,
|
|
|
|
|
niri: &mut Niri,
|
2023-08-13 12:46:53 +04:00
|
|
|
connector: connector::Info,
|
|
|
|
|
crtc: crtc::Handle,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
let output_name = format!(
|
|
|
|
|
"{}-{}",
|
2023-08-09 11:03:38 +04:00
|
|
|
connector.interface().as_str(),
|
|
|
|
|
connector.interface_id(),
|
|
|
|
|
);
|
2023-08-13 12:46:53 +04:00
|
|
|
debug!("connecting connector: {output_name}");
|
|
|
|
|
|
|
|
|
|
let device = self.output_device.as_mut().unwrap();
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
let mut mode = connector.modes().get(0);
|
|
|
|
|
connector.modes().iter().for_each(|m| {
|
|
|
|
|
debug!("mode: {m:?}");
|
|
|
|
|
|
|
|
|
|
if m.mode_type().contains(ModeTypeFlags::PREFERRED) {
|
|
|
|
|
// Pick the highest refresh rate.
|
|
|
|
|
if mode
|
|
|
|
|
.map(|curr| curr.vrefresh() < m.vrefresh())
|
|
|
|
|
.unwrap_or(true)
|
|
|
|
|
{
|
|
|
|
|
mode = Some(m);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
let mode = mode.ok_or_else(|| anyhow!("no mode"))?;
|
2023-08-13 12:46:53 +04:00
|
|
|
debug!("picking mode: {mode:?}");
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let surface = device
|
|
|
|
|
.drm
|
|
|
|
|
.create_surface(crtc, *mode, &[connector.handle()])?;
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
// Create GBM allocator.
|
|
|
|
|
let gbm_flags = GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT;
|
2023-08-13 12:46:53 +04:00
|
|
|
let allocator = GbmAllocator::new(device.gbm.clone(), gbm_flags);
|
2023-08-09 11:03:38 +04:00
|
|
|
|
|
|
|
|
// Update the output mode.
|
|
|
|
|
let (physical_width, physical_height) = connector.size().unwrap_or((0, 0));
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
let (make, model) = EdidInfo::for_connector(&device.drm, connector.handle())
|
2023-08-09 11:03:38 +04:00
|
|
|
.map(|info| (info.manufacturer, info.model))
|
|
|
|
|
.unwrap_or_else(|| ("Unknown".into(), "Unknown".into()));
|
|
|
|
|
|
|
|
|
|
let output = Output::new(
|
2023-09-04 08:01:50 +04:00
|
|
|
output_name.clone(),
|
2023-08-09 11:03:38 +04:00
|
|
|
PhysicalProperties {
|
|
|
|
|
size: (physical_width as i32, physical_height as i32).into(),
|
|
|
|
|
subpixel: Subpixel::Unknown,
|
|
|
|
|
model,
|
|
|
|
|
make,
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
let wl_mode = Mode::from(*mode);
|
|
|
|
|
output.change_current_state(Some(wl_mode), None, None, Some((0, 0).into()));
|
|
|
|
|
output.set_preferred(wl_mode);
|
|
|
|
|
|
2023-08-13 12:46:53 +04:00
|
|
|
output.user_data().insert_if_missing(|| TtyOutputState {
|
|
|
|
|
device_id: device.id,
|
|
|
|
|
crtc,
|
|
|
|
|
});
|
2023-08-09 11:03:38 +04:00
|
|
|
|
2023-09-04 15:09:58 +04:00
|
|
|
let mut planes = surface.planes().clone();
|
2023-09-04 10:24:23 +04:00
|
|
|
// Disable overlay planes as they cause weird performance issues on my system.
|
|
|
|
|
planes.overlay.clear();
|
2023-09-04 15:09:58 +04:00
|
|
|
let scanout_formats = planes
|
|
|
|
|
.primary
|
|
|
|
|
.formats
|
|
|
|
|
.iter()
|
|
|
|
|
.chain(planes.overlay.iter().flat_map(|p| &p.formats))
|
|
|
|
|
.copied()
|
2023-09-03 15:15:55 +04:00
|
|
|
.collect::<HashSet<_>>();
|
|
|
|
|
let scanout_formats = scanout_formats.intersection(&device.formats).copied();
|
|
|
|
|
|
2023-08-09 11:03:38 +04:00
|
|
|
// Create the compositor.
|
|
|
|
|
let compositor = DrmCompositor::new(
|
2023-08-13 12:46:53 +04:00
|
|
|
OutputModeSource::Auto(output.clone()),
|
2023-08-09 11:03:38 +04:00
|
|
|
surface,
|
2023-09-04 10:24:23 +04:00
|
|
|
Some(planes),
|
2023-08-09 11:03:38 +04:00
|
|
|
allocator,
|
2023-08-13 12:46:53 +04:00
|
|
|
device.gbm.clone(),
|
2023-08-09 11:03:38 +04:00
|
|
|
SUPPORTED_COLOR_FORMATS,
|
2023-08-13 12:46:53 +04:00
|
|
|
device.formats.clone(),
|
|
|
|
|
device.drm.cursor_size(),
|
|
|
|
|
Some(device.gbm.clone()),
|
2023-08-09 11:03:38 +04:00
|
|
|
)?;
|
2023-08-13 12:46:53 +04:00
|
|
|
|
2023-09-03 15:15:55 +04:00
|
|
|
let dmabuf_feedback = DmabufFeedbackBuilder::new(device.id, device.formats.clone())
|
|
|
|
|
.add_preference_tranche(device.id, Some(TrancheFlags::Scanout), scanout_formats)
|
|
|
|
|
.build()
|
|
|
|
|
.unwrap();
|
|
|
|
|
|
2023-09-04 08:57:11 +04:00
|
|
|
let vblank_frame_name = unsafe {
|
|
|
|
|
tracy_client::internal::create_frame_name(format!("vblank on {output_name}\0").leak())
|
|
|
|
|
};
|
2023-09-14 09:19:20 +04:00
|
|
|
let vblank_plot_name = unsafe {
|
|
|
|
|
tracy_client::internal::create_plot(
|
|
|
|
|
format!("{output_name} vblank dispatch offset, ms\0").leak(),
|
|
|
|
|
)
|
|
|
|
|
};
|
2023-09-04 08:57:11 +04:00
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
self.connectors
|
|
|
|
|
.lock()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.insert(output_name.clone(), output.clone());
|
|
|
|
|
|
2023-09-04 07:49:50 +04:00
|
|
|
let surface = Surface {
|
2023-09-04 08:01:50 +04:00
|
|
|
name: output_name,
|
2023-09-04 07:49:50 +04:00
|
|
|
compositor,
|
|
|
|
|
dmabuf_feedback,
|
2023-09-04 08:57:11 +04:00
|
|
|
vblank_frame: None,
|
|
|
|
|
vblank_frame_name,
|
2023-09-14 09:19:20 +04:00
|
|
|
vblank_plot_name,
|
2023-09-04 07:49:50 +04:00
|
|
|
};
|
|
|
|
|
let res = device.surfaces.insert(crtc, surface);
|
2023-08-13 12:46:53 +04:00
|
|
|
assert!(res.is_none(), "crtc must not have already existed");
|
|
|
|
|
|
2023-08-14 15:53:24 +04:00
|
|
|
niri.add_output(output.clone(), Some(refresh_interval(*mode)));
|
2023-08-13 12:46:53 +04:00
|
|
|
niri.queue_redraw(output);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn connector_disconnected(
|
|
|
|
|
&mut self,
|
|
|
|
|
niri: &mut Niri,
|
|
|
|
|
connector: connector::Info,
|
|
|
|
|
crtc: crtc::Handle,
|
|
|
|
|
) {
|
|
|
|
|
debug!("disconnecting connector: {connector:?}");
|
|
|
|
|
let device = self.output_device.as_mut().unwrap();
|
|
|
|
|
|
2023-09-08 17:54:02 +04:00
|
|
|
let Some(surface) = device.surfaces.remove(&crtc) else {
|
|
|
|
|
debug!("crtc wasn't enabled");
|
2023-08-13 12:46:53 +04:00
|
|
|
return;
|
2023-09-08 17:54:02 +04:00
|
|
|
};
|
2023-08-13 12:46:53 +04:00
|
|
|
|
|
|
|
|
let output = niri
|
|
|
|
|
.global_space
|
|
|
|
|
.outputs()
|
|
|
|
|
.find(|output| {
|
|
|
|
|
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
|
|
|
|
|
tty_state.device_id == device.id && tty_state.crtc == crtc
|
|
|
|
|
})
|
|
|
|
|
.unwrap()
|
|
|
|
|
.clone();
|
|
|
|
|
|
|
|
|
|
niri.remove_output(&output);
|
2023-09-08 17:54:02 +04:00
|
|
|
|
|
|
|
|
self.connectors.lock().unwrap().remove(&surface.name);
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-09 14:21:29 +04:00
|
|
|
|
2023-09-03 13:04:32 +04:00
|
|
|
pub fn seat_name(&self) -> String {
|
|
|
|
|
self.session.seat()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn renderer(&mut self) -> &mut GlesRenderer {
|
|
|
|
|
&mut self.output_device.as_mut().unwrap().gles
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn render(
|
|
|
|
|
&mut self,
|
|
|
|
|
niri: &mut Niri,
|
|
|
|
|
output: &Output,
|
|
|
|
|
elements: &[OutputRenderElements<GlesRenderer>],
|
2023-09-03 15:15:55 +04:00
|
|
|
) -> Option<&DmabufFeedback> {
|
2023-09-03 13:04:32 +04:00
|
|
|
let _span = tracy_client::span!("Tty::render");
|
|
|
|
|
|
|
|
|
|
let device = self.output_device.as_mut().unwrap();
|
|
|
|
|
let tty_state: &TtyOutputState = output.user_data().get().unwrap();
|
2023-09-04 07:49:50 +04:00
|
|
|
let surface = device.surfaces.get_mut(&tty_state.crtc).unwrap();
|
|
|
|
|
let drm_compositor = &mut surface.compositor;
|
2023-09-03 13:04:32 +04:00
|
|
|
|
|
|
|
|
match drm_compositor.render_frame::<_, _, GlesTexture>(
|
|
|
|
|
&mut device.gles,
|
|
|
|
|
elements,
|
|
|
|
|
BACKGROUND_COLOR,
|
|
|
|
|
) {
|
|
|
|
|
Ok(res) => {
|
|
|
|
|
assert!(!res.needs_sync());
|
2023-09-04 09:56:28 +04:00
|
|
|
|
|
|
|
|
// if let PrimaryPlaneElement::Swapchain(element) = res.primary_element {
|
|
|
|
|
// let _span = tracy_client::span!("wait for sync");
|
|
|
|
|
// element.sync.wait();
|
|
|
|
|
// }
|
|
|
|
|
|
2023-09-03 13:04:32 +04:00
|
|
|
if res.damage.is_some() {
|
|
|
|
|
let presentation_feedbacks =
|
|
|
|
|
niri.take_presentation_feedbacks(output, &res.states);
|
|
|
|
|
|
|
|
|
|
match drm_compositor.queue_frame(presentation_feedbacks) {
|
|
|
|
|
Ok(()) => {
|
|
|
|
|
niri.output_state
|
|
|
|
|
.get_mut(output)
|
|
|
|
|
.unwrap()
|
2023-09-03 15:15:55 +04:00
|
|
|
.waiting_for_vblank = true;
|
|
|
|
|
|
2023-09-04 07:49:50 +04:00
|
|
|
return Some(&surface.dmabuf_feedback);
|
2023-09-03 13:04:32 +04:00
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
error!("error queueing frame: {err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
// Can fail if we switched to a different TTY.
|
|
|
|
|
error!("error rendering frame: {err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-03 15:15:55 +04:00
|
|
|
|
2023-09-04 08:57:11 +04:00
|
|
|
// We're not expecting a vblank right after this.
|
|
|
|
|
drop(surface.vblank_frame.take());
|
2023-09-03 15:15:55 +04:00
|
|
|
None
|
2023-09-03 13:04:32 +04:00
|
|
|
}
|
|
|
|
|
|
2023-09-03 14:10:02 +04:00
|
|
|
pub fn change_vt(&mut self, vt: i32) {
|
2023-08-09 14:21:29 +04:00
|
|
|
if let Err(err) = self.session.change_vt(vt) {
|
|
|
|
|
error!("error changing VT: {err}");
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-03 14:10:02 +04:00
|
|
|
|
|
|
|
|
pub fn suspend(&self) {
|
|
|
|
|
if let Err(err) = suspend() {
|
|
|
|
|
warn!("error suspending: {err:?}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn toggle_debug_tint(&mut self) {
|
|
|
|
|
if let Some(device) = self.output_device.as_mut() {
|
2023-09-04 07:49:50 +04:00
|
|
|
for surface in device.surfaces.values_mut() {
|
|
|
|
|
let compositor = &mut surface.compositor;
|
2023-09-03 14:10:02 +04:00
|
|
|
compositor.set_debug_flags(compositor.debug_flags() ^ DebugFlags::TINT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-03 14:22:04 +04:00
|
|
|
|
|
|
|
|
pub fn dmabuf_state(&mut self) -> &mut DmabufState {
|
|
|
|
|
&mut self.output_device.as_mut().unwrap().dmabuf_state
|
|
|
|
|
}
|
2023-09-08 17:54:02 +04:00
|
|
|
|
|
|
|
|
pub fn connectors(&self) -> Arc<Mutex<HashMap<String, Output>>> {
|
|
|
|
|
self.connectors.clone()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn gbm_device(&self) -> Option<GbmDevice<DrmDeviceFd>> {
|
|
|
|
|
self.output_device.as_ref().map(|d| d.gbm.clone())
|
|
|
|
|
}
|
2023-08-09 11:03:38 +04:00
|
|
|
}
|
2023-08-14 15:53:24 +04:00
|
|
|
|
|
|
|
|
fn refresh_interval(mode: DrmMode) -> Duration {
|
|
|
|
|
let clock = mode.clock() as u64;
|
|
|
|
|
let htotal = mode.hsync().2 as u64;
|
|
|
|
|
let vtotal = mode.vsync().2 as u64;
|
|
|
|
|
|
|
|
|
|
let mut numerator = htotal * vtotal * 1_000_000;
|
|
|
|
|
let mut denominator = clock;
|
|
|
|
|
|
|
|
|
|
if mode.flags().contains(ModeFlags::INTERLACE) {
|
|
|
|
|
denominator *= 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mode.flags().contains(ModeFlags::DBLSCAN) {
|
|
|
|
|
numerator *= 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if mode.vscan() > 1 {
|
|
|
|
|
numerator *= mode.vscan() as u64;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let refresh_interval = (numerator + denominator / 2) / denominator;
|
|
|
|
|
Duration::from_nanos(refresh_interval)
|
|
|
|
|
}
|
2023-09-03 07:31:44 +04:00
|
|
|
|
|
|
|
|
fn suspend() -> anyhow::Result<()> {
|
|
|
|
|
let conn = zbus::blocking::Connection::system().context("error connecting to system bus")?;
|
|
|
|
|
let manager = logind_zbus::manager::ManagerProxyBlocking::new(&conn)
|
|
|
|
|
.context("error creating login manager proxy")?;
|
|
|
|
|
manager.suspend(true).context("error suspending")
|
|
|
|
|
}
|