From b4b803eed0c9ccc34d8188846f16f25e1433ed5f Mon Sep 17 00:00:00 2001 From: Lucas Schumacher Date: Sun, 22 Sep 2024 22:20:31 -0400 Subject: [PATCH] Initial commit --- .gitignore | 1 + Cargo.lock | 275 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 10 ++ src/main.rs | 390 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 676 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a417d0d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,275 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "poppable" +version = "0.0.1" +source = "git+https://git.kealoha.me/lks/poppable.git#5f238c7bc42e4abc2c72fb1f93e623934487ff99" +dependencies = [ + "anyhow", + "poppable-derive", +] + +[[package]] +name = "poppable-derive" +version = "0.1.0" +source = "git+https://git.kealoha.me/lks/poppable.git#5f238c7bc42e4abc2c72fb1f93e623934487ff99" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "waycli" +version = "0.1.0" +dependencies = [ + "anyhow", + "poppable", + "rand", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4763506 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "waycli" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.89" +poppable = { git = "https://git.kealoha.me/lks/poppable.git", version = "0.0.1" } +rand = "0.8.5" +rustix = { version = "0.38.37", features = ["mm", "shm"] } diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c51370e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,390 @@ +//https://gaultier.github.io/blog/wayland_from_scratch.html +use std::{ffi::CStr, io::Read, ptr::null_mut, u32}; + +use poppable::Poppable; +use rustix::{ + fs::{ftruncate, Mode}, + mm::{mmap, MapFlags, ProtFlags}, + shm, +}; +use wayland::WAYLAND_XDG_BASE_EVENT_PING; + +mod wayland { + use anyhow::Result; + use poppable::{PopFromNE, Pushable}; + use rustix::fd::OwnedFd; + use std::ffi::c_void; + use std::{env, io::Write, os::unix::net::UnixStream}; + + pub const WAYLAND_DISPLAY_OBJECT_ID: u32 = 1; + pub const WAYLAND_WL_DISPLAY_GET_REGISTRY_OPCODE: u16 = 1; + pub const WAYLAND_WL_REGISTRY_BIND_OPCODE: u16 = 0; + pub const COLOR_CHANNELS: u32 = 4; + pub const WAYLAND_WL_REGISTRY_EVENT_GLOBAL: u16 = 0; + pub const WAYLAND_WL_DISPLAY_ERROR_EVENT: u16 = 0; + pub const WAYLAND_XDG_BASE_EVENT_PING: u16 = 0; + pub const WAYLAND_XDG_SURFACE_EVENT_CONFIGURE: u16 = 0; + + pub fn write_string(buf: &mut Vec, src: &str) -> Result { + let mut n_write = 0; + (src.len() as u32 + 1).push_ne_into(buf)?; + for b in src.bytes().chain([0_u8]) { + buf.push(b); + n_write += 1; + } + let required_size = nearest_multiple_4(n_write as i32) as usize; + while n_write < required_size { + buf.push(0); + } + + return Ok(n_write); + } + #[inline(always)] + pub fn nearest_multiple_4(n: i32) -> i32 { + return ((n) + 3) & -4; + } + + #[derive(Default, Debug, PartialEq)] + pub enum StateEnum { + #[default] + None, + SurfaceAckedConfigure, + SurfaceAttached, + } + #[derive(Default)] + pub struct State { + pub wl_registry: u32, + pub wl_shm: u32, + pub wl_shm_pool: u32, + pub wl_buffer: u32, + pub xdg_wm_base: u32, + pub xdg_surface: u32, + pub wl_compositor: u32, + pub wl_surface: u32, + pub xdg_toplevel: u32, + pub stride: u32, + pub w: u32, + pub h: u32, + pub shm_pool_size: u32, + pub shm_fd: Option, + pub shm_pool_data: Option<*mut c_void>, + + pub state: StateEnum, + } + + pub struct Wayland { + pub sock: UnixStream, + pub resource_counter: u32, + } + pub fn connect() -> Result { + let xdg_runtime_dir = env::var("XDG_RUNTIME_DIR")?; + let wayland_display = env::var("WAYLAND_DISPLAY").unwrap_or("wayland-0".into()); + let wayland_path = xdg_runtime_dir + &wayland_display; + let sock = UnixStream::connect(&wayland_path)?; + Ok(Wayland { + sock, + resource_counter: 2, + }) + } + + struct RawWaylandMsg { + resource_id: u32, + opcode: u16, + size: u16, + payload: Vec, + } + impl RawWaylandMsg { + const HEADER_SIZE: u16 = + (std::mem::size_of::() + (2 * std::mem::size_of::())) as u16; + } + + impl Wayland { + pub fn wl_compositor_create_surface(&mut self, state: &mut State) -> Result { + todo!() + } + pub fn xdg_wm_base_get_xdg_surface(&mut self, state: &mut State) -> Result { + todo!() + } + pub fn xdg_surface_get_toplevel(&mut self, state: &mut State) -> Result { + todo!() + } + pub fn wl_surface_commit(&mut self, state: &mut State) -> Result { + todo!() + } + fn raw_send(&mut self, msg: RawWaylandMsg) -> Result<()> { + let mut buffer = vec![]; + msg.resource_id.push_ne_into(&mut buffer)?; + msg.opcode.push_ne_into(&mut buffer)?; + msg.size.push_ne_into(&mut buffer)?; + buffer.push_vec(msg.payload)?; + self.sock.write(&buffer)?; + Ok(()) + } + + pub fn get_registry(&mut self) -> Result { + let mut payload: Vec = Vec::new(); + self.resource_counter += 1; + self.resource_counter.push_ne_into(&mut payload)?; + let size = RawWaylandMsg::HEADER_SIZE + payload.len() as u16; + let msg = RawWaylandMsg { + resource_id: WAYLAND_DISPLAY_OBJECT_ID, + opcode: WAYLAND_WL_DISPLAY_GET_REGISTRY_OPCODE, + size, + payload, + }; + + self.raw_send(msg)?; + + return Ok(self.resource_counter); + } + pub fn registry_bind( + &mut self, + wl_registry: u32, + name: u32, + interface: &str, + version: u32, + ) -> Result { + println!( + "-> wl_registry@{}.bind: name={} interface={} version={}", + wl_registry, name, interface, version + ); + let resource_id = wl_registry; + let opcode = WAYLAND_WL_REGISTRY_BIND_OPCODE; + + let mut payload = vec![]; + name.push_ne_into(&mut payload)?; + write_string(&mut payload, interface)?; + version.push_ne_into(&mut payload)?; + self.resource_counter += 1; + self.resource_counter.push_ne_into(&mut payload)?; + + let size = RawWaylandMsg::HEADER_SIZE + payload.len() as u16; + + let msg = RawWaylandMsg { + resource_id, + opcode, + size, + payload, + }; + self.raw_send(msg)?; + return Ok(self.resource_counter); + } + + pub fn xdg_wm_base_pong(&self, state: &mut State, ping: u32) -> Result<()> { + todo!() + } + + pub fn xdg_surface_ack_configure(&self, state: &mut State, configure: u32) -> Result<()> { + todo!() + } + + pub fn wl_shm_create_pool(&self, state: &mut State) -> Result { + todo!() + } + + pub fn wl_shm_pool_create_buffer(&self, state: &mut State) -> Result { + todo!() + } + + pub(crate) fn wl_surface_attach(&self, state: &mut State) -> Result<()> { + todo!() + } + } +} + +fn gen_rand_name(len: usize) -> String { + let mut s = String::from("/"); + for _ in 0..(len - 1) { + let mut r: f64 = rand::random(); + let a = ('a' as u8) as f64; + r = (r * 26.0) + a; + let c: char = r as u8 as char; + s.push(c); + } + todo!() +} + +fn wayland_handle_message( + fd: &mut wayland::Wayland, + state: &mut wayland::State, + msg: &mut &[u8], +) -> anyhow::Result<()> { + if msg.len() < 8 { + anyhow::bail!("msg too small for mesage header"); + } + let object_id: u32 = msg.try_pop_ne()?; + let opcode: u16 = msg.try_pop_ne()?; + let msglen: u16 = msg.try_pop_ne()?; + let payloadlen = msglen as usize - 8; + + let mut payload = &msg[0..payloadlen as usize]; + *msg = &msg[payloadlen..]; + + if object_id == state.wl_registry && opcode == wayland::WAYLAND_WL_REGISTRY_EVENT_GLOBAL { + let name: u32 = payload.try_pop_ne()?; + let interface_len: u32 = payload.try_pop_ne()?; + let padded_interface_len = wayland::nearest_multiple_4(interface_len as i32) as usize; + + let interface = CStr::from_bytes_until_nul(&payload[..padded_interface_len])?.to_str()?; + payload = &payload[padded_interface_len..]; + + let version: u32 = payload.try_pop_ne()?; + println!( + "<- wl_registry@{}.global: name={name} interface={interface} version={version}", + state.wl_registry + ); + + use std::mem::size_of_val; + assert_eq!( + msglen as usize, + size_of_val(&object_id) + + size_of_val(&opcode) + + size_of_val(&msglen) + + size_of_val(&name) + + size_of_val(&interface_len) + + padded_interface_len + + size_of_val(&version) + ); + + match interface { + "wl_shm" => { + state.wl_shm = fd.registry_bind(state.wl_registry, name, interface, version)?; + } + "xdg_wm_base" => { + state.xdg_wm_base = + fd.registry_bind(state.wl_registry, name, interface, version)?; + } + "wl_compositor" => { + state.wl_compositor = + fd.registry_bind(state.wl_registry, name, interface, version)?; + } + _ => (), + }; + + return Ok(()); + } else if object_id == wayland::WAYLAND_DISPLAY_OBJECT_ID + && opcode == wayland::WAYLAND_WL_DISPLAY_ERROR_EVENT + { + let target_object_id: u32 = payload.try_pop_ne()?; + let code: u32 = payload.try_pop_ne()?; + let error_len: u32 = payload.try_pop_ne()?; + let padded_err_len: usize = wayland::nearest_multiple_4(error_len as i32) as usize; + + let error = CStr::from_bytes_until_nul(&payload[..padded_err_len])?.to_str()?; + //payload = &payload[padded_err_len..]; + anyhow::bail!( + "fatal error: target_object_id={} code={} error={}", + target_object_id, + code, + error + ); + } else if object_id == state.xdg_wm_base && opcode == WAYLAND_XDG_BASE_EVENT_PING { + let ping: u32 = payload.try_pop_ne()?; + println!("<- xdg_wm_base@{}.ping: ping={}", state.xdg_wm_base, ping); + fd.xdg_wm_base_pong(state, ping)?; + } else if object_id == state.xdg_surface + && opcode == wayland::WAYLAND_XDG_SURFACE_EVENT_CONFIGURE + { + let configure: u32 = payload.try_pop_ne()?; + println!( + "<- xdg_surface@{}.configure: configure={}", + state.xdg_surface, configure + ); + fd.xdg_surface_ack_configure(state, configure)?; + // TODO: move into xdg_surface_ack_configure + state.state = wayland::StateEnum::SurfaceAckedConfigure; + } + + Ok(()) +} + +fn main() -> anyhow::Result<()> { + let mut display = wayland::connect()?; + let reg = display.get_registry()?; + + let mut state = wayland::State::default(); + state.wl_registry = reg; + state.w = 117; + state.h = 150; + state.stride = state.w * wayland::COLOR_CHANNELS; + + // Single buffering. + state.shm_pool_size = state.h * state.stride; + + let shm_path = gen_rand_name(255); + let fd = shm::open( + &shm_path, + shm::OFlags::RDWR | shm::OFlags::EXCL | shm::OFlags::CREATE, + Mode::WUSR | Mode::RUSR, + )?; + + // file descriptor remains valid since our process still has the file open + shm::unlink(shm_path)?; + ftruncate(&fd, state.shm_pool_size as u64)?; + + let ptr = unsafe { + mmap( + null_mut(), + state.shm_pool_size as usize, + ProtFlags::READ | ProtFlags::WRITE, + MapFlags::SHARED, + &fd, + 0, + )? + }; + state.shm_pool_data = Some(ptr); + state.shm_fd = Some(fd); + + let mut buf = [0_u8; 4096]; + loop { + let read_bytes = display.sock.read(&mut buf)?; + let mut msgs = &buf[..read_bytes]; + while msgs.len() > 0 { + wayland_handle_message(&mut display, &mut state, &mut msgs)?; + + if state.wl_compositor != 0 + && state.wl_shm != 0 + && state.xdg_wm_base != 0 + && state.wl_surface == 0 + { + assert_eq!(state.state, wayland::StateEnum::None); + + state.wl_surface = display.wl_compositor_create_surface(&mut state)?; + state.xdg_surface = display.xdg_wm_base_get_xdg_surface(&mut state)?; + state.xdg_toplevel = display.xdg_surface_get_toplevel(&mut state)?; + display.wl_surface_commit(&mut state)?; + } + if state.state == wayland::StateEnum::SurfaceAckedConfigure { + assert_ne!(state.wl_surface, 0); + assert_ne!(state.xdg_surface, 0); + assert_ne!(state.xdg_toplevel, 0); + + if state.wl_shm_pool == 0 { + state.wl_shm_pool = display.wl_shm_create_pool(&mut state)?; + } + if state.wl_buffer == 0 { + state.wl_buffer = display.wl_shm_pool_create_buffer(&mut state)?; + } + + let n_pixels = state.w as usize * state.h as usize; + let pixels; + let ptr = state.shm_pool_data.unwrap() as *mut u32; + unsafe { + pixels = std::slice::from_raw_parts_mut(ptr, n_pixels); + } + for pixel in pixels { + let r: u32 = 255; + let g: u32 = 0; + let b: u32 = 0; + *pixel = (r << 16) | (g << 8) | b; + } + + display.wl_surface_attach(&mut state)?; + display.wl_surface_commit(&mut state)?; + state.state = wayland::StateEnum::SurfaceAttached; + } + } + } + + //Ok(()) +}