Skip to content

Commit

Permalink
Merge branch 'master' into flake
Browse files Browse the repository at this point in the history
  • Loading branch information
w-lfchen authored Apr 4, 2024
2 parents 8cb468f + 149727c commit ff0084e
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 60 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 13 additions & 10 deletions crates/eww/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
paths::EwwPaths,
script_var_handler::ScriptVarHandlerHandle,
state::scope_graph::{ScopeGraph, ScopeIndex},
widgets::window::Window,
window_arguments::WindowArguments,
window_initiator::WindowInitiator,
*,
Expand Down Expand Up @@ -92,7 +93,7 @@ pub struct EwwWindow {
pub instance_id: String,
pub name: String,
pub scope_index: ScopeIndex,
pub gtk_window: gtk::Window,
pub gtk_window: Window,
pub destroy_event_handler_id: Option<glib::SignalHandlerId>,
}

Expand Down Expand Up @@ -524,15 +525,21 @@ fn initialize_window<B: DisplayBackend>(
window_scope: ScopeIndex,
) -> Result<EwwWindow> {
let monitor_geometry = monitor.geometry();
let window = B::initialize_window(window_init, monitor_geometry)
let (actual_window_rect, x, y) = match window_init.geometry {
Some(geometry) => {
let rect = get_window_rectangle(geometry, monitor_geometry);
(Some(rect), rect.x(), rect.y())
}
_ => (None, 0, 0),
};
let window = B::initialize_window(window_init, monitor_geometry, x, y)
.with_context(|| format!("monitor {} is unavailable", window_init.monitor.clone().unwrap()))?;

window.set_title(&format!("Eww - {}", window_init.name));
window.set_position(gtk::WindowPosition::None);
window.set_gravity(gdk::Gravity::Center);

if let Some(geometry) = window_init.geometry {
let actual_window_rect = get_window_rectangle(geometry, monitor_geometry);
if let Some(actual_window_rect) = actual_window_rect {
window.set_size_request(actual_window_rect.width(), actual_window_rect.height());
window.set_default_size(actual_window_rect.width(), actual_window_rect.height());
}
Expand Down Expand Up @@ -575,11 +582,7 @@ fn initialize_window<B: DisplayBackend>(

/// Apply the provided window-positioning rules to the window.
#[cfg(feature = "x11")]
fn apply_window_position(
mut window_geometry: WindowGeometry,
monitor_geometry: gdk::Rectangle,
window: &gtk::Window,
) -> Result<()> {
fn apply_window_position(mut window_geometry: WindowGeometry, monitor_geometry: gdk::Rectangle, window: &Window) -> Result<()> {
let gdk_window = window.window().context("Failed to get gdk window from gtk window")?;
window_geometry.size = Coords::from_pixels(window.size());
let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);
Expand All @@ -593,7 +596,7 @@ fn apply_window_position(
Ok(())
}

fn on_screen_changed(window: &gtk::Window, _old_screen: Option<&gdk::Screen>) {
fn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {
let visual = gtk::prelude::GtkWindowExt::screen(window)
.and_then(|screen| screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual()));
window.set_visual(visual.as_ref());
Expand Down
24 changes: 12 additions & 12 deletions crates/eww/src/display_backend.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::window_initiator::WindowInitiator;
use crate::{widgets::window::Window, window_initiator::WindowInitiator};

#[cfg(feature = "wayland")]
pub use platform_wayland::WaylandBackend;
Expand All @@ -9,22 +9,22 @@ pub use platform_x11::{set_xprops, X11Backend};
pub trait DisplayBackend: Send + Sync + 'static {
const IS_X11: bool;

fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window>;
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window>;
}

pub struct NoBackend;

impl DisplayBackend for NoBackend {
const IS_X11: bool = false;

fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
Some(gtk::Window::new(gtk::WindowType::Toplevel))
fn initialize_window(_window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
Some(Window::new(gtk::WindowType::Toplevel, x, y))
}
}

#[cfg(feature = "wayland")]
mod platform_wayland {
use crate::window_initiator::WindowInitiator;
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use gtk::prelude::*;
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};

Expand All @@ -35,8 +35,8 @@ mod platform_wayland {
impl DisplayBackend for WaylandBackend {
const IS_X11: bool = false;

fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle) -> Option<gtk::Window> {
let window = gtk::Window::new(gtk::WindowType::Toplevel);
fn initialize_window(window_init: &WindowInitiator, monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
let window = Window::new(gtk::WindowType::Toplevel, x, y);
// Initialising a layer shell surface
gtk_layer_shell::init_for_window(&window);
// Sets the monitor where the surface is shown
Expand Down Expand Up @@ -112,7 +112,7 @@ mod platform_wayland {

#[cfg(feature = "x11")]
mod platform_x11 {
use crate::window_initiator::WindowInitiator;
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
use anyhow::{Context, Result};
use gdk::Monitor;
use gtk::{self, prelude::*};
Expand All @@ -135,10 +135,10 @@ mod platform_x11 {
impl DisplayBackend for X11Backend {
const IS_X11: bool = true;

fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle) -> Option<gtk::Window> {
fn initialize_window(window_init: &WindowInitiator, _monitor: gdk::Rectangle, x: i32, y: i32) -> Option<Window> {
let window_type =
if window_init.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel };
let window = gtk::Window::new(window_type);
let window = Window::new(window_type, x, y);
window.set_resizable(window_init.resizable);
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
window.set_keep_below(window_init.stacking == WindowStacking::Background);
Expand All @@ -151,7 +151,7 @@ mod platform_x11 {
}
}

pub fn set_xprops(window: &gtk::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
pub fn set_xprops(window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let backend = X11BackendConnection::new()?;
backend.set_xprops_for(window, monitor, window_init)?;
Ok(())
Expand All @@ -171,7 +171,7 @@ mod platform_x11 {
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
}

fn set_xprops_for(&self, window: &gtk::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
fn set_xprops_for(&self, window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
let monitor_rect = monitor.geometry();
let scale_factor = monitor.scale_factor() as u32;
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
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 @@ -7,6 +7,7 @@ pub mod graph;
mod systray;
pub mod transform;
pub mod widget_definitions;
pub mod window;

/// Run a command that was provided as an attribute.
/// This command may use placeholders which will be replaced by the values of the arguments given.
Expand Down
94 changes: 77 additions & 17 deletions crates/eww/src/widgets/systray.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::widgets::window::Window;
use futures::StreamExt;
use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*};
use notifier_host;
use std::{cell::RefCell, future::Future, rc::Rc};

// DBus state shared between systray instances, to avoid creating too many connections etc.
struct DBusSession {
Expand All @@ -23,14 +25,20 @@ async fn dbus_session() -> zbus::Result<&'static DBusSession> {
.await
}

fn run_async_task<F: Future>(f: F) -> F::Output {
let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().expect("Failed to initialize tokio runtime");
rt.block_on(f)
}

pub struct Props {
icon_size_tx: tokio::sync::watch::Sender<i32>,
pub prepend_new: Rc<RefCell<bool>>,
}

impl Props {
pub fn new() -> Self {
let (icon_size_tx, _) = tokio::sync::watch::channel(24);
Self { icon_size_tx }
Self { icon_size_tx, prepend_new: Rc::new(RefCell::new(false)) }
}

pub fn icon_size(&self, value: i32) {
Expand All @@ -46,14 +54,20 @@ impl Props {
}

struct Tray {
menubar: gtk::MenuBar,
container: gtk::Box,
items: std::collections::HashMap<String, Item>,

icon_size: tokio::sync::watch::Receiver<i32>,
prepend_new: Rc<RefCell<bool>>,
}

pub fn spawn_systray(menubar: &gtk::MenuBar, props: &Props) {
let mut systray = Tray { menubar: menubar.clone(), items: Default::default(), icon_size: props.icon_size_tx.subscribe() };
pub fn spawn_systray(container: &gtk::Box, props: &Props) {
let mut systray = Tray {
container: container.clone(),
items: Default::default(),
icon_size: props.icon_size_tx.subscribe(),
prepend_new: props.prepend_new.clone(),
};

let task = glib::MainContext::default().spawn_local(async move {
let s = match dbus_session().await {
Expand All @@ -64,29 +78,33 @@ pub fn spawn_systray(menubar: &gtk::MenuBar, props: &Props) {
}
};

systray.menubar.show();
systray.container.show();
let e = notifier_host::run_host(&mut systray, &s.snw).await;
log::error!("notifier host error: {}", e);
});

// stop the task when the widget is dropped
menubar.connect_destroy(move |_| {
container.connect_destroy(move |_| {
task.abort();
});
}

impl notifier_host::Host for Tray {
fn add_item(&mut self, id: &str, item: notifier_host::Item) {
let item = Item::new(id.to_owned(), item, self.icon_size.clone());
self.menubar.add(&item.widget);
if *self.prepend_new.borrow() {
self.container.pack_end(&item.widget, true, true, 0);
} else {
self.container.pack_start(&item.widget, true, true, 0);
}
if let Some(old_item) = self.items.insert(id.to_string(), item) {
self.menubar.remove(&old_item.widget);
self.container.remove(&old_item.widget);
}
}

fn remove_item(&mut self, id: &str) {
if let Some(item) = self.items.get(id) {
self.menubar.remove(&item.widget);
self.container.remove(&item.widget);
} else {
log::warn!("Tried to remove nonexistent item {:?} from systray", id);
}
Expand All @@ -96,7 +114,7 @@ impl notifier_host::Host for Tray {
/// Item represents a single icon being shown in the system tray.
struct Item {
/// Main widget representing this tray item.
widget: gtk::MenuItem,
widget: gtk::EventBox,

/// Async task to stop when this item gets removed.
task: Option<glib::JoinHandle<()>>,
Expand All @@ -112,7 +130,7 @@ impl Drop for Item {

impl Item {
fn new(id: String, item: notifier_host::Item, icon_size: tokio::sync::watch::Receiver<i32>) -> Self {
let widget = gtk::MenuItem::new();
let widget = gtk::EventBox::new();
let out_widget = widget.clone(); // copy so we can return it

let task = glib::MainContext::default().spawn_local(async move {
Expand All @@ -125,8 +143,8 @@ impl Item {
}

async fn maintain(
widget: gtk::MenuItem,
item: notifier_host::Item,
widget: gtk::EventBox,
mut item: notifier_host::Item,
mut icon_size: tokio::sync::watch::Receiver<i32>,
) -> zbus::Result<()> {
// init icon
Expand All @@ -135,9 +153,8 @@ impl Item {
icon.show();

// init menu
match item.menu().await {
Ok(m) => widget.set_submenu(Some(&m)),
Err(e) => log::warn!("failed to get menu: {}", e),
if let Err(e) = item.set_menu(&widget).await {
log::warn!("failed to set menu: {}", e);
}

// TODO this is a lot of code duplication unfortunately, i'm not really sure how to
Expand All @@ -156,9 +173,52 @@ impl Item {
let scale = icon.scale_factor();
load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await;

let item = Rc::new(item);
let window =
widget.toplevel().expect("Failed to obtain toplevel window").downcast::<Window>().expect("Failed to downcast window");
widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);
widget.connect_button_press_event(glib::clone!(@strong item => move |_, evt| {
let (x, y) = (evt.root().0 as i32 + window.x(), evt.root().1 as i32 + window.y());
let item_is_menu = run_async_task(async { item.sni.item_is_menu().await });
let have_item_is_menu = item_is_menu.is_ok();
let item_is_menu = item_is_menu.unwrap_or(false);
log::debug!(
"mouse click button={}, x={}, y={}, have_item_is_menu={}, item_is_menu={}",
evt.button(),
x,
y,
have_item_is_menu,
item_is_menu
);

let result = match (evt.button(), item_is_menu) {
(gdk::BUTTON_PRIMARY, false) => {
let result = run_async_task(async { item.sni.activate(x, y).await });
if result.is_err() && !have_item_is_menu {
log::debug!("fallback to context menu due to: {}", result.unwrap_err());
// Some applications are in fact menu-only (don't have Activate method)
// but don't report so through ItemIsMenu property. Fallback to menu if
// activate failed in this case.
run_async_task(async { item.popup_menu( evt, x, y).await })
} else {
result
}
}
(gdk::BUTTON_MIDDLE, _) => run_async_task(async { item.sni.secondary_activate(x, y).await }),
(gdk::BUTTON_SECONDARY, _) | (gdk::BUTTON_PRIMARY, true) => {
run_async_task(async { item.popup_menu( evt, x, y).await })
}
_ => Err(zbus::Error::Failure(format!("unknown button {}", evt.button()))),
};
if let Err(result) = result {
log::error!("failed to handle mouse click {}: {}", evt.button(), result);
}
gtk::Inhibit(true)
}));

// updates
let mut status_updates = item.sni.receive_new_status().await?;
let mut title_updates = item.sni.receive_new_status().await?;
let mut title_updates = item.sni.receive_new_title().await?;
let mut icon_updates = item.sni.receive_new_icon().await?;

loop {
Expand Down
Loading

0 comments on commit ff0084e

Please sign in to comment.