Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
858 changes: 576 additions & 282 deletions Cargo.lock

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ features = ["io-util"]

# [patch.crates-io]
# freedesktop-desktop-entry = { path = "../freedesktop-desktop-entry" }

[patch."https://github.com/pop-os/cosmic-protocols"]
"cosmic-client-toolkit" = { git = "https://github.com/pop-os//cosmic-protocols" }
1 change: 1 addition & 0 deletions bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ tracing-subscriber = { version = "0.3.18", default-features = false, features =
"env-filter",
"chrono",
] }
tracing-journald = "0.3.0"
dirs.workspace = true
mimalloc = "0.1.43"

Expand Down
31 changes: 20 additions & 11 deletions bin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ use pop_launcher_toolkit::plugins;
use pop_launcher_toolkit::service;

use mimalloc::MiMalloc;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::EnvFilter;
use tracing::info;

#[global_allocator]
static GLOBAL: MiMalloc = MiMalloc;
Expand All @@ -19,6 +18,8 @@ async fn main() {

init_logging(cmd);

info!("starting {}", cmd);

match cmd {
"calc" => plugins::calc::main().await,
"desktop-entries" => plugins::desktop_entries::main().await,
Expand All @@ -39,9 +40,9 @@ async fn main() {
}
}

// todo: support journald once this issue is resolved: https://github.com/tokio-rs/tracing/issues/2348
fn init_logging(cmd: &str) {
use tracing_subscriber::{fmt, Registry};
use tracing_subscriber::prelude::*;
use tracing_subscriber::{fmt, EnvFilter};

let logdir = match dirs::state_dir() {
Some(dir) => dir.join("pop-launcher/"),
Expand All @@ -64,17 +65,25 @@ fn init_logging(cmd: &str) {
}
}

let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("warn"))
.unwrap();

let fmt_layer = fmt::layer()
let fmt_layer = tracing_subscriber::fmt::layer()
.with_target(false)
.with_timer(fmt::time::ChronoLocal::new("%T".into()))
.with_writer(file);

let subscriber = Registry::default().with(filter_layer).with(fmt_layer);
let filter_layer = EnvFilter::try_from_default_env()
.or_else(|_| EnvFilter::try_new("warn"))
.unwrap();

tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber");
let registry = tracing_subscriber::registry()
.with(fmt_layer)
.with(filter_layer);

// would be nice to implement this tracing issue
// for journald https://github.com/tokio-rs/tracing/issues/2348
if let Ok(journald_layer) = tracing_journald::layer() {
registry.with(journald_layer).init();
} else {
registry.init();
}
}
}
76 changes: 39 additions & 37 deletions plugins/src/cosmic_toplevel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
mod toplevel_handler;

use cctk::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::State;
use cctk::wayland_client::Proxy;
use cctk::{cosmic_protocols, sctk::reexports::calloop, toplevel_info::ToplevelInfo};
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
use fde::DesktopEntry;
use freedesktop_desktop_entry as fde;
use toplevel_handler::TopLevelsUpdate;
use tracing::{debug, error, info, warn};

use crate::desktop_entries::utils::get_description;
use crate::desktop_entries::utils::{get_description, is_session_cosmic};
use crate::send;
use futures::{
channel::mpsc,
Expand All @@ -21,14 +24,12 @@ use std::borrow::Cow;
use std::iter;
use tokio::io::{AsyncWrite, AsyncWriteExt};

use self::toplevel_handler::{toplevel_handler, ToplevelAction, ToplevelEvent};
use self::toplevel_handler::{toplevel_handler, ToplevelAction};

pub async fn main() {
tracing::info!("starting cosmic-toplevel");

let mut tx = async_stdout();

if !session_is_cosmic() {
if !is_session_cosmic() {
send(&mut tx, PluginResponse::Deactivate).await;
return;
}
Expand All @@ -47,12 +48,12 @@ pub async fn main() {
match request {
Ok(request) => match request {
Request::Activate(id) => {
tracing::info!("activating {id}");
debug!("activating {id}");
app.activate(id);
}
Request::Quit(id) => app.quit(id),
Request::Search(query) => {
tracing::info!("searching {query}");
debug!("searching {query}");
app.search(&query).await;
// clear the ids to ignore, as all just sent are valid
app.ids_to_ignore.clear();
Expand All @@ -61,27 +62,37 @@ pub async fn main() {
_ => (),
},
Err(why) => {
tracing::error!("malformed JSON request: {}", why);
error!("malformed JSON request: {}", why);
}
};
}
Either::Right((Some(event), second_to_next_request)) => {
Either::Right((Some(updates), second_to_next_request)) => {
next_event = toplevel_rx.next();
next_request = second_to_next_request;
match event {
ToplevelEvent::Add(handle, info) => {
tracing::info!("{}", &info.app_id);
app.toplevels.retain(|t| t.0 != handle);
app.toplevels.push((handle, info));
}
ToplevelEvent::Remove(handle) => {
app.toplevels.retain(|t| t.0 != handle);
// ignore requests for this id until after the next search
app.ids_to_ignore.push(handle.id().protocol_id());
}
ToplevelEvent::Update(handle, info) => {
if let Some(t) = app.toplevels.iter_mut().find(|t| t.0 == handle) {
t.1 = info;

for (handle, info) in updates {
match info {
Some(info) => {
if let Some(pos) = app.toplevels.iter().position(|t| t.0 == handle) {
if info.state.contains(&State::Activated) {
app.toplevels.remove(pos);
app.toplevels.push((handle, Box::new(info)));
} else {
app.toplevels[pos].1 = Box::new(info);
}
} else {
app.toplevels.push((handle, Box::new(info)));
}
}
// no info means remove
None => {
if let Some(pos) = app.toplevels.iter().position(|t| t.0 == handle) {
app.toplevels.remove(pos);
// ignore requests for this id until after the next search
app.ids_to_ignore.push(handle.id().protocol_id());
} else {
warn!("no toplevel to remove");
}
}
}
}
Expand All @@ -95,13 +106,13 @@ struct App<W> {
locales: Vec<String>,
desktop_entries: Vec<DesktopEntry<'static>>,
ids_to_ignore: Vec<u32>,
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
toplevels: Vec<(ZcosmicToplevelHandleV1, Box<ToplevelInfo>)>,
calloop_tx: calloop::channel::Sender<ToplevelAction>,
tx: W,
}

impl<W: AsyncWrite + Unpin> App<W> {
fn new(tx: W) -> (Self, mpsc::UnboundedReceiver<ToplevelEvent>) {
fn new(tx: W) -> (Self, mpsc::UnboundedReceiver<TopLevelsUpdate>) {
let (toplevels_tx, toplevel_rx) = mpsc::unbounded();
let (calloop_tx, calloop_rx) = calloop::channel::channel();
let _handle = std::thread::spawn(move || toplevel_handler(toplevels_tx, calloop_rx));
Expand All @@ -128,7 +139,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
}

fn activate(&mut self, id: u32) {
tracing::info!("requested to activate: {id}");
info!("requested to activate: {id}");
if self.ids_to_ignore.contains(&id) {
return;
}
Expand All @@ -139,7 +150,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
None
}
}) {
tracing::info!("activating: {id}");
info!("activating: {id}");
let _res = self.calloop_tx.send(ToplevelAction::Activate(handle));
}
}
Expand All @@ -162,7 +173,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
async fn search(&mut self, query: &str) {
let query = query.to_ascii_lowercase();

for (handle, info) in &self.toplevels {
for (handle, info) in self.toplevels.iter().rev() {
let entry = if query.is_empty() {
fde::matching::get_best_match(
&[&info.app_id, &info.title],
Expand Down Expand Up @@ -219,12 +230,3 @@ impl<W: AsyncWrite + Unpin> App<W> {
let _ = self.tx.flush().await;
}
}

#[must_use]
fn session_is_cosmic() -> bool {
if let Ok(var) = std::env::var("XDG_CURRENT_DESKTOP") {
return var.contains("COSMIC");
}

false
}
1 change: 1 addition & 0 deletions plugins/src/cosmic_toplevel/plugin.ron
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
query: (persistent: true, priority: High),
bin: (path: "cosmic-toplevel"),
icon: Name("focus-windows-symbolic"),
long_lived: true,
)
64 changes: 30 additions & 34 deletions plugins/src/cosmic_toplevel/toplevel_handler.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use std::collections::HashSet;

use cctk::{
cosmic_protocols,
toplevel_info::{ToplevelInfo, ToplevelInfoHandler, ToplevelInfoState},
toplevel_management::{ToplevelManagerHandler, ToplevelManagerState},
wayland_client::{self, protocol::wl_output::WlOutput, WEnum},
wayland_client::{self, WEnum},
};
use sctk::{
self,
Expand All @@ -15,10 +17,10 @@ use sctk::{
use cosmic_protocols::{
toplevel_info::v1::client::zcosmic_toplevel_handle_v1::{self, ZcosmicToplevelHandleV1},
toplevel_management::v1::client::zcosmic_toplevel_manager_v1,
workspace::v1::server::zcosmic_workspace_handle_v1::ZcosmicWorkspaceHandleV1,
};
use futures::channel::mpsc::UnboundedSender;
use sctk::registry::{ProvidesRegistryState, RegistryState};
use tracing::warn;
use wayland_client::{globals::registry_queue_init, Connection, QueueHandle};

#[derive(Debug, Clone)]
Expand All @@ -27,31 +29,19 @@ pub enum ToplevelAction {
Close(ZcosmicToplevelHandleV1),
}

#[derive(Debug, Clone)]
pub enum ToplevelEvent {
Add(ZcosmicToplevelHandleV1, ToplevelInfo),
Remove(ZcosmicToplevelHandleV1),
Update(ZcosmicToplevelHandleV1, ToplevelInfo),
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Toplevel {
pub name: String,
pub app_id: String,
pub toplevel_handle: ZcosmicToplevelHandleV1,
pub states: Vec<zcosmic_toplevel_handle_v1::State>,
pub output: Option<WlOutput>,
pub workspace: Option<ZcosmicWorkspaceHandleV1>,
}
pub type TopLevelsUpdate = Vec<(
zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
Option<ToplevelInfo>,
)>;

struct AppData {
exit: bool,
tx: UnboundedSender<ToplevelEvent>,
tx: UnboundedSender<TopLevelsUpdate>,
registry_state: RegistryState,
toplevel_info_state: ToplevelInfoState,
toplevel_manager_state: ToplevelManagerState,
seat_state: SeatState,
pending_update: HashSet<zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1>,
}

impl ProvidesRegistryState for AppData {
Expand Down Expand Up @@ -115,11 +105,7 @@ impl ToplevelInfoHandler for AppData {
_qh: &QueueHandle<Self>,
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
) {
if let Some(info) = self.toplevel_info_state.info(toplevel) {
let _ = self
.tx
.unbounded_send(ToplevelEvent::Add(toplevel.clone(), info.clone()));
}
self.pending_update.insert(toplevel.clone());
}

fn update_toplevel(
Expand All @@ -128,11 +114,7 @@ impl ToplevelInfoHandler for AppData {
_qh: &QueueHandle<Self>,
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
) {
if let Some(info) = self.toplevel_info_state.info(toplevel) {
let _ = self
.tx
.unbounded_send(ToplevelEvent::Update(toplevel.clone(), info.clone()));
}
self.pending_update.insert(toplevel.clone());
}

fn toplevel_closed(
Expand All @@ -141,14 +123,27 @@ impl ToplevelInfoHandler for AppData {
_qh: &QueueHandle<Self>,
toplevel: &zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1,
) {
let _ = self
.tx
.unbounded_send(ToplevelEvent::Remove(toplevel.clone()));
self.pending_update.insert(toplevel.clone());
}

fn info_done(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>) {
let mut res = Vec::with_capacity(self.pending_update.len());

for toplevel_handle in self.pending_update.drain() {
res.push((
toplevel_handle.clone(),
self.toplevel_info_state.info(&toplevel_handle).cloned(),
));
}

if let Err(err) = self.tx.unbounded_send(res) {
warn!("{err}");
}
}
}

pub(crate) fn toplevel_handler(
tx: UnboundedSender<ToplevelEvent>,
tx: UnboundedSender<TopLevelsUpdate>,
rx: calloop::channel::Channel<ToplevelAction>,
) -> anyhow::Result<()> {
let conn = Connection::connect_to_env()?;
Expand Down Expand Up @@ -188,6 +183,7 @@ pub(crate) fn toplevel_handler(
toplevel_info_state: ToplevelInfoState::new(&registry_state, &qh),
toplevel_manager_state: ToplevelManagerState::new(&registry_state, &qh),
registry_state,
pending_update: HashSet::new(),
};

loop {
Expand Down
Loading