Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/bus/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::bus::dbus_codegen::{self, OrgFreedesktopNotifications};
use crate::config::ZeroTimeoutBehavior;
use crate::maths_utility;
use crate::Config;
use crate::icons;

static ID_COUNT: AtomicU32 = AtomicU32::new(1);
pub fn fetch_id() -> u32 {
Expand Down Expand Up @@ -408,7 +409,8 @@ impl Notification {
}
}

let app_image = image_from_path(&app_icon);
let app_image = icons::resolve_icon_path(&app_icon)
.and_then(|p| image_from_path(p.to_str().unwrap()));

// Structs are stored internally in the rust dbus implementation as VecDeque.
// https://github.com/diwic/dbus-rs/issues/363
Expand Down
3 changes: 3 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ pub struct Config {
// The threshold before pausing notifications due to being idle. Unspecified = ignore.
pub idle_threshold: Option<u64>,

#[serde(default)]
pub icon_theme: Option<String>,

// Whether a notification should be sent when the config is reloaded.
#[serde(default = "maths_utility::val_true")]
pub notify_on_reload: bool,
Expand Down
130 changes: 130 additions & 0 deletions src/icons.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use std::path::{Path, PathBuf};
use std::env;
use crate::config::Config;

/// Resolve icon path from theme (supports svg, png, xpm, scalable, symbolic, etc.)
pub fn resolve_icon_path(icon_name: &str) -> Option<PathBuf> {
if icon_name.is_empty() { return None }

let path = Path::new(icon_name);

// If already a valid file path
if path.exists() {
return Some(path.to_path_buf());
}

// Get theme from config or fallback
let theme = Config::get()
.icon_theme
.clone()
.unwrap_or_else(|| "Adwaita".to_string());


// Collect all possible icon base directories
let mut icon_dirs = vec![
"/usr/share/icons".to_string(),
"/usr/local/share/icons".to_string(),
expand_tilde("~/.icons"),
expand_tilde("~/.local/share/icons"),
"/usr/share/pixmaps".to_string(),
"/var/lib/flatpak/exports/share/icons".to_string(),
];

// Add any extra paths from XDG_DATA_DIRS
if let Ok(xdg_data_dirs) = env::var("XDG_DATA_DIRS") {
for p in xdg_data_dirs.split(':') {
let p = format!("{}/icons", p.trim_end_matches('/'));
icon_dirs.push(p);
}
}


let subdirs = vec![
"apps", "status", "actions", "devices", "categories", "places", "mimetypes",
];

let sizes = vec![
"scalable", "symbolic", "16x16", "22x22", "24x24", "32x32", "48x48", "64x64", "128x128",
];

let extensions = vec!["svg", "svgz", "png", "xpm"];

for base_dir in &icon_dirs {
let base_path = Path::new(base_dir);

// skip base dirs that don't exists
if !base_path.exists() {
continue;
}
for size in &sizes {
for subdir in &subdirs {
let dir_path = base_path
.join(&theme)
.join(size)
.join(subdir);

// skip nonexistent subdirectories to save time
if !dir_path.is_dir() {
continue;
}
for ext in &extensions {
let full = dir_path.join(format!("{}.{}", icon_name, ext));

if full.exists() {
return Some(full);
}
}
}
}
}

// NOTE: fallback to hicolor theme (optional if you to make an option in config)
// PERF: skip dirs thats does not exists
//
// for base_dir in &icon_dirs {
// let base_path = Path::new(base_dir);
// if !base_path.exists() {
// continue;
// }
//
// for size in &sizes {
// for subdir in &subdirs {
// let dir_path = base_path.join("hicolor").join(size).join(subdir);
// if !dir_path.is_dir() {
// continue;
// }
//
// for ext in &extensions {
// let full = dir_path.join(format!("{}.{}", icon_name, ext));
//
// if full.exists() {
// return Some(full);
// }
// }
// }
// }
// }

// Last resort: search directly in /usr/share/pixmaps
// for ext in &extensions {
// let full = Path::new("/usr/share/pixmaps").join(format!("{}.{}", icon_name, ext));
// if full.exists() {
// return Some(full);
// }
// }

println!("Icon '{}' not found in any theme path.", icon_name);
None
}

/// Expand ~ to home directory manually (no external crates)
fn expand_tilde(path: &str) -> String {
if path.starts_with("~/") {
if let Some(home) = std::env::var_os("HOME") {
let mut p = PathBuf::from(home);
p.push(path.trim_start_matches("~/"));
return p.display().to_string();
}
}
path.to_string()
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod bus;
mod cli;
mod config;
mod manager;
mod icons;
#[rustfmt::skip]
mod maths_utility;
mod rendering;
Expand Down
54 changes: 43 additions & 11 deletions src/rendering/blocks/notification_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct NotificationBlockParameters {

pub border_width: f64,
pub border_rounding: f64,
pub rounded_border_corners: bool,
pub background_color: Color,
pub border_color: Color,

Expand Down Expand Up @@ -58,24 +59,55 @@ impl DrawableLayoutElement for NotificationBlockParameters {
};

//let bd_color = &self.border_color;
window
.context
.set_source_rgba(bd_color.r, bd_color.g, bd_color.b, bd_color.a);
window.context.paint()?;

let bg_color = &self.background_color;
let bw = &self.border_width;
window
.context
.set_source_rgba(bg_color.r, bg_color.g, bg_color.b, bg_color.a);
let radius = &self.border_rounding;
let w = parent_rect.width();
let h = parent_rect.height();

let outer_radius = if self.rounded_border_corners {
radius.min(w.min(h) / 2.0 )
} else {
0.0
};
let inner_radius = (radius - bw).max(0.0).min((w - bw * 2.0).min(h - bw * 2.0) / 2.0);

// Draw border
window.context.new_path();
maths_utility::cairo_path_rounded_rectangle(
&window.context,
0.0,
0.0,
w,
h,
outer_radius,
)?;

window.context.new_sub_path();
maths_utility::cairo_path_rounded_rectangle(
&window.context,
*bw,
*bw, // x, y
parent_rect.width() - bw * 2.0,
parent_rect.height() - bw * 2.0,
self.border_rounding,
w - bw * 2.0,
h - bw * 2.0,
inner_radius,
)?;
window.context.set_source_rgba(bd_color.r, bd_color.g, bd_color.b, bd_color.a);
window.context.set_fill_rule(cairo::FillRule::EvenOdd);
window.context.fill()?;

// Draw background
maths_utility::cairo_path_rounded_rectangle(
&window.context,
*bw,
*bw, // x, y
w - bw * 2.0,
h - bw * 2.0,
inner_radius,
)?;

window.context.set_source_rgba(bg_color.r, bg_color.g, bg_color.b, bg_color.a);
window.context.set_fill_rule(cairo::FillRule::Winding);
window.context.fill()?;

Ok(Rect::new(
Expand Down
4 changes: 4 additions & 0 deletions wired.ron
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
// A value of 0 means that there is no limit.
max_notifications: 0,

// Icon theme for icons lookup
icon_theme: "Adwaita",

// The default timeout, in milliseconds, for notifications that don't have an initial timeout set.
// 1000ms = 1s.
timeout: 10000,
Expand Down Expand Up @@ -106,6 +109,7 @@
monitor: 0,
border_width: 3.0,
border_rounding: 3.0,
rounded_border_corners: true,
//background_color: Color(r: 0.15686, g: 0.15686, b: 0.15686, a: 1.0),
background_color: Color(hex: "#282828"),
border_color: Color(hex: "#ebdbb2"),
Expand Down