Skip to content

Commit

Permalink
Move the wayland backend into the app_tray module
Browse files Browse the repository at this point in the history
This PR moves the wayland backend and the app tray config into the AppTray struct in the app_tray module. This is much cleaner than having it in the overarching Panel struct. Also looking into turning the AppTray struct into an iced component, as it's relatively self-contained, and should only need a config passed into it

Signed-off-by: Ryan Brue <ryanbrue.dev@gmail.com>
  • Loading branch information
ryanabx committed Aug 21, 2024
1 parent 197c154 commit 67e06ef
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ use iced::{
};
use once_cell::sync::Lazy;

use crate::{
app_tray::{self, AppTray},
use crate::app_tray::{
self,
compositor::{WindowHandle, WindowInfo},
AppTray, AppTrayMessage, ApplicationGroup,
};

use super::WaylandOutgoing;
Expand Down Expand Up @@ -534,9 +535,9 @@ impl CosmicCompBackend {

pub fn handle_incoming(
&mut self,
app_tray: &mut crate::app_tray::AppTray,
active_toplevels: &mut HashMap<String, ApplicationGroup>,
incoming: CosmicIncoming,
) -> Option<iced::Command<crate::Message>> {
) -> Option<iced::Command<AppTrayMessage>> {
match incoming {
CosmicIncoming::Init(wayland_sender) => {
self.wayland_sender.replace(wayland_sender);
Expand All @@ -546,15 +547,14 @@ impl CosmicCompBackend {
CosmicIncoming::Toplevel(toplevel_update) => match toplevel_update {
ToplevelUpdate::Add(handle, info) => {
let app_id = info.app_id.clone();
if app_tray.active_toplevels.contains_key(&app_id) {
app_tray
.active_toplevels
if active_toplevels.contains_key(&app_id) {
active_toplevels
.get_mut(&info.app_id)
.unwrap()
.toplevels
.insert(WindowHandle::Cosmic(handle), WindowInfo::Cosmic(info));
} else {
app_tray.active_toplevels.insert(
active_toplevels.insert(
app_id.clone(),
crate::app_tray::ApplicationGroup {
toplevels: HashMap::from([(
Expand All @@ -570,15 +570,12 @@ impl CosmicCompBackend {
// TODO probably want to make sure it is removed
if info.app_id.is_empty() {
return Some(iced::Command::none());
} else if !app_tray.active_toplevels.contains_key(&info.app_id) {
} else if !active_toplevels.contains_key(&info.app_id) {
return Some(iced::Command::none());
}

for (t_handle, t_info) in &mut app_tray
.active_toplevels
.get_mut(&info.app_id)
.unwrap()
.toplevels
for (t_handle, t_info) in
&mut active_toplevels.get_mut(&info.app_id).unwrap().toplevels
{
if let WindowHandle::Cosmic(c_handle) = t_handle {
if &handle == c_handle {
Expand All @@ -592,7 +589,7 @@ impl CosmicCompBackend {
}
ToplevelUpdate::Remove(handle) => {
let mut target_app_id: Option<String> = None;
for (app_id, app_info) in app_tray.active_toplevels.iter_mut() {
for (app_id, app_info) in active_toplevels.iter_mut() {
if app_info
.toplevels
.contains_key(&WindowHandle::Cosmic(handle.clone()))
Expand All @@ -605,7 +602,7 @@ impl CosmicCompBackend {
}
}
if let Some(app_id) = target_app_id {
app_tray.active_toplevels.remove(&app_id);
active_toplevels.remove(&app_id);
}
None
}
Expand Down Expand Up @@ -634,9 +631,9 @@ impl CosmicCompBackend {

pub fn handle_outgoing(
&mut self,
app_tray: &mut crate::app_tray::AppTray,
active_toplevels: &mut HashMap<String, ApplicationGroup>,
outgoing: WaylandOutgoing,
) -> Option<iced::Command<crate::Message>> {
) -> Option<iced::Command<AppTrayMessage>> {
match outgoing {
WaylandOutgoing::Exec(app_id, exec) => {
println!("Sending a tokenrequest {} {}", &app_id, &exec);
Expand All @@ -655,7 +652,7 @@ impl CosmicCompBackend {
if let Some(tx) = self.wayland_sender.as_ref() {
let _ = tx.send(WaylandRequest::Toplevel(
if self
.active_window(app_tray)
.active_window(active_toplevels)
.is_some_and(|x| x == WindowHandle::Cosmic(toplevel.clone()))
{
ToplevelRequest::Minimize(toplevel)
Expand Down Expand Up @@ -691,13 +688,16 @@ impl CosmicCompBackend {
}
}

pub fn active_window(&self, app_tray: &AppTray) -> Option<WindowHandle> {
pub fn active_window(
&self,
active_toplevels: &HashMap<String, ApplicationGroup>,
) -> Option<WindowHandle> {
if self.active_workspaces.is_empty() {
return None;
}
let mut focused_toplevels: Vec<ZcosmicToplevelHandleV1> = Vec::new();
let active_workspaces = self.active_workspaces.clone();
for (_, app_group) in app_tray.active_toplevels.iter() {
for (_, app_group) in active_toplevels.iter() {
for (handle, info) in &app_group.toplevels {
if let (WindowHandle::Cosmic(t_handle), WindowInfo::Cosmic(t_info)) = (handle, info)
{
Expand Down
23 changes: 14 additions & 9 deletions src/compositor/mod.rs → src/app_tray/compositor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use std::env;
use std::{collections::HashMap, env};

use cctk::toplevel_info::ToplevelInfo;
use cosmic_comp::CosmicCompBackend;
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;

use crate::app_tray::AppTray;

use super::{AppTrayMessage, ApplicationGroup};

pub mod cosmic_comp;
pub mod wlr;

Expand Down Expand Up @@ -39,12 +41,12 @@ impl CompositorBackend {

pub fn handle_message(
&mut self,
app_tray: &mut AppTray,
active_toplevels: &mut HashMap<String, ApplicationGroup>,
incoming: WaylandIncoming,
) -> Option<iced::Command<crate::Message>> {
) -> Option<iced::Command<AppTrayMessage>> {
match (self, incoming) {
(Self::Cosmic(backend), WaylandIncoming::Cosmic(evt)) => {
backend.handle_incoming(app_tray, evt)
backend.handle_incoming(active_toplevels, evt)
}
(Self::NotSupported, _) => todo!(),
(_, WaylandIncoming::NotSupported) => todo!(),
Expand All @@ -53,18 +55,21 @@ impl CompositorBackend {

pub fn handle_outgoing_message(
&mut self,
app_tray: &mut AppTray,
active_toplevels: &mut HashMap<String, ApplicationGroup>,
outgoing: WaylandOutgoing,
) -> Option<iced::Command<crate::Message>> {
) -> Option<iced::Command<AppTrayMessage>> {
match self {
Self::Cosmic(backend) => backend.handle_outgoing(app_tray, outgoing),
Self::Cosmic(backend) => backend.handle_outgoing(active_toplevels, outgoing),
_ => todo!(),
}
}

pub fn active_window<'a>(&self, app_tray: &AppTray<'a>) -> Option<WindowHandle> {
pub fn active_window<'a>(
&self,
active_toplevels: &HashMap<String, ApplicationGroup>,
) -> Option<WindowHandle> {
match self {
Self::Cosmic(backend) => backend.active_window(app_tray),
Self::Cosmic(backend) => backend.active_window(active_toplevels),
_ => todo!(),
}
}
Expand Down
File renamed without changes.
139 changes: 126 additions & 13 deletions src/app_tray/mod.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,164 @@
use std::{collections::HashMap, path::PathBuf};

use cctk::wayland_client::protocol::wl_seat::WlSeat;
use compositor::WaylandIncoming;
use desktop_entry::DesktopEntryCache;
use freedesktop_desktop_entry::DesktopEntry;
use iced::{
event::{self, listen_with},
widget::{button, column, row, Container, Rule},
Background, Border, Color, Length, Radius, Theme,
Background, Border, Color, Element, Length, Radius, Theme,
};

use crate::{
compositor::{WaylandOutgoing, WindowHandle, WindowInfo},
app_tray::compositor::{CompositorBackend, WaylandOutgoing, WindowHandle, WindowInfo},
Message,
};

mod compositor;
pub mod desktop_entry;

#[derive(Clone, Debug)]
pub struct AppTray<'a> {
pub de_cache: DesktopEntryCache<'a>,
pub active_toplevels: HashMap<String, ApplicationGroup>,
backend: CompositorBackend,
config: AppTrayConfig,
}

impl<'a> Default for AppTray<'a> {
#[derive(Clone, Debug)]
pub struct AppTrayConfig {
pub favorites: Vec<String>,
}

#[derive(Clone, Debug)]
pub enum AppTrayMessage {
WaylandIn(WaylandIncoming),
WaylandOut(WaylandOutgoing),
NewSeat(WlSeat),
RemovedSeat(WlSeat),
}

#[derive(Clone, Debug)]
pub enum AppTrayRequest {
Window(WindowOperationMessage),
}

#[derive(Clone, Debug)]
pub enum WindowOperationMessage {
Activate(WindowHandle),
Minimize(WindowHandle),
Launch(String),
}

impl<'a> Default for AppTrayConfig {
fn default() -> Self {
Self {
favorites: vec![
"com.system76.CosmicTerm".to_string(),
"org.mozilla.firefox".to_string(),
"org.kde.discover".to_string(),
],
}
}
}

impl<'a> AppTray<'a> {
pub fn new(config: AppTrayConfig) -> Self {
Self {
de_cache: DesktopEntryCache::new(),
active_toplevels: HashMap::new(),
backend: CompositorBackend::new(),
config,
}
}

pub fn handle_message(
&mut self,
app_tray_message: AppTrayMessage,
) -> iced::Command<AppTrayMessage> {
match app_tray_message {
AppTrayMessage::WaylandIn(evt) => self
.backend
.handle_message(&mut self.active_toplevels, evt)
.unwrap_or(iced::Command::none()),
AppTrayMessage::WaylandOut(evt) => self
.backend
.handle_outgoing_message(&mut self.active_toplevels, evt)
.unwrap_or(iced::Command::none()),
AppTrayMessage::NewSeat(_) => {
println!("New seat!");
iced::Command::none()
}
AppTrayMessage::RemovedSeat(_) => {
println!("Removed seat!");
iced::Command::none()
}
}
}

pub fn view(&self) -> iced::Element<AppTrayMessage> {
// Get app tray apps
let app_tray_apps = self
.config
.favorites
.iter()
.map(|x| {
let app_id = x.clone();
(
app_id,
self.active_toplevels
.get(x)
.cloned()
.unwrap_or(ApplicationGroup::default()),
)
})
.chain(self.active_toplevels.iter().filter_map(|(app_id, info)| {
if self.config.favorites.contains(app_id) {
None
} else {
Some((app_id.clone(), info.clone()))
}
}))
.map(|(app_id, group)| {
let entry = &self.de_cache.0.get(&app_id);
let active_window = self.backend.active_window(&self.active_toplevels);

get_tray_widget(&app_id, *entry, group, active_window.map(|f| f.clone()))
})
.map(|x| Element::from(iced::widget::container(x).width(48).height(48).padding(6)));
iced::widget::row(app_tray_apps).into()
}

pub fn subscription(&self) -> iced::Subscription<AppTrayMessage> {
iced::Subscription::batch(vec![
self.backend
.wayland_subscription()
.map(AppTrayMessage::WaylandIn),
listen_with(|e, _, _| match e {
iced::Event::PlatformSpecific(event::PlatformSpecific::Wayland(
event::wayland::Event::Seat(e, seat),
)) => match e {
event::wayland::SeatEvent::Enter => Some(AppTrayMessage::NewSeat(seat)),
event::wayland::SeatEvent::Leave => Some(AppTrayMessage::RemovedSeat(seat)),
},
_ => None,
}),
])
}
}

#[derive(Clone, Debug, Default)]
pub struct ApplicationGroup {
pub toplevels: HashMap<WindowHandle, WindowInfo>,
}

impl<'a> AppTray<'a> {
pub fn get_desktop_entry(&mut self, app_id: &str) -> Option<DesktopEntry<'a>> {
self.de_cache.0.get(app_id).cloned()
}
}

pub fn get_tray_widget<'a>(
app_id: &str,
desktop_entry: Option<&DesktopEntry<'a>>,
app_info: ApplicationGroup,
active_window: Option<WindowHandle>,
) -> iced::widget::Button<'a, crate::Message> {
) -> iced::widget::Button<'a, AppTrayMessage> {
let icon_path = desktop_entry
.and_then(|entry| entry.icon())
.and_then(|icon| freedesktop_icons::lookup(icon).with_cache().find())
Expand Down Expand Up @@ -79,10 +192,10 @@ pub fn get_tray_widget<'a>(
.padding(4)
.on_press_maybe(if app_info.toplevels.is_empty() {
desktop_entry.and_then(|entry| entry.exec()).map(|exec| {
Message::WaylandOut(WaylandOutgoing::Exec(app_id.to_string(), exec.to_string()))
AppTrayMessage::WaylandOut(WaylandOutgoing::Exec(app_id.to_string(), exec.to_string()))
})
} else if app_info.toplevels.len() == 1 {
Some(Message::WaylandOut(WaylandOutgoing::Toggle(
Some(AppTrayMessage::WaylandOut(WaylandOutgoing::Toggle(
app_info.toplevels.keys().next().unwrap().clone(),
)))
} else {
Expand All @@ -104,7 +217,7 @@ pub fn get_tray_widget<'a>(
fn get_horizontal_rule<'a>(
app_info: &ApplicationGroup,
active_window: &Option<&WindowHandle>,
) -> Container<'a, Message> {
) -> Container<'a, AppTrayMessage> {
if app_info.toplevels.is_empty() {
iced::widget::container(iced::widget::Space::new(
Length::Fixed(6.0),
Expand Down
12 changes: 2 additions & 10 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
#[derive(Debug, Clone)]
pub struct PanelConfig {
pub favorites: Vec<String>,
}
pub struct PanelConfig {}

impl<'a> Default for PanelConfig {
fn default() -> Self {
Self {
favorites: vec![
"com.system76.CosmicTerm".to_string(),
"org.mozilla.firefox".to_string(),
"org.kde.discover".to_string(),
],
}
Self {}
}
}
Loading

0 comments on commit 67e06ef

Please sign in to comment.