app-list: Add xdg-activation

This commit is contained in:
Victoria Brekenfeld 2023-11-06 16:46:44 +01:00 committed by Ashley Wulber
parent e69396dac0
commit 99da3eda4a
4 changed files with 218 additions and 115 deletions

View file

@ -2,9 +2,11 @@ use crate::config;
use crate::config::AppListConfig;
use crate::config::APP_ID;
use crate::fl;
use crate::toplevel_subscription::toplevel_subscription;
use crate::toplevel_subscription::ToplevelRequest;
use crate::toplevel_subscription::ToplevelUpdate;
use crate::wayland_subscription::wayland_subscription;
use crate::wayland_subscription::ToplevelRequest;
use crate::wayland_subscription::ToplevelUpdate;
use crate::wayland_subscription::WaylandRequest;
use crate::wayland_subscription::WaylandUpdate;
use cctk::sctk::reexports::calloop::channel::Sender;
use cctk::toplevel_info::ToplevelInfo;
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
@ -207,7 +209,7 @@ struct CosmicAppList {
favorite_list: Vec<DockItem>,
dnd_source: Option<(window::Id, DockItem, DndAction)>,
config: AppListConfig,
toplevel_sender: Option<Sender<ToplevelRequest>>,
wayland_sender: Option<Sender<WaylandRequest>>,
seat: Option<WlSeat>,
rectangle_tracker: Option<RectangleTracker<u32>>,
rectangles: HashMap<u32, iced::Rectangle>,
@ -218,7 +220,7 @@ struct CosmicAppList {
// TODO DnD after sctk merges DnD
#[derive(Debug, Clone)]
enum Message {
Toplevel(ToplevelUpdate),
Wayland(WaylandUpdate),
Favorite(String),
UnFavorite(String),
Popup(String),
@ -490,8 +492,8 @@ impl cosmic::Application for CosmicAppList {
if let Some(p) = self.popup.take() {
return destroy_popup(p.0);
}
if let Some(tx) = self.toplevel_sender.as_ref() {
let _ = tx.send(ToplevelRequest::Activate(handle));
if let Some(tx) = self.wayland_sender.as_ref() {
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Activate(handle)));
}
}
Message::Quit(id) => {
@ -502,8 +504,10 @@ impl cosmic::Application for CosmicAppList {
.find(|t| t.desktop_info.id == id)
{
for (handle, _) in &toplevel_group.toplevels {
if let Some(tx) = self.toplevel_sender.as_ref() {
let _ = tx.send(ToplevelRequest::Quit(handle.clone()));
if let Some(tx) = self.wayland_sender.as_ref() {
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Quit(
handle.clone(),
)));
}
}
}
@ -695,37 +699,12 @@ impl cosmic::Application for CosmicAppList {
}
return finish_dnd();
}
Message::Toplevel(event) => {
Message::Wayland(event) => {
match event {
ToplevelUpdate::AddToplevel(handle, info) => {
if info.app_id.is_empty() {
return Command::none();
}
if let Some(t) = self
.active_list
.iter_mut()
.chain(self.favorite_list.iter_mut())
.find(|DockItem { desktop_info, .. }| {
desktop_info.id == info.app_id
|| desktop_info.wm_class.as_ref() == Some(&info.app_id)
})
{
t.toplevels.push((handle, info));
} else {
let desktop_info =
desktop_info_for_app_ids(vec![info.app_id.clone()]).remove(0);
self.item_ctr += 1;
self.active_list.push(DockItem {
id: self.item_ctr,
toplevels: vec![(handle, info)],
desktop_info,
});
}
WaylandUpdate::Init(tx) => {
self.wayland_sender.replace(tx);
}
ToplevelUpdate::Init(tx) => {
self.toplevel_sender.replace(tx);
}
ToplevelUpdate::Finished => {
WaylandUpdate::Finished => {
for t in &mut self.favorite_list {
t.toplevels.clear();
}
@ -748,33 +727,78 @@ impl cosmic::Application for CosmicAppList {
)
.map(cosmic::app::message::app);
}
ToplevelUpdate::RemoveToplevel(handle) => {
for t in self
.active_list
.iter_mut()
.chain(self.favorite_list.iter_mut())
{
t.toplevels.retain(|(t_handle, _)| t_handle != &handle);
WaylandUpdate::Toplevel(event) => match event {
ToplevelUpdate::AddToplevel(handle, info) => {
if info.app_id.is_empty() {
return Command::none();
}
if let Some(t) = self
.active_list
.iter_mut()
.chain(self.favorite_list.iter_mut())
.find(|DockItem { desktop_info, .. }| {
desktop_info.id == info.app_id
|| desktop_info.wm_class.as_ref() == Some(&info.app_id)
})
{
t.toplevels.push((handle, info));
} else {
let desktop_info =
desktop_info_for_app_ids(vec![info.app_id.clone()]).remove(0);
self.item_ctr += 1;
self.active_list.push(DockItem {
id: self.item_ctr,
toplevels: vec![(handle, info)],
desktop_info,
});
}
}
self.active_list.retain(|t| !t.toplevels.is_empty());
}
ToplevelUpdate::UpdateToplevel(handle, info) => {
// TODO probably want to make sure it is removed
if info.app_id.is_empty() {
return Command::none();
ToplevelUpdate::RemoveToplevel(handle) => {
for t in self
.active_list
.iter_mut()
.chain(self.favorite_list.iter_mut())
{
t.toplevels.retain(|(t_handle, _)| t_handle != &handle);
}
self.active_list.retain(|t| !t.toplevels.is_empty());
}
'toplevel_loop: for toplevel_list in self
.active_list
.iter_mut()
.chain(self.favorite_list.iter_mut())
{
for (t_handle, t_info) in &mut toplevel_list.toplevels {
if &handle == t_handle {
*t_info = info;
break 'toplevel_loop;
ToplevelUpdate::UpdateToplevel(handle, info) => {
// TODO probably want to make sure it is removed
if info.app_id.is_empty() {
return Command::none();
}
'toplevel_loop: for toplevel_list in self
.active_list
.iter_mut()
.chain(self.favorite_list.iter_mut())
{
for (t_handle, t_info) in &mut toplevel_list.toplevels {
if &handle == t_handle {
*t_info = info;
break 'toplevel_loop;
}
}
}
}
},
WaylandUpdate::ActivationToken { token, exec } => {
let mut exec = shlex::Shlex::new(&exec);
let mut cmd = match exec.next() {
Some(cmd) if !cmd.contains('=') => tokio::process::Command::new(cmd),
_ => return Command::none(),
};
for arg in exec {
// TODO handle "%" args here if necessary?
if !arg.starts_with('%') {
cmd.arg(arg);
}
}
if let Some(token) = token {
cmd.env("XDG_ACTIVATION_TOKEN", &token);
cmd.env("DESKTOP_STARTUP_ID", &token);
}
let _ = cmd.spawn();
}
}
}
@ -784,19 +808,13 @@ impl cosmic::Application for CosmicAppList {
Message::RemovedSeat(_) => {
self.seat.take();
}
Message::Exec(exec_str) => {
let mut exec = shlex::Shlex::new(&exec_str);
let mut cmd = match exec.next() {
Some(cmd) if !cmd.contains('=') => tokio::process::Command::new(cmd),
_ => return Command::none(),
};
for arg in exec {
// TODO handle "%" args here if necessary?
if !arg.starts_with('%') {
cmd.arg(arg);
}
Message::Exec(exec) => {
if let Some(tx) = self.wayland_sender.as_ref() {
let _ = tx.send(WaylandRequest::TokenRequest {
app_id: Self::APP_ID.to_string(),
exec,
});
}
let _ = cmd.spawn();
}
Message::Rectangle(u) => match u {
RectangleUpdate::Rectangle(r) => {
@ -1080,7 +1098,7 @@ impl cosmic::Application for CosmicAppList {
fn subscription(&self) -> Subscription<Message> {
Subscription::batch(vec![
toplevel_subscription(self.subscription_ctr).map(Message::Toplevel),
wayland_subscription(self.subscription_ctr).map(Message::Wayland),
events_with(|e, _| match e {
cosmic::iced_runtime::core::Event::PlatformSpecific(
event::PlatformSpecific::Wayland(event::wayland::Event::Seat(e, seat)),

View file

@ -2,8 +2,8 @@
mod app;
mod config;
mod localize;
mod toplevel_handler;
mod toplevel_subscription;
mod wayland_handler;
mod wayland_subscription;
use log::info;

View file

@ -1,28 +1,36 @@
use crate::toplevel_subscription::{ToplevelRequest, ToplevelUpdate};
use crate::wayland_subscription::{ToplevelRequest, ToplevelUpdate, WaylandRequest, WaylandUpdate};
use cctk::{
sctk::{
self,
reexports::{
calloop, calloop_wayland_source::WaylandSource, client::protocol::wl_seat::WlSeat,
},
activation::{RequestData, RequestDataExt},
reexports::{calloop, calloop_wayland_source::WaylandSource},
seat::{SeatHandler, SeatState},
},
toplevel_info::{ToplevelInfoHandler, ToplevelInfoState},
toplevel_management::{ToplevelManagerHandler, ToplevelManagerState},
wayland_client::{self, WEnum},
wayland_client::{
self,
protocol::{wl_seat::WlSeat, wl_surface::WlSurface},
WEnum,
},
};
use cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
};
use futures::channel::mpsc::UnboundedSender;
use sctk::registry::{ProvidesRegistryState, RegistryState};
use sctk::{
activation::{ActivationHandler, ActivationState},
registry::{ProvidesRegistryState, RegistryState},
};
use wayland_client::{globals::registry_queue_init, Connection, QueueHandle};
struct AppData {
exit: bool,
tx: UnboundedSender<ToplevelUpdate>,
tx: UnboundedSender<WaylandUpdate>,
queue_handle: QueueHandle<Self>,
registry_state: RegistryState,
activation_state: Option<ActivationState>,
toplevel_info_state: ToplevelInfoState,
toplevel_manager_state: ToplevelManagerState,
seat_state: SeatState,
@ -36,6 +44,35 @@ impl ProvidesRegistryState for AppData {
sctk::registry_handlers!();
}
struct ExecRequestData {
data: RequestData,
exec: String,
}
impl RequestDataExt for ExecRequestData {
fn app_id(&self) -> Option<&str> {
self.data.app_id()
}
fn seat_and_serial(&self) -> Option<(&WlSeat, u32)> {
self.data.seat_and_serial()
}
fn surface(&self) -> Option<&WlSurface> {
self.data.surface()
}
}
impl ActivationHandler for AppData {
type RequestData = ExecRequestData;
fn new_token(&mut self, token: String, data: &ExecRequestData) {
let _ = self.tx.unbounded_send(WaylandUpdate::ActivationToken {
token: Some(token),
exec: data.exec.clone(),
});
}
}
impl SeatHandler for AppData {
fn seat_state(&mut self) -> &mut sctk::seat::SeatState {
&mut self.seat_state
@ -93,7 +130,10 @@ impl ToplevelInfoHandler for AppData {
if let Some(info) = self.toplevel_info_state.info(toplevel) {
let _ = self
.tx
.unbounded_send(ToplevelUpdate::AddToplevel(toplevel.clone(), info.clone()));
.unbounded_send(WaylandUpdate::Toplevel(ToplevelUpdate::AddToplevel(
toplevel.clone(),
info.clone(),
)));
}
}
@ -104,10 +144,12 @@ impl ToplevelInfoHandler for AppData {
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
) {
if let Some(info) = self.toplevel_info_state.info(toplevel) {
let _ = self.tx.unbounded_send(ToplevelUpdate::UpdateToplevel(
toplevel.clone(),
info.clone(),
));
let _ =
self.tx
.unbounded_send(WaylandUpdate::Toplevel(ToplevelUpdate::UpdateToplevel(
toplevel.clone(),
info.clone(),
)));
}
}
@ -119,16 +161,19 @@ impl ToplevelInfoHandler for AppData {
) {
let _ = self
.tx
.unbounded_send(ToplevelUpdate::RemoveToplevel(toplevel.clone()));
.unbounded_send(WaylandUpdate::Toplevel(ToplevelUpdate::RemoveToplevel(
toplevel.clone(),
)));
}
}
pub(crate) fn toplevel_handler(
tx: UnboundedSender<ToplevelUpdate>,
rx: calloop::channel::Channel<ToplevelRequest>,
pub(crate) fn wayland_handler(
tx: UnboundedSender<WaylandUpdate>,
rx: calloop::channel::Channel<WaylandRequest>,
) {
let conn = Connection::connect_to_env().unwrap();
let (globals, event_queue) = registry_queue_init(&conn).unwrap();
let mut event_loop = calloop::EventLoop::<AppData>::try_new().unwrap();
let qh = event_queue.handle();
let wayland_source = WaylandSource::new(conn, event_queue);
@ -140,18 +185,43 @@ pub(crate) fn toplevel_handler(
if handle
.insert_source(rx, |event, _, state| match event {
calloop::channel::Event::Msg(req) => match req {
ToplevelRequest::Activate(handle) => {
if let Some(seat) = state.seat_state.seats().next() {
let manager = &state.toplevel_manager_state.manager;
manager.activate(&handle, &seat);
WaylandRequest::Toplevel(req) => match req {
ToplevelRequest::Activate(handle) => {
if let Some(seat) = state.seat_state.seats().next() {
let manager = &state.toplevel_manager_state.manager;
manager.activate(&handle, &seat);
}
}
ToplevelRequest::Quit(handle) => {
let manager = &state.toplevel_manager_state.manager;
manager.close(&handle);
}
ToplevelRequest::Exit => {
state.exit = true;
}
},
WaylandRequest::TokenRequest { app_id, exec } => {
if let Some(activation_state) = state.activation_state.as_ref() {
activation_state.request_token_with_data(
&state.queue_handle,
ExecRequestData {
data: RequestData {
app_id: Some(app_id),
seat_and_serial: state
.seat_state
.seats()
.next()
.map(|seat| (seat, 0)),
surface: None,
},
exec,
},
);
} else {
let _ = state
.tx
.unbounded_send(WaylandUpdate::ActivationToken { token: None, exec });
}
}
ToplevelRequest::Quit(handle) => {
let manager = &state.toplevel_manager_state.manager;
manager.close(&handle);
}
ToplevelRequest::Exit => {
state.exit = true;
}
},
calloop::channel::Event::Closed => {
@ -166,6 +236,8 @@ pub(crate) fn toplevel_handler(
let mut app_data = AppData {
exit: false,
tx,
queue_handle: qh.clone(),
activation_state: ActivationState::bind::<AppData>(&globals, &qh).ok(),
seat_state: SeatState::new(&globals, &qh),
toplevel_info_state: ToplevelInfoState::new(&registry_state, &qh),
toplevel_manager_state: ToplevelManagerState::new(&registry_state, &qh),
@ -180,6 +252,7 @@ pub(crate) fn toplevel_handler(
}
}
sctk::delegate_activation!(AppData, ExecRequestData);
sctk::delegate_seat!(AppData);
sctk::delegate_registry!(AppData);
cctk::delegate_toplevel_info!(AppData);

View file

@ -2,7 +2,7 @@
//!
//! This code was generated by `zbus-xmlgen` `2.0.1` from DBus introspection data.
//! Source: `Interface '/org/freedesktop/UPower/KbdBacklight' from service 'org.freedesktop.UPower' on system bus`.
use cctk::sctk::reexports::{calloop, client::protocol::wl_seat::WlSeat};
use cctk::sctk::reexports::calloop;
use cctk::toplevel_info::ToplevelInfo;
use cosmic::iced;
use cosmic::iced::subscription;
@ -13,11 +13,11 @@ use futures::{
};
use std::{fmt::Debug, hash::Hash, thread::JoinHandle};
use crate::toplevel_handler::toplevel_handler;
use crate::wayland_handler::wayland_handler;
pub fn toplevel_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
pub fn wayland_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
id: I,
) -> iced::Subscription<ToplevelUpdate> {
) -> iced::Subscription<WaylandUpdate> {
subscription::channel(id, 50, move |mut output| async move {
let mut state = State::Ready;
@ -30,8 +30,8 @@ pub fn toplevel_subscription<I: 'static + Hash + Copy + Send + Sync + Debug>(
pub enum State {
Ready,
Waiting(
UnboundedReceiver<ToplevelUpdate>,
calloop::channel::Sender<ToplevelRequest>,
UnboundedReceiver<WaylandUpdate>,
calloop::channel::Sender<WaylandRequest>,
JoinHandle<()>,
),
Finished,
@ -39,22 +39,22 @@ pub enum State {
async fn start_listening(
state: State,
output: &mut futures::channel::mpsc::Sender<ToplevelUpdate>,
output: &mut futures::channel::mpsc::Sender<WaylandUpdate>,
) -> State {
match state {
State::Ready => {
let (calloop_tx, calloop_rx) = calloop::channel::channel();
let (toplevel_tx, toplevel_rx) = unbounded();
let handle = std::thread::spawn(move || {
toplevel_handler(toplevel_tx, calloop_rx);
wayland_handler(toplevel_tx, calloop_rx);
});
let tx = calloop_tx.clone();
_ = output.send(ToplevelUpdate::Init(tx)).await;
_ = output.send(WaylandUpdate::Init(tx)).await;
State::Waiting(toplevel_rx, calloop_tx, handle)
}
State::Waiting(mut rx, tx, handle) => {
if handle.is_finished() {
_ = output.send(ToplevelUpdate::Finished).await;
_ = output.send(WaylandUpdate::Finished).await;
return State::Finished;
}
match rx.next().await {
@ -63,7 +63,7 @@ async fn start_listening(
State::Waiting(rx, tx, handle)
}
None => {
_ = output.send(ToplevelUpdate::Finished).await;
_ = output.send(WaylandUpdate::Finished).await;
return State::Finished;
}
}
@ -73,12 +73,24 @@ async fn start_listening(
}
#[derive(Clone, Debug)]
pub enum ToplevelUpdate {
pub enum WaylandUpdate {
Init(calloop::channel::Sender<WaylandRequest>),
Finished,
Toplevel(ToplevelUpdate),
ActivationToken { token: Option<String>, exec: String },
}
#[derive(Clone, Debug)]
pub enum ToplevelUpdate {
AddToplevel(ZcosmicToplevelHandleV1, ToplevelInfo),
UpdateToplevel(ZcosmicToplevelHandleV1, ToplevelInfo),
RemoveToplevel(ZcosmicToplevelHandleV1),
Init(calloop::channel::Sender<ToplevelRequest>),
}
#[derive(Clone, Debug)]
pub enum WaylandRequest {
Toplevel(ToplevelRequest),
TokenRequest { app_id: String, exec: String },
}
#[derive(Debug, Clone)]