Skip to content

Commit

Permalink
Issue 272 - removing the dependency on pango and cairo (#274)
Browse files Browse the repository at this point in the history
* #272 replacing usage of pango and cairo with xlib and fontconfig

* #272 fixing text offset

* #272 sorting out dependencies and feature flags

* #272 adding missing build dep for CI

* #272 simplifying TextStyle and making it Copy

* #272 fixing broken doctests

* simplifying method implementations for Color

* ensuring that graphics state is cleaned up when status bars are recreated

* adding safety comments and warn level linting for future missing safety comments

* documenting the updated penrose_ui crate
  • Loading branch information
sminez authored Jul 16, 2023
1 parent 83789ad commit 020231f
Show file tree
Hide file tree
Showing 21 changed files with 981 additions and 448 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
rust-version: ${{ matrix.rust }}

- name: Install C deps
run: sudo apt-get update && sudo apt-get install -y libxrandr-dev libx11-xcb-dev libxcb-randr0-dev libpango1.0-dev libcairo2-dev --fix-missing
run: sudo apt-get update && sudo apt-get install -y libxrandr-dev libx11-xcb-dev libxcb-randr0-dev libxft-dev --fix-missing

- name: Run tests
run: cargo test --workspace --features ${{ matrix.features }} --verbose
Expand All @@ -55,7 +55,7 @@ jobs:
- uses: hecrj/setup-rust-action@v1
with:
components: clippy
- run: sudo apt-get update && sudo apt-get install -y libxrandr-dev libx11-xcb-dev libxcb-randr0-dev libpango1.0-dev libcairo2-dev --fix-missing
- run: sudo apt-get update && sudo apt-get install -y libxrandr-dev libx11-xcb-dev libxcb-randr0-dev libxft-dev --fix-missing
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -70,5 +70,5 @@ jobs:
- uses: hecrj/setup-rust-action@v1
with:
rust-version: nightly
- run: sudo apt-get update && sudo apt-get install -y libxrandr-dev libx11-xcb-dev libxcb-randr0-dev libpango1.0-dev libcairo2-dev --fix-missing
- run: sudo apt-get update && sudo apt-get install -y libxrandr-dev libx11-xcb-dev libxcb-randr0-dev libxft-dev --fix-missing
- run: cargo rustdoc --all-features
17 changes: 7 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "penrose"
version = "0.3.2"
version = "0.3.3"
edition = "2021"
authors = ["sminez <innes.andersonmorrison@gmail.com>"]
license = "MIT"
Expand All @@ -23,24 +23,21 @@ members = [
]

[features]
default = ["x11rb-xcb", "keysyms"]
default = ["x11rb", "keysyms"]
keysyms = ["penrose_keysyms"]
x11rb-xcb = ["x11rb", "x11rb/allow-unsafe-code"]

[dependencies]
penrose_keysyms = { version = "0.1.1", path = "crates/penrose_keysyms", optional = true }
# penrose_proc = { version = "0.1.3", path = "crates/penrose_proc" }

anymap = "0.12"
bitflags = { version = "2.3", features = ["serde"] }
nix = "0.26"
nix = { version = "0.26", default-features = false, features = ["signal"] }
penrose_keysyms = { version = "0.3", path = "crates/penrose_keysyms", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
strum = { version = "0.25", features = ["derive"] }
strum_macros = "0.25"
thiserror = "1.0"
tracing = { version = "0.1", features = ["attributes", "log"] }

serde = { version = "1.0", features = ["derive"], optional = true }
tracing = { version = "0.1", features = ["attributes"] }
x11rb = { version = "0.12", features = ["randr"], optional = true }
anymap = "0.12"

[dev-dependencies]
paste = "1.0.13"
Expand Down
2 changes: 1 addition & 1 deletion crates/penrose_keysyms/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "penrose_keysyms"
version = "0.1.1"
version = "0.3.3"
authors = ["IDAM <innes.andersonmorrison@gmail.com>"]
edition = "2018"
license = "MIT"
Expand Down
19 changes: 19 additions & 0 deletions crates/penrose_keysyms/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2020 Innes Anderson-Morrison

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
13 changes: 7 additions & 6 deletions crates/penrose_ui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "penrose_ui"
version = "0.1.3"
version = "0.3.3"
edition = "2021"
authors = ["sminez <innes.andersonmorrison@gmail.com>"]
license = "MIT"
Expand All @@ -12,10 +12,11 @@ description = "UI elements for the penrose window manager library"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cairo-rs = { version = "0.17.10", features = ["xcb"] }
pangocairo = { version = "0.17.10" }
pango = { version = "0.17.10" }
penrose = { version = "0.3", path = "../../" }
tracing = { version = "0.1", features = ["attributes", "log"] }
tracing = { version = "0.1", features = ["attributes"] }
thiserror = "1.0"
x11rb = { version = "0.12", features = ["randr", "render"] }
yeslogic-fontconfig-sys = "4.0"
x11 = { version = "2.21", features = ["xft", "xlib"] }

[dev-dependencies]
anyhow = "1.0.71"
19 changes: 19 additions & 0 deletions crates/penrose_ui/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2020 Innes Anderson-Morrison

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
5 changes: 5 additions & 0 deletions crates/penrose_ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@

_GUI elements for the penrose window manager library_

The functionality provided by this crate is a _very_ thin wrapper over xlib and fontconfig
to support minimal text based UIs such as a status bar or simple menu. It may be possible
to use this to write a stand alone UI outside of direct integration with the Penrose window
manager crate but that is not the supported use case this crate has been written for.

## Bar
A lightweight and minimal status bar.
51 changes: 51 additions & 0 deletions crates/penrose_ui/examples/txt-demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Demo of the text rendering API
use penrose::{
pure::geometry::Rect,
x::{Atom, WinType},
Color,
};
use penrose_ui::Draw;
use std::{thread::sleep, time::Duration};

const DX: u32 = 100;
const DY: u32 = 100;
const W: u32 = 500;
const H: u32 = 60;
const FONT: &str = "ProFont For Powerline";
const TXT: &str = "  text is great! ◈ ζ ᛄ ℚ";

fn main() -> anyhow::Result<()> {
let fg1 = Color::try_from("#fad07b")?;
let fg2 = Color::try_from("#458588")?;
let fg3 = Color::try_from("#a6cc70")?;
let fg4 = Color::try_from("#b16286")?;
let bg = Color::try_from("#282828")?;

let mut drw = Draw::new(FONT, 12, bg)?;
let w = drw.new_window(
WinType::InputOutput(Atom::NetWindowTypeDock),
Rect::new(DX, DY, W, H),
false,
)?;

let mut ctx = drw.context_for(w)?;
ctx.clear()?;

let (dx, dy) = ctx.draw_text(TXT, 0, (10, 0), fg1)?;

ctx.set_x_offset(dx as i32 + 10);
ctx.draw_text(TXT, 0, (5, 0), fg2)?;

ctx.translate(0, dy as i32 + 10);
ctx.draw_text(TXT, 0, (5, 0), fg3)?;

ctx.translate(-(dx as i32), 0);
ctx.draw_text(TXT, 0, (0, 0), fg4)?;

ctx.flush();
drw.flush(w)?;

sleep(Duration::from_secs(2));

Ok(())
}
102 changes: 44 additions & 58 deletions crates/penrose_ui/src/bar/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
//! A lightweight and configurable status bar for penrose
use crate::{
core::{Context, Draw},
Result,
};
use crate::{core::Draw, Result};
use penrose::{
core::{State, WindowManager},
pure::geometry::Rect,
Expand All @@ -11,7 +8,6 @@ use penrose::{
};
use std::fmt;
use tracing::{debug, error, info};
use x11rb::protocol::xproto::ConnectionExt as _;

pub mod widgets;

Expand All @@ -31,9 +27,8 @@ pub struct StatusBar<X: XConn> {
draw: Draw,
position: Position,
widgets: Vec<Box<dyn Widget<X>>>,
screens: Vec<(Xid, f64)>,
hpx: u32,
h: f64,
screens: Vec<(Xid, u32)>,
h: u32,
bg: Color,
active_screen: usize,
}
Expand All @@ -44,7 +39,7 @@ impl<X: XConn> fmt::Debug for StatusBar<X> {
.field("position", &self.position)
.field("widgets", &stringify!(self.widgets))
.field("screens", &self.screens)
.field("hpx", &self.hpx)
.field("h", &self.h)
.field("bg", &self.bg)
.field("active_screen", &self.active_screen)
.finish()
Expand All @@ -58,25 +53,22 @@ impl<X: XConn> StatusBar<X> {
position: Position,
h: u32,
bg: impl Into<Color>,
fonts: &[&str],
font: &str,
point_size: u8,
widgets: Vec<Box<dyn Widget<X>>>,
) -> Result<Self> {
let draw = Draw::new()?;
let bg = bg.into();
let draw = Draw::new(font, point_size, bg)?;

let mut bar = Self {
Ok(Self {
draw,
position,
widgets,
screens: vec![],
hpx: h,
h: h as f64,
bg: bg.into(),
h,
bg,
active_screen: 0,
};

fonts.iter().for_each(|f| bar.draw.register_font(f));

Ok(bar)
})
}

/// Add this [`StatusBar`] into the given [`WindowManager`] along with the required
Expand All @@ -103,13 +95,13 @@ impl<X: XConn> StatusBar<X> {
.map(|&Rect { x, y, w, h }| {
let y = match self.position {
Position::Top => y,
Position::Bottom => h - self.hpx,
Position::Bottom => h - self.h,
};

debug!("creating new window");
let id = self.draw.new_window(
WinType::InputOutput(Atom::NetWindowTypeDock),
Rect::new(x, y, w, self.hpx),
Rect::new(x, y, w, self.h),
false,
)?;

Expand All @@ -125,9 +117,9 @@ impl<X: XConn> StatusBar<X> {
debug!("flushing");
self.draw.flush(id)?;

Ok((id, w as f64))
Ok((id, w))
})
.collect::<Result<Vec<(Xid, f64)>>>()?;
.collect::<Result<Vec<(Xid, u32)>>>()?;

Ok(())
}
Expand All @@ -138,18 +130,35 @@ impl<X: XConn> StatusBar<X> {
let screen_has_focus = self.active_screen == i;
let mut ctx = self.draw.context_for(id)?;

ctx.clear()?;
ctx.fill_rect(Rect::new(0, 0, w, self.h), self.bg)?;

let mut extents = Vec::with_capacity(self.widgets.len());
let mut greedy_indices = vec![];

ctx.color(&self.bg);
ctx.rectangle(0.0, 0.0, w, self.h)?;
for (i, w) in self.widgets.iter_mut().enumerate() {
extents.push(w.current_extent(&mut ctx, self.h)?);
if w.is_greedy() {
greedy_indices.push(i)
}
}

let total = extents.iter().map(|(w, _)| w).sum::<u32>();
let n_greedy = greedy_indices.len();

if total < w && n_greedy > 0 {
let per_greedy = (w - total) / n_greedy as u32;
for i in greedy_indices.iter() {
let (w, h) = extents[*i];
extents[*i] = (w + per_greedy, h);
}
}

let extents = self.layout(&mut ctx, w)?;
let mut x = 0.0;
let mut x = 0;
for (wd, (w, _)) in self.widgets.iter_mut().zip(extents) {
wd.draw(&mut ctx, self.active_screen, screen_has_focus, w, self.h)?;
x += w;
ctx.flush();
ctx.set_x_offset(x);
ctx.set_x_offset(x as i32);
}

self.draw.flush(id)?;
Expand All @@ -158,32 +167,6 @@ impl<X: XConn> StatusBar<X> {
Ok(())
}

fn layout(&mut self, ctx: &mut Context, w: f64) -> Result<Vec<(f64, f64)>> {
let mut extents = Vec::with_capacity(self.widgets.len());
let mut greedy_indices = vec![];

for (i, w) in self.widgets.iter_mut().enumerate() {
extents.push(w.current_extent(ctx, self.h)?);
if w.is_greedy() {
greedy_indices.push(i)
}
}

let total = extents.iter().map(|(w, _)| w).sum::<f64>();
let n_greedy = greedy_indices.len();

if total < w && n_greedy > 0 {
let per_greedy = (w - total) / n_greedy as f64;
for i in greedy_indices.iter() {
let (w, h) = extents[*i];
extents[*i] = (w + per_greedy, h);
}
}

// Allowing overflow to happen
Ok(extents)
}

fn redraw_if_needed(&mut self) -> Result<()> {
if self.widgets.iter().any(|w| w.require_draw()) {
self.redraw()?;
Expand Down Expand Up @@ -253,10 +236,13 @@ pub fn event_hook<X: XConn + 'static>(

if matches!(event, RandrNotify) || matches!(event, ConfigureNotify(e) if e.is_root) {
info!("screens have changed: recreating status bars");
let screens: Vec<_> = bar.screens.drain(0..).collect();

for &(id, _) in bar.screens.iter() {
for (id, _) in screens {
info!(%id, "removing previous status bar");
bar.draw.conn.connection().destroy_window(*id)?;
if let Err(e) = bar.draw.destroy_window_and_surface(id) {
error!(%e, "error when removing previous status bar state");
}
}

if let Err(e) = bar.init_for_screens() {
Expand Down
Loading

0 comments on commit 020231f

Please sign in to comment.