feat: app list click: two or more toplevel behavior
This commit is contained in:
parent
5b78a9f22e
commit
0629b18e00
5 changed files with 697 additions and 164 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -873,16 +873,20 @@ dependencies = [
|
|||
name = "cosmic-app-list"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cosmic-client-toolkit",
|
||||
"cosmic-protocols",
|
||||
"futures",
|
||||
"i18n-embed",
|
||||
"i18n-embed-fl",
|
||||
"image 0.25.0",
|
||||
"itertools",
|
||||
"libcosmic",
|
||||
"memmap2 0.9.4",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rust-embed",
|
||||
"rustix 0.38.32",
|
||||
"serde",
|
||||
"switcheroo-control",
|
||||
"tokio",
|
||||
|
|
|
|||
|
|
@ -5,16 +5,20 @@ edition = "2021"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
cctk.workspace = true
|
||||
cosmic-protocols.workspace = true
|
||||
futures.workspace = true
|
||||
i18n-embed.workspace = true
|
||||
i18n-embed-fl.workspace = true
|
||||
image = { version = "0.25.0", default-features = false }
|
||||
itertools = "0.12.1"
|
||||
libcosmic.workspace = true
|
||||
memmap2 = "0.9.4"
|
||||
once_cell = "1.19"
|
||||
rand = "0.8.5"
|
||||
rust-embed.workspace = true
|
||||
rustix.workspace = true
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||
tokio = { version = "1.36.0", features = ["sync", "rt", "rt-multi-thread", "macros", "process"] }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::fl;
|
|||
use crate::wayland_subscription::wayland_subscription;
|
||||
use crate::wayland_subscription::ToplevelRequest;
|
||||
use crate::wayland_subscription::ToplevelUpdate;
|
||||
use crate::wayland_subscription::WaylandImage;
|
||||
use crate::wayland_subscription::WaylandRequest;
|
||||
use crate::wayland_subscription::WaylandUpdate;
|
||||
use crate::wayland_subscription::WorkspaceUpdate;
|
||||
|
|
@ -13,6 +14,7 @@ use cctk::toplevel_info::ToplevelInfo;
|
|||
use cctk::wayland_client::protocol::wl_data_device_manager::DndAction;
|
||||
use cctk::wayland_client::protocol::wl_seat::WlSeat;
|
||||
use cosmic::cosmic_config::{Config, CosmicConfigEntry};
|
||||
use cosmic::desktop::IconSource;
|
||||
use cosmic::desktop::{
|
||||
app_id_or_fallback_matches, load_applications_for_app_ids, DesktopEntryData,
|
||||
};
|
||||
|
|
@ -39,7 +41,9 @@ use cosmic::iced_sctk::commands::data_device::request_dnd_data;
|
|||
use cosmic::iced_sctk::commands::data_device::set_actions;
|
||||
use cosmic::iced_sctk::commands::data_device::start_drag;
|
||||
use cosmic::iced_style::application;
|
||||
use cosmic::iced_widget::button;
|
||||
use cosmic::theme::Button;
|
||||
use cosmic::theme::Container;
|
||||
use cosmic::widget::divider;
|
||||
use cosmic::widget::rectangle_tracker::rectangle_tracker_subscription;
|
||||
use cosmic::widget::rectangle_tracker::RectangleTracker;
|
||||
|
|
@ -48,6 +52,12 @@ use cosmic::{
|
|||
applet::{cosmic_panel_config::PanelAnchor, Context},
|
||||
Command,
|
||||
};
|
||||
use cosmic::{
|
||||
iced::{alignment::Vertical, Limits},
|
||||
iced_core::{layout, overlay, widget::Tree, Layout, Size, Vector},
|
||||
iced_widget::text,
|
||||
widget::{image::Handle, Image, Widget},
|
||||
};
|
||||
use cosmic::{Element, Theme};
|
||||
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State;
|
||||
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
||||
|
|
@ -99,7 +109,7 @@ pub fn load_applications_for_app_ids_sorted<'a, 'b>(
|
|||
#[derive(Debug, Clone, Default)]
|
||||
struct DockItem {
|
||||
id: u32,
|
||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
|
||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
|
||||
desktop_info: DesktopEntryData,
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +132,7 @@ impl DataFromMimeType for DockItem {
|
|||
impl DockItem {
|
||||
fn new(
|
||||
id: u32,
|
||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
|
||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo, Option<WaylandImage>)>,
|
||||
desktop_info: DesktopEntryData,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
|
@ -170,53 +180,28 @@ impl DockItem {
|
|||
})
|
||||
.collect_vec()
|
||||
} else {
|
||||
if focused {
|
||||
(0..min(toplevels.len(), 3))
|
||||
.map(|_| {
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(dot_radius)
|
||||
.style(<Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||
|theme| container::Appearance {
|
||||
text_color: Some(Color::TRANSPARENT),
|
||||
background: Some(Background::Color(
|
||||
theme.cosmic().accent_color().into(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 0.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
icon_color: Some(Color::TRANSPARENT),
|
||||
(0..min(toplevels.len(), 3))
|
||||
.map(|_| {
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(dot_radius)
|
||||
.style(<Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||
|theme| container::Appearance {
|
||||
text_color: Some(Color::TRANSPARENT),
|
||||
background: Some(Background::Color(
|
||||
theme.cosmic().on_bg_color().into(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 0.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
)))
|
||||
.into()
|
||||
})
|
||||
.collect_vec()
|
||||
} else {
|
||||
(0..min(toplevels.len(), 3))
|
||||
.map(|_| {
|
||||
container(vertical_space(Length::Fixed(0.0)))
|
||||
.padding(dot_radius)
|
||||
.style(<Theme as container::StyleSheet>::Style::Custom(Box::new(
|
||||
|theme| container::Appearance {
|
||||
text_color: Some(Color::TRANSPARENT),
|
||||
background: Some(Background::Color(
|
||||
theme.cosmic().on_bg_color().into(),
|
||||
)),
|
||||
border: Border {
|
||||
radius: 4.0.into(),
|
||||
width: 0.0,
|
||||
color: Color::TRANSPARENT,
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
icon_color: Some(Color::TRANSPARENT),
|
||||
},
|
||||
)))
|
||||
.into()
|
||||
})
|
||||
.collect_vec()
|
||||
}
|
||||
shadow: Shadow::default(),
|
||||
icon_color: Some(Color::TRANSPARENT),
|
||||
},
|
||||
)))
|
||||
.into()
|
||||
})
|
||||
.collect_vec()
|
||||
};
|
||||
|
||||
let icon_wrapper: Element<_> = match applet.anchor {
|
||||
|
|
@ -289,20 +274,14 @@ impl DockItem {
|
|||
} else if toplevels.len() == 1 {
|
||||
toplevels.first().map(|t| {
|
||||
if focused {
|
||||
Message::Minimize(t.0.clone())
|
||||
// FIXME: Change to Message::Minimize once focus tracking is fixed
|
||||
Message::Activate(t.0.clone())
|
||||
} else {
|
||||
Message::Activate(t.0.clone())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// TODO: Change this
|
||||
toplevels.first().map(|t| {
|
||||
if focused {
|
||||
Message::Minimize(t.0.clone())
|
||||
} else {
|
||||
Message::Activate(t.0.clone())
|
||||
}
|
||||
})
|
||||
Some(Message::TopLevelListPopup(desktop_info.id.clone()))
|
||||
})
|
||||
.width(Length::Shrink)
|
||||
.height(Length::Shrink),
|
||||
|
|
@ -333,7 +312,7 @@ struct DndOffer {
|
|||
#[derive(Clone, Default)]
|
||||
struct CosmicAppList {
|
||||
core: cosmic::app::Core,
|
||||
popup: Option<(window::Id, u32)>,
|
||||
popup: Option<(window::Id, u32, PopupType)>,
|
||||
subscription_ctr: u32,
|
||||
item_ctr: u32,
|
||||
active_list: Vec<DockItem>,
|
||||
|
|
@ -350,6 +329,12 @@ struct CosmicAppList {
|
|||
active_workspace: Option<ZcosmicWorkspaceHandleV1>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum PopupType {
|
||||
RightClickMenu,
|
||||
TopLevelList,
|
||||
}
|
||||
|
||||
// TODO DnD after sctk merges DnD
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
|
|
@ -357,6 +342,7 @@ enum Message {
|
|||
Favorite(String),
|
||||
UnFavorite(String),
|
||||
Popup(String),
|
||||
TopLevelListPopup(String),
|
||||
GpuRequest(Option<Vec<Gpu>>),
|
||||
CloseRequested(window::Id),
|
||||
ClosePopup,
|
||||
|
|
@ -447,6 +433,77 @@ pub fn menu_button<'a, Message>(
|
|||
.width(Length::Fill)
|
||||
}
|
||||
|
||||
const TOPLEVEL_BUTTON_WIDTH: f32 = 160.0;
|
||||
const TOPLEVEL_BUTTON_HEIGHT: f32 = 130.0;
|
||||
|
||||
pub fn toplevel_button<'a, Msg>(
|
||||
img: Option<WaylandImage>,
|
||||
icon: &IconSource,
|
||||
on_press: Msg,
|
||||
title: String,
|
||||
text_size: f32,
|
||||
) -> cosmic::widget::Button<'a, Msg, cosmic::Theme, cosmic::Renderer>
|
||||
where
|
||||
Msg: 'static + Clone,
|
||||
{
|
||||
let border = 1.0;
|
||||
cosmic::widget::Button::new(
|
||||
container(
|
||||
column![
|
||||
container(if let Some(img) = img {
|
||||
Element::from(
|
||||
Image::new(Handle::from_pixels(
|
||||
img.img.width(),
|
||||
img.img.height(),
|
||||
img.clone(),
|
||||
))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.content_fit(cosmic::iced_core::ContentFit::Contain),
|
||||
)
|
||||
} else {
|
||||
Element::from(
|
||||
icon.as_cosmic_icon()
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill),
|
||||
)
|
||||
})
|
||||
.style(Container::Custom(Box::new(move |theme| {
|
||||
container::Appearance {
|
||||
border: Border {
|
||||
color: theme.cosmic().bg_divider().into(),
|
||||
width: border,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
})))
|
||||
.padding(border as u16)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill),
|
||||
container(
|
||||
text(title)
|
||||
.size(text_size)
|
||||
.horizontal_alignment(Horizontal::Center),
|
||||
)
|
||||
.height(Length::Fixed(14.0))
|
||||
.width(Length::Fill)
|
||||
.center_x(),
|
||||
]
|
||||
.spacing(4)
|
||||
.align_items(Alignment::Center),
|
||||
)
|
||||
.align_x(cosmic::iced_core::alignment::Horizontal::Center)
|
||||
.align_y(cosmic::iced_core::alignment::Vertical::Center)
|
||||
.height(Length::Fill)
|
||||
.width(Length::Fill),
|
||||
)
|
||||
.on_press(on_press)
|
||||
.style(Button::AppletMenu)
|
||||
.width(Length::Fixed(TOPLEVEL_BUTTON_WIDTH))
|
||||
.height(Length::Fixed(TOPLEVEL_BUTTON_HEIGHT))
|
||||
}
|
||||
|
||||
pub fn menu_control_padding() -> Padding {
|
||||
let theme = cosmic::theme::active();
|
||||
let cosmic = theme.cosmic();
|
||||
|
|
@ -509,7 +566,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
) -> iced::Command<cosmic::app::Message<Self::Message>> {
|
||||
match message {
|
||||
Message::Popup(id) => {
|
||||
if let Some((popup_id, _toplevel)) = self.popup.take() {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
}
|
||||
if let Some(toplevel_group) = self
|
||||
|
|
@ -524,7 +581,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
};
|
||||
|
||||
let new_id = window::Id::unique();
|
||||
self.popup = Some((new_id, toplevel_group.id));
|
||||
self.popup = Some((new_id, toplevel_group.id, PopupType::RightClickMenu));
|
||||
|
||||
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||
window::Id::MAIN,
|
||||
|
|
@ -552,6 +609,51 @@ impl cosmic::Application for CosmicAppList {
|
|||
return Command::batch([gpu_update, get_popup(popup_settings)]);
|
||||
}
|
||||
}
|
||||
Message::TopLevelListPopup(id) => {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
}
|
||||
if let Some(toplevel_group) = self
|
||||
.active_list
|
||||
.iter()
|
||||
.chain(self.favorite_list.iter())
|
||||
.find(|t| t.desktop_info.id == id)
|
||||
{
|
||||
let rectangle = match self.rectangles.get(&toplevel_group.id) {
|
||||
Some(r) => r,
|
||||
None => return Command::none(),
|
||||
};
|
||||
|
||||
let new_id = window::Id::unique();
|
||||
self.popup = Some((new_id, toplevel_group.id, PopupType::TopLevelList));
|
||||
|
||||
let mut popup_settings = self.core.applet.get_popup_settings(
|
||||
window::Id::MAIN,
|
||||
new_id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let iced::Rectangle {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
} = *rectangle;
|
||||
popup_settings.positioner.anchor_rect = iced::Rectangle::<i32> {
|
||||
x: x as i32,
|
||||
y: y as i32,
|
||||
width: width as i32,
|
||||
height: height as i32,
|
||||
};
|
||||
popup_settings.positioner.size_limits = Limits::NONE
|
||||
.max_width(56.0 + (TOPLEVEL_BUTTON_WIDTH + 12.0) * 7.0)
|
||||
.min_width(30.0)
|
||||
.min_height(100.0)
|
||||
.max_height(16.0 + TOPLEVEL_BUTTON_HEIGHT);
|
||||
return get_popup(popup_settings);
|
||||
}
|
||||
}
|
||||
Message::Favorite(id) => {
|
||||
if let Some(i) = self
|
||||
.active_list
|
||||
|
|
@ -564,7 +666,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
|
||||
self.config
|
||||
.add_favorite(id, &Config::new(APP_ID, AppListConfig::VERSION).unwrap());
|
||||
if let Some((popup_id, _toplevel)) = self.popup.take() {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -584,7 +686,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
self.active_list.push(entry);
|
||||
}
|
||||
}
|
||||
if let Some((popup_id, _toplevel)) = self.popup.take() {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -611,7 +713,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
.chain(self.favorite_list.iter())
|
||||
.find(|t| t.desktop_info.id == id)
|
||||
{
|
||||
for (handle, _) in &toplevel_group.toplevels {
|
||||
for (handle, _, _) in &toplevel_group.toplevels {
|
||||
if let Some(tx) = self.wayland_sender.as_ref() {
|
||||
let _ = tx.send(WaylandRequest::Toplevel(ToplevelRequest::Quit(
|
||||
handle.clone(),
|
||||
|
|
@ -619,7 +721,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
}
|
||||
}
|
||||
}
|
||||
if let Some((popup_id, _toplevel)) = self.popup.take() {
|
||||
if let Some((popup_id, _toplevel, _)) = self.popup.take() {
|
||||
return destroy_popup(popup_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -793,6 +895,22 @@ impl cosmic::Application for CosmicAppList {
|
|||
WaylandUpdate::Init(tx) => {
|
||||
self.wayland_sender.replace(tx);
|
||||
}
|
||||
WaylandUpdate::Image(handle, img) => {
|
||||
'img_update: for x in self
|
||||
.active_list
|
||||
.iter_mut()
|
||||
.chain(self.favorite_list.iter_mut())
|
||||
{
|
||||
if let Some((_, _, ref mut handle_img)) = x
|
||||
.toplevels
|
||||
.iter_mut()
|
||||
.find(|(toplevel_handle, _, _)| toplevel_handle.clone() == handle)
|
||||
{
|
||||
*handle_img = Some(img);
|
||||
break 'img_update;
|
||||
}
|
||||
}
|
||||
}
|
||||
WaylandUpdate::Finished => {
|
||||
for t in &mut self.favorite_list {
|
||||
t.toplevels.clear();
|
||||
|
|
@ -826,7 +944,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
app_id_or_fallback_matches(&info.app_id, desktop_info)
|
||||
})
|
||||
{
|
||||
t.toplevels.push((handle, info));
|
||||
t.toplevels.push((handle, info, None));
|
||||
} else {
|
||||
if info.app_id.is_empty() {
|
||||
info.app_id = format!("Unknown Application {}", self.item_ctr);
|
||||
|
|
@ -842,7 +960,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
.unwrap();
|
||||
self.active_list.push(DockItem {
|
||||
id: self.item_ctr,
|
||||
toplevels: vec![(handle, info)],
|
||||
toplevels: vec![(handle, info, None)],
|
||||
desktop_info,
|
||||
});
|
||||
}
|
||||
|
|
@ -853,7 +971,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
.iter_mut()
|
||||
.chain(self.favorite_list.iter_mut())
|
||||
{
|
||||
t.toplevels.retain(|(t_handle, _)| t_handle != &handle);
|
||||
t.toplevels.retain(|(t_handle, _, _)| t_handle != &handle);
|
||||
}
|
||||
self.active_list.retain(|t| !t.toplevels.is_empty());
|
||||
}
|
||||
|
|
@ -867,7 +985,7 @@ impl cosmic::Application for CosmicAppList {
|
|||
.iter_mut()
|
||||
.chain(self.favorite_list.iter_mut())
|
||||
{
|
||||
for (t_handle, t_info) in &mut toplevel_list.toplevels {
|
||||
for (t_handle, t_info, _) in &mut toplevel_list.toplevels {
|
||||
if &handle == t_handle {
|
||||
*t_info = info;
|
||||
break 'toplevel_loop;
|
||||
|
|
@ -1162,7 +1280,8 @@ impl cosmic::Application for CosmicAppList {
|
|||
.as_cosmic_icon()
|
||||
.size(self.core.applet.suggested_size().0)
|
||||
.into()
|
||||
} else if let Some((_popup_id, id)) = self.popup.as_ref().filter(|p| id == p.0) {
|
||||
} else if let Some((_popup_id, id, popup_type)) = self.popup.as_ref().filter(|p| id == p.0)
|
||||
{
|
||||
let Some(DockItem {
|
||||
toplevels,
|
||||
desktop_info,
|
||||
|
|
@ -1175,91 +1294,116 @@ impl cosmic::Application for CosmicAppList {
|
|||
else {
|
||||
return iced::widget::text("").into();
|
||||
};
|
||||
match popup_type {
|
||||
PopupType::RightClickMenu => {
|
||||
let is_favorite = self
|
||||
.config
|
||||
.favorites
|
||||
.iter()
|
||||
.any(|x| app_id_or_fallback_matches(&x, desktop_info));
|
||||
|
||||
let is_favorite = self
|
||||
.config
|
||||
.favorites
|
||||
.iter()
|
||||
.any(|x| app_id_or_fallback_matches(&x, desktop_info));
|
||||
let mut content = column![container(
|
||||
iced::widget::text(&desktop_info.name)
|
||||
.horizontal_alignment(Horizontal::Center)
|
||||
)
|
||||
.padding(menu_control_padding()),]
|
||||
.padding([8, 0])
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
let mut content = column![container(
|
||||
iced::widget::text(&desktop_info.name).horizontal_alignment(Horizontal::Center)
|
||||
)
|
||||
.padding(menu_control_padding()),]
|
||||
.padding([8, 0])
|
||||
.align_items(Alignment::Center);
|
||||
|
||||
if let Some(exec) = desktop_info.exec.clone() {
|
||||
if !toplevels.is_empty() {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(fl!("new-window")))
|
||||
.on_press(Message::Exec(exec, None)),
|
||||
);
|
||||
} else if let Some(gpus) = self.gpus.as_ref() {
|
||||
let default_idx = if desktop_info.prefers_dgpu {
|
||||
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
|
||||
} else {
|
||||
gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
|
||||
};
|
||||
for (i, gpu) in gpus.iter().enumerate() {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(format!(
|
||||
"{} {}",
|
||||
fl!("run-on", gpu = gpu.name.clone()),
|
||||
if i == default_idx {
|
||||
fl!("run-on-default")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
)))
|
||||
.on_press(Message::Exec(exec.clone(), Some(i))),
|
||||
);
|
||||
if let Some(exec) = desktop_info.exec.clone() {
|
||||
if !toplevels.is_empty() {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(fl!("new-window")))
|
||||
.on_press(Message::Exec(exec, None)),
|
||||
);
|
||||
} else if let Some(gpus) = self.gpus.as_ref() {
|
||||
let default_idx = if desktop_info.prefers_dgpu {
|
||||
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
|
||||
} else {
|
||||
gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
|
||||
};
|
||||
for (i, gpu) in gpus.iter().enumerate() {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(format!(
|
||||
"{} {}",
|
||||
fl!("run-on", gpu = gpu.name.clone()),
|
||||
if i == default_idx {
|
||||
fl!("run-on-default")
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
)))
|
||||
.on_press(Message::Exec(exec.clone(), Some(i))),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(fl!("run")))
|
||||
.on_press(Message::Exec(exec, None)),
|
||||
);
|
||||
}
|
||||
content = content.push(divider::horizontal::default());
|
||||
}
|
||||
} else {
|
||||
content = content.push(
|
||||
menu_button(iced::widget::text(fl!("run")))
|
||||
.on_press(Message::Exec(exec, None)),
|
||||
);
|
||||
}
|
||||
content = content.push(divider::horizontal::default());
|
||||
}
|
||||
|
||||
if !toplevels.is_empty() {
|
||||
let mut list_col = column![];
|
||||
for (handle, info) in toplevels {
|
||||
let title = if info.title.len() > 20 {
|
||||
format!("{:.24}...", &info.title)
|
||||
if !toplevels.is_empty() {
|
||||
let mut list_col = column![];
|
||||
for (handle, info, _) in toplevels {
|
||||
let title = if info.title.len() > 20 {
|
||||
format!("{:.24}...", &info.title)
|
||||
} else {
|
||||
info.title.clone()
|
||||
};
|
||||
list_col = list_col.push(
|
||||
menu_button(iced::widget::text(title))
|
||||
.on_press(Message::Activate(handle.clone())),
|
||||
);
|
||||
}
|
||||
content = content.push(list_col);
|
||||
content = content.push(divider::horizontal::default());
|
||||
}
|
||||
content = content.push(if is_favorite {
|
||||
menu_button(iced::widget::text(fl!("unfavorite")))
|
||||
.on_press(Message::UnFavorite(desktop_info.id.clone()))
|
||||
} else {
|
||||
info.title.clone()
|
||||
};
|
||||
list_col = list_col.push(
|
||||
menu_button(iced::widget::text(title))
|
||||
.on_press(Message::Activate(handle.clone())),
|
||||
);
|
||||
}
|
||||
content = content.push(list_col);
|
||||
content = content.push(divider::horizontal::default());
|
||||
}
|
||||
content = content.push(if is_favorite {
|
||||
menu_button(iced::widget::text(fl!("unfavorite")))
|
||||
.on_press(Message::UnFavorite(desktop_info.id.clone()))
|
||||
} else {
|
||||
menu_button(iced::widget::text(fl!("favorite")))
|
||||
.on_press(Message::Favorite(desktop_info.id.clone()))
|
||||
});
|
||||
menu_button(iced::widget::text(fl!("favorite")))
|
||||
.on_press(Message::Favorite(desktop_info.id.clone()))
|
||||
});
|
||||
|
||||
content = match toplevels.len() {
|
||||
0 => content,
|
||||
1 => content.push(
|
||||
menu_button(iced::widget::text(fl!("quit")))
|
||||
.on_press(Message::Quit(desktop_info.id.clone())),
|
||||
),
|
||||
_ => content.push(
|
||||
menu_button(iced::widget::text(&fl!("quit-all")))
|
||||
.on_press(Message::Quit(desktop_info.id.clone())),
|
||||
),
|
||||
};
|
||||
self.core.applet.popup_container(content).into()
|
||||
content = match toplevels.len() {
|
||||
0 => content,
|
||||
1 => content.push(
|
||||
menu_button(iced::widget::text(fl!("quit")))
|
||||
.on_press(Message::Quit(desktop_info.id.clone())),
|
||||
),
|
||||
_ => content.push(
|
||||
menu_button(iced::widget::text(&fl!("quit-all")))
|
||||
.on_press(Message::Quit(desktop_info.id.clone())),
|
||||
),
|
||||
};
|
||||
self.core.applet.popup_container(content).into()
|
||||
}
|
||||
PopupType::TopLevelList => {
|
||||
let mut content = row![]
|
||||
.padding([8, 12])
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(12);
|
||||
for (handle, info, img) in toplevels {
|
||||
let title = if info.title.len() > 18 {
|
||||
format!("{:.15}...", &info.title)
|
||||
} else {
|
||||
info.title.clone()
|
||||
};
|
||||
content = content.push(toplevel_button(
|
||||
img.clone(),
|
||||
&desktop_info.icon,
|
||||
Message::Activate(handle.clone()),
|
||||
title,
|
||||
10.0,
|
||||
));
|
||||
}
|
||||
self.core.applet.popup_container(content).into()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let suggested = self.core.applet.suggested_size();
|
||||
iced::widget::row!()
|
||||
|
|
@ -1332,7 +1476,7 @@ impl CosmicAppList {
|
|||
}
|
||||
let active_workspace = self.active_workspace.as_ref().unwrap().clone();
|
||||
for toplevel_list in self.active_list.iter().chain(self.favorite_list.iter()) {
|
||||
for (t_handle, t_info) in &toplevel_list.toplevels {
|
||||
for (t_handle, t_info, _) in &toplevel_list.toplevels {
|
||||
if t_info.workspace.contains(&active_workspace)
|
||||
&& t_info.state.contains(&State::Activated)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,43 +1,62 @@
|
|||
use crate::wayland_subscription::{
|
||||
ToplevelRequest, ToplevelUpdate, WaylandRequest, WaylandUpdate, WorkspaceUpdate,
|
||||
ToplevelRequest, ToplevelUpdate, WaylandImage, WaylandRequest, WaylandUpdate, WorkspaceUpdate,
|
||||
};
|
||||
use std::os::{
|
||||
fd::{FromRawFd, RawFd},
|
||||
unix::net::UnixStream,
|
||||
use std::{
|
||||
os::{
|
||||
fd::{AsFd, FromRawFd, RawFd},
|
||||
unix::net::UnixStream,
|
||||
},
|
||||
sync::{Arc, Condvar, Mutex, MutexGuard},
|
||||
};
|
||||
|
||||
use cctk::{
|
||||
screencopy::{
|
||||
capture, Formats, Frame, ScreencopyFrameData, ScreencopyFrameDataExt, ScreencopyHandler,
|
||||
ScreencopySessionData, ScreencopySessionDataExt, ScreencopyState,
|
||||
},
|
||||
sctk::{
|
||||
self,
|
||||
activation::{RequestData, RequestDataExt},
|
||||
output::{OutputHandler, OutputState},
|
||||
reexports::{calloop, calloop_wayland_source::WaylandSource},
|
||||
seat::{SeatHandler, SeatState},
|
||||
shm::{Shm, ShmHandler},
|
||||
},
|
||||
toplevel_info::{ToplevelInfoHandler, ToplevelInfoState},
|
||||
toplevel_management::{ToplevelManagerHandler, ToplevelManagerState},
|
||||
wayland_client::{
|
||||
self,
|
||||
protocol::{wl_output, wl_seat::WlSeat, wl_surface::WlSurface},
|
||||
WEnum,
|
||||
globals::registry_queue_init,
|
||||
protocol::{
|
||||
wl_buffer, wl_output,
|
||||
wl_seat::WlSeat,
|
||||
wl_shm::{self, WlShm},
|
||||
wl_shm_pool,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Connection, Dispatch, Proxy, QueueHandle, WEnum,
|
||||
},
|
||||
workspace::{WorkspaceHandler, WorkspaceState},
|
||||
};
|
||||
use cosmic_protocols::{
|
||||
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
|
||||
image_source::v1::client::zcosmic_toplevel_image_source_manager_v1::ZcosmicToplevelImageSourceManagerV1,
|
||||
screencopy::v2::client::{
|
||||
zcosmic_screencopy_frame_v2, zcosmic_screencopy_manager_v2, zcosmic_screencopy_session_v2,
|
||||
},
|
||||
toplevel_info::v1::client::zcosmic_toplevel_handle_v1::{
|
||||
self, State as ToplevelUpdateState, ZcosmicToplevelHandleV1,
|
||||
},
|
||||
toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
|
||||
workspace::v1::client::zcosmic_workspace_handle_v1::State,
|
||||
workspace::v1::client::zcosmic_workspace_handle_v1::State as WorkspaceUpdateState,
|
||||
};
|
||||
use futures::channel::mpsc::UnboundedSender;
|
||||
use sctk::{
|
||||
activation::{ActivationHandler, ActivationState},
|
||||
registry::{ProvidesRegistryState, RegistryState},
|
||||
};
|
||||
use wayland_client::{globals::registry_queue_init, Connection, QueueHandle};
|
||||
|
||||
struct AppData {
|
||||
exit: bool,
|
||||
tx: UnboundedSender<WaylandUpdate>,
|
||||
conn: Connection,
|
||||
queue_handle: QueueHandle<Self>,
|
||||
registry_state: RegistryState,
|
||||
activation_state: Option<ActivationState>,
|
||||
|
|
@ -45,9 +64,13 @@ struct AppData {
|
|||
toplevel_manager_state: ToplevelManagerState,
|
||||
seat_state: SeatState,
|
||||
workspace_state: WorkspaceState,
|
||||
shm_state: Shm,
|
||||
screencopy_state: ScreencopyState,
|
||||
output_state: OutputState,
|
||||
}
|
||||
|
||||
// Workspace and toplevel handling
|
||||
|
||||
// Need to bind output globals just so workspace can get output events
|
||||
impl OutputHandler for AppData {
|
||||
fn output_state(&mut self) -> &mut OutputState {
|
||||
|
|
@ -87,7 +110,10 @@ impl WorkspaceHandler for AppData {
|
|||
fn done(&mut self) {
|
||||
'workspaces_loop: for group in self.workspace_state.workspace_groups() {
|
||||
for workspace in &group.workspaces {
|
||||
if workspace.state.contains(&WEnum::Value(State::Active)) {
|
||||
if workspace
|
||||
.state
|
||||
.contains(&WEnum::Value(WorkspaceUpdateState::Active))
|
||||
{
|
||||
let _ =
|
||||
self.tx
|
||||
.unbounded_send(WaylandUpdate::Workspace(WorkspaceUpdate::Enter(
|
||||
|
|
@ -194,6 +220,9 @@ impl ToplevelInfoHandler for AppData {
|
|||
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
|
||||
) {
|
||||
if let Some(info) = self.toplevel_info_state.info(toplevel) {
|
||||
// spawn thread for sending the image
|
||||
self.send_image(toplevel.clone());
|
||||
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Toplevel(ToplevelUpdate::Add(
|
||||
|
|
@ -210,6 +239,8 @@ impl ToplevelInfoHandler for AppData {
|
|||
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
|
||||
) {
|
||||
if let Some(info) = self.toplevel_info_state.info(toplevel) {
|
||||
// spawn thread for sending the image
|
||||
self.send_image(toplevel.clone());
|
||||
let _ = self
|
||||
.tx
|
||||
.unbounded_send(WaylandUpdate::Toplevel(ToplevelUpdate::Update(
|
||||
|
|
@ -233,6 +264,327 @@ impl ToplevelInfoHandler for AppData {
|
|||
}
|
||||
}
|
||||
|
||||
// Screencopy handling
|
||||
|
||||
#[derive(Default)]
|
||||
struct SessionInner {
|
||||
formats: Option<Formats>,
|
||||
res: Option<Result<(), WEnum<zcosmic_screencopy_frame_v2::FailureReason>>>,
|
||||
}
|
||||
|
||||
// TODO: dmabuf? need to handle modifier negotation
|
||||
#[derive(Default)]
|
||||
struct Session {
|
||||
condvar: Condvar,
|
||||
inner: Mutex<SessionInner>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SessionData {
|
||||
session: Arc<Session>,
|
||||
session_data: ScreencopySessionData,
|
||||
}
|
||||
|
||||
struct FrameData {
|
||||
frame_data: ScreencopyFrameData,
|
||||
session: zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2,
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn for_session(
|
||||
session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2,
|
||||
) -> Option<&Self> {
|
||||
Some(&session.data::<SessionData>()?.session)
|
||||
}
|
||||
|
||||
fn update<F: FnOnce(&mut SessionInner)>(&self, f: F) {
|
||||
f(&mut self.inner.lock().unwrap());
|
||||
self.condvar.notify_all();
|
||||
}
|
||||
|
||||
fn wait_while<F: FnMut(&SessionInner) -> bool>(&self, mut f: F) -> MutexGuard<SessionInner> {
|
||||
self.condvar
|
||||
.wait_while(self.inner.lock().unwrap(), |data| f(data))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreencopySessionDataExt for SessionData {
|
||||
fn screencopy_session_data(&self) -> &ScreencopySessionData {
|
||||
&self.session_data
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreencopyFrameDataExt for FrameData {
|
||||
fn screencopy_frame_data(&self) -> &ScreencopyFrameData {
|
||||
&self.frame_data
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_shm_pool::WlShmPool, ()> for AppData {
|
||||
fn event(
|
||||
_app_data: &mut Self,
|
||||
_buffer: &wl_shm_pool::WlShmPool,
|
||||
_event: wl_shm_pool::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_buffer::WlBuffer, ()> for AppData {
|
||||
fn event(
|
||||
_app_data: &mut Self,
|
||||
_buffer: &wl_buffer::WlBuffer,
|
||||
_event: wl_buffer::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
struct CaptureData {
|
||||
qh: QueueHandle<AppData>,
|
||||
conn: Connection,
|
||||
wl_shm: WlShm,
|
||||
screencopy_manager: zcosmic_screencopy_manager_v2::ZcosmicScreencopyManagerV2,
|
||||
toplevel_source_manager: ZcosmicToplevelImageSourceManagerV1,
|
||||
}
|
||||
|
||||
impl CaptureData {
|
||||
pub fn capture_source_shm_fd<Fd: AsFd>(
|
||||
&self,
|
||||
overlay_cursor: bool,
|
||||
source: ZcosmicToplevelHandleV1,
|
||||
fd: Fd,
|
||||
len: Option<u32>,
|
||||
) -> Option<ShmImage<Fd>> {
|
||||
// XXX error type?
|
||||
// TODO: way to get cursor metadata?
|
||||
|
||||
#[allow(unused_variables)] // TODO
|
||||
let overlay_cursor = if overlay_cursor { 1 } else { 0 };
|
||||
|
||||
let session = Arc::new(Session::default());
|
||||
let image_source = self
|
||||
.toplevel_source_manager
|
||||
.create_source(&source, &self.qh, ());
|
||||
let screencopy_session = self.screencopy_manager.create_session(
|
||||
&image_source,
|
||||
zcosmic_screencopy_manager_v2::Options::empty(),
|
||||
&self.qh,
|
||||
SessionData {
|
||||
session: session.clone(),
|
||||
session_data: Default::default(),
|
||||
},
|
||||
);
|
||||
self.conn.flush().unwrap();
|
||||
|
||||
let formats = session
|
||||
.wait_while(|data| data.formats.is_none())
|
||||
.formats
|
||||
.take()
|
||||
.unwrap();
|
||||
let (width, height) = formats.buffer_size;
|
||||
|
||||
// XXX
|
||||
if !formats
|
||||
.shm_formats
|
||||
.contains(&wl_shm::Format::Abgr8888.into())
|
||||
{
|
||||
tracing::error!("No suitable buffer format found");
|
||||
tracing::warn!("Available formats: {:#?}", formats);
|
||||
return None;
|
||||
};
|
||||
|
||||
let buf_len = width * height * 4;
|
||||
if let Some(len) = len {
|
||||
if len != buf_len {
|
||||
return None;
|
||||
}
|
||||
} else if let Err(_err) = rustix::fs::ftruncate(&fd, buf_len as _) {
|
||||
};
|
||||
let pool = self
|
||||
.wl_shm
|
||||
.create_pool(fd.as_fd(), buf_len as i32, &self.qh, ());
|
||||
let buffer = pool.create_buffer(
|
||||
0,
|
||||
width as i32,
|
||||
height as i32,
|
||||
width as i32 * 4,
|
||||
wl_shm::Format::Abgr8888,
|
||||
&self.qh,
|
||||
(),
|
||||
);
|
||||
|
||||
capture(
|
||||
&screencopy_session,
|
||||
&buffer,
|
||||
&[],
|
||||
&self.qh,
|
||||
FrameData {
|
||||
frame_data: Default::default(),
|
||||
session: screencopy_session.clone(),
|
||||
},
|
||||
);
|
||||
self.conn.flush().unwrap();
|
||||
|
||||
// TODO: wait for server to release buffer?
|
||||
let res = session
|
||||
.wait_while(|data| data.res.is_none())
|
||||
.res
|
||||
.take()
|
||||
.unwrap();
|
||||
pool.destroy();
|
||||
buffer.destroy();
|
||||
|
||||
//std::thread::sleep(std::time::Duration::from_millis(16));
|
||||
|
||||
if res.is_ok() {
|
||||
Some(ShmImage { fd, width, height })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShmImage<T: AsFd> {
|
||||
fd: T,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl<T: AsFd> ShmImage<T> {
|
||||
pub fn image(&self) -> anyhow::Result<image::RgbaImage> {
|
||||
let mmap = unsafe { memmap2::Mmap::map(&self.fd.as_fd())? };
|
||||
image::RgbaImage::from_raw(self.width, self.height, mmap.to_vec())
|
||||
.ok_or_else(|| anyhow::anyhow!("ShmImage had incorrect size"))
|
||||
}
|
||||
}
|
||||
|
||||
impl AppData {
|
||||
fn send_image(&self, handle: ZcosmicToplevelHandleV1) {
|
||||
let tx = self.tx.clone();
|
||||
let capture_data = CaptureData {
|
||||
qh: self.queue_handle.clone(),
|
||||
conn: self.conn.clone(),
|
||||
wl_shm: self.shm_state.wl_shm().clone(),
|
||||
screencopy_manager: self.screencopy_state.screencopy_manager.clone(),
|
||||
toplevel_source_manager: self
|
||||
.screencopy_state
|
||||
.toplevel_source_manager
|
||||
.clone()
|
||||
.unwrap(),
|
||||
};
|
||||
std::thread::spawn(move || {
|
||||
use std::ffi::CStr;
|
||||
let name = unsafe { CStr::from_bytes_with_nul_unchecked(b"app-list-screencopy\0") };
|
||||
let Ok(fd) = rustix::fs::memfd_create(name, rustix::fs::MemfdFlags::CLOEXEC) else {
|
||||
tracing::error!("Failed to get fd for capture");
|
||||
return;
|
||||
};
|
||||
|
||||
// XXX is this going to use to much memory?
|
||||
let img = capture_data.capture_source_shm_fd(false, handle.clone(), fd, None);
|
||||
if let Some(img) = img {
|
||||
let Ok(img) = img.image() else {
|
||||
tracing::error!("Failed to get RgbaImage");
|
||||
return;
|
||||
};
|
||||
|
||||
// resize to 128x128
|
||||
let max = img.width().max(img.height());
|
||||
let ratio = max as f32 / 128.0;
|
||||
|
||||
let img = if ratio > 1.0 {
|
||||
let new_width = (img.width() as f32 / ratio).round();
|
||||
let new_height = (img.height() as f32 / ratio).round();
|
||||
|
||||
image::imageops::resize(
|
||||
&img,
|
||||
new_width as u32,
|
||||
new_height as u32,
|
||||
image::imageops::FilterType::Lanczos3,
|
||||
)
|
||||
} else {
|
||||
img
|
||||
};
|
||||
|
||||
if let Err(err) =
|
||||
tx.unbounded_send(WaylandUpdate::Image(handle, WaylandImage::new(img)))
|
||||
{
|
||||
tracing::error!("Failed to send image event to subscription {err:?}");
|
||||
};
|
||||
} else {
|
||||
tracing::error!("Failed to capture image");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmHandler for AppData {
|
||||
fn shm_state(&mut self) -> &mut Shm {
|
||||
&mut self.shm_state
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreencopyHandler for AppData {
|
||||
fn screencopy_state(&mut self) -> &mut ScreencopyState {
|
||||
&mut self.screencopy_state
|
||||
}
|
||||
|
||||
fn init_done(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2,
|
||||
formats: &Formats,
|
||||
) {
|
||||
Session::for_session(session).unwrap().update(|data| {
|
||||
data.formats = Some(formats.clone());
|
||||
});
|
||||
}
|
||||
|
||||
fn ready(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
screencopy_frame: &zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2,
|
||||
_frame: Frame,
|
||||
) {
|
||||
let session = &screencopy_frame.data::<FrameData>().unwrap().session;
|
||||
Session::for_session(session).unwrap().update(|data| {
|
||||
data.res = Some(Ok(()));
|
||||
});
|
||||
session.destroy();
|
||||
}
|
||||
|
||||
fn failed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
screencopy_frame: &zcosmic_screencopy_frame_v2::ZcosmicScreencopyFrameV2,
|
||||
reason: WEnum<zcosmic_screencopy_frame_v2::FailureReason>,
|
||||
) {
|
||||
// TODO send message to thread
|
||||
let session = &screencopy_frame.data::<FrameData>().unwrap().session;
|
||||
Session::for_session(session).unwrap().update(|data| {
|
||||
data.res = Some(Err(reason));
|
||||
});
|
||||
session.destroy();
|
||||
}
|
||||
|
||||
fn stopped(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_session: &zcosmic_screencopy_session_v2::ZcosmicScreencopySessionV2,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wayland_handler(
|
||||
tx: UnboundedSender<WaylandUpdate>,
|
||||
rx: calloop::channel::Channel<WaylandRequest>,
|
||||
|
|
@ -254,7 +606,7 @@ pub(crate) fn wayland_handler(
|
|||
|
||||
let mut event_loop = calloop::EventLoop::<AppData>::try_new().unwrap();
|
||||
let qh = event_queue.handle();
|
||||
let wayland_source = WaylandSource::new(conn, event_queue);
|
||||
let wayland_source = WaylandSource::new(conn.clone(), event_queue);
|
||||
let handle = event_loop.handle();
|
||||
wayland_source
|
||||
.insert(handle.clone())
|
||||
|
|
@ -319,9 +671,13 @@ pub(crate) fn wayland_handler(
|
|||
return;
|
||||
}
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
let screencopy_state = ScreencopyState::new(&globals, &qh);
|
||||
let shm_state = Shm::bind(&globals, &qh).expect("Failed to get shm state");
|
||||
|
||||
let mut app_data = AppData {
|
||||
exit: false,
|
||||
tx,
|
||||
conn,
|
||||
queue_handle: qh.clone(),
|
||||
activation_state: ActivationState::bind::<AppData>(&globals, &qh).ok(),
|
||||
seat_state: SeatState::new(&globals, &qh),
|
||||
|
|
@ -329,6 +685,8 @@ pub(crate) fn wayland_handler(
|
|||
toplevel_manager_state: ToplevelManagerState::new(®istry_state, &qh),
|
||||
output_state: OutputState::new(&globals, &qh),
|
||||
workspace_state: WorkspaceState::new(®istry_state, &qh),
|
||||
shm_state,
|
||||
screencopy_state,
|
||||
registry_state,
|
||||
};
|
||||
|
||||
|
|
@ -340,11 +698,14 @@ pub(crate) fn wayland_handler(
|
|||
}
|
||||
}
|
||||
|
||||
sctk::delegate_activation!(AppData, ExecRequestData);
|
||||
sctk::delegate_shm!(AppData);
|
||||
sctk::delegate_seat!(AppData);
|
||||
sctk::delegate_registry!(AppData);
|
||||
cctk::delegate_toplevel_info!(AppData);
|
||||
cctk::delegate_toplevel_manager!(AppData);
|
||||
cctk::delegate_screencopy!(AppData, session: [SessionData], frame: [FrameData]);
|
||||
|
||||
sctk::delegate_activation!(AppData, ExecRequestData);
|
||||
|
||||
sctk::delegate_output!(AppData);
|
||||
cctk::delegate_workspace!(AppData);
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ use cosmic::iced;
|
|||
use cosmic::iced::subscription;
|
||||
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
||||
use cosmic_protocols::workspace::v1::client::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1;
|
||||
use image::EncodableLayout;
|
||||
|
||||
use futures::{
|
||||
channel::mpsc::{unbounded, UnboundedReceiver},
|
||||
SinkExt, StreamExt,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::fmt::Debug;
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::wayland_handler::wayland_handler;
|
||||
|
|
@ -40,6 +42,23 @@ pub enum State {
|
|||
Finished,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WaylandImage {
|
||||
pub img: Arc<image::RgbaImage>,
|
||||
}
|
||||
|
||||
impl WaylandImage {
|
||||
pub fn new(img: image::RgbaImage) -> Self {
|
||||
Self { img: Arc::new(img) }
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for WaylandImage {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.img.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
async fn start_listening(
|
||||
state: State,
|
||||
output: &mut futures::channel::mpsc::Sender<WaylandUpdate>,
|
||||
|
|
@ -86,6 +105,7 @@ pub enum WaylandUpdate {
|
|||
exec: String,
|
||||
gpu_idx: Option<usize>,
|
||||
},
|
||||
Image(ZcosmicToplevelHandleV1, WaylandImage),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue