feat(status-area): provide activation token on menu item activation

This commit is contained in:
Ashley Wulber 2025-08-05 12:14:53 -04:00 committed by Ashley Wulber
parent 1d3d869f4a
commit a5d813dc9b
3 changed files with 72 additions and 5 deletions

View file

@ -4,6 +4,8 @@
use cosmic::{ use cosmic::{
app, app,
applet::cosmic_panel_config::PanelAnchor, applet::cosmic_panel_config::PanelAnchor,
applet::token::subscription::{activation_token_subscription, TokenRequest, TokenUpdate},
cctk::sctk::reexports::calloop,
iced::{ iced::{
self, self,
overlay::menu, overlay::menu,
@ -15,6 +17,7 @@ use cosmic::{
Element, Task, Element, Task,
}; };
use std::collections::BTreeMap; use std::collections::BTreeMap;
use zbus::connection::socket::channel;
use crate::{components::status_menu, subscriptions::status_notifier_watcher}; use crate::{components::status_menu, subscriptions::status_notifier_watcher};
@ -29,10 +32,11 @@ pub enum Msg {
Surface(surface::Action), Surface(surface::Action),
ToggleOverflow, ToggleOverflow,
HoveredOverflow, HoveredOverflow,
Token(TokenUpdate),
} }
#[derive(Default)] #[derive(Default)]
struct App { pub(crate) struct App {
core: app::Core, core: app::Core,
connection: Option<zbus::Connection>, connection: Option<zbus::Connection>,
menus: BTreeMap<usize, status_menu::State>, menus: BTreeMap<usize, status_menu::State>,
@ -40,6 +44,7 @@ struct App {
max_menu_id: usize, max_menu_id: usize,
popup: Option<window::Id>, popup: Option<window::Id>,
overflow_popup: Option<window::Id>, overflow_popup: Option<window::Id>,
token_tx: Option<calloop::channel::Sender<TokenRequest>>,
} }
impl App { impl App {
@ -167,7 +172,7 @@ impl cosmic::Application for App {
} }
Msg::StatusMenu((id, msg)) => match self.menus.get_mut(&id) { Msg::StatusMenu((id, msg)) => match self.menus.get_mut(&id) {
Some(state) => state Some(state) => state
.update(msg) .update(msg, id, self.token_tx.as_ref())
.map(move |msg| cosmic::action::app(Msg::StatusMenu((id, msg)))), .map(move |msg| cosmic::action::app(Msg::StatusMenu((id, msg)))),
None => Task::none(), None => Task::none(),
}, },
@ -267,6 +272,32 @@ impl cosmic::Application for App {
} }
Task::none() Task::none()
} }
Msg::Token(u) => match u {
TokenUpdate::Init(tx) => {
self.token_tx = Some(tx);
return Task::none();
}
TokenUpdate::Finished => {
self.token_tx = None;
return Task::none();
}
TokenUpdate::ActivationToken { token, exec: id } => {
if let Some(((state, id), token)) = str::parse(&id)
.ok()
.and_then(|id: usize| self.menus.get_mut(&id).map(|m| (m, id)))
.zip(token)
{
return state
.update(
status_menu::Msg::ClickToken(token),
id,
self.token_tx.as_ref(),
)
.map(move |msg| cosmic::action::app(Msg::StatusMenu((id, msg))));
}
return Task::none();
}
},
Msg::Hovered(id) => { Msg::Hovered(id) => {
let mut cmds = Vec::new(); let mut cmds = Vec::new();
if let Some(old_id) = self.open_menu.take() { if let Some(old_id) = self.open_menu.take() {
@ -417,6 +448,7 @@ impl cosmic::Application for App {
for (id, menu) in self.menus.iter() { for (id, menu) in self.menus.iter() {
subscriptions.push(menu.subscription().with(*id).map(Msg::StatusMenu)); subscriptions.push(menu.subscription().with(*id).map(Msg::StatusMenu));
} }
subscriptions.push(activation_token_subscription(0).map(Msg::Token));
iced::Subscription::batch(subscriptions) iced::Subscription::batch(subscriptions)
} }

View file

@ -2,9 +2,14 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::{ use cosmic::{
applet::menu_button, applet::{
iced::{self, Padding}, menu_button,
token::{self, subscription::TokenRequest},
},
cctk::sctk::reexports::calloop,
iced,
widget::icon, widget::icon,
Application,
}; };
use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem}; use crate::subscriptions::status_notifier_item::{IconUpdate, Layout, StatusNotifierItem};
@ -14,6 +19,7 @@ pub enum Msg {
Layout(Result<Layout, String>), Layout(Result<Layout, String>),
Icon(IconUpdate), Icon(IconUpdate),
Click(i32, bool), Click(i32, bool),
ClickToken(String),
} }
pub struct State { pub struct State {
@ -23,6 +29,7 @@ pub struct State {
icon_name: String, icon_name: String,
// TODO handle icon with multiple sizes? // TODO handle icon with multiple sizes?
icon_pixmap: Option<icon::Handle>, icon_pixmap: Option<icon::Handle>,
click_event: Option<(i32, bool)>,
} }
impl State { impl State {
@ -34,12 +41,18 @@ impl State {
expanded: None, expanded: None,
icon_name: String::new(), icon_name: String::new(),
icon_pixmap: None, icon_pixmap: None,
click_event: None,
}, },
iced::Task::none(), iced::Task::none(),
) )
} }
pub fn update(&mut self, message: Msg) -> iced::Task<Msg> { pub fn update(
&mut self,
message: Msg,
menu_id: usize,
token_tx: Option<&calloop::channel::Sender<TokenRequest>>,
) -> iced::Task<Msg> {
match message { match message {
Msg::Layout(layout) => { Msg::Layout(layout) => {
match layout { match layout {
@ -78,8 +91,24 @@ impl State {
iced::Task::none() iced::Task::none()
} }
Msg::Click(id, is_submenu) => { Msg::Click(id, is_submenu) => {
if let Some(token_tx) = token_tx {
_ = token_tx.send(TokenRequest {
app_id: super::app::App::APP_ID.to_string(),
exec: menu_id.to_string(),
});
}
self.click_event = Some((id, is_submenu));
iced::Task::none()
}
Msg::ClickToken(token) => {
let Some((id, is_submenu)) = self.click_event else {
return iced::Task::none();
};
let menu_proxy = self.item.menu_proxy().clone(); let menu_proxy = self.item.menu_proxy().clone();
let item_proxy = self.item.item_proxy().clone();
tokio::spawn(async move { tokio::spawn(async move {
let _ = item_proxy.provide_xdg_activation_token(token).await;
let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await; let _ = menu_proxy.event(id, "clicked", &0.into(), 0).await;
}); });
if is_submenu { if is_submenu {

View file

@ -113,6 +113,10 @@ impl StatusNotifierItem {
pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> { pub fn menu_proxy(&self) -> &DBusMenuProxy<'static> {
&self.menu_proxy &self.menu_proxy
} }
pub fn item_proxy(&self) -> &StatusNotifierItemProxy<'static> {
&self.item_proxy
}
} }
async fn get_layout(menu_proxy: DBusMenuProxy<'static>) -> Result<Layout, String> { async fn get_layout(menu_proxy: DBusMenuProxy<'static>) -> Result<Layout, String> {
@ -136,6 +140,8 @@ pub trait StatusNotifierItem {
#[zbus(signal)] #[zbus(signal)]
fn new_icon(&self) -> zbus::Result<()>; fn new_icon(&self) -> zbus::Result<()>;
fn provide_xdg_activation_token(&self, token: String) -> zbus::Result<()>;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]