Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix crash when icon not found in either theme directory #2

Closed
wants to merge 16 commits into from
Closed
460 changes: 454 additions & 6 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions crates/eww/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ once_cell = "1.14"
nix = "0.26.2"
simple-signal = "1.1"
unescape = "0.1"
zbus = { version = "3.7.0", default-features = false, features = ["tokio"] }

tokio = { version = "1.26.0", features = ["full"] }
futures-core = "0.3.27"
Expand All @@ -64,3 +65,4 @@ codespan-reporting = "0.11"
simplexpr = { version = "0.1.0", path = "../simplexpr" }
eww_shared_util = { version = "0.1.0", path = "../eww_shared_util" }
yuck = { version = "0.1.0", path = "../yuck", default-features = false}
notifier_host = { version = "0.1.0", path = "../notifier_host" }
5 changes: 4 additions & 1 deletion crates/eww/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ fn main() {
if std::env::var("RUST_LOG").is_ok() {
pretty_env_logger::init_timed();
} else {
pretty_env_logger::formatted_timed_builder().filter(Some("eww"), log_level_filter).init();
pretty_env_logger::formatted_timed_builder()
.filter(Some("eww"), log_level_filter)
.filter(Some("notifier_host"), log_level_filter)
.init();
}

#[allow(unused)]
Expand Down
22 changes: 14 additions & 8 deletions crates/eww/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub fn initialize_server<B: DisplayBackend>(
}

// initialize all the handlers and tasks running asyncronously
init_async_part(app.paths.clone(), ui_send);
let tokio_handle = init_async_part(app.paths.clone(), ui_send);

glib::MainContext::default().spawn_local(async move {
// if an action was given to the daemon initially, execute it first.
Expand All @@ -119,22 +119,26 @@ pub fn initialize_server<B: DisplayBackend>(
}
});

// allow the GTK main thread to do tokio things
let _g = tokio_handle.enter();

gtk::main();
log::info!("main application thread finished");

Ok(ForkResult::Child)
}

fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) {
fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>) -> tokio::runtime::Handle {
let rt = tokio::runtime::Builder::new_multi_thread()
.thread_name("main-async-runtime")
.enable_all()
.build()
.expect("Failed to initialize tokio runtime");
let handle = rt.handle().clone();

std::thread::Builder::new()
.name("outer-main-async-runtime".to_string())
.spawn(move || {
let rt = tokio::runtime::Builder::new_multi_thread()
.thread_name("main-async-runtime")
.enable_all()
.build()
.expect("Failed to initialize tokio runtime");

rt.block_on(async {
let filewatch_join_handle = {
let ui_send = ui_send.clone();
Expand Down Expand Up @@ -166,6 +170,8 @@ fn init_async_part(paths: EwwPaths, ui_send: UnboundedSender<app::DaemonCommand>
})
})
.expect("Failed to start outer-main-async-runtime thread");

handle
}

/// Watch configuration files for changes, sending reload events to the eww app when the files change.
Expand Down
1 change: 1 addition & 0 deletions crates/eww/src/widgets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub mod build_widget;
pub mod circular_progressbar;
pub mod def_widget_macro;
pub mod graph;
mod systray;
pub mod transform;
pub mod widget_definitions;

Expand Down
100 changes: 100 additions & 0 deletions crates/eww/src/widgets/systray.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#![allow(unused)]

use gtk::prelude::*;
use notifier_host;

struct Host {
menubar: gtk::MenuBar,
items: std::collections::HashMap<String, gtk::MenuItem>,
}

async fn watch_foreach<T: std::fmt::Debug>(mut rx: tokio::sync::watch::Receiver<T>, mut f: impl FnMut(&T)) {
f(&rx.borrow());
while rx.changed().await.is_ok() {
f(&rx.borrow());
}
}

impl notifier_host::Host for Host {
fn add_item(&mut self, id: &str, item: notifier_host::Item) {
let mi = gtk::MenuItem::new();
self.menubar.add(&mi);
if let Some(old_mi) = self.items.insert(id.to_string(), mi.clone()) {
self.menubar.remove(&old_mi);
}

// maintain title
glib::MainContext::default().spawn_local({
let mi = mi.clone();
watch_foreach(item.title(), move |title| {
mi.set_tooltip_text(Some(title));
})
});

let icon = gtk::Image::new();
mi.add(&icon);

// other initialisation
glib::MainContext::default().spawn_local({
let mi = mi.clone();
async move {
let img = item.icon(24).await.unwrap();
icon.set_from_pixbuf(Some(&img));

let menu = item.menu().await.unwrap();
mi.set_submenu(Some(&menu));
}
});
mi.show_all();
}
fn remove_item(&mut self, id: &str) {
if let Some(mi) = self.items.get(id) {
self.menubar.remove(mi);
} else {
log::warn!("Tried to remove nonexistent item {:?} from systray", id);
}
}
}

struct DBusGlobalState {
con: zbus::Connection,
name: zbus::names::WellKnownName<'static>,
}

async fn dbus_state() -> std::sync::Arc<DBusGlobalState> {
use tokio::sync::Mutex;
use std::sync::{Weak, Arc};
use once_cell::sync::Lazy;
static DBUS_STATE: Lazy<Mutex<Weak<DBusGlobalState>>> = Lazy::new(Default::default);

let mut dbus_state = DBUS_STATE.lock().await;
if let Some(state) = dbus_state.upgrade() {
state
} else {
// TODO error handling?
let con = zbus::Connection::session().await.unwrap();
notifier_host::watcher_on(&con).await.unwrap();

let name = notifier_host::attach_new_wellknown_name(&con).await.unwrap();

let arc = Arc::new(DBusGlobalState {
con,
name,
});
*dbus_state = Arc::downgrade(&arc);

arc
}
}

pub fn maintain_menubar(menubar: gtk::MenuBar) {
menubar.show_all();
glib::MainContext::default().spawn_local(async move {
let mut host = Host {
menubar,
items: std::collections::HashMap::new(),
};
let s = &dbus_state().await;
notifier_host::run_host_forever(&mut host, &s.con, &s.name).await.unwrap();
});
}
29 changes: 28 additions & 1 deletion crates/eww/src/widgets/widget_definitions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::{build_widget::BuilderArgs, circular_progressbar::*, run_command, tra
use crate::{
def_widget, enum_parse, error_handling_ctx,
util::{list_difference, unindent},
widgets::build_widget::build_gtk_widget,
widgets::{build_widget::build_gtk_widget, systray},
};
use anyhow::{anyhow, Context, Result};
use codespan_reporting::diagnostic::Severity;
Expand Down Expand Up @@ -80,6 +80,7 @@ pub const BUILTIN_WIDGET_NAMES: &[&str] = &[
WIDGET_NAME_REVEALER,
WIDGET_NAME_SCROLL,
WIDGET_NAME_OVERLAY,
WIDGET_NAME_SYSTRAY,
];

//// widget definitions
Expand Down Expand Up @@ -107,6 +108,7 @@ pub(super) fn widget_use_to_gtk_widget(bargs: &mut BuilderArgs) -> Result<gtk::W
WIDGET_NAME_REVEALER => build_gtk_revealer(bargs)?.upcast(),
WIDGET_NAME_SCROLL => build_gtk_scrolledwindow(bargs)?.upcast(),
WIDGET_NAME_OVERLAY => build_gtk_overlay(bargs)?.upcast(),
WIDGET_NAME_SYSTRAY => build_systray(bargs)?.upcast(),
_ => {
return Err(DiagError(gen_diagnostic! {
msg = format!("referenced unknown widget `{}`", bargs.widget_use.name),
Expand Down Expand Up @@ -1035,6 +1037,21 @@ fn build_graph(bargs: &mut BuilderArgs) -> Result<super::graph::Graph> {
Ok(w)
}

const WIDGET_NAME_SYSTRAY: &str = "systray";
/// @widget systray
/// @desc Tray for system notifier icons
fn build_systray(bargs: &mut BuilderArgs) -> Result<gtk::MenuBar> {
let gtk_widget = gtk::MenuBar::new();

def_widget!(bargs, _g, gtk_widget, {
// @prop pack-direction - how to arrange tray items
prop(pack_direction: as_string) { gtk_widget.set_pack_direction(parse_packdirection(&pack_direction)?); },
});

systray::maintain_menubar(gtk_widget.clone());
Ok(gtk_widget)
}

/// @var orientation - "vertical", "v", "horizontal", "h"
fn parse_orientation(o: &str) -> Result<gtk::Orientation> {
enum_parse! { "orientation", o,
Expand Down Expand Up @@ -1089,6 +1106,16 @@ fn parse_justification(j: &str) -> Result<gtk::Justification> {
}
}

/// @var packdirection - "right", "ltr", "left", "rtl", "down", "ttb", "up", "btt"
fn parse_packdirection(o: &str) -> Result<gtk::PackDirection> {
enum_parse! { "packdirection", o,
"right" | "ltr" => gtk::PackDirection::Ltr,
"left" | "rtl" => gtk::PackDirection::Rtl,
"down" | "ttb" => gtk::PackDirection::Ttb,
"up" | "btt" => gtk::PackDirection::Btt,
}
}

/// Connect a function to the first map event of a widget. After that first map, the handler will get disconnected.
fn connect_first_map<W: IsA<gtk::Widget>, F: Fn(&W) + 'static>(widget: &W, func: F) {
let signal_handler_id = std::rc::Rc::new(std::cell::RefCell::new(None));
Expand Down
17 changes: 17 additions & 0 deletions crates/notifier_host/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "notifier_host"
version = "0.1.0"
authors = ["elkowar <5300871+elkowar@users.noreply.github.com>"]
edition = "2021"
license = "MIT"
description = "SystemNotifierHost implementation"
repository = "https://github.com/elkowar/eww"
homepage = "https://github.com/elkowar/eww"

[dependencies]
gtk = { version = "0.15", features = [ "v3_22" ] }
log = "0.4"
thiserror = "1.0"
tokio = { version = "^1.18", features = ["full"] }
zbus = { version = "3.7.0", default-features = false, features = ["tokio"] }
dbusmenu-gtk3 = "0.1.0"
69 changes: 69 additions & 0 deletions crates/notifier_host/src/dbus/dbus_menu.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="com.canonical.dbusmenu">
<!-- Properties -->
<property name="Version" type="u" access="read" />
<property name="TextDirection" type="s" access="read" />
<property name="Status" type="s" access="read" />
<property name="IconThemePath" type="as" access="read" />

<!-- Functions -->
<method name="GetLayout">
<arg type="i" name="parentId" direction="in" />
<arg type="i" name="recursionDepth" direction="in" />
<arg type="as" name="propertyNames" direction="in" />
<arg type="u" name="revision" direction="out" />
<arg type="(ia{sv}av)" name="layout" direction="out" />
</method>

<method name="GetGroupProperties">
<arg type="ai" name="ids" direction="in" />
<arg type="as" name="propertyNames" direction="in" />
<arg type="a(ia{sv})" name="properties" direction="out" />
</method>

<method name="GetProperty">
<arg type="i" name="id" direction="in" />
<arg type="s" name="name" direction="in" />
<arg type="v" name="value" direction="out" />
</method>

<method name="Event">
<arg type="i" name="id" direction="in" />
<arg type="s" name="eventId" direction="in" />
<arg type="v" name="data" direction="in" />
<arg type="u" name="timestamp" direction="in" />
</method>

<method name="EventGroup">
<arg type="a(isvu)" name="events" direction="in" />
<arg type="ai" name="idErrors" direction="out" />
</method>

<method name="AboutToShow">
<arg type="i" name="id" direction="in" />
<arg type="b" name="needUpdate" direction="out" />
</method>

<method name="AboutToShowGroup">
<arg type="ai" name="ids" direction="in" />
<arg type="ai" name="updatesNeeded" direction="out" />
<arg type="ai" name="idErrors" direction="out" />
</method>

<!-- Signals -->
<signal name="ItemsPropertiesUpdated">
<arg type="a(ia{sv})" name="updatedProps" direction="out" />
<arg type="a(ias)" name="removedProps" direction="out" />
</signal>
<signal name="LayoutUpdated">
<arg type="u" name="revision" direction="out" />
<arg type="i" name="parent" direction="out" />
</signal>
<signal name="ItemActivationRequested">
<arg type="i" name="id" direction="out" />
<arg type="u" name="timestamp" direction="out" />
</signal>
</interface>
</node>
Loading