diff --git a/Cargo.lock b/Cargo.lock index f959f2b..9521404 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index df1b55f..4f2f6cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/handlers/compositor.rs b/src/handlers/compositor.rs index fdce340..00780d0 100644 --- a/src/handlers/compositor.rs +++ b/src/handlers/compositor.rs @@ -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 diff --git a/src/handlers/xdg_shell.rs b/src/handlers/xdg_shell.rs index 0fc3343..6c909bc 100644 --- a/src/handlers/xdg_shell.rs +++ b/src/handlers/xdg_shell.rs @@ -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::() { + let data = data.lock().unwrap(); + + window.set_title(data.title.clone().unwrap_or_default()); + } + }); + } } fn check_grab( diff --git a/src/render.rs b/src/render.rs index b3242a2..85cb091 100644 --- a/src/render.rs +++ b/src/render.rs @@ -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 where R: ImportMem + ImportDmaWl + ImportMemWl; Window=WaylandSurfaceRenderElement, - Background=SolidColorRenderElement, + Border=MemoryRenderBufferRenderElement, } -#[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, + 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, 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, diff --git a/src/state.rs b/src/state.rs index 81d8984..86537fe 100644 --- a/src/state.rs +++ b/src/state.rs @@ -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::(&dh); let xdg_shell_state = XdgShellState::new::(&dh); - let xdg_decoration_state = XdgDecorationState::new::(&dh); - let xdg_shell_state = XdgShellState::new::(&dh); - let xdg_decoration_state = XdgDecorationState::new::(&dh); + // let xdg_decoration_state = XdgDecorationState::new::(&dh); let xdg_activation_state = XdgActivationState::new::(&dh); let xdg_toplevel_icon_manager_state = XdgToplevelIconManager::new::(&dh); let xdg_toplevel_tag_manager_state = XdgToplevelTagManager::new::(&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, diff --git a/src/xwayland.rs b/src/xwayland.rs index 177eec6..faf4abb 100644 --- a/src/xwayland.rs +++ b/src/xwayland.rs @@ -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, @@ -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, );