brewery
notashelf /
e172d4fbb3fc1d15c4e9eed58b8d68ce9cbb7e72

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git

Commit e172d4fbb3fc

tarball

NotAShelf <raf@notashelf.dev> · 2026-06-26 08:57 UTC

unverified4 files changed+107-0
wayland: idle-inhibit while focused and a content-type hint

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ib25e27fc913c3af009e85496412002366a6a6964
diff --git a/doc/beer.toml.5.scd b/doc/beer.toml.5.scdindex 15acd1e..9317efd 100644--- a/doc/beer.toml.5.scd+++ b/doc/beer.toml.5.scd@@ -39,6 +39,11 @@ channel). 	built-in set (whitespace and common punctuation, keeping _.-/:~\__ inside 	words). +*idle-inhibit* = _bool_+	While the window is focused, ask the compositor not to blank the screen or+	start the screensaver (idle-inhibit-v1). A backgrounded window stops+	inhibiting. Default _false_.+ # [colors]  *foreground* = _color_, *background* = _color_diff --git a/src/config.rs b/src/config.rsindex fcdcccb..fbff91a 100644--- a/src/config.rs+++ b/src/config.rs@@ -154,6 +154,9 @@ pub struct Main {     /// Characters that break a word for double-click selection. Empty/unset     /// keeps the built-in default.     pub word_delimiters: Option<String>,+    /// Hold an idle inhibitor while the window is focused, so the compositor+    /// does not blank the screen or start the screensaver. Default off.+    pub idle_inhibit: bool, }  impl Default for Main {@@ -167,6 +170,7 @@ impl Default for Main {             pad_x: 2,             pad_y: 2,             word_delimiters: None,+            idle_inhibit: false,         }     } }diff --git a/src/wayland/handlers.rs b/src/wayland/handlers.rsindex 7761442..4ca5233 100644--- a/src/wayland/handlers.rs+++ b/src/wayland/handlers.rs@@ -70,6 +70,7 @@ impl WindowHandler for App {             }         }         self.focused = configure.is_activated();+        self.sync_idle_inhibit();         if self.session.is_none() {             self.spawn_session();         } else {@@ -188,6 +189,7 @@ impl KeyboardHandler for App {         self.activate_keyboard(keyboard);         self.serial = serial;         self.focused = true;+        self.sync_idle_inhibit();         self.report_focus(true);         self.needs_draw = true;     }@@ -201,6 +203,7 @@ impl KeyboardHandler for App {         _: u32,     ) {         self.focused = false;+        self.sync_idle_inhibit();         // Drop held-key state so a key released while unfocused can't leak a         // stale kitty release event later.         self.keys_down.clear();@@ -634,6 +637,56 @@ impl Dispatch<WpViewport, ()> for App {     } } +// idle-inhibit and content-type are likewise raw protocol objects; none of them+// emit events we act on.+impl Dispatch<ZwpIdleInhibitManagerV1, ()> for App {+    fn event(+        _: &mut Self,+        _: &ZwpIdleInhibitManagerV1,+        _: <ZwpIdleInhibitManagerV1 as Proxy>::Event,+        _: &(),+        _: &Connection,+        _: &QueueHandle<Self>,+    ) {+    }+}++impl Dispatch<ZwpIdleInhibitorV1, ()> for App {+    fn event(+        _: &mut Self,+        _: &ZwpIdleInhibitorV1,+        _: zwp_idle_inhibitor_v1::Event,+        _: &(),+        _: &Connection,+        _: &QueueHandle<Self>,+    ) {+    }+}++impl Dispatch<WpContentTypeManagerV1, ()> for App {+    fn event(+        _: &mut Self,+        _: &WpContentTypeManagerV1,+        _: <WpContentTypeManagerV1 as Proxy>::Event,+        _: &(),+        _: &Connection,+        _: &QueueHandle<Self>,+    ) {+    }+}++impl Dispatch<WpContentTypeV1, ()> for App {+    fn event(+        _: &mut Self,+        _: &WpContentTypeV1,+        _: wp_content_type_v1::Event,+        _: &(),+        _: &Connection,+        _: &QueueHandle<Self>,+    ) {+    }+}+ impl ActivationHandler for App {     type RequestData = RequestData; diff --git a/src/wayland/mod.rs b/src/wayland/mod.rsindex 5e6301b..a20c7e7 100644--- a/src/wayland/mod.rs+++ b/src/wayland/mod.rs@@ -84,10 +84,18 @@ use wayland_client::{         wl_surface,     }, };+use wayland_protocols::wp::content_type::v1::client::{+    wp_content_type_manager_v1::WpContentTypeManagerV1,+    wp_content_type_v1::{self, WpContentTypeV1},+}; use wayland_protocols::wp::fractional_scale::v1::client::{     wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1,     wp_fractional_scale_v1::{self, WpFractionalScaleV1}, };+use wayland_protocols::wp::idle_inhibit::zv1::client::{+    zwp_idle_inhibit_manager_v1::ZwpIdleInhibitManagerV1,+    zwp_idle_inhibitor_v1::{self, ZwpIdleInhibitorV1},+}; use wayland_protocols::wp::text_input::zv3::client::{     zwp_text_input_manager_v3::ZwpTextInputManagerV3,     zwp_text_input_v3::{self, ContentHint, ContentPurpose, ZwpTextInputV3},@@ -181,6 +189,14 @@ pub fn run(config: Config, config_path: Option<std::path::PathBuf>) -> anyhow::R     });     let text_input_manager = bind_global::<ZwpTextInputManagerV3>(&globals, &qh);     let activation = ActivationState::bind(&globals, &qh).ok();+    let idle_inhibit_manager = bind_global::<ZwpIdleInhibitManagerV1>(&globals, &qh);+    // Tag the surface as plain content (a terminal is none of photo/video/game)+    // so the compositor applies no media-specific treatment. Applies on commit.+    let content_type = bind_global::<WpContentTypeManagerV1>(&globals, &qh)+        .map(|mgr| mgr.get_surface_content_type(window.wl_surface(), &qh, ()));+    if let Some(ct) = &content_type {+        ct.set_content_type(wp_content_type_v1::Type::None);+    }      // First commit with no buffer kicks off the initial configure.     window.commit();@@ -219,6 +235,9 @@ pub fn run(config: Config, config_path: Option<std::path::PathBuf>) -> anyhow::R         cursor_shape_manager,         text_input_manager,         activation,+        idle_inhibit_manager,+        idle_inhibitor: None,+        content_type,         preedit: String::new(),         ime_preedit_pending: String::new(),         ime_commit_pending: String::new(),@@ -397,6 +416,17 @@ struct App {     text_input_manager: Option<ZwpTextInputManagerV3>,     /// xdg-activation, used to request attention on an urgent bell.     activation: Option<ActivationState>,+    /// idle-inhibit-v1 manager; an inhibitor is held while focused when the+    /// `[main] idle-inhibit` config is on, so the screen does not blank.+    idle_inhibit_manager: Option<ZwpIdleInhibitManagerV1>,+    idle_inhibitor: Option<ZwpIdleInhibitorV1>,+    /// content-type-v1 hint object. Set once at startup; held only so the+    /// object (and thus the hint) outlives construction.+    #[allow(+        dead_code,+        reason = "kept alive to preserve the surface content-type hint"+    )]+    content_type: Option<WpContentTypeV1>,     /// Committed IME preedit string shown inline at the cursor while composing.     preedit: String,     /// Preedit/commit accumulated since the last text-input `done`.@@ -1409,6 +1439,21 @@ impl App {         }     } +    /// Create or drop the idle inhibitor to match `[main] idle-inhibit` and the+    /// current focus: inhibit only while focused, so a backgrounded terminal+    /// still lets the screen blank. Idempotent; called on every focus change.+    fn sync_idle_inhibit(&mut self) {+        let want = self.config.main.idle_inhibit && self.focused;+        if want && self.idle_inhibitor.is_none() {+            if let Some(mgr) = &self.idle_inhibit_manager {+                self.idle_inhibitor =+                    Some(mgr.create_inhibitor(self.window.wl_surface(), &self.qh, ()));+            }+        } else if !want && let Some(inhibitor) = self.idle_inhibitor.take() {+            inhibitor.destroy();+        }+    }+     /// Write bytes to the PTY master, logging on failure.     fn write_to_pty(&mut self, bytes: &[u8]) {         if let Some(session) = self.session.as_mut()