Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: alt tab cycle #227

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
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
1,081 changes: 614 additions & 467 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ 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", branch = "toplevel-info" }

1 change: 1 addition & 0 deletions bin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ publish = false
pop-launcher-toolkit = { path = "../toolkit" }
tracing.workspace = true
tracing-subscriber = { version = "0.3.18", default-features = false, features = ["std", "fmt", "env-filter", "chrono"] }
tracing-journald = "0.3.0"
dirs.workspace = true
mimalloc = "0.1.39"

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();
}
}
}
2 changes: 1 addition & 1 deletion plugins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ recently-used-xbel = "1.0.0"

# dependencies cosmic toplevel
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit" }
sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "3bed072", features = [
sctk = { package = "smithay-client-toolkit", version = "0.19.1", features = [
"calloop",
] }

Expand Down
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