use crate::client::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::{
self, State::Activated, ZcosmicToplevelHandleV1,
};
use crate::client::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_info_v1::{
self, ZcosmicToplevelInfoV1,
};
use crate::client::cosmic_protocols::toplevel_management::v1::client::zcosmic_toplevel_manager_v1::{
self, ZcosmicToplevelManagerV1,
};
use crate::client::{Client, WindowInfo};
use anyhow::{Context, Result};
use std::collections::HashMap;
use wayland_client::backend::ObjectId;
use wayland_client::globals::{registry_queue_init, GlobalListContents};
use wayland_client::protocol::wl_registry::{self, WlRegistry};
use wayland_client::{event_created_child, Connection, Dispatch, EventQueue, Proxy, QueueHandle};
#[derive(Debug)]
struct CosmicWindow {
handle: ZcosmicToplevelHandleV1,
app_class: Option<String>,
title: Option<String>,
}
struct State {
toplevel_manager: ZcosmicToplevelManagerV1,
windows: HashMap<ObjectId, CosmicWindow>,
active_window: Option<ObjectId>,
}
#[derive(Default)]
pub struct CosmicClient {
queue: Option<EventQueue<State>>,
state: Option<State>,
}
impl CosmicClient {
pub fn new() -> Self {
Default::default()
}
fn connect(&mut self) -> Result<()> {
let connection = Connection::connect_to_env()?;
let (globals, mut queue) = registry_queue_init::<State>(&connection)?;
globals
.bind::<ZcosmicToplevelInfoV1, _, _>(&queue.handle(), 1..=1, ())
.context("zcosmic_toplevel_info_v1 protocol is not supported")?;
let toplevel_manager = globals
.bind::<ZcosmicToplevelManagerV1, _, _>(&queue.handle(), 1..=1, ())
.context("zcosmic_toplevel_manager_v1 protocol is not supported")?;
let mut state = State {
toplevel_manager,
windows: HashMap::new(),
active_window: None,
};
queue.roundtrip(&mut state)?;
self.queue = Some(queue);
self.state = Some(state);
Ok(())
}
fn get_focused_window<'a>(&'a mut self) -> Result<Option<&'a CosmicWindow>> {
let (_, state) = self.borrow()?;
Ok(state.active_window.as_ref().and_then(|id| state.windows.get(&id)))
}
fn borrow<'a>(&'a mut self) -> Result<(&'a mut EventQueue<State>, &'a mut State)> {
if self.queue.is_none() {
self.connect()?;
}
let queue = self
.queue
.as_mut()
.ok_or_else(|| anyhow::format_err!("This cannot happen"))?;
let state = self
.state
.as_mut()
.ok_or_else(|| anyhow::format_err!("This cannot happen"))?;
queue.roundtrip(state)?;
Ok((queue, state))
}
}
impl Client for CosmicClient {
fn supported(&mut self) -> bool {
match self.connect() {
Ok(_) => true,
Err(err) => {
eprintln!("{err}");
false
}
}
}
fn current_window(&mut self) -> Option<String> {
match self.get_focused_window() {
Ok(window) => window.and_then(|window| window.title.clone()),
Err(e) => {
eprintln!("Error when fetching window title: {e:?}");
None
}
}
}
fn current_application(&mut self) -> Option<String> {
match self.get_focused_window() {
Ok(window) => window.and_then(|window| window.app_class.clone()),
Err(e) => {
eprintln!("Error when fetching app_id: {e:?}");
None
}
}
}
fn window_list(&mut self) -> Result<Vec<WindowInfo>> {
let (_, state) = self.borrow()?;
let windows: Vec<WindowInfo> = state
.windows
.iter()
.map(
|(
_,
CosmicWindow {
handle,
app_class,
title,
},
)| WindowInfo {
winid: Some(format!("{}", handle.id())),
app_class: app_class.clone(),
title: title.clone(),
},
)
.collect();
Ok(windows)
}
fn close_windows_by_app_class(&mut self, app_class: &str) -> Result<()> {
let (queue, state) = self.borrow()?;
for window in state.windows.values() {
if window.app_class.as_deref() == Some(app_class) {
state.toplevel_manager.close(&window.handle);
}
}
queue.flush()?;
Ok(())
}
}
impl Dispatch<WlRegistry, GlobalListContents> for State {
fn event(
_: &mut Self,
_: &WlRegistry,
_: wl_registry::Event,
_: &GlobalListContents,
_: &Connection,
_: &QueueHandle<State>,
) {
}
}
impl Dispatch<ZcosmicToplevelInfoV1, ()> for State {
fn event(
state: &mut Self,
_: &ZcosmicToplevelInfoV1,
event: zcosmic_toplevel_info_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
use zcosmic_toplevel_info_v1::Event::{Finished, Toplevel};
match event {
Toplevel { toplevel } => {
let info = CosmicWindow {
handle: toplevel,
app_class: None,
title: None,
};
state.windows.insert(info.handle.id(), info);
}
Finished => {}
}
}
event_created_child!(
State,
ZcosmicToplevelInfoV1,
[
zcosmic_toplevel_info_v1::EVT_TOPLEVEL_OPCODE => (ZcosmicToplevelHandleV1, ()),
]
);
}
impl Dispatch<ZcosmicToplevelHandleV1, ()> for State {
fn event(
state: &mut Self,
handle: &ZcosmicToplevelHandleV1,
event: zcosmic_toplevel_handle_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<State>,
) {
use zcosmic_toplevel_handle_v1::Event::{AppId, Closed, State, Title};
match event {
Title { title } => {
state
.windows
.get_mut(&handle.id())
.map(|window| window.title = Some(title));
}
AppId { app_id } => {
state
.windows
.get_mut(&handle.id())
.map(|window| window.app_class = Some(app_id));
}
State { state: window_state } => {
let (chunks, _) = window_state.as_chunks::<4>();
let activated = chunks
.iter()
.map(|&chunk| u32::from_ne_bytes(chunk))
.any(|state| state == Activated as u32);
if activated {
state.active_window = Some(handle.id())
}
}
Closed => {
state.windows.remove(&handle.id());
}
_ => {}
}
}
}
impl Dispatch<ZcosmicToplevelManagerV1, ()> for State {
fn event(
_: &mut Self,
_: &ZcosmicToplevelManagerV1,
_: zcosmic_toplevel_manager_v1::Event,
_: &(),
_: &Connection,
_: &QueueHandle<Self>,
) {
}
}