systray: handle mouse click events

This commit is contained in:
MoetaYuko 2023-12-27 12:32:32 +08:00 committed by ElKowar
parent f80e093223
commit 361b8d1b66
5 changed files with 137 additions and 22 deletions

View File

@ -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,
*,
@ -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>,
}
@ -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());
}
@ -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);
@ -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());

View File

@ -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;
@ -9,7 +9,7 @@ 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;
@ -17,14 +17,14 @@ 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};
@ -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
@ -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::*};
@ -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);
@ -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(())
@ -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")?;

View File

@ -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.

View File

@ -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::{future::Future, rc::Rc};
// DBus state shared between systray instances, to avoid creating too many connections etc.
struct DBusSession {
@ -23,6 +25,11 @@ 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>,
}
@ -156,6 +163,46 @@ 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
);
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
let mut status_updates = item.sni.receive_new_status().await?;
let mut title_updates = item.sni.receive_new_title().await?;

View 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 {}