aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-01-18 11:15:48 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-01-18 12:44:05 +0400
commitfa9b3ed10616e7039e2b0e2c4b797f396b74995f (patch)
treefde49a773c66201db730b507765c5fdb0a5d1eac /src
parentcc62a403c0144e8002de2f5a25fd50f203c661ee (diff)
downloadniri-fa9b3ed10616e7039e2b0e2c4b797f396b74995f.tar.gz
niri-fa9b3ed10616e7039e2b0e2c4b797f396b74995f.tar.bz2
niri-fa9b3ed10616e7039e2b0e2c4b797f396b74995f.zip
Add a config parse error notification
We can't rely on a notification daemon being available, especially during initial niri setup. So, render our own.
Diffstat (limited to 'src')
-rw-r--r--src/config_error_notification.rs197
-rw-r--r--src/main.rs8
-rw-r--r--src/niri.rs40
-rw-r--r--src/render_helpers.rs7
4 files changed, 250 insertions, 2 deletions
diff --git a/src/config_error_notification.rs b/src/config_error_notification.rs
new file mode 100644
index 00000000..6e54ead6
--- /dev/null
+++ b/src/config_error_notification.rs
@@ -0,0 +1,197 @@
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::time::Duration;
+
+use pangocairo::cairo::{self, ImageSurface};
+use pangocairo::pango::FontDescription;
+use smithay::backend::renderer::element::memory::{
+ MemoryRenderBuffer, MemoryRenderBufferRenderElement,
+};
+use smithay::backend::renderer::element::utils::{Relocate, RelocateRenderElement};
+use smithay::backend::renderer::element::{Element, Kind};
+use smithay::output::Output;
+use smithay::reexports::gbm::Format as Fourcc;
+use smithay::utils::Transform;
+
+use crate::animation::Animation;
+use crate::render_helpers::NiriRenderer;
+
+const TEXT: &str = "Failed to parse the config file. \
+ Please run <span face='monospace' bgcolor='#000000'>niri validate</span> \
+ to see the errors.";
+const PADDING: i32 = 8;
+const FONT: &str = "sans 14px";
+const BORDER: i32 = 4;
+
+pub struct ConfigErrorNotification {
+ state: State,
+ buffers: RefCell<HashMap<i32, Option<MemoryRenderBuffer>>>,
+}
+
+enum State {
+ Hidden,
+ Showing(Animation),
+ Shown(Duration),
+ Hiding(Animation),
+}
+
+pub type ConfigErrorNotificationRenderElement<R> =
+ RelocateRenderElement<MemoryRenderBufferRenderElement<R>>;
+
+impl ConfigErrorNotification {
+ pub fn new() -> Self {
+ Self {
+ state: State::Hidden,
+ buffers: RefCell::new(HashMap::new()),
+ }
+ }
+
+ pub fn show(&mut self) {
+ // Show from scratch even if already showing to bring attention.
+ self.state = State::Showing(Animation::new(0., 1., Duration::from_millis(250)));
+ }
+
+ pub fn hide(&mut self) {
+ if matches!(self.state, State::Hidden) {
+ return;
+ }
+
+ self.state = State::Hiding(Animation::new(1., 0., Duration::from_millis(250)));
+ }
+
+ pub fn advance_animations(&mut self, target_presentation_time: Duration) {
+ match &mut self.state {
+ State::Hidden => (),
+ State::Showing(anim) => {
+ anim.set_current_time(target_presentation_time);
+ if anim.is_done() {
+ self.state = State::Shown(target_presentation_time + Duration::from_secs(4));
+ }
+ }
+ State::Shown(deadline) => {
+ if target_presentation_time >= *deadline {
+ self.hide();
+ }
+ }
+ State::Hiding(anim) => {
+ anim.set_current_time(target_presentation_time);
+ if anim.is_done() {
+ self.state = State::Hidden;
+ }
+ }
+ }
+ }
+
+ pub fn are_animations_ongoing(&self) -> bool {
+ !matches!(self.state, State::Hidden)
+ }
+
+ pub fn render<R: NiriRenderer>(
+ &self,
+ renderer: &mut R,
+ output: &Output,
+ ) -> Option<ConfigErrorNotificationRenderElement<R>> {
+ if matches!(self.state, State::Hidden) {
+ return None;
+ }
+
+ let scale = output.current_scale().integer_scale();
+
+ let mut buffers = self.buffers.borrow_mut();
+ let buffer = buffers
+ .entry(scale)
+ .or_insert_with_key(move |&scale| render(scale).ok());
+ let buffer = buffer.as_ref()?;
+
+ let elem = MemoryRenderBufferRenderElement::from_buffer(
+ renderer,
+ (0., 0.),
+ buffer,
+ Some(0.9),
+ None,
+ None,
+ Kind::Unspecified,
+ )
+ .ok()?;
+
+ let output_transform = output.current_transform();
+ let output_mode = output.current_mode().unwrap();
+ let output_size = output_transform.transform_size(output_mode.size);
+
+ let buffer_size = elem
+ .geometry(output.current_scale().fractional_scale().into())
+ .size;
+
+ let y_range = buffer_size.h + PADDING * 2 * scale;
+
+ let x = (output_size.w / 2 - buffer_size.w / 2).max(0);
+ let y = match &self.state {
+ State::Hidden => unreachable!(),
+ State::Showing(anim) | State::Hiding(anim) => {
+ (-buffer_size.h as f64 + anim.value() * y_range as f64).round() as i32
+ }
+ State::Shown(_) => PADDING * 2 * scale,
+ };
+ let elem = RelocateRenderElement::from_element(elem, (x, y), Relocate::Absolute);
+
+ Some(elem)
+ }
+}
+
+fn render(scale: i32) -> anyhow::Result<MemoryRenderBuffer> {
+ let _span = tracy_client::span!("config_error_notification::render");
+
+ let padding = PADDING * scale;
+
+ let mut font = FontDescription::from_string(FONT);
+ font.set_absolute_size((font.size() * scale).into());
+
+ let surface = ImageSurface::create(cairo::Format::ARgb32, 0, 0)?;
+ let cr = cairo::Context::new(&surface)?;
+ let layout = pangocairo::create_layout(&cr);
+ layout.set_font_description(Some(&font));
+ layout.set_markup(TEXT);
+
+ let (mut width, mut height) = layout.pixel_size();
+ width += padding * 2;
+ height += padding * 2;
+
+ // FIXME: fix bug in Smithay that rounds pixel sizes down to scale.
+ width = (width + scale - 1) / scale * scale;
+ height = (height + scale - 1) / scale * scale;
+
+ let surface = ImageSurface::create(cairo::Format::ARgb32, width, height)?;
+ let cr = cairo::Context::new(&surface)?;
+ cr.set_source_rgb(0.1, 0.1, 0.1);
+ cr.paint()?;
+
+ cr.move_to(padding.into(), padding.into());
+ let layout = pangocairo::create_layout(&cr);
+ layout.set_font_description(Some(&font));
+ layout.set_markup(TEXT);
+
+ cr.set_source_rgb(1., 1., 1.);
+ pangocairo::show_layout(&cr, &layout);
+
+ cr.move_to(0., 0.);
+ cr.line_to(width.into(), 0.);
+ cr.line_to(width.into(), height.into());
+ cr.line_to(0., height.into());
+ cr.line_to(0., 0.);
+ cr.set_source_rgb(1., 0.3, 0.3);
+ cr.set_line_width((BORDER * scale).into());
+ cr.stroke()?;
+ drop(cr);
+
+ let data = surface.take_data().unwrap();
+ let buffer = MemoryRenderBuffer::from_memory(
+ &data,
+ Fourcc::Argb8888,
+ (width, height),
+ scale,
+ Transform::Normal,
+ None,
+ );
+
+ Ok(buffer)
+}
diff --git a/src/main.rs b/src/main.rs
index 8a8096b6..9fe1394a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,6 +3,7 @@ extern crate tracing;
mod animation;
mod backend;
+mod config_error_notification;
mod cursor;
#[cfg(feature = "dbus")]
mod dbus;
@@ -157,12 +158,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load the config.
let path = cli.config.or_else(default_config_path);
+ let mut config_errored = false;
let mut config = path
.as_deref()
.and_then(|path| match Config::load(path) {
Ok(config) => Some(config),
Err(err) => {
warn!("{err:?}");
+ config_errored = true;
None
}
})
@@ -239,6 +242,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
spawn(elem.command);
}
+ // Show the config error notification right away if needed.
+ if config_errored {
+ state.niri.config_error_notification.show();
+ }
+
// Run the compositor.
event_loop
.run(None, &mut state, |state| state.refresh_and_flush_clients())
diff --git a/src/niri.rs b/src/niri.rs
index e0762ae7..8109f2eb 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -90,6 +90,9 @@ use smithay::wayland::virtual_keyboard::VirtualKeyboardManagerState;
use crate::animation;
use crate::backend::tty::{SurfaceDmabufFeedback, TtyFrame, TtyRenderer, TtyRendererError};
use crate::backend::{Backend, RenderResult, Tty, Winit};
+use crate::config_error_notification::{
+ ConfigErrorNotification, ConfigErrorNotificationRenderElement,
+};
use crate::cursor::{CursorManager, CursorTextureCache, RenderCursor, XCursor};
#[cfg(feature = "dbus")]
use crate::dbus::gnome_shell_screenshot::{NiriToScreenshot, ScreenshotToNiri};
@@ -185,6 +188,7 @@ pub struct Niri {
pub lock_state: LockState,
pub screenshot_ui: ScreenshotUi,
+ pub config_error_notification: ConfigErrorNotification,
#[cfg(feature = "dbus")]
pub dbus: Option<crate::dbus::DBusServers>,
@@ -540,10 +544,14 @@ impl State {
Ok(config) => config,
Err(err) => {
warn!("{:?}", err.context("error loading config"));
+ self.niri.config_error_notification.show();
+ self.niri.queue_redraw_all();
return;
}
};
+ self.niri.config_error_notification.hide();
+
self.niri.layout.update_config(&config);
animation::ANIMATION_SLOWDOWN.store(config.debug.animation_slowdown, Ordering::Relaxed);
@@ -850,6 +858,7 @@ impl Niri {
});
let screenshot_ui = ScreenshotUi::new();
+ let config_error_notification = ConfigErrorNotification::new();
let socket_source = ListeningSocketSource::new_auto().unwrap();
let socket_name = socket_source.socket_name().to_os_string();
@@ -954,6 +963,7 @@ impl Niri {
lock_state: LockState::Unlocked,
screenshot_ui,
+ config_error_notification,
#[cfg(feature = "dbus")]
dbus: None,
@@ -1788,6 +1798,11 @@ impl Niri {
elements = self.pointer_element(renderer, output);
}
+ // The config error notification too.
+ if let Some(element) = self.config_error_notification.render(renderer, output) {
+ elements.push(element.into());
+ }
+
// If the session is locked, draw the lock surface.
if self.is_locked() {
let state = self.output_state.get(output).unwrap();
@@ -1915,6 +1930,11 @@ impl Niri {
.unwrap()
.are_animations_ongoing();
+ self.config_error_notification
+ .advance_animations(target_presentation_time);
+ state.unfinished_animations_remain |=
+ self.config_error_notification.are_animations_ongoing();
+
// Also keep redrawing if the current cursor is animated.
state.unfinished_animations_remain |= self
.cursor_manager
@@ -2828,6 +2848,7 @@ pub enum OutputRenderElements<R: NiriRenderer> {
NamedPointer(PrimaryGpuTextureRenderElement),
SolidColor(SolidColorRenderElement),
ScreenshotUi(ScreenshotUiRenderElement),
+ ConfigErrorNotification(ConfigErrorNotificationRenderElement<R>),
}
impl<R: NiriRenderer> Element for OutputRenderElements<R> {
@@ -2838,6 +2859,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.id(),
Self::SolidColor(elem) => elem.id(),
Self::ScreenshotUi(elem) => elem.id(),
+ Self::ConfigErrorNotification(elem) => elem.id(),
}
}
@@ -2848,6 +2870,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.current_commit(),
Self::SolidColor(elem) => elem.current_commit(),
Self::ScreenshotUi(elem) => elem.current_commit(),
+ Self::ConfigErrorNotification(elem) => elem.current_commit(),
}
}
@@ -2858,6 +2881,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.geometry(scale),
Self::SolidColor(elem) => elem.geometry(scale),
Self::ScreenshotUi(elem) => elem.geometry(scale),
+ Self::ConfigErrorNotification(elem) => elem.geometry(scale),
}
}
@@ -2868,6 +2892,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.transform(),
Self::SolidColor(elem) => elem.transform(),
Self::ScreenshotUi(elem) => elem.transform(),
+ Self::ConfigErrorNotification(elem) => elem.transform(),
}
}
@@ -2878,6 +2903,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.src(),
Self::SolidColor(elem) => elem.src(),
Self::ScreenshotUi(elem) => elem.src(),
+ Self::ConfigErrorNotification(elem) => elem.src(),
}
}
@@ -2892,6 +2918,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.damage_since(scale, commit),
Self::SolidColor(elem) => elem.damage_since(scale, commit),
Self::ScreenshotUi(elem) => elem.damage_since(scale, commit),
+ Self::ConfigErrorNotification(elem) => elem.damage_since(scale, commit),
}
}
@@ -2902,6 +2929,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.opaque_regions(scale),
Self::SolidColor(elem) => elem.opaque_regions(scale),
Self::ScreenshotUi(elem) => elem.opaque_regions(scale),
+ Self::ConfigErrorNotification(elem) => elem.opaque_regions(scale),
}
}
@@ -2912,6 +2940,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.alpha(),
Self::SolidColor(elem) => elem.alpha(),
Self::ScreenshotUi(elem) => elem.alpha(),
+ Self::ConfigErrorNotification(elem) => elem.alpha(),
}
}
@@ -2922,6 +2951,7 @@ impl<R: NiriRenderer> Element for OutputRenderElements<R> {
Self::NamedPointer(elem) => elem.kind(),
Self::SolidColor(elem) => elem.kind(),
Self::ScreenshotUi(elem) => elem.kind(),
+ Self::ConfigErrorNotification(elem) => elem.kind(),
}
}
}
@@ -2946,6 +2976,7 @@ impl RenderElement<GlesRenderer> for OutputRenderElements<GlesRenderer> {
Self::ScreenshotUi(elem) => {
RenderElement::<GlesRenderer>::draw(&elem, frame, src, dst, damage)
}
+ Self::ConfigErrorNotification(elem) => elem.draw(frame, src, dst, damage),
}
}
@@ -2956,6 +2987,7 @@ impl RenderElement<GlesRenderer> for OutputRenderElements<GlesRenderer> {
Self::NamedPointer(elem) => elem.underlying_storage(renderer),
Self::SolidColor(elem) => elem.underlying_storage(renderer),
Self::ScreenshotUi(elem) => elem.underlying_storage(renderer),
+ Self::ConfigErrorNotification(elem) => elem.underlying_storage(renderer),
}
}
}
@@ -2982,6 +3014,7 @@ impl<'render, 'alloc> RenderElement<TtyRenderer<'render, 'alloc>>
Self::ScreenshotUi(elem) => {
RenderElement::<TtyRenderer<'render, 'alloc>>::draw(&elem, frame, src, dst, damage)
}
+ Self::ConfigErrorNotification(elem) => elem.draw(frame, src, dst, damage),
}
}
@@ -2995,6 +3028,7 @@ impl<'render, 'alloc> RenderElement<TtyRenderer<'render, 'alloc>>
Self::NamedPointer(elem) => elem.underlying_storage(renderer),
Self::SolidColor(elem) => elem.underlying_storage(renderer),
Self::ScreenshotUi(elem) => elem.underlying_storage(renderer),
+ Self::ConfigErrorNotification(elem) => elem.underlying_storage(renderer),
}
}
}
@@ -3028,3 +3062,9 @@ impl<R: NiriRenderer> From<ScreenshotUiRenderElement> for OutputRenderElements<R
Self::ScreenshotUi(x)
}
}
+
+impl<R: NiriRenderer> From<ConfigErrorNotificationRenderElement<R>> for OutputRenderElements<R> {
+ fn from(x: ConfigErrorNotificationRenderElement<R>) -> Self {
+ Self::ConfigErrorNotification(x)
+ }
+}
diff --git a/src/render_helpers.rs b/src/render_helpers.rs
index 6cdc9fc2..5402cba7 100644
--- a/src/render_helpers.rs
+++ b/src/render_helpers.rs
@@ -3,7 +3,9 @@ use smithay::backend::renderer::element::texture::TextureRenderElement;
use smithay::backend::renderer::element::{Element, Id, Kind, RenderElement, UnderlyingStorage};
use smithay::backend::renderer::gles::{GlesError, GlesFrame, GlesRenderer, GlesTexture};
use smithay::backend::renderer::utils::CommitCounter;
-use smithay::backend::renderer::{Bind, ExportMem, ImportAll, Offscreen, Renderer, Texture};
+use smithay::backend::renderer::{
+ Bind, ExportMem, ImportAll, ImportMem, Offscreen, Renderer, Texture,
+};
use smithay::utils::{Buffer, Physical, Rectangle, Scale, Transform};
use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
@@ -11,6 +13,7 @@ use crate::backend::tty::{TtyFrame, TtyRenderer, TtyRendererError};
/// Trait with our main renderer requirements to save on the typing.
pub trait NiriRenderer:
ImportAll
+ + ImportMem
+ ExportMem
+ Bind<Dmabuf>
+ Offscreen<GlesTexture>
@@ -28,7 +31,7 @@ pub trait NiriRenderer:
impl<R> NiriRenderer for R
where
- R: ImportAll + ExportMem + Bind<Dmabuf> + Offscreen<GlesTexture> + AsGlesRenderer,
+ R: ImportAll + ImportMem + ExportMem + Bind<Dmabuf> + Offscreen<GlesTexture> + AsGlesRenderer,
R::TextureId: Texture + Clone + 'static,
R::Error: std::error::Error + Send + Sync + From<<GlesRenderer as Renderer>::Error> + 'static,
{