feat: popups

This commit is contained in:
Ashley Wulber 2022-12-15 14:35:31 -05:00 • committed by Ashley Wulber
parent e3065a0668
commit 9f23110a25
6 changed files with 306 additions and 158 deletions

View file

@ -252,9 +252,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.77" version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -450,15 +450,15 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-theme" name = "cosmic-theme"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-theme.git#896b228e4462c2fdeea0e89d1a657458b4a0a144" source = "git+https://github.com/pop-os/cosmic-theme.git#77aad06a6e3e2996ef24fc12e5f59d6da7226692"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"csscolorparser", "csscolorparser",
"directories",
"lazy_static", "lazy_static",
"palette", "palette",
"ron", "ron",
"serde", "serde",
"xdg",
] ]
[[package]] [[package]]
@ -644,6 +644,15 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "directories"
version = "4.0.1"
source = "git+https://github.com/edfloreshz/directories-rs#b93c018bc319f066fbeadcd8e3b865f1fccaaa8c"
dependencies = [
"anyhow",
"dirs-sys",
]
[[package]] [[package]]
name = "dirs" name = "dirs"
version = "3.0.2" version = "3.0.2"
@ -1415,7 +1424,7 @@ dependencies = [
[[package]] [[package]]
name = "iced" name = "iced"
version = "0.5.2" version = "0.5.2"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"iced_core", "iced_core",
"iced_futures", "iced_futures",
@ -1431,7 +1440,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_core" name = "iced_core"
version = "0.6.1" version = "0.6.1"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"palette", "palette",
@ -1441,7 +1450,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_futures" name = "iced_futures"
version = "0.5.1" version = "0.5.1"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"futures", "futures",
"log", "log",
@ -1453,7 +1462,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_glow" name = "iced_glow"
version = "0.4.1" version = "0.4.1"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"euclid", "euclid",
@ -1468,7 +1477,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_graphics" name = "iced_graphics"
version = "0.4.0" version = "0.4.0"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytemuck", "bytemuck",
@ -1488,7 +1497,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_lazy" name = "iced_lazy"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"iced_native", "iced_native",
"ouroboros", "ouroboros",
@ -1497,7 +1506,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_native" name = "iced_native"
version = "0.6.1" version = "0.6.1"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"iced_core", "iced_core",
"iced_futures", "iced_futures",
@ -1511,7 +1520,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_sctk" name = "iced_sctk"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"enum-repr", "enum-repr",
"futures", "futures",
@ -1530,7 +1539,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_style" name = "iced_style"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"iced_core", "iced_core",
"once_cell", "once_cell",
@ -1540,7 +1549,7 @@ dependencies = [
[[package]] [[package]]
name = "iced_wgpu" name = "iced_wgpu"
version = "0.6.1" version = "0.6.1"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytemuck", "bytemuck",
@ -1719,7 +1728,7 @@ checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]] [[package]]
name = "libcosmic" name = "libcosmic"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic/?branch=master#db8b53b83699bf4e10c205873ba281de8d6a5bc3" source = "git+https://github.com/pop-os/libcosmic/?branch=master#5fe445118ed1fbf8a98270777379c16826e2e8ad"
dependencies = [ dependencies = [
"apply", "apply",
"cosmic-panel-config", "cosmic-panel-config",
@ -2983,9 +2992,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.5.9" version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f"
dependencies = [ dependencies = [
"serde", "serde",
] ]

View file

@ -1 +1,6 @@
cosmic-app-list = Cosmic Dock App List cosmic-app-list = Cosmic Dock App List
favorite = Favorite
unfavorite = Un-Favorite
quit = Quit
quit-all = Quit All
new-window = New Window

View file

@ -1,8 +1,10 @@
use std::collections::HashMap;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::path::PathBuf; use std::path::PathBuf;
use crate::config; use crate::config;
use crate::config::AppListConfig; use crate::config::AppListConfig;
use crate::fl;
use crate::toplevel_subscription::toplevel_subscription; use crate::toplevel_subscription::toplevel_subscription;
use crate::toplevel_subscription::ToplevelRequest; use crate::toplevel_subscription::ToplevelRequest;
use crate::toplevel_subscription::ToplevelUpdate; use crate::toplevel_subscription::ToplevelUpdate;
@ -14,12 +16,16 @@ use cosmic::iced;
use cosmic::iced::wayland::popup::destroy_popup; use cosmic::iced::wayland::popup::destroy_popup;
use cosmic::iced::wayland::popup::get_popup; use cosmic::iced::wayland::popup::get_popup;
use cosmic::iced::wayland::SurfaceIdWrapper; use cosmic::iced::wayland::SurfaceIdWrapper;
use cosmic::iced::widget::event_container;
use cosmic::iced::widget::{column, row}; use cosmic::iced::widget::{column, row};
use cosmic::iced::{executor, window, Application, Command, Subscription}; use cosmic::iced::{executor, window, Application, Command, Subscription};
use cosmic::iced_native::subscription::events_with; use cosmic::iced_native::subscription::events_with;
use cosmic::iced_style::application::{self, Appearance}; use cosmic::iced_style::application::{self, Appearance};
use cosmic::iced_style::Color; use cosmic::iced_style::Color;
use cosmic::theme::Button; use cosmic::theme::Button;
use cosmic::widget::rectangle_tracker::rectangle_tracker_subscription;
use cosmic::widget::rectangle_tracker::RectangleTracker;
use cosmic::widget::rectangle_tracker::RectangleUpdate;
use cosmic::widget::{horizontal_rule, vertical_rule}; use cosmic::widget::{horizontal_rule, vertical_rule};
use cosmic::{Element, Theme}; use cosmic::{Element, Theme};
use cosmic_panel_config::PanelAnchor; use cosmic_panel_config::PanelAnchor;
@ -42,21 +48,50 @@ pub fn run() -> cosmic::iced::Result {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct Toplevel { struct Toplevel {
id: u32,
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>, toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
desktop_info: DesktopInfo, desktop_info: DesktopInfo,
popup: Option<window::Id>,
} }
#[derive(Clone, Default)] #[derive(Clone, Default)]
struct CosmicAppList { struct CosmicAppList {
theme: Theme, theme: Theme,
popup: Option<window::Id>, popup: Option<window::Id>,
id_ctr: u32, surface_id_ctr: u32,
subscription_ctr: u32, subscription_ctr: u32,
toplevel_ctr: u32,
toplevel_list: Vec<Toplevel>, toplevel_list: Vec<Toplevel>,
config: AppListConfig, config: AppListConfig,
toplevel_sender: Option<Sender<ToplevelRequest>>, toplevel_sender: Option<Sender<ToplevelRequest>>,
applet_helper: CosmicAppletHelper, applet_helper: CosmicAppletHelper,
seat: Option<WlSeat>, seat: Option<WlSeat>,
rectangle_tracker: Option<RectangleTracker<u32>>,
rectangles: HashMap<u32, iced::Rectangle>,
}
impl CosmicAppList {
fn window_size(&self) -> (u32, u32) {
let pixel_size = self.applet_helper.suggested_icon_size();
let padding = 8;
let dot_size = 4;
let spacing = 4;
let mut length = self
.toplevel_list
.iter()
.map(|t| {
(pixel_size + 2 * padding).max((dot_size + spacing) * t.toplevels.len() as u16)
as u32
+ spacing as u32
})
.sum();
length += spacing as u32 * 2 + 2;
let thickness = (pixel_size + 2 * padding + dot_size + spacing) as u32;
match self.applet_helper.anchor {
PanelAnchor::Left | PanelAnchor::Right => (thickness, length),
PanelAnchor::Top | PanelAnchor::Bottom => (length, thickness),
}
}
} }
// TODO DnD after sctk merges DnD // TODO DnD after sctk merges DnD
@ -65,14 +100,15 @@ enum Message {
Toplevel(ToplevelUpdate), Toplevel(ToplevelUpdate),
Favorite(String), Favorite(String),
UnFavorite(String), UnFavorite(String),
TogglePopup(usize), Popup(String),
Activate(ZcosmicToplevelHandleV1), Activate(ZcosmicToplevelHandleV1),
Exec(String), Exec(String),
Quit(ZcosmicToplevelHandleV1), Quit(String),
Errored(String), Errored(String),
Ignore, Ignore,
NewSeat(WlSeat), NewSeat(WlSeat),
RemovedSeat(WlSeat), RemovedSeat(WlSeat),
Rectangle((RectangleUpdate<u32>)),
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -88,13 +124,21 @@ fn desktop_info_for_app_ids(mut app_ids: Vec<String>) -> Vec<DesktopInfo> {
.filter_map(|path| { .filter_map(|path| {
std::fs::read_to_string(&path).ok().and_then(|input| { std::fs::read_to_string(&path).ok().and_then(|input| {
DesktopEntry::decode(&path, &input).ok().and_then(|de| { DesktopEntry::decode(&path, &input).ok().and_then(|de| {
if let Some(i) = app_ids.iter().position(|s| s == de.appid || s.eq(&de.name(None).unwrap_or_default())) { if let Some(i) = app_ids
.iter()
.position(|s| s == de.appid || s.eq(&de.name(None).unwrap_or_default()))
{
let id = app_ids.remove(i); let id = app_ids.remove(i);
freedesktop_icons::lookup(de.icon().unwrap_or(de.appid)) freedesktop_icons::lookup(de.icon().unwrap_or(de.appid))
.with_size(128) .with_size(128)
.with_cache() .with_cache()
.find() .find()
.map(|buf| DesktopInfo {id, icon: buf, exec: de.exec().unwrap_or_default().to_string(), name: de.name(None).unwrap_or_default().to_string()}) .map(|buf| DesktopInfo {
id,
icon: buf,
exec: de.exec().unwrap_or_default().to_string(),
name: de.name(None).unwrap_or_default().to_string(),
})
} else { } else {
None None
} }
@ -105,7 +149,10 @@ fn desktop_info_for_app_ids(mut app_ids: Vec<String>) -> Vec<DesktopInfo> {
ret.append( ret.append(
&mut app_ids &mut app_ids
.into_iter() .into_iter()
.map(|id| DesktopInfo { id, ..Default::default() }) .map(|id| DesktopInfo {
id,
..Default::default()
})
.collect_vec(), .collect_vec(),
); );
ret ret
@ -119,16 +166,23 @@ impl Application for CosmicAppList {
fn new(_flags: ()) -> (Self, Command<Message>) { fn new(_flags: ()) -> (Self, Command<Message>) {
let config = config::AppListConfig::load().unwrap_or_default(); let config = config::AppListConfig::load().unwrap_or_default();
let mut toplevel_ctr = 0;
( (
CosmicAppList { CosmicAppList {
toplevel_list: desktop_info_for_app_ids(config.favorites.clone()) toplevel_list: desktop_info_for_app_ids(config.favorites.clone())
.into_iter() .into_iter()
.map(|e| Toplevel { .map(|e| {
toplevels: Default::default(), toplevel_ctr += 1;
desktop_info: e Toplevel {
id: toplevel_ctr,
toplevels: Default::default(),
desktop_info: e,
popup: None,
}
}) })
.collect(), .collect(),
config, config,
toplevel_ctr,
..Default::default() ..Default::default()
}, },
Command::none(), Command::none(),
@ -144,21 +198,45 @@ impl Application for CosmicAppList {
Message::Errored(_) => { Message::Errored(_) => {
// TODO log errors // TODO log errors
} }
Message::TogglePopup(_) => { Message::Popup(id) => {
if let Some(p) = self.popup.take() { if let Some(toplevel_group) = self
return destroy_popup(p); .toplevel_list
} else { .iter_mut()
self.id_ctr += 1; .find(|t| t.desktop_info.id == id)
let new_id = window::Id::new(self.id_ctr); {
self.popup.replace(new_id); if let Some(p) = self.popup.take() {
toplevel_group.popup.take();
return destroy_popup(p);
}
let rectangle = match self.rectangles.get(&toplevel_group.id) {
Some(r) => r,
None => return Command::none(),
};
let popup_settings = self.applet_helper.get_popup_settings( self.surface_id_ctr += 1;
let new_id = window::Id::new(self.surface_id_ctr);
self.popup.replace(new_id);
toplevel_group.popup.replace(new_id);
let mut popup_settings = self.applet_helper.get_popup_settings(
window::Id::new(0), window::Id::new(0),
new_id, new_id,
(400, 240), (200, 240 + toplevel_group.toplevels.len() as u32 * 20),
None, 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,
};
return get_popup(popup_settings); return get_popup(popup_settings);
} }
} }
@ -167,58 +245,58 @@ impl Application for CosmicAppList {
} }
Message::UnFavorite(id) => { Message::UnFavorite(id) => {
let _ = self.config.remove_favorite(id); let _ = self.config.remove_favorite(id);
self.toplevel_list.retain(|t| {
self.config.favorites.contains(&t.desktop_info.id)
|| self.config.favorites.contains(&t.desktop_info.name)
})
} }
Message::Activate(handle) => { Message::Activate(handle) => {
if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref()) { if let Some(p) = self.popup.take() {
if let Some(toplevel_group) =
self.toplevel_list.iter_mut().find(|t| t.popup == Some(p))
{
toplevel_group.popup.take();
}
return destroy_popup(p);
}
if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref())
{
let _ = tx.send(ToplevelRequest::Activate(handle, seat.clone())); let _ = tx.send(ToplevelRequest::Activate(handle, seat.clone()));
} }
} }
Message::Quit(handle) => { Message::Quit(id) => {
if let Some(tx) = self.toplevel_sender.as_ref() { if let Some(toplevel_group) =
let _ = tx.send(ToplevelRequest::Quit(handle)); self.toplevel_list.iter().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()));
}
}
} }
} }
Message::Toplevel(event) => { Message::Toplevel(event) => {
// dbg!(&self.toplevel_list);
match event { match event {
ToplevelUpdate::AddToplevel(handle, info) => { ToplevelUpdate::AddToplevel(handle, info) => {
if info.app_id == "" { if info.app_id == "" {
return Command::none(); return Command::none();
} }
if let Some(i) = self if let Some(i) = self.toplevel_list.iter().position(
.toplevel_list |Toplevel { desktop_info, .. }| &desktop_info.id == &info.app_id,
.iter() ) {
.position(|Toplevel { desktop_info, .. }| &desktop_info.id == &info.app_id)
{
self.toplevel_list[i].toplevels.push((handle, info)); self.toplevel_list[i].toplevels.push((handle, info));
} else { } else {
let desktop_info = let desktop_info =
desktop_info_for_app_ids(vec![info.app_id.clone()]).remove(0); desktop_info_for_app_ids(vec![info.app_id.clone()]).remove(0);
self.toplevel_ctr += 1;
self.toplevel_list.push(Toplevel { self.toplevel_list.push(Toplevel {
id: self.toplevel_ctr,
toplevels: vec![(handle, info)], toplevels: vec![(handle, info)],
desktop_info desktop_info,
popup: None,
}); });
// TODO better way of setting window size?
let pixel_size = self.applet_helper.suggested_icon_size(); let (w, h) = self.window_size();
let padding = 8;
let dot_size = 4;
let spacing = 4;
let length = self
.toplevel_list
.iter()
.map(|t| {
(pixel_size + 2 * padding)
.max((dot_size + spacing) * t.toplevels.len() as u16)
as u32
+ spacing as u32
})
.sum();
let thickness = (pixel_size + 2 * padding + dot_size + spacing) as u32;
let (w, h) = match self.applet_helper.anchor {
PanelAnchor::Left | PanelAnchor::Right => (thickness, length),
PanelAnchor::Top | PanelAnchor::Bottom => (length, thickness),
};
return resize_window(window::Id::new(0), w, h); return resize_window(window::Id::new(0), w, h);
} }
} }
@ -234,11 +312,15 @@ impl Application for CosmicAppList {
ToplevelUpdate::RemoveToplevel(handle) => { ToplevelUpdate::RemoveToplevel(handle) => {
if let Some(i) = self.toplevel_list.iter_mut().position( if let Some(i) = self.toplevel_list.iter_mut().position(
|Toplevel { |Toplevel {
toplevels, desktop_info, .. toplevels,
desktop_info,
..
}| { }| {
if let Some(ret) = toplevels.iter().position(|t| &t.0 == &handle) { if let Some(ret) = toplevels.iter().position(|t| &t.0 == &handle) {
toplevels.remove(ret); toplevels.remove(ret);
toplevels.is_empty() && self.config.favorites.contains(&desktop_info.id) toplevels.is_empty()
&& !self.config.favorites.contains(&desktop_info.id)
&& !self.config.favorites.contains(&desktop_info.name)
} else { } else {
false false
} }
@ -246,26 +328,7 @@ impl Application for CosmicAppList {
) { ) {
self.toplevel_list.remove(i); self.toplevel_list.remove(i);
} }
// TODO better way of setting window size? let (w, h) = self.window_size();
let pixel_size = self.applet_helper.suggested_icon_size();
let padding = 8;
let dot_size = 4;
let spacing = 4;
let length = self
.toplevel_list
.iter()
.map(|t| {
(pixel_size + 2 * padding)
.max((dot_size + spacing) * t.toplevels.len() as u16)
as u32
+ spacing as u32
})
.sum();
let thickness = (pixel_size + 2 * padding + dot_size + spacing) as u32;
let (w, h) = match self.applet_helper.anchor {
PanelAnchor::Left | PanelAnchor::Right => (thickness, length),
PanelAnchor::Top | PanelAnchor::Bottom => (length, thickness),
};
return resize_window(window::Id::new(0), w, h); return resize_window(window::Id::new(0), w, h);
} }
ToplevelUpdate::UpdateToplevel(handle, info) => { ToplevelUpdate::UpdateToplevel(handle, info) => {
@ -281,37 +344,17 @@ impl Application for CosmicAppList {
} }
} }
} }
// TODO better way of setting window size? let (w, h) = self.window_size();
let pixel_size = self.applet_helper.suggested_icon_size();
let padding = 8;
let dot_size = 4;
let spacing = 4;
let length = self
.toplevel_list
.iter()
.map(|t| {
(pixel_size + 2 * padding)
.max((dot_size + spacing) * t.toplevels.len() as u16)
as u32
+ spacing as u32
})
.sum();
let thickness = (pixel_size + 2 * padding + dot_size + spacing) as u32;
let (w, h) = match self.applet_helper.anchor {
PanelAnchor::Left | PanelAnchor::Right => (thickness, length),
PanelAnchor::Top | PanelAnchor::Bottom => (length, thickness),
};
return resize_window(window::Id::new(0), w, h); return resize_window(window::Id::new(0), w, h);
} }
} }
} }
Message::Ignore => {}
Message::NewSeat(s) => { Message::NewSeat(s) => {
self.seat.replace(s); self.seat.replace(s);
}, }
Message::RemovedSeat(_) => { Message::RemovedSeat(_) => {
self.seat.take(); self.seat.take();
}, }
Message::Exec(exec_str) => { Message::Exec(exec_str) => {
let mut exec = shlex::Shlex::new(&exec_str); let mut exec = shlex::Shlex::new(&exec_str);
let mut cmd = match exec.next() { let mut cmd = match exec.next() {
@ -325,7 +368,16 @@ impl Application for CosmicAppList {
} }
} }
let _ = cmd.spawn(); let _ = cmd.spawn();
}
Message::Rectangle(u) => match u {
RectangleUpdate::Rectangle(r) => {
self.rectangles.insert(r.0, r.1);
}
RectangleUpdate::Init(tracker) => {
self.rectangle_tracker.replace(tracker);
}
}, },
Message::Ignore => {}
} }
Command::none() Command::none()
} }
@ -334,16 +386,15 @@ impl Application for CosmicAppList {
match id { match id {
SurfaceIdWrapper::LayerSurface(_) => unimplemented!(), SurfaceIdWrapper::LayerSurface(_) => unimplemented!(),
SurfaceIdWrapper::Window(_) => { SurfaceIdWrapper::Window(_) => {
let (favorites, running) = self.toplevel_list.iter().enumerate().fold( let (favorites, running) = self.toplevel_list.iter().fold(
(Vec::new(), Vec::new()), (Vec::new(), Vec::new()),
|(mut favorites, mut running), |(mut favorites, mut running),
( Toplevel {
i, id,
Toplevel { toplevels,
toplevels, desktop_info,
desktop_info ..
}, }| {
)| {
let icon = if desktop_info.icon.extension() == Some(&OsStr::new("svg")) { let icon = if desktop_info.icon.extension() == Some(&OsStr::new("svg")) {
let handle = svg::Handle::from_path(&desktop_info.icon); let handle = svg::Handle::from_path(&desktop_info.icon);
svg::Svg::new(handle) svg::Svg::new(handle)
@ -396,12 +447,28 @@ impl Application for CosmicAppList {
.into(), .into(),
}; };
// TODO tooltip on hover // TODO tooltip on hover
let icon_button = cosmic::widget::button(Button::Text) let icon_button = event_container(
.custom(vec![icon_wrapper]) cosmic::widget::button(Button::Text)
.on_press(toplevels.first().map(|t| Message::Activate(t.0.clone())).unwrap_or_else(|| Message::Exec(desktop_info.exec.clone()))) .custom(vec![icon_wrapper])
.padding(8) .on_press(
.into(); toplevels
if self.config.favorites.contains(&desktop_info.id) || self.config.favorites.contains(&desktop_info.name) { .first()
.map(|t| Message::Activate(t.0.clone()))
.unwrap_or_else(|| {
Message::Exec(desktop_info.exec.clone())
}),
)
.padding(8),
)
.on_right_release(Message::Popup(desktop_info.id.clone()));
let icon_button = if let Some(tracker) = self.rectangle_tracker.as_ref() {
tracker.container(*id, icon_button).into()
} else {
icon_button.into()
};
if self.config.favorites.contains(&desktop_info.id)
|| self.config.favorites.contains(&desktop_info.name)
{
favorites.push(icon_button) favorites.push(icon_button)
} else { } else {
running.push(icon_button); running.push(icon_button);
@ -409,6 +476,7 @@ impl Application for CosmicAppList {
(favorites, running) (favorites, running)
}, },
); );
match &self.applet_helper.anchor { match &self.applet_helper.anchor {
PanelAnchor::Left | PanelAnchor::Right => { PanelAnchor::Left | PanelAnchor::Right => {
column![column(favorites), horizontal_rule(1), column(running)] column![column(favorites), horizontal_rule(1), column(running)]
@ -428,8 +496,69 @@ impl Application for CosmicAppList {
} }
} }
} }
SurfaceIdWrapper::Popup(_) => { SurfaceIdWrapper::Popup(p) => {
todo!(); if let Some(Toplevel {
toplevels,
desktop_info,
..
}) = self.toplevel_list.iter().find(|t| t.popup == Some(p))
{
let is_favorite = self.config.favorites.contains(&desktop_info.id)
|| self.config.favorites.contains(&desktop_info.name);
let mut content = column![
iced::widget::text(&desktop_info.name),
cosmic::widget::button(Button::Text)
.custom(vec![iced::widget::text(fl!("new-window")).into()])
.on_press(Message::Exec(desktop_info.exec.clone())),
]
.padding(4)
.spacing(4)
.align_items(Alignment::Center);
if !toplevels.is_empty() {
let mut list_col = column![];
for (handle, info) in toplevels {
let title = if info.title.len() > 20 {
format!("{:.20}...", &info.title)
} else {
info.title.clone()
};
list_col = list_col.push(
cosmic::widget::button(Button::Text)
.custom(vec![iced::widget::text(title).into()])
.on_press(Message::Activate(handle.clone())),
);
}
content = content.push(horizontal_rule(1));
content = content.push(list_col);
content = content.push(horizontal_rule(1));
}
content = content.push(if is_favorite {
cosmic::widget::button(Button::Text)
.custom(vec![iced::widget::text(fl!("unfavorite")).into()])
.on_press(Message::UnFavorite(desktop_info.id.clone()))
} else {
cosmic::widget::button(Button::Text)
.custom(vec![iced::widget::text(fl!("favorite")).into()])
.on_press(Message::Favorite(desktop_info.id.clone()))
});
if toplevels.len() == 1 {
content = content.push(
cosmic::widget::button(Button::Text)
.custom(vec![iced::widget::text(fl!("quit")).into()])
.on_press(Message::Quit(desktop_info.id.clone())),
)
} else if toplevels.len() > 1 {
content = content.push(
cosmic::widget::button(Button::Text)
.custom(vec![iced::widget::text(&fl!("quit-all")).into()])
.on_press(Message::Quit(desktop_info.id.clone())),
)
}
return self.applet_helper.popup_container(content).into();
}
return horizontal_space(Length::Units(0)).into();
} }
} }
} }
@ -437,7 +566,7 @@ impl Application for CosmicAppList {
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
Subscription::batch(vec![ Subscription::batch(vec![
toplevel_subscription(self.subscription_ctr).map(|(_, event)| Message::Toplevel(event)), toplevel_subscription(self.subscription_ctr).map(|(_, event)| Message::Toplevel(event)),
events_with(|e, status| match e { events_with(|e, _| match e {
cosmic::iced_native::Event::PlatformSpecific( cosmic::iced_native::Event::PlatformSpecific(
cosmic::iced_native::event::PlatformSpecific::Wayland( cosmic::iced_native::event::PlatformSpecific::Wayland(
cosmic::iced_native::event::wayland::Event::Seat(e, seat), cosmic::iced_native::event::wayland::Event::Seat(e, seat),
@ -445,13 +574,14 @@ impl Application for CosmicAppList {
) => match e { ) => match e {
cosmic::iced_native::event::wayland::SeatEvent::Enter => { cosmic::iced_native::event::wayland::SeatEvent::Enter => {
Some(Message::NewSeat(seat)) Some(Message::NewSeat(seat))
}, }
cosmic::iced_native::event::wayland::SeatEvent::Leave => { cosmic::iced_native::event::wayland::SeatEvent::Leave => {
Some(Message::RemovedSeat(seat)) Some(Message::RemovedSeat(seat))
}, }
}, },
_ => None _ => None,
}), }),
rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),
]) ])
} }

View file

@ -1,5 +1,5 @@
use anyhow::anyhow; use anyhow::anyhow;
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
use std::fs::File; use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;

View file

@ -16,7 +16,6 @@ fn main() -> cosmic::iced::Result {
pretty_env_logger::init(); pretty_env_logger::init();
info!("Iced Workspaces Applet ({})", APP_ID); info!("Iced Workspaces Applet ({})", APP_ID);
info!("Version: {}", VERSION); info!("Version: {}", VERSION);
config::AppListConfig::default().save().unwrap();
// Prepare i18n // Prepare i18n
localize(); localize();

View file

@ -1,18 +1,19 @@
use crate::toplevel_subscription::{ToplevelRequest, ToplevelUpdate};
use cctk::{ use cctk::{
sctk::{self, event_loop::WaylandSource, seat::{SeatHandler, SeatState}, reexports::client::protocol::wl_seat::WlSeat}, sctk::{
self,
event_loop::WaylandSource,
reexports::client::protocol::wl_seat::WlSeat,
seat::{SeatHandler, SeatState},
},
toplevel_info::{ToplevelInfoHandler, ToplevelInfoState}, toplevel_info::{ToplevelInfoHandler, ToplevelInfoState},
toplevel_management::{ToplevelManagerHandler, ToplevelManagerState}, toplevel_management::{ToplevelManagerHandler, ToplevelManagerState},
wayland_client::{self, Proxy, backend::ObjectId}, wayland_client,
};
use cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1,
toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
}; };
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1;
use futures::channel::mpsc::UnboundedSender; use futures::channel::mpsc::UnboundedSender;
use sctk::registry::{ProvidesRegistryState, RegistryState}; use sctk::registry::{ProvidesRegistryState, RegistryState};
use wayland_client::{globals::registry_queue_init, Connection, QueueHandle}; use wayland_client::{globals::registry_queue_init, Connection, QueueHandle};
use itertools::Itertools;
use crate::toplevel_subscription::{ToplevelRequest, ToplevelUpdate};
struct AppData { struct AppData {
exit: bool, exit: bool,
@ -36,25 +37,27 @@ impl SeatHandler for AppData {
&mut self.seat_state &mut self.seat_state
} }
fn new_seat(&mut self, conn: &Connection, qh: &QueueHandle<Self>, seat: WlSeat) {} fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlSeat) {}
fn new_capability( fn new_capability(
&mut self, &mut self,
conn: &Connection, _: &Connection,
qh: &QueueHandle<Self>, _: &QueueHandle<Self>,
seat: WlSeat, _: WlSeat,
capability: sctk::seat::Capability, _: sctk::seat::Capability,
) {} ) {
}
fn remove_capability( fn remove_capability(
&mut self, &mut self,
conn: &Connection, _: &Connection,
qh: &QueueHandle<Self>, _: &QueueHandle<Self>,
seat: WlSeat, _: WlSeat,
capability: sctk::seat::Capability, _: sctk::seat::Capability,
) {} ) {
}
fn remove_seat(&mut self, conn: &Connection, qh: &QueueHandle<Self>, seat: WlSeat) {} fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlSeat) {}
} }
impl ToplevelManagerHandler for AppData { impl ToplevelManagerHandler for AppData {
@ -62,7 +65,7 @@ impl ToplevelManagerHandler for AppData {
&mut self.toplevel_manager_state &mut self.toplevel_manager_state
} }
fn capabilities(&mut self, conn: &Connection, qh: &QueueHandle<Self>, capabilities: Vec<u8>) { fn capabilities(&mut self, _: &Connection, _: &QueueHandle<Self>, _: Vec<u8>) {
// TODO capabilities could affect the options in the applet // TODO capabilities could affect the options in the applet
} }
} }
@ -133,11 +136,13 @@ pub(crate) fn toplevel_handler(
.insert_source(rx, |event, _, state| match event { .insert_source(rx, |event, _, state| match event {
calloop::channel::Event::Msg(req) => match req { calloop::channel::Event::Msg(req) => match req {
ToplevelRequest::Activate(handle, seat) => { ToplevelRequest::Activate(handle, seat) => {
let manager = &state.toplevel_manager_state.manager; let manager = &state.toplevel_manager_state.manager;
manager.activate(&handle, &seat); manager.activate(&handle, &seat);
} // TODO }
ToplevelRequest::Quit(_) => {} // TODO ToplevelRequest::Quit(handle) => {
let manager = &state.toplevel_manager_state.manager;
manager.close(&handle);
}
ToplevelRequest::Exit => { ToplevelRequest::Exit => {
state.exit = true; state.exit = true;
} }