prototype decor (only for xwayland rn)

This commit is contained in:
IoIxD 2026-07-07 15:52:44 -07:00
commit db49a29fad
7 changed files with 426 additions and 32 deletions

143
Cargo.lock generated
View file

@ -234,6 +234,29 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
[[package]]
name = "cairo-rs"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
dependencies = [
"bitflags 2.13.0",
"cairo-sys-rs",
"glib",
"libc",
]
[[package]]
name = "cairo-sys-rs"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "calloop"
version = "0.13.0"
@ -303,6 +326,16 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfg-expr"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb693542bcafa528e198be0ebd9d3632ca5b7c93dbe7237460e199910835997c"
dependencies = [
"smallvec",
"target-lexicon",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
@ -763,12 +796,66 @@ dependencies = [
"xml-rs",
]
[[package]]
name = "glib"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100"
dependencies = [
"bitflags 2.13.0",
"glib-macros",
"glib-sys",
"gobject-sys",
"libc",
"memchr",
"smallvec",
]
[[package]]
name = "glib-macros"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "glib-sys"
version = "0.22.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233"
dependencies = [
"libc",
"system-deps",
]
[[package]]
name = "gobject-sys"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c"
dependencies = [
"glib-sys",
"libc",
"system-deps",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.3.9"
@ -1072,6 +1159,7 @@ name = "milkyway"
version = "0.1.0"
dependencies = [
"bitflags 2.13.0",
"cairo-rs",
"parking_lot",
"rodio",
"smithay",
@ -1982,6 +2070,15 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_spanned"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
[[package]]
name = "sha2"
version = "0.10.9"
@ -2323,6 +2420,25 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "system-deps"
version = "7.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7"
dependencies = [
"cfg-expr",
"heck",
"pkg-config",
"toml",
"version-compare",
]
[[package]]
name = "target-lexicon"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
[[package]]
name = "tempfile"
version = "3.27.0"
@ -2410,6 +2526,21 @@ dependencies = [
"strict-num",
]
[[package]]
name = "toml"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
@ -2440,6 +2571,12 @@ dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
[[package]]
name = "tracing"
version = "0.1.44"
@ -2544,6 +2681,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "version-compare"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
[[package]]
name = "version_check"
version = "0.9.5"

View file

@ -12,3 +12,4 @@ winit = "0.30.13"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
bitflags = "2.2.1"
rodio = "0.22.2"
cairo-rs = "0.22.0"

View file

@ -1,4 +1,4 @@
use crate::{grabs::resize_grab, state::ClientState, state::State};
use crate::{state::ClientState, state::State};
use smithay::{
backend::renderer::utils::on_commit_buffer_handler,
reexports::wayland_server::{
@ -16,8 +16,6 @@ use smithay::{
xwayland::XWaylandClientData,
};
use super::xdg_shell;
impl CompositorHandler for State {
fn compositor_state(&mut self) -> &mut CompositorState {
&mut self.compositor_state

View file

@ -37,7 +37,7 @@ impl XdgShellHandler for State {
fn new_toplevel(&mut self, surface: ToplevelSurface) {
let window = Window::new_wayland_window(surface);
self.space
.map_element(DecoratedWindow::new(window), (0, 0), false);
.map_element(DecoratedWindow::new(window, false), (0, 0), false);
}
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
@ -131,6 +131,23 @@ impl XdgShellHandler for State {
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
// TODO popup grabs
}
fn title_changed(&mut self, surface: ToplevelSurface) {
let window = self
.space
.elements()
.filter(|w| w.window.toplevel().is_some())
.find(|w| *w.window.toplevel().unwrap() == surface)
.unwrap();
smithay::wayland::compositor::with_states(surface.wl_surface(), |states| {
if let Some(data) = states.data_map.get::<XdgToplevelSurfaceData>() {
let data = data.lock().unwrap();
window.set_title(data.title.clone().unwrap_or_default());
}
});
}
}
fn check_grab(

View file

@ -1,13 +1,19 @@
#![allow(dead_code)]
use crate::State;
use cairo::{Context, Format, ImageSurface};
use parking_lot::Mutex;
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::memory::MemoryRenderBuffer;
use smithay::utils::Transform;
use smithay::{
backend::renderer::{
damage::OutputDamageTracker,
element::{
memory::MemoryRenderBufferRenderElement, solid::SolidColorRenderElement,
surface::WaylandSurfaceRenderElement, AsRenderElements, Id, Kind,
memory::MemoryRenderBufferRenderElement, surface::WaylandSurfaceRenderElement,
AsRenderElements, Kind,
},
gles::{GlesRenderer, GlesTarget},
utils::CommitCounter,
Color32F, ImportAll, ImportDmaWl, ImportMem, ImportMemWl, Renderer, RendererSuper,
ImportAll, ImportDmaWl, ImportMem, ImportMemWl, Renderer, RendererSuper,
},
desktop::{space::SpaceElement, Window},
output::Output,
@ -16,26 +22,53 @@ use smithay::{
};
use wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
use crate::State;
render_elements! {
pub OutputElement<R> where R: ImportMem + ImportDmaWl + ImportMemWl;
Window=WaylandSurfaceRenderElement<R>,
Background=SolidColorRenderElement,
Border=MemoryRenderBufferRenderElement<R>,
}
#[derive(PartialEq, Eq, Clone)]
const CSD_BORDER_FRAME_LEFT: f64 = 5.0;
const CSD_BORDER_FRAME_RIGHT: f64 = 5.0;
const CSD_BORDER_FRAME_TOP: f64 = 23.0;
const CSD_BORDER_FRAME_BOTTOM: f64 = 4.0;
pub struct DecoratedWindow {
pub window: Window,
title: Mutex<String>,
decorations: bool,
}
impl PartialEq for DecoratedWindow {
fn eq(&self, other: &Self) -> bool {
self.window == other.window
}
}
impl Eq for DecoratedWindow {}
impl Clone for DecoratedWindow {
fn clone(&self) -> Self {
let title = self.title.lock().clone();
Self {
window: self.window.clone(),
title: Mutex::new(title),
decorations: self.decorations,
}
}
}
impl DecoratedWindow {
pub fn new(window: Window) -> Self {
Self { window }
pub fn new(window: Window, decorations: bool) -> Self {
// let surface = ImageSurface::create(Format::ARgb32, width, height).unwrap();
Self {
window,
title: Mutex::new(String::new()),
decorations,
}
}
fn decorated(&self) -> bool {
let mut b = false;
let mut b = self.decorations;
if let Some(toplevel) = self.window.toplevel() {
toplevel.with_pending_state(|f| {
if let Some(decor) = f.decoration_mode {
@ -45,6 +78,183 @@ impl DecoratedWindow {
}
b
}
pub fn set_title(&self, title: String) {
*self.title.lock() = title;
}
fn render_border(&self, width: i32, height: i32) -> (Vec<u8>, i32) {
let mut surface = ImageSurface::create(Format::ARgb32, width, height).unwrap();
let stride = surface.stride();
{
let ctx = Context::new(&surface).unwrap(); // scoped — see note below
ctx.set_source_rgba(0.2, 0.2, 0.2, 1.0);
ctx.set_line_width(1.0);
let (w, h) = (width as f64, height as f64);
macro_rules! do_box {
($x:expr, $y:expr, $col:expr, $w:expr, $h:expr) => {
ctx.rectangle(($x) as f64, ($y) as f64, ($w) as f64, ($h) as f64);
ctx.set_source_rgb(($col) as f64, ($col) as f64, ($col) as f64);
ctx.fill().unwrap();
};
}
macro_rules! do_line {
($off:expr, $col:expr) => {
ctx.move_to($off, $off);
ctx.line_to($off, h - $off);
ctx.set_source_rgb($col, $col, $col);
ctx.stroke().unwrap();
ctx.move_to($off, $off);
ctx.line_to(w - $off, $off);
ctx.set_source_rgb($col, $col, $col);
ctx.stroke().unwrap();
};
}
ctx.set_antialias(cairo::Antialias::None);
/* border */
do_box!(0.0, 0.0, 0.14901961, w, h);
do_box!(1.0, 1.0, 0.55686275, w - 2.0, h - 2.0);
do_box!(2.0, 2.0, 0.82352941, w - 4.0, h - 4.0);
do_box!(3.0, 3.0, 0.93333333, w - 6.0, h - 6.0);
do_box!(4.0, 4.0, 1.0, w - 8.0, h - 8.0);
do_box!(4.0, 4.0, 0.14901961, w - 9.0, h - 9.0);
do_line!(1.0, 1.0);
do_line!(2.0, 0.93333333);
do_line!(3.0, 0.82352941);
do_line!(4.0, 0.55686275);
do_line!(5.0, 0.14901961);
for y in 0..18 {
let y = y as f64;
let a = FloatColor {
r: 0.2627451,
g: 0.37254902,
b: 0.49411765,
};
let b = FloatColor {
r: 0.05490196,
g: 0.17254902,
b: 0.30588235,
};
let c = lerp_color(a, b, y / 18.0);
ctx.set_source_rgb(c.r, c.g, c.b);
ctx.move_to(5.0, y + 6.0);
ctx.line_to(w as f64 - 5.0, y + 6.0);
ctx.stroke().unwrap();
}
/* icon */
do_box!(5, 5, 1, 18, 18);
do_box!(6, 6, 0.35294118, 17, 17);
do_box!(6, 6, 0.82352941, 16, 16);
/* close */
do_box!(
w - 18.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 1.0,
1,
18,
18
);
do_box!(
w - 17.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 2.0,
0.35294118,
17,
17
);
do_box!(
w - 17.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 2.0,
0.82352941,
16,
16
);
do_box!(
w - 17.0 - CSD_BORDER_FRAME_LEFT + 5.0,
CSD_BORDER_FRAME_BOTTOM + 7.0,
0,
6,
6
);
do_box!(
w - 17.0 - CSD_BORDER_FRAME_LEFT + 6.0,
CSD_BORDER_FRAME_BOTTOM + 8.0,
0.82352941,
4,
4
);
/* maxim */
do_box!(
w - 36.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 1.0,
1,
18,
18
);
do_box!(
w - 35.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 2.0,
0.35294118,
17,
17
);
do_box!(
w - 35.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 2.0,
0.82352941,
16,
16
);
do_box!(
w - 35.0 - CSD_BORDER_FRAME_LEFT + 5.0,
CSD_BORDER_FRAME_BOTTOM + 7.0,
0,
6,
6
);
do_box!(
w - 35.0 - CSD_BORDER_FRAME_LEFT,
CSD_BORDER_FRAME_BOTTOM + 9.0,
0.82352941,
16,
2
);
do_box!(
w - 35.0 - CSD_BORDER_FRAME_LEFT + 7.0,
CSD_BORDER_FRAME_BOTTOM + 4.0,
0.82352941,
2,
14
);
do_box!(
w - 35.0 - CSD_BORDER_FRAME_LEFT + 6.0,
CSD_BORDER_FRAME_BOTTOM + 8.0,
0.82352941,
4,
4
);
/* title */
ctx.set_antialias(cairo::Antialias::Best);
ctx.move_to(28.0, 19.0);
ctx.set_font_size(12.0);
ctx.select_font_face("", cairo::FontSlant::Normal, cairo::FontWeight::Normal);
ctx.text_path(&self.title.lock().clone());
ctx.fill().unwrap();
} // ctx dropped here
surface.flush();
(surface.data().unwrap().to_vec(), stride)
}
}
impl SpaceElement for DecoratedWindow {
@ -53,7 +263,7 @@ impl SpaceElement for DecoratedWindow {
if self.decorated() {
bbox.loc.x -= 5;
bbox.loc.y -= 22;
bbox.loc.y -= 24;
}
bbox
@ -118,24 +328,51 @@ where
if decorated {
let mut bg_rect = self.window.geometry().to_physical_precise_round(scale);
bg_rect.size.w += 10;
bg_rect.size.h += 27;
bg_rect.size.w += 11;
bg_rect.size.h += 29;
let background = SolidColorRenderElement::new(
Id::new(),
bg_rect,
CommitCounter::default(),
Color32F::new(1.0, 0.75, 0.26, 1.0),
Kind::Unspecified,
let width = bg_rect.size.w;
let height = bg_rect.size.h;
let data = self.render_border(width, height);
let buffer = MemoryRenderBuffer::from_slice(
&data.0,
Fourcc::Argb8888,
(width, height),
1, // scale
Transform::Normal,
None,
);
elements.push(C::from(OutputElement::Background(background)));
let elem = MemoryRenderBufferRenderElement::from_buffer(
renderer,
Point::new(location.x as f64 - 5.0, location.y as f64 - 23.0),
&buffer,
None,
None,
None,
Kind::Unspecified,
)
.unwrap();
elements.push(C::from(OutputElement::Border(elem)));
}
elements
}
}
struct FloatColor {
r: f64,
g: f64,
b: f64,
}
fn lerp_color(a: FloatColor, b: FloatColor, t: f64) -> FloatColor {
return FloatColor {
r: a.r + (b.r - a.r) * t,
g: a.g + (b.g - a.g) * t,
b: a.b + (b.b - a.b) * t,
};
}
impl State {
pub fn render(
&self,

View file

@ -53,7 +53,7 @@ pub struct State {
// xdg
pub xdg_shell_state: XdgShellState,
pub xdg_decoration_state: XdgDecorationState,
// pub xdg_decoration_state: XdgDecorationState,
pub xdg_activation_state: XdgActivationState,
pub xdg_toplevel_icon_manager_state: XdgToplevelIconManager,
pub xdg_toplevel_tag_manager_state: XdgToplevelTagManager,
@ -81,10 +81,8 @@ impl State {
let compositor_state = CompositorState::new::<Self>(&dh);
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
let xdg_decoration_state = XdgDecorationState::new::<Self>(&dh);
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
let xdg_decoration_state = XdgDecorationState::new::<Self>(&dh);
// let xdg_decoration_state = XdgDecorationState::new::<Self>(&dh);
let xdg_activation_state = XdgActivationState::new::<Self>(&dh);
let xdg_toplevel_icon_manager_state = XdgToplevelIconManager::new::<Self>(&dh);
let xdg_toplevel_tag_manager_state = XdgToplevelTagManager::new::<Self>(&dh);
@ -152,7 +150,7 @@ impl State {
wl_fixes_state,
output_manager_state,
xdg_decoration_state,
// xdg_decoration_state,
xdg_activation_state,
xdg_toplevel_icon_manager_state,
xdg_toplevel_tag_manager_state,

View file

@ -12,7 +12,7 @@ use smithay::{
};
use wayland_server::{Display, DisplayHandle};
use crate::{state::State, render::DecoratedWindow};
use crate::{render::DecoratedWindow, state::State};
pub struct XWaylandState {
pub xwm: Option<X11Wm>,
@ -32,7 +32,7 @@ impl XwmHandler for State {
window.set_mapped(true).unwrap();
self.space.map_element(
DecoratedWindow::new(Window::new_x11_window(window.clone())),
DecoratedWindow::new(Window::new_x11_window(window.clone()), true),
(0, 0),
true,
);