Attempt 2 to fix launching permission denied
This commit is contained in:
parent
d9fa1323c3
commit
5e154e19f9
2 changed files with 294 additions and 181 deletions
447
src/main.rs
447
src/main.rs
|
|
@ -36,7 +36,7 @@ use smithay::{
|
||||||
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
|
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
|
||||||
reexports::{
|
reexports::{
|
||||||
drm::control::{connector, Device as _, ModeTypeFlags},
|
drm::control::{connector, Device as _, ModeTypeFlags},
|
||||||
rustix::fs::{Mode, OFlags},
|
rustix::fs::OFlags,
|
||||||
wayland_server::{Display, ListeningSocket},
|
wayland_server::{Display, ListeningSocket},
|
||||||
},
|
},
|
||||||
utils::{Buffer, DeviceFd, Physical, Rectangle, Transform},
|
utils::{Buffer, DeviceFd, Physical, Rectangle, Transform},
|
||||||
|
|
@ -47,7 +47,6 @@ use smithay::{
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::{
|
use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
thread,
|
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
use tracing::{error, info, trace, warn};
|
use tracing::{error, info, trace, warn};
|
||||||
|
|
@ -83,8 +82,7 @@ struct EventLoopData {
|
||||||
frame_timestamps: std::collections::HashMap<String, Instant>,
|
frame_timestamps: std::collections::HashMap<String, Instant>,
|
||||||
gles_tex_cache: std::collections::HashMap<String, smithay::backend::renderer::gles::GlesTexture>,
|
gles_tex_cache: std::collections::HashMap<String, smithay::backend::renderer::gles::GlesTexture>,
|
||||||
input_events_pending: bool,
|
input_events_pending: bool,
|
||||||
#[allow(dead_code)]
|
session: smithay::backend::session::libseat::LibSeatSession,
|
||||||
session: Option<smithay::backend::session::libseat::LibSeatSession>,
|
|
||||||
active: bool,
|
active: bool,
|
||||||
vblank_pending: bool,
|
vblank_pending: bool,
|
||||||
consecutive_flip_failures: u32,
|
consecutive_flip_failures: u32,
|
||||||
|
|
@ -106,11 +104,33 @@ fn write_flip_failure_report(report: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_would_block_message(msg: &str) -> bool {
|
fn is_permission_denied_message(msg: &str) -> bool {
|
||||||
let lowered = msg.to_ascii_lowercase();
|
let lowered = msg.to_ascii_lowercase();
|
||||||
lowered.contains("wouldblock")
|
lowered.contains("permission denied") || lowered.contains("eacces") || lowered.contains("eperm")
|
||||||
|| lowered.contains("resource temporarily unavailable")
|
}
|
||||||
|
|
||||||
|
fn is_eagain_message(msg: &str) -> bool {
|
||||||
|
let lowered = msg.to_ascii_lowercase();
|
||||||
|
lowered.contains("resource temporarily unavailable")
|
||||||
|| lowered.contains("eagain")
|
|| lowered.contains("eagain")
|
||||||
|
|| lowered.contains("wouldblock")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enumerate_drm_card_paths() -> Vec<std::path::PathBuf> {
|
||||||
|
let mut paths: Vec<std::path::PathBuf> = std::fs::read_dir("/dev/dri")
|
||||||
|
.ok()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|entries| entries.filter_map(Result::ok))
|
||||||
|
.map(|entry| entry.path())
|
||||||
|
.filter(|path| {
|
||||||
|
path.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.map(|name| name.starts_with("card"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
paths.sort();
|
||||||
|
paths
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_egl_context_or_surface_lost(err: &str) -> bool {
|
fn is_egl_context_or_surface_lost(err: &str) -> bool {
|
||||||
|
|
@ -254,7 +274,7 @@ fn render_frame(data: &mut EventLoopData) {
|
||||||
data.frame_time
|
data.frame_time
|
||||||
};
|
};
|
||||||
|
|
||||||
if data.last_render.elapsed() < adaptive_frame_time {
|
if !data.first_render && data.last_render.elapsed() < adaptive_frame_time {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
data.last_render = std::time::Instant::now();
|
data.last_render = std::time::Instant::now();
|
||||||
|
|
@ -530,122 +550,233 @@ fn render_frame(data: &mut EventLoopData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
let mut _session_notifier = None;
|
let (session, session_notifier) = LibSeatSession::new().map_err(|err| {
|
||||||
let mut session = match LibSeatSession::new() {
|
anyhow::anyhow!(
|
||||||
Ok((session, notifier)) => {
|
"Failed to create libseat session: {err}. wo requires a seat-managed session to access DRM/input devices safely. Run from a proper login session (logind/seatd)."
|
||||||
info!("LibSeat session created successfully");
|
)
|
||||||
_session_notifier = Some(notifier);
|
})?;
|
||||||
Some(session)
|
info!("LibSeat session created successfully");
|
||||||
}
|
let mut session = session;
|
||||||
Err(err) => {
|
|
||||||
warn!(
|
|
||||||
"Failed to create libseat session: {err}. Falling back to direct DRM open; libinput and VT switching will be disabled"
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut event_loop: EventLoop<()> = EventLoop::try_new().context("creating event loop")?;
|
// Create the event loop early—before opening any DRM devices.
|
||||||
|
// Niri, wlroots, and KWin all register the session notifier on the event
|
||||||
|
// loop before attempting to open device fds. Without this, calloop never
|
||||||
|
// polls the libseat fd, seat.dispatch() is never called, the EnableSeat
|
||||||
|
// message from seatd/logind stays unread, and libseat_open_device()
|
||||||
|
// returns EAGAIN for every attempt.
|
||||||
|
let mut event_loop: EventLoop<Option<EventLoopData>> =
|
||||||
|
EventLoop::try_new().context("creating event loop")?;
|
||||||
let loop_handle = event_loop.handle();
|
let loop_handle = event_loop.handle();
|
||||||
|
let loop_signal = event_loop.get_signal();
|
||||||
if let Some(notifier) = _session_notifier.take() {
|
|
||||||
loop_handle
|
loop_handle
|
||||||
.insert_source(notifier, |_, _, _: &mut ()| {
|
.insert_source(session_notifier, |event, _, data: &mut Option<EventLoopData>| {
|
||||||
trace!("Session event during init");
|
use smithay::backend::session::Event;
|
||||||
})
|
let Some(data) = data.as_mut() else {
|
||||||
.map_err(|e| anyhow::anyhow!("Error inserting session notifier: {e:?}"))?;
|
// Pre-init phase: ActivateSession received before DRM setup is
|
||||||
info!("Session notifier registered, dispatching initial events");
|
// complete. The session is now active; device opening can proceed.
|
||||||
|
return;
|
||||||
// Dispatch events to ensure session is activated
|
};
|
||||||
|
match event {
|
||||||
|
Event::PauseSession => {
|
||||||
|
info!("Session paused (VT switched away). Releasing DRM master and halting rendering.");
|
||||||
|
data.active = false;
|
||||||
|
data.vblank_pending = false;
|
||||||
|
if let Err(e) = drm::Device::release_master_lock(&data.drm_device_fd) {
|
||||||
|
warn!("Failed to release DRM master on session pause: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::ActivateSession => {
|
||||||
|
info!("Session activated (VT switched back). Re-acquiring DRM master and resuming rendering.");
|
||||||
|
if let Err(e) = drm::Device::acquire_master_lock(&data.drm_device_fd) {
|
||||||
|
error!("Failed to re-acquire DRM master on session activation: {e}");
|
||||||
|
data.active = false;
|
||||||
|
data.vblank_pending = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
data.active = true;
|
||||||
|
data.vblank_pending = false;
|
||||||
|
if let Err(e) = data.display.flush_clients() {
|
||||||
|
warn!("Failed to flush Wayland clients on activation: {e}");
|
||||||
|
}
|
||||||
|
render_frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| anyhow::anyhow!("Error inserting session notifier: {e:?}"))?;
|
||||||
|
info!("Session notifier registered for VT switching");
|
||||||
|
|
||||||
|
// Dispatch the event loop until the seat is active and ready for device
|
||||||
|
// requests. This must run even when session.is_active() is already true:
|
||||||
|
// LibSeatSession::new() may have set the active flag via an internal
|
||||||
|
// dispatch(0), but calloop still needs to poll the libseat fd at least
|
||||||
|
// once *after* the notifier is registered before open_device() will
|
||||||
|
// succeed. Without this, libseat_open_device() returns EAGAIN immediately.
|
||||||
|
if !session.is_active() {
|
||||||
|
info!("Waiting for seat activation (EnableSeat from seat manager)...");
|
||||||
|
}
|
||||||
|
let mut pre_init: Option<EventLoopData> = None;
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(5);
|
||||||
|
loop {
|
||||||
|
let remaining = deadline.saturating_duration_since(Instant::now());
|
||||||
|
if remaining.is_zero() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
event_loop
|
event_loop
|
||||||
.dispatch(Some(std::time::Duration::from_millis(100)), &mut ())
|
.dispatch(
|
||||||
.context("initial event dispatch")?;
|
Some(remaining.min(Duration::from_millis(200))),
|
||||||
|
&mut pre_init,
|
||||||
|
)
|
||||||
|
.context("dispatching event loop for session activation")?;
|
||||||
|
if session.is_active() {
|
||||||
|
info!("Session is active, ready to open devices");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !session.is_active() {
|
||||||
|
return Err(anyhow::anyhow!(
|
||||||
|
"Seat session never became active. \
|
||||||
|
Run wo from a proper TTY login session managed by logind or seatd. \
|
||||||
|
If another compositor holds the seat, switch to a free VT first."
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let seat_name = session
|
// One unconditional drain after the activation break: the dispatch() call
|
||||||
.as_ref()
|
// that set is_active()=true may have left additional buffered libseat events
|
||||||
.map(|s| s.seat())
|
// unread. A zero-timeout pump clears them so that the first open_device()
|
||||||
.unwrap_or_else(|| "seat0".to_string());
|
// request sees a clean state and does not get a spurious EAGAIN.
|
||||||
|
let _ = event_loop.dispatch(Some(Duration::from_millis(0)), &mut pre_init);
|
||||||
|
|
||||||
|
let seat_name = session.seat();
|
||||||
|
|
||||||
info!("Using seat: {}", seat_name);
|
info!("Using seat: {}", seat_name);
|
||||||
|
|
||||||
let mut display: Display<WoState> = Display::new().context("creating Wayland display")?;
|
let mut display: Display<WoState> = Display::new().context("creating Wayland display")?;
|
||||||
let dh = display.handle();
|
let dh = display.handle();
|
||||||
|
|
||||||
let gpu_path = if let Some(p) = &config.compositor.drm_device {
|
let configured_gpu = config
|
||||||
std::path::PathBuf::from(p)
|
.compositor
|
||||||
|
.drm_device
|
||||||
|
.as_ref()
|
||||||
|
.map(std::path::PathBuf::from);
|
||||||
|
let detected_primary_gpu = primary_gpu(&seat_name).ok().flatten();
|
||||||
|
|
||||||
|
let mut gpu_candidates = Vec::new();
|
||||||
|
if let Some(path) = configured_gpu {
|
||||||
|
gpu_candidates.push(path);
|
||||||
} else {
|
} else {
|
||||||
primary_gpu(&seat_name)
|
if let Some(path) = detected_primary_gpu {
|
||||||
.ok()
|
gpu_candidates.push(path);
|
||||||
.flatten()
|
}
|
||||||
.context("no primary GPU found")?
|
for path in enumerate_drm_card_paths() {
|
||||||
};
|
if !gpu_candidates.iter().any(|p| p == &path) {
|
||||||
|
gpu_candidates.push(path);
|
||||||
info!(gpu = %gpu_path.display(), "using GPU");
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let drm_fd = if let Some(session) = session.as_mut() {
|
if gpu_candidates.is_empty() {
|
||||||
info!("Opening DRM device via libseat session: {:?}", gpu_path);
|
return Err(anyhow::anyhow!(
|
||||||
let mut last_err = None;
|
"no DRM card candidates found (checked primary GPU and /dev/dri/card*)"
|
||||||
let mut opened_fd = None;
|
));
|
||||||
let max_attempts = 30;
|
}
|
||||||
let retry_delay = Duration::from_millis(100);
|
|
||||||
|
|
||||||
for attempt in 1..=max_attempts {
|
info!(
|
||||||
match session.open(
|
candidates = ?gpu_candidates,
|
||||||
&gpu_path,
|
"trying DRM devices in priority order"
|
||||||
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
|
);
|
||||||
) {
|
|
||||||
|
let mut chosen_gpu_path = None;
|
||||||
|
let mut chosen_drm_fd = None;
|
||||||
|
let mut open_errors: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
// Open DRM in blocking mode. Some drivers can return EAGAIN for
|
||||||
|
// O_NONBLOCK opens even when no other compositor/session owns the device.
|
||||||
|
// libseat already provides asynchronous behavior and transient EAGAIN
|
||||||
|
// handling through the dispatch/retry loop below.
|
||||||
|
let open_flags = OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY;
|
||||||
|
|
||||||
|
// Round-robin EAGAIN retry across all GPU candidates.
|
||||||
|
//
|
||||||
|
// The old per-GPU retry loop had a structural flaw: EAGAIN from libseat is
|
||||||
|
// a seat-level condition (the seat manager hasn't finished processing an
|
||||||
|
// earlier request), not a GPU-level one. If every candidate gets EAGAIN the
|
||||||
|
// old loop would spend its entire 500 ms budget on the first GPU and then
|
||||||
|
// immediately abandon all remaining ones without any further dispatching.
|
||||||
|
//
|
||||||
|
// The new approach keeps a "pending" list of candidates that returned EAGAIN.
|
||||||
|
// After each global round it pumps the event loop once (50 ms) and retries
|
||||||
|
// all pending candidates. Up to MAX_EAGAIN_ROUNDS × 50 ms ≤ 1 s total.
|
||||||
|
const MAX_EAGAIN_ROUNDS: u32 = 20;
|
||||||
|
let mut eagain_pending: Vec<std::path::PathBuf> = gpu_candidates;
|
||||||
|
'outer: for round in 0..=MAX_EAGAIN_ROUNDS {
|
||||||
|
let mut still_eagain: Vec<std::path::PathBuf> = Vec::new();
|
||||||
|
for gpu_path in eagain_pending {
|
||||||
|
if round == 0 {
|
||||||
|
info!("Opening DRM device: {:?}", gpu_path);
|
||||||
|
}
|
||||||
|
match session.open(&gpu_path, open_flags) {
|
||||||
Ok(fd) => {
|
Ok(fd) => {
|
||||||
info!("Successfully opened DRM device via session, fd={:?}", fd);
|
info!(gpu = %gpu_path.display(), "using GPU");
|
||||||
opened_fd = Some(fd);
|
chosen_gpu_path = Some(gpu_path);
|
||||||
break;
|
chosen_drm_fd = Some(fd);
|
||||||
|
break 'outer;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let err_msg = e.to_string();
|
let detail = e.to_string();
|
||||||
if is_would_block_message(&err_msg) && attempt < max_attempts {
|
if is_eagain_message(&detail) {
|
||||||
|
if round == 0 {
|
||||||
|
info!(
|
||||||
|
gpu = %gpu_path.display(),
|
||||||
|
"libseat returned EAGAIN; will retry after event loop pump..."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
still_eagain.push(gpu_path);
|
||||||
|
} else {
|
||||||
|
let is_perm = is_permission_denied_message(&detail);
|
||||||
warn!(
|
warn!(
|
||||||
attempt,
|
|
||||||
max_attempts,
|
|
||||||
gpu = %gpu_path.display(),
|
gpu = %gpu_path.display(),
|
||||||
"DRM device busy (WouldBlock/EAGAIN), retrying open"
|
permission_related = is_perm,
|
||||||
|
round,
|
||||||
|
"Failed to open DRM device: {detail}"
|
||||||
);
|
);
|
||||||
last_err = Some(err_msg);
|
open_errors.push(format!("{}: {detail}", gpu_path.display()));
|
||||||
thread::sleep(retry_delay);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
last_err = Some(err_msg);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
eagain_pending = still_eagain;
|
||||||
match opened_fd {
|
if eagain_pending.is_empty() {
|
||||||
Some(fd) => fd,
|
break;
|
||||||
None => {
|
}
|
||||||
let detail = last_err.unwrap_or_else(|| "unknown error".to_string());
|
if round < MAX_EAGAIN_ROUNDS {
|
||||||
error!(
|
let _ = event_loop.dispatch(Some(Duration::from_millis(50)), &mut pre_init);
|
||||||
gpu = %gpu_path.display(),
|
} else {
|
||||||
"Failed to open DRM device via session after retries: {detail}"
|
// Final round exhausted; record EAGAIN as a permanent failure for
|
||||||
);
|
// each remaining candidate so the error message is informative.
|
||||||
return Err(anyhow::anyhow!(
|
for gpu_path in &eagain_pending {
|
||||||
"opening DRM device via session failed after retries: {}. This usually means another active compositor/session still owns the seat DRM node.",
|
open_errors.push(format!(
|
||||||
detail
|
"{}: libseat returned EAGAIN after {} retries (seat manager unresponsive?)",
|
||||||
|
gpu_path.display(),
|
||||||
|
MAX_EAGAIN_ROUNDS,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
info!("Opening DRM device directly (no session): {:?}", gpu_path);
|
|
||||||
smithay::reexports::rustix::fs::open(
|
let gpu_path = chosen_gpu_path.ok_or_else(|| {
|
||||||
&gpu_path,
|
let detail = if open_errors.is_empty() {
|
||||||
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
|
"no candidates produced a detailed error".to_string()
|
||||||
Mode::empty(),
|
} else {
|
||||||
|
open_errors.join("; ")
|
||||||
|
};
|
||||||
|
anyhow::anyhow!(
|
||||||
|
"no usable DRM device found. Tried: {detail}. Check for stale holders with: fuser /dev/dri/card*"
|
||||||
)
|
)
|
||||||
.context("opening DRM device directly (no session)")?
|
})?;
|
||||||
};
|
let drm_fd = chosen_drm_fd.context("no usable DRM device fd found")?;
|
||||||
|
|
||||||
let drm_device_fd = DrmDeviceFd::new(DeviceFd::from(drm_fd));
|
let drm_device_fd = DrmDeviceFd::new(DeviceFd::from(drm_fd));
|
||||||
let (mut drm, _drm_notifier) =
|
|
||||||
DrmDevice::new(drm_device_fd.clone(), true).context("creating DRM device")?;
|
|
||||||
|
|
||||||
if let Err(e) = drm::Device::acquire_master_lock(&drm_device_fd) {
|
if let Err(e) = drm::Device::acquire_master_lock(&drm_device_fd) {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
|
|
@ -654,6 +785,25 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to create DRM device with atomic mode first
|
||||||
|
// If it fails due to invalid state restoration, try without atomic mode
|
||||||
|
let (mut drm, _drm_notifier) = match DrmDevice::new(drm_device_fd.clone(), true) {
|
||||||
|
Ok(result) => {
|
||||||
|
info!("DRM device created in atomic mode");
|
||||||
|
result
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let err_msg = e.to_string();
|
||||||
|
if err_msg.contains("Invalid argument") || err_msg.contains("EINVAL") {
|
||||||
|
warn!("Atomic mode initialization failed with EINVAL (likely stale state on display), trying legacy mode: {e}");
|
||||||
|
DrmDevice::new(drm_device_fd.clone(), false)
|
||||||
|
.map_err(|e| anyhow::anyhow!("creating DRM device in legacy mode after atomic failure: {e}"))?
|
||||||
|
} else {
|
||||||
|
return Err(anyhow::anyhow!("creating DRM device: {e}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let gbm_device = GbmDevice::new(drm_device_fd.clone()).context("creating GBM device")?;
|
let gbm_device = GbmDevice::new(drm_device_fd.clone()).context("creating GBM device")?;
|
||||||
|
|
||||||
let egl_display =
|
let egl_display =
|
||||||
|
|
@ -683,12 +833,20 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
.max_by_key(|m| (m.mode_type().contains(ModeTypeFlags::PREFERRED), m.size()))
|
.max_by_key(|m| (m.mode_type().contains(ModeTypeFlags::PREFERRED), m.size()))
|
||||||
.context("no DRM mode")?;
|
.context("no DRM mode")?;
|
||||||
|
|
||||||
let crtc_h = resources
|
let crtc_h = connector_info
|
||||||
.crtcs()
|
.encoders()
|
||||||
.iter()
|
.iter()
|
||||||
.next()
|
.filter_map(|&encoder_h| drm.get_encoder(encoder_h).ok())
|
||||||
.copied()
|
.find_map(|encoder_info| encoder_info.crtc())
|
||||||
.context("no suitable CRTC")?;
|
.or_else(|| {
|
||||||
|
connector_info
|
||||||
|
.encoders()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&encoder_h| drm.get_encoder(encoder_h).ok())
|
||||||
|
.flat_map(|encoder_info| resources.filter_crtcs(encoder_info.possible_crtcs()))
|
||||||
|
.next()
|
||||||
|
})
|
||||||
|
.context("no suitable CRTC for connected connector")?;
|
||||||
|
|
||||||
let drm_surface = drm
|
let drm_surface = drm
|
||||||
.create_surface(crtc_h, mode, &[connector_h])
|
.create_surface(crtc_h, mode, &[connector_h])
|
||||||
|
|
@ -734,7 +892,7 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
DrmNode::from_path(&gpu_path).context("resolving DRM node for dmabuf feedback")?;
|
DrmNode::from_path(&gpu_path).context("resolving DRM node for dmabuf feedback")?;
|
||||||
|
|
||||||
let mut state = WoState::new(&mut display, config.clone(), render_node, backend);
|
let mut state = WoState::new(&mut display, config.clone(), render_node, backend);
|
||||||
state.can_switch_vt = session.is_some();
|
state.can_switch_vt = true;
|
||||||
|
|
||||||
let (ipc, msg_rx) = ElectronIpc::listen(&config.compositor.ipc_socket).context("IPC socket")?;
|
let (ipc, msg_rx) = ElectronIpc::listen(&config.compositor.ipc_socket).context("IPC socket")?;
|
||||||
|
|
||||||
|
|
@ -835,17 +993,12 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
let bg = Color32F::from(config.compositor.background);
|
let bg = Color32F::from(config.compositor.background);
|
||||||
|
|
||||||
// Initialize libinput for input events (only when session backend is available)
|
// Initialize libinput for input events (only when session backend is available)
|
||||||
let libinput_backend = if let Some(session_ref) = session.as_ref() {
|
let mut libinput_context =
|
||||||
let mut libinput_context =
|
Libinput::new_with_udev(LibinputSessionInterface::from(session.clone()));
|
||||||
Libinput::new_with_udev(LibinputSessionInterface::from(session_ref.clone()));
|
libinput_context
|
||||||
libinput_context
|
.udev_assign_seat(&session.seat())
|
||||||
.udev_assign_seat(&session_ref.seat())
|
.map_err(|()| anyhow::anyhow!("Failed to assign seat to libinput"))?;
|
||||||
.map_err(|()| anyhow::anyhow!("Failed to assign seat to libinput"))?;
|
let libinput_backend = LibinputInputBackend::new(libinput_context);
|
||||||
Some(LibinputInputBackend::new(libinput_context))
|
|
||||||
} else {
|
|
||||||
warn!("Running without libinput backend because no session backend is available");
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Prepare frame pacing using the active DRM mode refresh when available.
|
// Prepare frame pacing using the active DRM mode refresh when available.
|
||||||
let refresh_hz = (output_mode.refresh as f64) / 1000.0;
|
let refresh_hz = (output_mode.refresh as f64) / 1000.0;
|
||||||
|
|
@ -887,12 +1040,6 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
gles_tex_cache: std::collections::HashMap::new(),
|
gles_tex_cache: std::collections::HashMap::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create calloop event loop
|
|
||||||
let mut event_loop: EventLoop<EventLoopData> =
|
|
||||||
EventLoop::try_new().context("creating event loop")?;
|
|
||||||
let loop_handle = event_loop.handle();
|
|
||||||
let loop_signal = event_loop.get_signal();
|
|
||||||
|
|
||||||
let signal_for_handler = loop_signal.clone();
|
let signal_for_handler = loop_signal.clone();
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
info!("Received Ctrl+C, initiating shutdown");
|
info!("Received Ctrl+C, initiating shutdown");
|
||||||
|
|
@ -900,34 +1047,9 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
})
|
})
|
||||||
.expect("Error setting Ctrl-C handler");
|
.expect("Error setting Ctrl-C handler");
|
||||||
|
|
||||||
// 1. Hook up the Session Notifier to handle VT switching!
|
|
||||||
if let Some(notifier) = _session_notifier {
|
|
||||||
loop_handle
|
|
||||||
.insert_source(notifier, |event, _, data: &mut EventLoopData| {
|
|
||||||
use smithay::backend::session::Event;
|
|
||||||
match event {
|
|
||||||
Event::PauseSession => {
|
|
||||||
info!("Session paused (VT switched away). Halting rendering.");
|
|
||||||
data.active = false;
|
|
||||||
data.vblank_pending = false;
|
|
||||||
}
|
|
||||||
Event::ActivateSession => {
|
|
||||||
info!("Session activated (VT switched back). Resuming rendering.");
|
|
||||||
data.active = true;
|
|
||||||
data.vblank_pending = false; // Reset to ensure we can render
|
|
||||||
if let Err(e) = data.display.flush_clients() {
|
|
||||||
warn!("Failed to flush Wayland clients on activation: {e}");
|
|
||||||
}
|
|
||||||
render_frame(data); // Force an immediate redraw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map_err(|e| anyhow::anyhow!("Error inserting session notifier: {e:?}"))?;
|
|
||||||
info!("Session notifier registered for VT switching");
|
|
||||||
}
|
|
||||||
|
|
||||||
loop_handle
|
loop_handle
|
||||||
.insert_source(_drm_notifier, |event, _, data: &mut EventLoopData| {
|
.insert_source(_drm_notifier, |event, _, data: &mut Option<EventLoopData>| {
|
||||||
|
let data = data.as_mut().expect("EventLoopData initialized");
|
||||||
use smithay::backend::drm::DrmEvent;
|
use smithay::backend::drm::DrmEvent;
|
||||||
if !data.active {
|
if !data.active {
|
||||||
if let DrmEvent::VBlank(_) = event {
|
if let DrmEvent::VBlank(_) = event {
|
||||||
|
|
@ -945,39 +1067,36 @@ fn run_drm(config: Config) -> Result<(), anyhow::Error> {
|
||||||
.map_err(|e| anyhow::anyhow!("Error inserting DRM notifier: {e:?}"))?;
|
.map_err(|e| anyhow::anyhow!("Error inserting DRM notifier: {e:?}"))?;
|
||||||
|
|
||||||
// Insert libinput backend into event loop (operate on `loop_data.state`)
|
// Insert libinput backend into event loop (operate on `loop_data.state`)
|
||||||
if let Some(libinput_backend) = libinput_backend {
|
loop_handle
|
||||||
loop_handle
|
.insert_source(
|
||||||
.insert_source(
|
libinput_backend,
|
||||||
libinput_backend,
|
move |event, _, data: &mut Option<EventLoopData>| {
|
||||||
move |event, _, data: &mut EventLoopData| {
|
let data = data.as_mut().expect("EventLoopData initialized");
|
||||||
data.state.process_input_event(event);
|
data.state.process_input_event(event);
|
||||||
data.input_events_pending = true;
|
data.input_events_pending = true;
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.map_err(|e| anyhow::anyhow!("Error inserting libinput source: {e:?}"))?;
|
.map_err(|e| anyhow::anyhow!("Error inserting libinput source: {e:?}"))?;
|
||||||
}
|
|
||||||
|
|
||||||
// Kickstart the render loop with an initial frame
|
// Kickstart the render loop with an initial frame
|
||||||
info!("Kickstarting VBlank-driven rendering");
|
info!("Kickstarting VBlank-driven rendering");
|
||||||
render_frame(&mut loop_data);
|
render_frame(&mut loop_data);
|
||||||
|
|
||||||
// Main event loop - purely event-driven (blocking)
|
// Main event loop - purely event-driven (blocking)
|
||||||
|
let mut main_data: Option<EventLoopData> = Some(loop_data);
|
||||||
event_loop
|
event_loop
|
||||||
.run(None, &mut loop_data, |data| {
|
.run(None, &mut main_data, |opt_data| {
|
||||||
|
let data = opt_data.as_mut().expect("EventLoopData initialized");
|
||||||
if !data.state.running {
|
if !data.state.running {
|
||||||
loop_signal.stop();
|
loop_signal.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(vt) = data.state.pending_vt_switch.take() {
|
if let Some(vt) = data.state.pending_vt_switch.take() {
|
||||||
if let Some(session) = data.session.as_mut() {
|
if let Err(e) = data.session.change_vt(vt as i32) {
|
||||||
if let Err(e) = session.change_vt(vt as i32) {
|
warn!(vt, "Failed to switch VT: {e}");
|
||||||
warn!(vt, "Failed to switch VT: {e}");
|
|
||||||
} else {
|
|
||||||
info!(vt, "Switched to VT");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
warn!(vt, "Ignoring VT switch request: no active session backend");
|
info!(vt, "Switched to VT");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
whatever.txt
28
whatever.txt
|
|
@ -1,17 +1,11 @@
|
||||||
[2m2026-03-09T06:55:04.057372Z[0m [32m INFO[0m [2mwo[0m[2m:[0m wo compositor starting
|
[2m2026-03-09T13:10:06.828981Z[0m [32m INFO[0m [2mwo[0m[2m:[0m wo compositor starting
|
||||||
[2m2026-03-09T06:55:04.057548Z[0m [32m INFO[0m [2mwo::config[0m[2m:[0m Loading config from "/home/hellings/.config/wo/config.toml"
|
[2m2026-03-09T13:10:06.829194Z[0m [32m INFO[0m [2mwo::config[0m[2m:[0m Loading config from "/home/hellings/.config/wo/config.toml"
|
||||||
[2m2026-03-09T06:55:04.057918Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Running in DRM/KMS mode
|
[2m2026-03-09T13:10:06.829640Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Running in DRM/KMS mode
|
||||||
[2m2026-03-09T06:55:04.059240Z[0m [32m INFO[0m [2mwo[0m[2m:[0m LibSeat session created successfully
|
[2m2026-03-09T13:10:06.831907Z[0m [32m INFO[0m [2mwo[0m[2m:[0m LibSeat session created successfully
|
||||||
[2m2026-03-09T06:55:04.059383Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Session notifier registered, dispatching initial events
|
[2m2026-03-09T13:10:06.832112Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Session notifier registered for VT switching
|
||||||
[2m2026-03-09T06:55:04.059468Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Using seat: seat0
|
[2m2026-03-09T13:10:06.832217Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Session is active, ready to open devices
|
||||||
[2m2026-03-09T06:55:04.059874Z[0m [32m INFO[0m [2mwo[0m[2m:[0m using GPU [3mgpu[0m[2m=[0m/dev/dri/card1
|
[2m2026-03-09T13:10:06.832243Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Using seat: seat0
|
||||||
[2m2026-03-09T06:55:04.059895Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Opening DRM device via libseat session: "/dev/dri/card1"
|
[2m2026-03-09T13:10:06.834656Z[0m [32m INFO[0m [2mwo[0m[2m:[0m trying DRM devices in priority order [3mcandidates[0m[2m=[0m["/dev/dri/card1"]
|
||||||
[2m2026-03-09T06:55:04.060310Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Successfully opened DRM device via session, fd=OwnedFd { fd: 15 }
|
[2m2026-03-09T13:10:06.834715Z[0m [32m INFO[0m [2mwo[0m[2m:[0m Opening DRM device: "/dev/dri/card1"
|
||||||
[2m2026-03-09T06:55:04.060356Z[0m [33m WARN[0m [2msmithay::backend::drm::device::fd[0m[2m:[0m Unable to become drm master, assuming unprivileged mode
|
[2m2026-03-09T13:10:06.835267Z[0m [32m INFO[0m [2mwo[0m[2m:[0m libseat returned EAGAIN; pumping event loop and retrying device open... [3mgpu[0m[2m=[0m/dev/dri/card1
|
||||||
[2m2026-03-09T06:55:13.132016Z[0m [31mERROR[0m [2msmithay::backend::egl::ffi[0m[2m:[0m [EGL] 0x3003 (BAD_ALLOC) eglQueryDevicesEXT: EGL_BAD_ALLOC error: In eglQueryDevicesEXT: Failed to allocate device list.
|
[2m2026-03-09T13:10:07.351716Z[0m [33m WARN[0m [2mwo[0m[2m:[0m Failed to open DRM device: Failed to open device: Resource temporarily unavailable (os error 11) [3mgpu[0m[2m=[0m/dev/dri/card1 [3mpermission_related[0m[2m=[0mfalse [3mattempt[0m[2m=[0m10
|
||||||
|
|
||||||
[2m2026-03-09T06:55:13.132291Z[0m [31mERROR[0m [2msmithay::backend::egl::ffi[0m[2m:[0m [EGL] 0x3001 (NOT_INITIALIZED) eglInitialize: DRI2: gbm device using incorrect/incompatible backend
|
|
||||||
[2m2026-03-09T06:55:13.132304Z[0m [31mERROR[0m [2msmithay::backend::egl::ffi[0m[2m:[0m [EGL] 0x3001 (NOT_INITIALIZED) eglInitialize: DRI2: gbm device using incorrect/incompatible backend
|
|
||||||
[2m2026-03-09T06:55:13.132311Z[0m [31mERROR[0m [2msmithay::backend::egl::ffi[0m[2m:[0m [EGL] 0x3001 (NOT_INITIALIZED) eglInitialize: DRI2: gbm device using incorrect/incompatible backend
|
|
||||||
[2m2026-03-09T06:55:13.132318Z[0m [31mERROR[0m [2msmithay::backend::egl::ffi[0m[2m:[0m [EGL] 0x3001 (NOT_INITIALIZED) eglInitialize: eglInitialize
|
|
||||||
[2m2026-03-09T06:55:13.132487Z[0m [31mERROR[0m [2msmithay::backend::drm::device::atomic[0m[2m:[0m Failed to restore previous state. Error: Invalid argument (os error 22)
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue