[ MIRROR ] Customisable, minimalist screen locker for Wayland git.ngill.net/nlock.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311// SPDX-License-Identifier: GPL-3.0-or-later// Copyright (C) 2026, Nathan Gill
pub trait FontInfo { fn font_height(&self) -> f64;}
pub trait TextInfo { fn text_height(&self) -> f64; fn text_width(&self) -> f64; fn text_x(&self) -> f64; fn text_y(&self) -> f64;}
pub trait ShowText { fn show_text(&self, context: &cairo::Context) -> anyhow::Result<()>;}
#[cfg(not(feature = "pango"))]mod cairo_backend { use anyhow::Result; use clap::ValueEnum; use serde::Deserialize;
use crate::{ config::NLockConfig, font::{FontInfo, ShowText, TextInfo}, render::{DEFAULT_DPI, DEFAULT_SCALE}, };
pub struct NLockFont { font_height: f64, text: String, extents: cairo::TextExtents, }
impl NLockFont { pub fn new( config: &NLockConfig, context: &cairo::Context, dpi: Option<f64>, scale: Option<f64>, subpixel: Option<cairo::SubpixelOrder>, ) -> Result<Self> { let dpi = dpi.unwrap_or(DEFAULT_DPI); let scale = scale.unwrap_or(DEFAULT_SCALE); let subpixel = subpixel.unwrap_or(cairo::SubpixelOrder::Default);
let mut fo = cairo::FontOptions::new()?; fo.set_hint_style(cairo::HintStyle::Full); fo.set_antialias(cairo::Antialias::Subpixel); fo.set_subpixel_order(subpixel);
context.set_font_options(&fo); context.select_font_face( &config.font.family, config.font.slant.into(), config.font.weight.into(), ); context.set_font_size((config.font.size / 72.0) * dpi * scale);
let fe = context.font_extents()?; let extents = context.text_extents("")?;
Ok(Self { font_height: fe.height(), text: "".to_string(), extents, }) }
pub fn set_text<T>(&mut self, context: &cairo::Context, text: T) -> Result<()> where T: AsRef<str>, { self.text = text.as_ref().to_string(); self.extents = context.text_extents(text.as_ref())?; Ok(()) } }
impl FontInfo for NLockFont { fn font_height(&self) -> f64 { self.font_height } }
impl TextInfo for NLockFont { fn text_height(&self) -> f64 { self.extents.height() }
fn text_width(&self) -> f64 { self.extents.width() }
fn text_x(&self) -> f64 { self.extents.x_bearing() }
fn text_y(&self) -> f64 { self.extents.y_bearing() } }
impl ShowText for NLockFont { fn show_text(&self, context: &cairo::Context) -> Result<()> { context.show_text(&self.text)?; Ok(()) } }
#[derive(Debug, Deserialize, Copy, Clone, ValueEnum)] #[serde(rename_all = "lowercase")] pub enum FontSlant { Normal, Italic, Oblique, }
impl From<FontSlant> for cairo::FontSlant { fn from(value: FontSlant) -> Self { match value { FontSlant::Normal => Self::Normal, FontSlant::Italic => Self::Italic, FontSlant::Oblique => Self::Oblique, } } }
#[derive(Debug, Deserialize, Copy, Clone, ValueEnum)] #[serde(rename_all = "lowercase")] pub enum FontWeight { Normal, Bold, }
impl From<FontWeight> for cairo::FontWeight { fn from(value: FontWeight) -> Self { match value { FontWeight::Normal => Self::Normal, FontWeight::Bold => Self::Bold, } } }}
#[cfg(feature = "pango")]mod pango_backend { use anyhow::Result; use clap::ValueEnum; use pango::Rectangle; use pangocairo::functions::{create_layout, show_layout}; use serde::Deserialize;
use crate::{ config::NLockConfig, font::{FontInfo, ShowText, TextInfo}, render::{DEFAULT_DPI, DEFAULT_SCALE}, };
pub struct NLockFont { layout: pango::Layout, metrics: pango::FontMetrics, text: String, extents: Rectangle, }
impl NLockFont { pub fn new( config: &NLockConfig, context: &cairo::Context, dpi: Option<f64>, scale: Option<f64>, ) -> Self { let dpi = dpi.unwrap_or(DEFAULT_DPI); let scale = scale.unwrap_or(DEFAULT_SCALE);
let mut fd = pango::FontDescription::new(); fd.set_family(&config.font.family); fd.set_style(config.font.slant.into()); fd.set_weight(config.font.weight.into()); fd.set_absolute_size(((config.font.size / 72.0) * dpi * scale) * PANGO_SCALE as f64);
let layout = create_layout(context); layout.set_font_description(Some(&fd));
let p_ctx = layout.context(); let metrics = p_ctx.metrics(Some(&fd), None);
layout.set_text(""); let extents = layout.pixel_extents().0;
Self { layout, metrics, text: "".to_string(), extents, } }
pub fn set_text<T>(&mut self, text: T) where T: AsRef<str>, { self.text = text.as_ref().to_string(); self.layout.set_text(text.as_ref()); self.extents = self.layout.pixel_extents().0; } }
impl FontInfo for NLockFont { fn font_height(&self) -> f64 { (pango_pixels(self.metrics.ascent()) + pango_pixels(self.metrics.descent())) as f64 } }
impl TextInfo for NLockFont { fn text_height(&self) -> f64 { self.extents.height() as f64 }
fn text_width(&self) -> f64 { self.extents.width() as f64 }
fn text_x(&self) -> f64 { self.extents.x() as f64 }
fn text_y(&self) -> f64 { self.extents.y() as f64 } }
impl ShowText for NLockFont { fn show_text(&self, context: &cairo::Context) -> Result<()> { show_layout(context, &self.layout); Ok(()) } }
#[derive(Debug, Deserialize, Copy, Clone, ValueEnum)] #[serde(rename_all = "lowercase")] pub enum FontSlant { Normal, Italic, Oblique, }
impl From<FontSlant> for pango::Style { fn from(value: FontSlant) -> Self { match value { FontSlant::Normal => Self::Normal, FontSlant::Italic => Self::Italic, FontSlant::Oblique => Self::Oblique, } } }
#[derive(Debug, Deserialize, Copy, Clone, ValueEnum)] #[serde(rename_all = "lowercase")] pub enum FontWeight { Thin, Ultralight, Light, Semilight, Book, Normal, Medium, Semibold, Bold, Ultrabold, Heavy, Ultraheavy, }
impl From<FontWeight> for pango::Weight { fn from(value: FontWeight) -> Self { match value { FontWeight::Thin => Self::Thin, FontWeight::Ultralight => Self::Ultralight, FontWeight::Light => Self::Light, FontWeight::Semilight => Self::Semilight, FontWeight::Book => Self::Book, FontWeight::Normal => Self::Normal, FontWeight::Medium => Self::Medium, FontWeight::Semibold => Self::Semibold, FontWeight::Bold => Self::Bold, FontWeight::Ultrabold => Self::Ultrabold, FontWeight::Heavy => Self::Heavy, FontWeight::Ultraheavy => Self::Ultraheavy, } } }
// Pango scale factor const PANGO_SCALE: i32 = 1024;
#[inline] /// Convert Pango units to pixels fn pango_pixels(d: i32) -> i32 { (d + 512) >> 10 }}
#[cfg(not(feature = "pango"))]pub use cairo_backend::*;#[cfg(feature = "pango")]pub use pango_backend::*;