feat(kde): KDE backends implement the shared Backend through Shared

komorebi::backend::shared::Shared<B> wraps any KDE Backend (the KWin one, the fake, a dyn Backend) and implements komorebi_backend::Backend over it, so KDE is the first implementation of the shared trait without any change to its own trait or call sites. Ids are WindowId::from_u32 (an id wider than u32 is a window KDE never had); a failed window command becomes Error::WindowGone when the window is no longer alive and Error::Platform(report) otherwise; every optional field is Some; every Capability is supported; batches are no-ops (KDE batches by construction).

Tests (5, in backend/shared.rs): the KDE fake, through Shared, passes the shared contract (check_all); the error mapping; wide ids; every capability supported; fields coming through.

Verified: komorebi-for-kde cargo test --workspace --no-fail-fast: 881 passed, 0 failed over 80 binaries (876 baseline + these 5). clippy -D warnings on komorebi-for-kde reports 3 ptr_arg errors in static_config.rs that predate this work; none in backend/.
This commit is contained in:
LGUG2Z committed 2026-10-31 01:37:54 -07:00
1 parent 1497e5d62a
commit 9bf77e32b9
2 files changed
+285

No files matched your search

@@ -57,6 +57,7 @@ use std::path::Path;
use std::sync::Arc;
pub mod fake;
pub mod shared;
/// What the compositor currently knows about one window.
#[derive(Clone, Debug, Default, PartialEq)]
@@ -0,0 +1,284 @@
//! komorebi for KDE's backends, seen as the shared [`komorebi_backend::Backend`].
//!
//! [`Shared`] wraps any KDE [`Backend`](super::Backend) (the real KWin one or the fake) and
//! implements the trait that every komorebi shares, which is how KDE is its first implementation:
//! the daemon keeps calling its own trait (`u32` ids, `eyre` errors, the look, the wallpaper and
//! the drag frames) until the shared core is adopted, and everything the shared trait asks for is
//! answered by the KDE methods underneath. Nothing about how KDE behaves changes.
//!
//! What the translation does:
//!
//! - an id is a [`WindowId`] made from the `u32` ([`WindowId::from_u32`]); an id too wide for a
//! `u32` is a window KDE never had;
//! - an `eyre` error of a window command becomes [`Error::WindowGone`] if the window is not alive
//! any more by the time the command is answered (KDE fails only for an unknown or dead
//! window), and [`Error::Platform`] otherwise;
//! - KDE fills every field of a window, so the optional fields are all `Some`;
//! - every [`Capability`] is supported: KDE has workspaces, maximize, opacity and focus follows
//! mouse;
//! - a batch does nothing: KDE batches by construction (the answer to a held poll carries every
//! command that was queued).
use super::Backend;
use komorebi_backend as shared;
use komorebi_backend::Capability;
use komorebi_backend::Error;
use komorebi_backend::MonitorId;
use komorebi_backend::Rect;
use komorebi_backend::WindowId;
use std::sync::Arc;
/// A KDE [`Backend`] as the shared [`komorebi_backend::Backend`].
pub struct Shared<B: Backend + ?Sized>(pub Arc<B>);
impl<B: Backend + ?Sized> Shared<B> {
/// Returns the KDE backend underneath.
pub fn kde(&self) -> &B {
&self.0
}
/// Runs a window command through KDE's trait and says why it failed in the shared terms.
fn window_command(
&self,
id: WindowId,
command: impl FnOnce(&B, u32) -> color_eyre::eyre::Result<()>,
) -> shared::Result<()> {
let Some(raw) = id.to_u32() else {
return Err(Error::WindowGone(id));
};
command(&self.0, raw).map_err(|report| {
if self.0.is_window(raw) {
Error::Platform(report.into())
} else {
Error::WindowGone(id)
}
})
}
fn platform(report: color_eyre::eyre::Report) -> Error {
Error::Platform(report.into())
}
}
fn window_info(info: super::WindowInfo) -> shared::WindowInfo {
shared::WindowInfo {
id: WindowId::from_u32(info.id),
pid: info.pid,
rect: info.rect,
minimized: info.minimized,
alive: info.alive,
title: Some(info.title),
class: Some(info.class),
exe: Some(info.exe),
path: Some(info.path),
fullscreen: Some(info.fullscreen),
maximized: Some(info.maximized),
normal: Some(info.normal),
}
}
impl<B: Backend + ?Sized> shared::Backend for Shared<B> {
fn windows(&self) -> Vec<shared::WindowInfo> {
self.0.windows().into_iter().map(window_info).collect()
}
fn window(&self, id: WindowId) -> Option<shared::WindowInfo> {
self.0.window(id.to_u32()?).map(window_info)
}
fn foreground_window(&self) -> Option<WindowId> {
self.0.foreground_window().map(WindowId::from_u32)
}
fn monitors(&self) -> shared::Result<Vec<shared::MonitorInfo>> {
Ok(self
.0
.monitors()
.map_err(Self::platform)?
.into_iter()
.map(|m| shared::MonitorInfo {
id: MonitorId::from_u32(m.id),
size: m.size,
work_area: m.work_area,
name: Some(m.name),
serial_number_id: Some(m.serial_number_id),
})
.collect())
}
fn cursor_pos(&self) -> Option<(i32, i32)> {
self.0.cursor_pos()
}
fn set_rect(&self, id: WindowId, rect: &Rect) -> shared::Result<()> {
self.window_command(id, |b, raw| b.set_rect(raw, rect))
}
fn focus(&self, id: WindowId, mouse_follows_focus: bool) -> shared::Result<()> {
self.window_command(id, |b, raw| b.focus(raw, mouse_follows_focus))
}
fn raise(&self, id: WindowId) -> shared::Result<()> {
self.window_command(id, |b, raw| b.raise(raw))
}
fn hide(&self, id: WindowId) -> shared::Result<()> {
self.window_command(id, |b, raw| b.hide(raw))
}
fn restore(&self, id: WindowId) -> shared::Result<()> {
self.window_command(id, |b, raw| b.restore(raw))
}
fn minimize(&self, id: WindowId) -> shared::Result<()> {
self.window_command(id, |b, raw| b.minimize(raw))
}
fn unminimize(&self, id: WindowId) -> shared::Result<()> {
self.window_command(id, |b, raw| b.unminimize(raw))
}
fn close(&self, id: WindowId) -> shared::Result<()> {
self.window_command(id, |b, raw| b.close(raw))
}
fn center_cursor_in(&self, rect: &Rect) -> shared::Result<()> {
self.0.center_cursor_in(rect).map_err(Self::platform)
}
fn supports(&self, _capability: Capability) -> bool {
true
}
fn maximize(&self, id: WindowId, maximize: bool) -> shared::Result<()> {
self.window_command(id, |b, raw| b.maximize(raw, maximize))
}
fn set_opacity(&self, id: WindowId, alpha: u8) -> shared::Result<()> {
self.window_command(id, |b, raw| b.set_opacity(raw, alpha))
}
fn set_focus_follows_mouse(&self, enable: bool) -> shared::Result<()> {
self.0
.set_focus_follows_mouse(enable)
.map_err(Self::platform)
}
fn ensure_workspaces(&self, names: &[String]) -> shared::Result<()> {
self.0.ensure_workspaces(names).map_err(Self::platform)
}
fn switch_workspace(&self, monitor: MonitorId, idx: usize) -> shared::Result<()> {
let id = monitor.to_u32().unwrap_or(u32::MAX);
self.0.switch_workspace(id, idx).map_err(Self::platform)
}
fn set_window_workspace(&self, id: WindowId, idx: usize) -> shared::Result<()> {
self.window_command(id, |b, raw| b.set_window_workspace(raw, idx))
}
fn window_workspace(&self, id: WindowId) -> shared::Result<Option<usize>> {
Ok(id.to_u32().and_then(|raw| self.0.window_workspace(raw)))
}
fn current_workspaces(&self) -> shared::Result<Vec<(MonitorId, usize)>> {
Ok(self
.0
.current_workspaces()
.into_iter()
.map(|(monitor, idx)| (MonitorId::from_u32(monitor), idx))
.collect())
}
// `is_window`, `window_rect` and `monitor_from_point` are the provided ones, over `window` and
// `monitors`, exactly as KDE's own are; `begin_batch` and `end_batch` do nothing, as KDE's
// queue is flushed as a whole.
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::fake::FakeBackend;
use komorebi_backend::Backend as _;
use komorebi_backend::contract::Harness;
use komorebi_backend::contract::check_all;
struct KdeFake(Shared<FakeBackend>);
impl Harness for KdeFake {
type B = Shared<FakeBackend>;
fn new() -> Self {
Self(Shared(Arc::new(FakeBackend::with_one_monitor())))
}
fn backend(&self) -> &Self::B {
&self.0
}
fn open_window(&self, seed: u32) -> WindowId {
self.0.kde().open_window(seed, "foot", "foot", "a terminal");
WindowId::from_u32(seed)
}
fn destroy_window(&self, id: WindowId) {
self.0
.kde()
.close_window_now(id.to_u32().expect("a KDE id"));
}
}
#[test]
fn the_kde_fake_passes_the_shared_contract() {
check_all::<KdeFake>();
}
#[test]
fn kde_is_the_first_implementation_and_supports_every_capability() {
let kde = KdeFake::new();
for capability in Capability::ALL {
assert!(kde.backend().supports(capability));
}
}
#[test]
fn a_command_about_a_window_that_is_gone_is_window_gone_and_other_failures_are_not() {
let kde = KdeFake::new();
let id = kde.open_window(4);
let gone = kde.backend().focus(WindowId::from_u32(9), false);
assert!(matches!(gone, Err(Error::WindowGone(WindowId(9)))));
assert_eq!(gone.unwrap_err().to_string(), "no such window: 9");
assert!(kde.backend().focus(id, false).is_ok());
}
#[test]
fn an_id_too_wide_for_kde_is_a_window_kde_never_had() {
let kde = KdeFake::new();
let wide = WindowId(u64::from(u32::MAX) + 1);
assert!(kde.backend().window(wide).is_none());
assert!(kde.backend().hide(wide).unwrap_err().is_window_gone());
}
#[test]
fn every_field_kde_knows_comes_through() {
let kde = KdeFake::new();
let id = kde.open_window(4);
let info = kde.backend().window(id).expect("window 4 was opened");
assert_eq!(info.id, id);
assert_eq!(info.class.as_deref(), Some("foot"));
assert_eq!(info.normal, Some(true));
assert_eq!(info.fullscreen, Some(false));
assert_eq!(
kde.backend().monitors().expect("one monitor")[0]
.name
.as_deref(),
Some("Virtual-0")
);
}
}