systray: handle mouse click events
This commit is contained in:
parent
f80e093223
commit
361b8d1b66
5 changed files with 137 additions and 22 deletions
|
@ -7,6 +7,7 @@ use crate::{
|
||||||
paths::EwwPaths,
|
paths::EwwPaths,
|
||||||
script_var_handler::ScriptVarHandlerHandle,
|
script_var_handler::ScriptVarHandlerHandle,
|
||||||
state::scope_graph::{ScopeGraph, ScopeIndex},
|
state::scope_graph::{ScopeGraph, ScopeIndex},
|
||||||
|
widgets::window::Window,
|
||||||
window_arguments::WindowArguments,
|
window_arguments::WindowArguments,
|
||||||
window_initiator::WindowInitiator,
|
window_initiator::WindowInitiator,
|
||||||
*,
|
*,
|
||||||
|
@ -92,7 +93,7 @@ pub struct EwwWindow {
|
||||||
pub instance_id: String,
|
pub instance_id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub scope_index: ScopeIndex,
|
pub scope_index: ScopeIndex,
|
||||||
pub gtk_window: gtk::Window,
|
pub gtk_window: Window,
|
||||||
pub destroy_event_handler_id: Option<glib::SignalHandlerId>,
|
pub destroy_event_handler_id: Option<glib::SignalHandlerId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,15 +525,21 @@ fn initialize_window<B: DisplayBackend>(
|
||||||
window_scope: ScopeIndex,
|
window_scope: ScopeIndex,
|
||||||
) -> Result<EwwWindow> {
|
) -> Result<EwwWindow> {
|
||||||
let monitor_geometry = monitor.geometry();
|
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()))?;
|
.with_context(|| format!("monitor {} is unavailable", window_init.monitor.clone().unwrap()))?;
|
||||||
|
|
||||||
window.set_title(&format!("Eww - {}", window_init.name));
|
window.set_title(&format!("Eww - {}", window_init.name));
|
||||||
window.set_position(gtk::WindowPosition::None);
|
window.set_position(gtk::WindowPosition::None);
|
||||||
window.set_gravity(gdk::Gravity::Center);
|
window.set_gravity(gdk::Gravity::Center);
|
||||||
|
|
||||||
if let Some(geometry) = window_init.geometry {
|
if let Some(actual_window_rect) = actual_window_rect {
|
||||||
let actual_window_rect = get_window_rectangle(geometry, monitor_geometry);
|
|
||||||
window.set_size_request(actual_window_rect.width(), actual_window_rect.height());
|
window.set_size_request(actual_window_rect.width(), actual_window_rect.height());
|
||||||
window.set_default_size(actual_window_rect.width(), actual_window_rect.height());
|
window.set_default_size(actual_window_rect.width(), actual_window_rect.height());
|
||||||
}
|
}
|
||||||
|
@ -575,11 +582,7 @@ fn initialize_window<B: DisplayBackend>(
|
||||||
|
|
||||||
/// Apply the provided window-positioning rules to the window.
|
/// Apply the provided window-positioning rules to the window.
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
fn apply_window_position(
|
fn apply_window_position(mut window_geometry: WindowGeometry, monitor_geometry: gdk::Rectangle, window: &Window) -> Result<()> {
|
||||||
mut window_geometry: WindowGeometry,
|
|
||||||
monitor_geometry: gdk::Rectangle,
|
|
||||||
window: >k::Window,
|
|
||||||
) -> Result<()> {
|
|
||||||
let gdk_window = window.window().context("Failed to get gdk window from gtk window")?;
|
let gdk_window = window.window().context("Failed to get gdk window from gtk window")?;
|
||||||
window_geometry.size = Coords::from_pixels(window.size());
|
window_geometry.size = Coords::from_pixels(window.size());
|
||||||
let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);
|
let actual_window_rect = get_window_rectangle(window_geometry, monitor_geometry);
|
||||||
|
@ -593,7 +596,7 @@ fn apply_window_position(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_screen_changed(window: >k::Window, _old_screen: Option<&gdk::Screen>) {
|
fn on_screen_changed(window: &Window, _old_screen: Option<&gdk::Screen>) {
|
||||||
let visual = gtk::prelude::GtkWindowExt::screen(window)
|
let visual = gtk::prelude::GtkWindowExt::screen(window)
|
||||||
.and_then(|screen| screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual()));
|
.and_then(|screen| screen.rgba_visual().filter(|_| screen.is_composited()).or_else(|| screen.system_visual()));
|
||||||
window.set_visual(visual.as_ref());
|
window.set_visual(visual.as_ref());
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::window_initiator::WindowInitiator;
|
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
pub use platform_wayland::WaylandBackend;
|
pub use platform_wayland::WaylandBackend;
|
||||||
|
@ -9,7 +9,7 @@ pub use platform_x11::{set_xprops, X11Backend};
|
||||||
pub trait DisplayBackend: Send + Sync + 'static {
|
pub trait DisplayBackend: Send + Sync + 'static {
|
||||||
const IS_X11: bool;
|
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;
|
pub struct NoBackend;
|
||||||
|
@ -17,14 +17,14 @@ pub struct NoBackend;
|
||||||
impl DisplayBackend for NoBackend {
|
impl DisplayBackend for NoBackend {
|
||||||
const IS_X11: bool = false;
|
const IS_X11: bool = false;
|
||||||
|
|
||||||
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> {
|
||||||
Some(gtk::Window::new(gtk::WindowType::Toplevel))
|
Some(Window::new(gtk::WindowType::Toplevel, x, y))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
mod platform_wayland {
|
mod platform_wayland {
|
||||||
use crate::window_initiator::WindowInitiator;
|
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};
|
use yuck::config::{window_definition::WindowStacking, window_geometry::AnchorAlignment};
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ mod platform_wayland {
|
||||||
impl DisplayBackend for WaylandBackend {
|
impl DisplayBackend for WaylandBackend {
|
||||||
const IS_X11: bool = false;
|
const IS_X11: bool = false;
|
||||||
|
|
||||||
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 = gtk::Window::new(gtk::WindowType::Toplevel);
|
let window = Window::new(gtk::WindowType::Toplevel, x, y);
|
||||||
// Initialising a layer shell surface
|
// Initialising a layer shell surface
|
||||||
gtk_layer_shell::init_for_window(&window);
|
gtk_layer_shell::init_for_window(&window);
|
||||||
// Sets the monitor where the surface is shown
|
// Sets the monitor where the surface is shown
|
||||||
|
@ -112,7 +112,7 @@ mod platform_wayland {
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
mod platform_x11 {
|
mod platform_x11 {
|
||||||
use crate::window_initiator::WindowInitiator;
|
use crate::{widgets::window::Window, window_initiator::WindowInitiator};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use gdk::Monitor;
|
use gdk::Monitor;
|
||||||
use gtk::{self, prelude::*};
|
use gtk::{self, prelude::*};
|
||||||
|
@ -135,10 +135,10 @@ mod platform_x11 {
|
||||||
impl DisplayBackend for X11Backend {
|
impl DisplayBackend for X11Backend {
|
||||||
const IS_X11: bool = true;
|
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 =
|
let window_type =
|
||||||
if window_init.backend_options.x11.wm_ignore { gtk::WindowType::Popup } else { gtk::WindowType::Toplevel };
|
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_resizable(window_init.resizable);
|
||||||
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
|
window.set_keep_above(window_init.stacking == WindowStacking::Foreground);
|
||||||
window.set_keep_below(window_init.stacking == WindowStacking::Background);
|
window.set_keep_below(window_init.stacking == WindowStacking::Background);
|
||||||
|
@ -151,7 +151,7 @@ mod platform_x11 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_xprops(window: >k::Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
|
pub fn set_xprops(window: &Window, monitor: Monitor, window_init: &WindowInitiator) -> Result<()> {
|
||||||
let backend = X11BackendConnection::new()?;
|
let backend = X11BackendConnection::new()?;
|
||||||
backend.set_xprops_for(window, monitor, window_init)?;
|
backend.set_xprops_for(window, monitor, window_init)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -171,7 +171,7 @@ mod platform_x11 {
|
||||||
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
|
Ok(X11BackendConnection { conn, root_window: screen.root, atoms })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_xprops_for(&self, window: >k::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 monitor_rect = monitor.geometry();
|
||||||
let scale_factor = monitor.scale_factor() as u32;
|
let scale_factor = monitor.scale_factor() as u32;
|
||||||
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
|
let gdk_window = window.window().context("Couldn't get gdk window from gtk window")?;
|
||||||
|
|
|
@ -7,6 +7,7 @@ pub mod graph;
|
||||||
mod systray;
|
mod systray;
|
||||||
pub mod transform;
|
pub mod transform;
|
||||||
pub mod widget_definitions;
|
pub mod widget_definitions;
|
||||||
|
pub mod window;
|
||||||
|
|
||||||
/// Run a command that was provided as an attribute.
|
/// 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.
|
/// This command may use placeholders which will be replaced by the values of the arguments given.
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use crate::widgets::window::Window;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*};
|
use gtk::{cairo::Surface, gdk::ffi::gdk_cairo_surface_create_from_pixbuf, prelude::*};
|
||||||
use notifier_host;
|
use notifier_host;
|
||||||
|
use std::{future::Future, rc::Rc};
|
||||||
|
|
||||||
// DBus state shared between systray instances, to avoid creating too many connections etc.
|
// DBus state shared between systray instances, to avoid creating too many connections etc.
|
||||||
struct DBusSession {
|
struct DBusSession {
|
||||||
|
@ -23,6 +25,11 @@ async fn dbus_session() -> zbus::Result<&'static DBusSession> {
|
||||||
.await
|
.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 {
|
pub struct Props {
|
||||||
icon_size_tx: tokio::sync::watch::Sender<i32>,
|
icon_size_tx: tokio::sync::watch::Sender<i32>,
|
||||||
}
|
}
|
||||||
|
@ -156,6 +163,46 @@ impl Item {
|
||||||
let scale = icon.scale_factor();
|
let scale = icon.scale_factor();
|
||||||
load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await;
|
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
|
||||||
|
);
|
||||||
|
|
||||||
|
match (evt.button(), item_is_menu) {
|
||||||
|
(gdk::BUTTON_PRIMARY, false) => {
|
||||||
|
if let Err(e) = run_async_task(async { item.sni.activate(x, y).await }) {
|
||||||
|
log::error!("failed to send activate event: {}", e);
|
||||||
|
if !have_item_is_menu {
|
||||||
|
// 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.
|
||||||
|
return gtk::Inhibit(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(gdk::BUTTON_MIDDLE, _) => {
|
||||||
|
if let Err(e) = run_async_task(async { item.sni.secondary_activate(x, y).await }) {
|
||||||
|
log::error!("failed to send secondary activate event: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return gtk::Inhibit(false),
|
||||||
|
}
|
||||||
|
gtk::Inhibit(true)
|
||||||
|
}));
|
||||||
|
|
||||||
// updates
|
// updates
|
||||||
let mut status_updates = item.sni.receive_new_status().await?;
|
let mut status_updates = item.sni.receive_new_status().await?;
|
||||||
let mut title_updates = item.sni.receive_new_title().await?;
|
let mut title_updates = item.sni.receive_new_title().await?;
|
||||||
|
|
64
crates/eww/src/widgets/window.rs
Normal file
64
crates/eww/src/widgets/window.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use glib::{object_subclass, wrapper};
|
||||||
|
use glib_macros::Properties;
|
||||||
|
use gtk::{prelude::*, subclass::prelude::*};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
wrapper! {
|
||||||
|
pub struct Window(ObjectSubclass<WindowPriv>)
|
||||||
|
@extends gtk::Window, gtk::Bin, gtk::Container, gtk::Widget, @implements gtk::Buildable;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties)]
|
||||||
|
#[properties(wrapper_type = Window)]
|
||||||
|
pub struct WindowPriv {
|
||||||
|
#[property(get, name = "x", nick = "X", blurb = "Global x coordinate", default = 0)]
|
||||||
|
x: RefCell<i32>,
|
||||||
|
|
||||||
|
#[property(get, name = "y", nick = "Y", blurb = "Global y coordinate", default = 0)]
|
||||||
|
y: RefCell<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should match the default values from the ParamSpecs
|
||||||
|
impl Default for WindowPriv {
|
||||||
|
fn default() -> Self {
|
||||||
|
WindowPriv { x: RefCell::new(0), y: RefCell::new(0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[object_subclass]
|
||||||
|
impl ObjectSubclass for WindowPriv {
|
||||||
|
type ParentType = gtk::Window;
|
||||||
|
type Type = Window;
|
||||||
|
|
||||||
|
const NAME: &'static str = "WindowEww";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Window {
|
||||||
|
fn default() -> Self {
|
||||||
|
glib::Object::new::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Window {
|
||||||
|
pub fn new(type_: gtk::WindowType, x_: i32, y_: i32) -> Self {
|
||||||
|
let w: Self = glib::Object::builder().property("type", type_).build();
|
||||||
|
let priv_ = w.imp();
|
||||||
|
priv_.x.replace(x_);
|
||||||
|
priv_.y.replace(y_);
|
||||||
|
w
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for WindowPriv {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
Self::derived_properties()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
self.derived_property(id, pspec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WindowImpl for WindowPriv {}
|
||||||
|
impl BinImpl for WindowPriv {}
|
||||||
|
impl ContainerImpl for WindowPriv {}
|
||||||
|
impl WidgetImpl for WindowPriv {}
|
Loading…
Add table
Reference in a new issue