wayland: we have a (blank) window ladies and gentlemen

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Id2b91339a2d43cc95041dafe835c6a526a6a6964
This commit is contained in:
raf 2026-06-23 15:02:40 +03:00
commit c68d3445e7
No known key found for this signature in database
GPG key ID: 29D95B64378DB4BF
4 changed files with 649 additions and 1 deletions

384
Cargo.lock generated
View file

@ -22,9 +22,74 @@ name = "beer"
version = "0.0.0"
dependencies = [
"anyhow",
"calloop",
"calloop-wayland-source",
"pound",
"smithay-client-toolkit",
"tracing",
"tracing-subscriber",
"wayland-client",
]
[[package]]
name = "bitflags"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "bytemuck"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "calloop"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7"
dependencies = [
"bitflags",
"polling",
"rustix",
"slab",
"tracing",
]
[[package]]
name = "calloop-wayland-source"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
dependencies = [
"calloop",
"rustix",
"wayland-backend",
"wayland-client",
]
[[package]]
name = "cc"
version = "1.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
@ -33,12 +98,73 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "cursor-icon"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f"
[[package]]
name = "downcast-rs"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "log"
version = "0.4.32"
@ -60,6 +186,15 @@ version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
[[package]]
name = "memmap2"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
dependencies = [
"libc",
]
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
@ -81,6 +216,26 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "polling"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix",
"windows-sys",
]
[[package]]
name = "pound"
version = "0.1.6"
@ -110,6 +265,15 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.39.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.45"
@ -136,6 +300,19 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
@ -145,12 +322,54 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "smithay-client-toolkit"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
dependencies = [
"bitflags",
"bytemuck",
"calloop",
"calloop-wayland-source",
"cursor-icon",
"libc",
"log",
"memmap2",
"pkg-config",
"rustix",
"thiserror",
"wayland-backend",
"wayland-client",
"wayland-csd-frame",
"wayland-cursor",
"wayland-protocols",
"wayland-protocols-experimental",
"wayland-protocols-misc",
"wayland-protocols-wlr",
"wayland-scanner",
"xkbcommon",
"xkeysym",
]
[[package]]
name = "syn"
version = "2.0.118"
@ -162,6 +381,26 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.9"
@ -177,6 +416,7 @@ version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@ -254,6 +494,124 @@ dependencies = [
"quote",
]
[[package]]
name = "wayland-backend"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d"
dependencies = [
"cc",
"downcast-rs",
"rustix",
"smallvec",
"wayland-sys",
]
[[package]]
name = "wayland-client"
version = "0.31.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
dependencies = [
"bitflags",
"rustix",
"wayland-backend",
"wayland-scanner",
]
[[package]]
name = "wayland-csd-frame"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
dependencies = [
"bitflags",
"cursor-icon",
"wayland-backend",
]
[[package]]
name = "wayland-cursor"
version = "0.31.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d"
dependencies = [
"rustix",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
version = "0.32.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-experimental"
version = "20250721.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-misc"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
dependencies = [
"bitflags",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-scanner"
version = "0.31.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a"
dependencies = [
"proc-macro2",
"quick-xml",
"quote",
]
[[package]]
name = "wayland-sys"
version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
dependencies = [
"pkg-config",
]
[[package]]
name = "windows-link"
version = "0.2.1"
@ -268,3 +626,29 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "xcursor"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
[[package]]
name = "xkbcommon"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9"
dependencies = [
"libc",
"memmap2",
"xkeysym",
]
[[package]]
name = "xkeysym"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
dependencies = [
"bytemuck",
]

View file

@ -9,9 +9,13 @@ readme = true
[dependencies]
anyhow = "1.0.102"
calloop = "0.14.4"
calloop-wayland-source = "0.4.1"
pound = "0.1.6"
smithay-client-toolkit = "0.20.0"
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter"] }
wayland-client = "0.31.14"
[lints.rust]
unsafe_op_in_unsafe_fn = "deny"

View file

@ -1,5 +1,7 @@
//! beer, a fast, software-rendered, Wayland-native terminal emulator.
mod wayland;
use std::process::ExitCode;
use pound::Parse;
@ -45,5 +47,5 @@ fn run(cli: Cli) -> anyhow::Result<()> {
}
tracing::info!("starting beer");
todo!("window mode")
wayland::run()
}

258
src/wayland.rs Normal file
View file

@ -0,0 +1,258 @@
//! Wayland front-end: connection, surface, and software-drawn window.
//!
//! Uses smithay-client-toolkit for protocol boilerplate and calloop for the
//! event loop, so the PTY master fd and timers share one loop.
use std::time::Duration;
use anyhow::Context;
use calloop::EventLoop;
use calloop_wayland_source::WaylandSource;
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_output, delegate_registry, delegate_seat, delegate_shm,
delegate_xdg_shell, delegate_xdg_window,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::{Capability, SeatHandler, SeatState},
shell::{
WaylandSurface,
xdg::{
XdgShell,
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
},
},
shm::{Shm, ShmHandler, slot::SlotPool},
};
use wayland_client::{
Connection, QueueHandle,
globals::registry_queue_init,
protocol::{wl_output, wl_seat, wl_shm, wl_surface},
};
/// Default window size in pixels before the compositor suggests one.
const DEFAULT_W: u32 = 800;
const DEFAULT_H: u32 = 600;
/// Background fill, 0xAARRGGBB. Foot-ish dark grey.
const BG: u32 = 0xFF18_1818;
/// Run a single window until it is closed.
pub fn run() -> anyhow::Result<()> {
let conn = Connection::connect_to_env().context("connect to Wayland compositor")?;
let (globals, event_queue) =
registry_queue_init(&conn).context("initialize Wayland registry")?;
let qh = event_queue.handle();
let mut event_loop: EventLoop<App> =
EventLoop::try_new().context("create calloop event loop")?;
WaylandSource::new(conn, event_queue)
.insert(event_loop.handle())
.map_err(|e| anyhow::anyhow!("insert Wayland source into event loop: {e}"))?;
let compositor = CompositorState::bind(&globals, &qh).context("compositor not available")?;
let xdg_shell = XdgShell::bind(&globals, &qh).context("xdg_wm_base not available")?;
let shm = Shm::bind(&globals, &qh).context("wl_shm not available")?;
let surface = compositor.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
window.set_title("beer");
window.set_app_id("dev.notashelf.beer");
window.set_min_size(Some((1, 1)));
// First commit with no buffer kicks off the initial configure.
window.commit();
let pool = SlotPool::new(DEFAULT_W as usize * DEFAULT_H as usize * 4, &shm)
.context("create shm slot pool")?;
let mut app = App {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
seat_state: SeatState::new(&globals, &qh),
shm,
pool,
window,
width: DEFAULT_W,
height: DEFAULT_H,
configured: false,
exit: false,
};
while !app.exit {
event_loop
.dispatch(Duration::from_millis(16), &mut app)
.context("dispatch event loop")?;
}
Ok(())
}
/// Window + Wayland client state shared across all protocol handlers.
#[derive(Debug)]
struct App {
registry_state: RegistryState,
output_state: OutputState,
seat_state: SeatState,
shm: Shm,
pool: SlotPool,
window: Window,
width: u32,
height: u32,
configured: bool,
exit: bool,
}
impl App {
/// Fill the surface with the background colour and present it.
fn draw(&mut self) {
let (w, h) = (self.width, self.height);
let stride = w as i32 * 4;
let (buffer, canvas) =
match self
.pool
.create_buffer(w as i32, h as i32, stride, wl_shm::Format::Argb8888)
{
Ok(buf) => buf,
Err(err) => {
tracing::error!("allocate shm buffer: {err}");
return;
}
};
let bytes = BG.to_le_bytes();
for px in canvas.chunks_exact_mut(4) {
px.copy_from_slice(&bytes);
}
let surface = self.window.wl_surface();
if let Err(err) = buffer.attach_to(surface) {
tracing::error!("attach buffer: {err}");
return;
}
surface.damage_buffer(0, 0, w as i32, h as i32);
self.window.commit();
}
}
impl CompositorHandler for App {
fn scale_factor_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: i32,
) {
}
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: wl_output::Transform,
) {
}
fn frame(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &wl_surface::WlSurface, _: u32) {}
fn surface_enter(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: &wl_output::WlOutput,
) {
}
fn surface_leave(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &wl_surface::WlSurface,
_: &wl_output::WlOutput,
) {
}
}
impl WindowHandler for App {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
self.exit = true;
}
fn configure(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &Window,
configure: WindowConfigure,
_serial: u32,
) {
if let (Some(w), Some(h)) = configure.new_size {
self.width = w.get();
self.height = h.get();
}
self.configured = true;
self.draw();
}
}
impl ShmHandler for App {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl SeatHandler for App {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
fn new_capability(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: wl_seat::WlSeat,
_: Capability,
) {
}
fn remove_capability(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: wl_seat::WlSeat,
_: Capability,
) {
}
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
}
impl OutputHandler for App {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
}
impl ProvidesRegistryState for App {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState, SeatState];
}
delegate_compositor!(App);
delegate_output!(App);
delegate_shm!(App);
delegate_seat!(App);
delegate_xdg_shell!(App);
delegate_xdg_window!(App);
delegate_registry!(App);