@@ -415,63 +415,54 @@ wintypes: |
| 415 | 415 | let _ = Command::new("pkill").args(["-f", "garchomp"]).status(); |
| 416 | 416 | } |
| 417 | 417 | |
| 418 | | - /// Apply screen timeout/DPMS settings using xset |
| 418 | + /// Apply screen timeout settings using xset. |
| 419 | + /// |
| 420 | + /// Always disables hardware DPMS (causes Xid 79 GPU crashes on NVIDIA with |
| 421 | + /// multi-monitor HDMI setups). Uses the X11 screen saver extension for software |
| 422 | + /// blanking instead, which draws black without hardware power state changes. |
| 419 | 423 | pub fn apply_screen_timeout(&self) { |
| 420 | 424 | use std::process::Command; |
| 421 | 425 | |
| 422 | | - if self.screen_timeout_enabled { |
| 423 | | - // Enable DPMS and set timeout |
| 424 | | - let timeout = self.screen_timeout_seconds.to_string(); |
| 425 | | - match Command::new("xset") |
| 426 | | - .args(["dpms", &timeout, &timeout, &timeout]) |
| 427 | | - .status() |
| 428 | | - { |
| 429 | | - Ok(status) if status.success() => { |
| 430 | | - tracing::info!("Set DPMS timeout to {} seconds", self.screen_timeout_seconds); |
| 431 | | - } |
| 432 | | - Ok(_) => tracing::warn!("xset dpms command failed"), |
| 433 | | - Err(e) => tracing::warn!("Failed to run xset: {}", e), |
| 426 | + // Always disable hardware DPMS - it sends power state changes to monitors |
| 427 | + // via the NVIDIA driver, which can crash the GPU (Xid 79) when HDMI displays |
| 428 | + // have unreliable EDID links |
| 429 | + match Command::new("xset").args(["dpms", "0", "0", "0"]).status() { |
| 430 | + Ok(status) if status.success() => { |
| 431 | + tracing::debug!("Disabled hardware DPMS timeouts"); |
| 434 | 432 | } |
| 433 | + Ok(_) => tracing::warn!("xset dpms 0 command failed"), |
| 434 | + Err(e) => tracing::warn!("Failed to run xset: {}", e), |
| 435 | + } |
| 436 | + match Command::new("xset").args(["-dpms"]).status() { |
| 437 | + Ok(_) => {} |
| 438 | + Err(e) => tracing::warn!("Failed to disable DPMS: {}", e), |
| 439 | + } |
| 435 | 440 | |
| 436 | | - // Enable screen saver with same timeout |
| 441 | + if self.screen_timeout_enabled { |
| 442 | + // Use X11 screen saver for software blanking (safe, no hardware power changes) |
| 443 | + let timeout = self.screen_timeout_seconds.to_string(); |
| 437 | 444 | match Command::new("xset") |
| 438 | 445 | .args(["s", &timeout, &timeout]) |
| 439 | 446 | .status() |
| 440 | 447 | { |
| 441 | 448 | Ok(status) if status.success() => { |
| 442 | | - tracing::debug!("Set screen saver timeout to {} seconds", self.screen_timeout_seconds); |
| 449 | + tracing::info!( |
| 450 | + "Screen blanking enabled via X11 screen saver ({} seconds)", |
| 451 | + self.screen_timeout_seconds |
| 452 | + ); |
| 443 | 453 | } |
| 444 | 454 | Ok(_) => tracing::warn!("xset s command failed"), |
| 445 | 455 | Err(e) => tracing::warn!("Failed to run xset: {}", e), |
| 446 | 456 | } |
| 447 | | - |
| 448 | | - // Make sure DPMS is enabled |
| 449 | | - match Command::new("xset").args(["+dpms"]).status() { |
| 450 | | - Ok(_) => {} |
| 451 | | - Err(e) => tracing::warn!("Failed to enable DPMS: {}", e), |
| 452 | | - } |
| 453 | 457 | } else { |
| 454 | | - // Disable DPMS and screen saver |
| 455 | | - match Command::new("xset").args(["dpms", "0", "0", "0"]).status() { |
| 456 | | - Ok(status) if status.success() => { |
| 457 | | - tracing::info!("Disabled DPMS timeout"); |
| 458 | | - } |
| 459 | | - Ok(_) => tracing::warn!("xset dpms 0 command failed"), |
| 460 | | - Err(e) => tracing::warn!("Failed to run xset: {}", e), |
| 461 | | - } |
| 462 | | - |
| 458 | + // Disable screen saver blanking too |
| 463 | 459 | match Command::new("xset").args(["s", "off"]).status() { |
| 464 | 460 | Ok(status) if status.success() => { |
| 465 | | - tracing::debug!("Disabled screen saver"); |
| 461 | + tracing::info!("Screen blanking disabled"); |
| 466 | 462 | } |
| 467 | 463 | Ok(_) => tracing::warn!("xset s off command failed"), |
| 468 | 464 | Err(e) => tracing::warn!("Failed to run xset: {}", e), |
| 469 | 465 | } |
| 470 | | - |
| 471 | | - match Command::new("xset").args(["-dpms"]).status() { |
| 472 | | - Ok(_) => {} |
| 473 | | - Err(e) => tracing::warn!("Failed to disable DPMS: {}", e), |
| 474 | | - } |
| 475 | 466 | } |
| 476 | 467 | } |
| 477 | 468 | |
@@ -549,8 +540,8 @@ impl Default for Config { |
| 549 | 540 | notification_enabled: false, |
| 550 | 541 | // Monitor order: empty = sort by X position |
| 551 | 542 | monitor_order: Vec::new(), |
| 552 | | - // Screen timeout: disabled by default (DPMS causes Xid 79 GPU crashes on NVIDIA) |
| 553 | | - screen_timeout_enabled: false, |
| 543 | + // Screen timeout: enabled by default (uses software blanking, never hardware DPMS) |
| 544 | + screen_timeout_enabled: true, |
| 554 | 545 | screen_timeout_seconds: 600, |
| 555 | 546 | // Compositor selection: "picom" (default), "garchomp", or "none" |
| 556 | 547 | compositor: "picom".to_string(), |