feat: compositor enhancements + kirigami cmake + knewstuff fixes
Wave A/B background task output: - redbear-compositor: enhanced protocol handling, integration tests - kirigami: updated CMakeLists - knewstuff: recipe refinements
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
# KNewStuff — framework for downloading and sharing data. Real reduced build (NewStuffCore only, QML disabled).
|
||||
# Full NewStuffQuick/NewStuffWidgets require Qt6Quick which is not yet QML-proven.
|
||||
#TODO: KNewStuff — attempt an honest core-only build on Redox. Qt Quick, widgets, tools, and Kirigami-facing surfaces stay disabled; the remaining hard blockers are the missing KF6Attica package in-tree and the still-disabled QtNetwork surface in qtbase.
|
||||
[source]
|
||||
tar = "https://invent.kde.org/frameworks/knewstuff/-/archive/v6.10.0/knewstuff-v6.10.0.tar.gz"
|
||||
|
||||
@@ -7,11 +6,12 @@ tar = "https://invent.kde.org/frameworks/knewstuff/-/archive/v6.10.0/knewstuff-v
|
||||
template = "custom"
|
||||
dependencies = [
|
||||
"qtbase",
|
||||
"qtdeclarative",
|
||||
"kf6-extra-cmake-modules",
|
||||
"kf6-kcoreaddons",
|
||||
"kf6-ki18n",
|
||||
"kf6-kconfig",
|
||||
"kf6-karchive",
|
||||
"kf6-kpackage",
|
||||
]
|
||||
script = """
|
||||
DYNAMIC_INIT
|
||||
@@ -24,67 +24,40 @@ for qtdir in plugins mkspecs metatypes modules; do
|
||||
fi
|
||||
done
|
||||
|
||||
BUILD_DIR="${COOKBOOK_SOURCE}/redox_build"
|
||||
mkdir -p "${BUILD_DIR}"
|
||||
sed -i 's/^include(ECMQmlModule)/# include(ECMQmlModule) # disabled for Redox core-only build/' \
|
||||
"${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true
|
||||
sed -i 's/COMPONENTS Gui Widgets Xml Qml Quick QuickWidgets/COMPONENTS Gui Xml/' \
|
||||
"${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true
|
||||
sed -i 's/^find_package(KF6Kirigami2.*/# find_package(KF6Kirigami2 disabled for Redox core-only build)/' \
|
||||
"${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true
|
||||
sed -i 's/^ki18n_install(po)/#ki18n_install(po)/' \
|
||||
"${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true
|
||||
sed -i 's/^add_subdirectory(qtquick)/# add_subdirectory(qtquick) # disabled for Redox core-only build/' \
|
||||
"${COOKBOOK_SOURCE}/src/CMakeLists.txt" 2>/dev/null || true
|
||||
sed -i 's/^add_subdirectory(tools)/# add_subdirectory(tools) # disabled for Redox core-only build/' \
|
||||
"${COOKBOOK_SOURCE}/src/CMakeLists.txt" 2>/dev/null || true
|
||||
sed -i 's/^add_subdirectory(widgets)/# add_subdirectory(widgets) # disabled for Redox core-only build/' \
|
||||
"${COOKBOOK_SOURCE}/src/CMakeLists.txt" 2>/dev/null || true
|
||||
|
||||
cmake -B "${BUILD_DIR}" -S "${COOKBOOK_SOURCE}" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_SYSROOT}/usr/share/cmake/redox.cmake" \
|
||||
-DCMAKE_INSTALL_PREFIX="${COOKBOOK_STAGE}/usr" \
|
||||
-DCMAKE_PREFIX_PATH="${COOKBOOK_STAGE}/usr;${COOKBOOK_SYSROOT}/usr;${HOST_BUILD}" \
|
||||
-DBUILD_SHARED_LIBS=OFF \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DKF6_HOST_TOOLING="${HOST_BUILD}/lib/cmake" \
|
||||
-DBUILD_WITH_QML=OFF \
|
||||
-DBUILD_DESKTOPTOJSON=OFF \
|
||||
-DKF_NEWSTUFF_BUILD_CORE=ON \
|
||||
-DKF_NEWSTUFF_BUILD_QUICK=OFF \
|
||||
-DKF_NEWSTUFF_BUILD_WIDGETS=OFF \
|
||||
-DQT_MAJOR_VERSION=6 \
|
||||
rm -f CMakeCache.txt
|
||||
rm -rf CMakeFiles
|
||||
|
||||
cmake "${COOKBOOK_SOURCE}" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
|
||||
-DQT_HOST_PATH="${HOST_BUILD}" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
|| {
|
||||
echo "=== KNewStuff cmake configure failed — falling back to stub configs ==="
|
||||
STAGE="${COOKBOOK_STAGE}/usr"
|
||||
mkdir -p "${STAGE}/lib/cmake/KF6NewStuff"
|
||||
cat > "${STAGE}/lib/cmake/KF6NewStuff/KF6NewStuffConfig.cmake" << 'EOFCFG'
|
||||
add_library(KF6::NewStuff INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffCore INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffQuick INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffWidgets INTERFACE IMPORTED)
|
||||
EOFCFG
|
||||
cat > "${STAGE}/lib/cmake/KF6NewStuff/KF6NewStuffConfigVersion.cmake" << 'EOFVER'
|
||||
set(PACKAGE_VERSION "6.10.0")
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
EOFVER
|
||||
cat > "${STAGE}/lib/cmake/KF6NewStuff/KF6NewStuffTargets.cmake" << 'EOFTGT'
|
||||
add_library(KF6::NewStuff INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffCore INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffQuick INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffWidgets INTERFACE IMPORTED)
|
||||
EOFTGT
|
||||
mkdir -p "${STAGE}/lib"
|
||||
echo "/* dummy */" > "${STAGE}/lib/libKF6NewStuff.a"
|
||||
echo "=== KNewStuff stub installed (cmake configure failed) ==="
|
||||
exit 0
|
||||
}
|
||||
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}:${COOKBOOK_STAGE}/usr/lib/cmake" \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DBUILD_QCH=OFF \
|
||||
-DBUILD_DESIGNERPLUGIN=OFF \
|
||||
-Wno-dev
|
||||
|
||||
cmake --build "${BUILD_DIR}" -j "${COOKBOOK_MAKE_JOBS}" || {
|
||||
echo "=== KNewStuff build failed — falling back to stub ==="
|
||||
STAGE="${COOKBOOK_STAGE}/usr"
|
||||
mkdir -p "${STAGE}/lib/cmake/KF6NewStuff"
|
||||
cat > "${STAGE}/lib/cmake/KF6NewStuff/KF6NewStuffConfig.cmake" << 'EOFCFG'
|
||||
add_library(KF6::NewStuff INTERFACE IMPORTED)
|
||||
add_library(KF6::NewStuffCore INTERFACE IMPORTED)
|
||||
EOFCFG
|
||||
cat > "${STAGE}/lib/cmake/KF6NewStuff/KF6NewStuffConfigVersion.cmake" << 'EOFVER'
|
||||
set(PACKAGE_VERSION "6.10.0")
|
||||
set(PACKAGE_VERSION_COMPATIBLE TRUE)
|
||||
EOFVER
|
||||
mkdir -p "${STAGE}/lib"
|
||||
echo "/* dummy */" > "${STAGE}/lib/libKF6NewStuff.a"
|
||||
echo "=== KNewStuff stub installed (build failed) ==="
|
||||
exit 0
|
||||
}
|
||||
cmake --build . -j${COOKBOOK_MAKE_JOBS}
|
||||
cmake --install . --prefix "${COOKBOOK_STAGE}/usr"
|
||||
|
||||
cmake --install "${BUILD_DIR}"
|
||||
echo "=== KNewStuff real build (Core only, QML disabled) ==="
|
||||
for lib in "${COOKBOOK_STAGE}/usr/lib/"libKF6*.so.*; do
|
||||
[ -f "${lib}" ] || continue
|
||||
patchelf --remove-rpath "${lib}" 2>/dev/null || true
|
||||
done
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# Build only C++ core, no QML modules
|
||||
add_library(Kirigami STATIC)
|
||||
# Build only the non-QML C++ core for now.
|
||||
add_library(Kirigami)
|
||||
add_library(KF6::Kirigami ALIAS Kirigami)
|
||||
|
||||
# Core C++ sources that don't require QML/QtQuick
|
||||
|
||||
+415
-39
@@ -1,60 +1,427 @@
|
||||
// Red Bear Compositor Runtime Check — verifies the compositor and greeter surface are healthy.
|
||||
// Usage: redbear-compositor-check [--verbose]
|
||||
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem;
|
||||
use std::os::fd::AsRawFd;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::time::Duration;
|
||||
|
||||
fn check_wayland_socket() -> Result<(), String> {
|
||||
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
|
||||
.unwrap_or_else(|_| "/tmp/run/redbear-greeter".into());
|
||||
let display = std::env::var("WAYLAND_DISPLAY")
|
||||
.unwrap_or_else(|_| "wayland-0".into());
|
||||
let socket_path = format!("{}/{}", runtime_dir, display);
|
||||
const WL_DISPLAY_SYNC: u16 = 0;
|
||||
const WL_DISPLAY_GET_REGISTRY: u16 = 1;
|
||||
const WL_REGISTRY_BIND: u16 = 0;
|
||||
const WL_REGISTRY_GLOBAL: u16 = 0;
|
||||
const WL_COMPOSITOR_CREATE_SURFACE: u16 = 0;
|
||||
const WL_SHM_CREATE_POOL: u16 = 0;
|
||||
const WL_SHM_FORMAT: u16 = 0;
|
||||
const WL_SHM_POOL_CREATE_BUFFER: u16 = 0;
|
||||
const WL_SURFACE_ATTACH: u16 = 0;
|
||||
const WL_SURFACE_COMMIT: u16 = 5;
|
||||
const WL_CALLBACK_DONE: u16 = 0;
|
||||
const XDG_WM_BASE_GET_XDG_SURFACE: u16 = 2;
|
||||
const XDG_SURFACE_GET_TOPLEVEL: u16 = 1;
|
||||
const XDG_SURFACE_ACK_CONFIGURE: u16 = 4;
|
||||
const XDG_SURFACE_CONFIGURE: u16 = 0;
|
||||
const XDG_TOPLEVEL_CONFIGURE: u16 = 0;
|
||||
const WL_SHM_FORMAT_XRGB8888: u32 = 1;
|
||||
|
||||
if !std::path::Path::new(&socket_path).exists() {
|
||||
return Err(format!("Wayland socket {} does not exist", socket_path));
|
||||
fn push_u32(buf: &mut Vec<u8>, value: u32) {
|
||||
buf.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn push_i32(buf: &mut Vec<u8>, value: i32) {
|
||||
buf.extend_from_slice(&value.to_le_bytes());
|
||||
}
|
||||
|
||||
fn push_wayland_string(buf: &mut Vec<u8>, value: &str) {
|
||||
let bytes = value.as_bytes();
|
||||
push_u32(buf, (bytes.len() + 1) as u32);
|
||||
buf.extend_from_slice(bytes);
|
||||
buf.push(0);
|
||||
while buf.len() % 4 != 0 {
|
||||
buf.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u32(data: &[u8], cursor: &mut usize) -> Result<u32, String> {
|
||||
if *cursor + 4 > data.len() {
|
||||
return Err(String::from("unexpected end of message while reading u32"));
|
||||
}
|
||||
|
||||
let mut stream = UnixStream::connect(&socket_path)
|
||||
.map_err(|e| format!("failed to connect to {}: {}", socket_path, e))?;
|
||||
stream.set_read_timeout(Some(Duration::from_secs(2)))
|
||||
.map_err(|e| format!("failed to set timeout: {}", e))?;
|
||||
let value = u32::from_le_bytes([
|
||||
data[*cursor],
|
||||
data[*cursor + 1],
|
||||
data[*cursor + 2],
|
||||
data[*cursor + 3],
|
||||
]);
|
||||
*cursor += 4;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
// Send wl_display.sync request to verify protocol
|
||||
let display_id = 1u32;
|
||||
let callback_id = 2u32;
|
||||
let mut msg = Vec::new();
|
||||
msg.extend_from_slice(&display_id.to_ne_bytes());
|
||||
let size = 12u32;
|
||||
let opcode = 0u16; // wl_display.sync
|
||||
msg.extend_from_slice(&((size << 16) | opcode as u32).to_ne_bytes());
|
||||
msg.extend_from_slice(&callback_id.to_ne_bytes());
|
||||
stream.write_all(&msg)
|
||||
.map_err(|e| format!("wl_display.sync failed: {}", e))?;
|
||||
fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result<String, String> {
|
||||
let length = read_u32(data, cursor)? as usize;
|
||||
if length == 0 {
|
||||
return Ok(String::new());
|
||||
}
|
||||
if *cursor + length > data.len() {
|
||||
return Err(String::from("unexpected end of message while reading string"));
|
||||
}
|
||||
|
||||
// Read response
|
||||
let mut buf = [0u8; 256];
|
||||
let n = stream.read(&mut buf)
|
||||
.map_err(|e| format!("read failed: {}", e))?;
|
||||
let bytes = &data[*cursor..*cursor + length];
|
||||
let string_len = bytes.iter().position(|byte| *byte == 0).unwrap_or(bytes.len());
|
||||
*cursor += length;
|
||||
while *cursor % 4 != 0 {
|
||||
*cursor += 1;
|
||||
}
|
||||
|
||||
if n < 8 {
|
||||
return Err(format!("short response: {} bytes", n));
|
||||
std::str::from_utf8(&bytes[..string_len])
|
||||
.map(str::to_owned)
|
||||
.map_err(|err| format!("invalid UTF-8 in Wayland string: {err}"))
|
||||
}
|
||||
|
||||
struct WaylandProbe {
|
||||
stream: UnixStream,
|
||||
next_id: u32,
|
||||
}
|
||||
|
||||
impl WaylandProbe {
|
||||
fn connect(socket_path: &str) -> Result<Self, String> {
|
||||
if !std::path::Path::new(socket_path).exists() {
|
||||
return Err(format!("Wayland socket {} does not exist", socket_path));
|
||||
}
|
||||
|
||||
let stream = UnixStream::connect(socket_path)
|
||||
.map_err(|e| format!("failed to connect to {}: {}", socket_path, e))?;
|
||||
stream
|
||||
.set_read_timeout(Some(Duration::from_secs(2)))
|
||||
.map_err(|e| format!("failed to set timeout: {}", e))?;
|
||||
|
||||
Ok(Self { stream, next_id: 2 })
|
||||
}
|
||||
|
||||
fn alloc_id(&mut self) -> u32 {
|
||||
let id = self.next_id;
|
||||
self.next_id += 1;
|
||||
id
|
||||
}
|
||||
|
||||
fn send_message(&mut self, object_id: u32, opcode: u16, payload: &[u8]) -> Result<(), String> {
|
||||
let size = 8 + payload.len();
|
||||
let mut msg = Vec::with_capacity(size);
|
||||
push_u32(&mut msg, object_id);
|
||||
push_u32(&mut msg, ((size as u32) << 16) | u32::from(opcode));
|
||||
msg.extend_from_slice(payload);
|
||||
self.stream
|
||||
.write_all(&msg)
|
||||
.map_err(|e| format!("write failed: {}", e))
|
||||
}
|
||||
|
||||
fn send_message_with_fds(
|
||||
&mut self,
|
||||
object_id: u32,
|
||||
opcode: u16,
|
||||
payload: &[u8],
|
||||
fds: &[i32],
|
||||
) -> Result<(), String> {
|
||||
if fds.is_empty() {
|
||||
return self.send_message(object_id, opcode, payload);
|
||||
}
|
||||
|
||||
let size = 8 + payload.len();
|
||||
let mut msg = Vec::with_capacity(size);
|
||||
push_u32(&mut msg, object_id);
|
||||
push_u32(&mut msg, ((size as u32) << 16) | u32::from(opcode));
|
||||
msg.extend_from_slice(payload);
|
||||
|
||||
let mut iov = libc::iovec {
|
||||
iov_base: msg.as_mut_ptr().cast(),
|
||||
iov_len: msg.len(),
|
||||
};
|
||||
let control_len = unsafe {
|
||||
libc::CMSG_SPACE((fds.len() * mem::size_of::<i32>()) as u32) as usize
|
||||
};
|
||||
let mut control = vec![0u8; control_len];
|
||||
let mut header = libc::msghdr {
|
||||
msg_name: std::ptr::null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: &mut iov,
|
||||
msg_iovlen: 1,
|
||||
msg_control: control.as_mut_ptr().cast(),
|
||||
msg_controllen: control.len(),
|
||||
msg_flags: 0,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let cmsg = libc::CMSG_FIRSTHDR(&header);
|
||||
if cmsg.is_null() {
|
||||
return Err(String::from("failed to allocate SCM_RIGHTS header"));
|
||||
}
|
||||
(*cmsg).cmsg_level = libc::SOL_SOCKET;
|
||||
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
|
||||
(*cmsg).cmsg_len = libc::CMSG_LEN((fds.len() * mem::size_of::<i32>()) as u32) as _;
|
||||
std::ptr::copy_nonoverlapping(
|
||||
fds.as_ptr().cast::<u8>(),
|
||||
libc::CMSG_DATA(cmsg).cast::<u8>(),
|
||||
fds.len() * mem::size_of::<i32>(),
|
||||
);
|
||||
}
|
||||
|
||||
let written = unsafe { libc::sendmsg(self.stream.as_raw_fd(), &header, 0) };
|
||||
if written < 0 {
|
||||
return Err(format!("sendmsg failed: {}", std::io::Error::last_os_error()));
|
||||
}
|
||||
if written as usize != msg.len() {
|
||||
return Err(format!("short sendmsg write: expected {}, got {}", msg.len(), written));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_message(&mut self) -> Result<(u32, u16, Vec<u8>), String> {
|
||||
let mut header = [0u8; 8];
|
||||
self.stream
|
||||
.read_exact(&mut header)
|
||||
.map_err(|e| format!("read failed: {}", e))?;
|
||||
let object_id = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
|
||||
let size_opcode = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
|
||||
let size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
||||
let opcode = (size_opcode & 0xFFFF) as u16;
|
||||
let mut payload = vec![0u8; size.saturating_sub(8)];
|
||||
if size > 8 {
|
||||
self.stream
|
||||
.read_exact(&mut payload)
|
||||
.map_err(|e| format!("read payload failed: {}", e))?;
|
||||
}
|
||||
Ok((object_id, opcode, payload))
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> Result<u32, String> {
|
||||
let callback_id = self.alloc_id();
|
||||
self.send_message(1, WL_DISPLAY_SYNC, &callback_id.to_le_bytes())?;
|
||||
Ok(callback_id)
|
||||
}
|
||||
|
||||
fn get_registry(&mut self) -> Result<u32, String> {
|
||||
let registry_id = self.alloc_id();
|
||||
self.send_message(1, WL_DISPLAY_GET_REGISTRY, ®istry_id.to_le_bytes())?;
|
||||
Ok(registry_id)
|
||||
}
|
||||
|
||||
fn bind(&mut self, registry_id: u32, name: u32, interface: &str, version: u32) -> Result<u32, String> {
|
||||
let new_id = self.alloc_id();
|
||||
let mut payload = Vec::new();
|
||||
push_u32(&mut payload, name);
|
||||
push_wayland_string(&mut payload, interface);
|
||||
push_u32(&mut payload, version);
|
||||
push_u32(&mut payload, new_id);
|
||||
self.send_message(registry_id, WL_REGISTRY_BIND, &payload)?;
|
||||
Ok(new_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_globals(probe: &mut WaylandProbe) -> Result<HashMap<String, u32>, String> {
|
||||
let registry_id = probe.get_registry()?;
|
||||
let mut globals = HashMap::new();
|
||||
|
||||
for _ in 0..6 {
|
||||
let (object_id, opcode, payload) = probe.read_message()?;
|
||||
if object_id != registry_id || opcode != WL_REGISTRY_GLOBAL {
|
||||
return Err(format!(
|
||||
"unexpected registry event: object={} opcode={}",
|
||||
object_id, opcode
|
||||
));
|
||||
}
|
||||
|
||||
let mut cursor = 0;
|
||||
let name = read_u32(&payload, &mut cursor)?;
|
||||
let interface = read_wayland_string(&payload, &mut cursor)?;
|
||||
let _version = read_u32(&payload, &mut cursor)?;
|
||||
globals.insert(interface, name);
|
||||
}
|
||||
|
||||
Ok(globals)
|
||||
}
|
||||
|
||||
fn expect_shm_formats(probe: &mut WaylandProbe, shm_id: u32) -> Result<(), String> {
|
||||
let mut formats = Vec::new();
|
||||
|
||||
for _ in 0..2 {
|
||||
let (object_id, opcode, payload) = probe.read_message()?;
|
||||
if object_id != shm_id || opcode != WL_SHM_FORMAT || payload.len() != 4 {
|
||||
return Err(format!(
|
||||
"unexpected wl_shm event: object={} opcode={} payload_len={}",
|
||||
object_id,
|
||||
opcode,
|
||||
payload.len()
|
||||
));
|
||||
}
|
||||
formats.push(u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]));
|
||||
}
|
||||
|
||||
if !formats.contains(&0) || !formats.contains(&1) {
|
||||
return Err(format!("wl_shm.format list incomplete: {:?}", formats));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expect_xdg_configure(probe: &mut WaylandProbe, toplevel_id: u32, xdg_surface_id: u32) -> Result<u32, String> {
|
||||
let (object_id, opcode, payload) = probe.read_message()?;
|
||||
if object_id != toplevel_id || opcode != XDG_TOPLEVEL_CONFIGURE {
|
||||
return Err(format!(
|
||||
"unexpected xdg_toplevel event: object={} opcode={}",
|
||||
object_id, opcode
|
||||
));
|
||||
}
|
||||
if payload.len() < 12 {
|
||||
return Err(format!("short xdg_toplevel.configure payload: {} bytes", payload.len()));
|
||||
}
|
||||
|
||||
let states_len = u32::from_le_bytes([payload[8], payload[9], payload[10], payload[11]]) as usize;
|
||||
if payload.len() != 12 + states_len {
|
||||
return Err(format!(
|
||||
"invalid xdg_toplevel.configure payload length: {} (states_len={})",
|
||||
payload.len(), states_len
|
||||
));
|
||||
}
|
||||
if states_len % 4 != 0 {
|
||||
return Err(format!("invalid xdg_toplevel.configure states array length: {}", states_len));
|
||||
}
|
||||
|
||||
let (object_id, opcode, payload) = probe.read_message()?;
|
||||
if object_id != xdg_surface_id || opcode != XDG_SURFACE_CONFIGURE || payload.len() != 4 {
|
||||
return Err(format!(
|
||||
"unexpected xdg_surface event: object={} opcode={} payload_len={}",
|
||||
object_id,
|
||||
opcode,
|
||||
payload.len()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]))
|
||||
}
|
||||
|
||||
fn exercise_shm_pool(probe: &mut WaylandProbe, shm_id: u32, surface_id: u32) -> Result<(), String> {
|
||||
let temp_path = std::env::temp_dir().join(format!(
|
||||
"redbear-compositor-check-{}-{}.shm",
|
||||
std::process::id(),
|
||||
surface_id
|
||||
));
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
.read(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(&temp_path)
|
||||
.map_err(|err| format!("failed to create temp SHM file: {err}"))?;
|
||||
file.set_len(16)
|
||||
.map_err(|err| format!("failed to size temp SHM file: {err}"))?;
|
||||
|
||||
let mut file = file;
|
||||
file.write_all(&[0x40, 0x40, 0xFF, 0xFF, 0x40, 0x40, 0xFF, 0xFF, 0x40, 0x40, 0xFF, 0xFF, 0x40, 0x40, 0xFF, 0xFF])
|
||||
.map_err(|err| format!("failed to seed temp SHM file: {err}"))?;
|
||||
|
||||
let pool_id = probe.alloc_id();
|
||||
let mut payload = Vec::new();
|
||||
push_u32(&mut payload, pool_id);
|
||||
push_i32(&mut payload, 16);
|
||||
probe.send_message_with_fds(shm_id, WL_SHM_CREATE_POOL, &payload, &[file.as_raw_fd()])?;
|
||||
|
||||
let buffer_id = probe.alloc_id();
|
||||
let mut payload = Vec::new();
|
||||
push_u32(&mut payload, buffer_id);
|
||||
push_u32(&mut payload, 0);
|
||||
push_i32(&mut payload, 2);
|
||||
push_i32(&mut payload, 2);
|
||||
push_i32(&mut payload, 8);
|
||||
push_u32(&mut payload, WL_SHM_FORMAT_XRGB8888);
|
||||
probe.send_message(pool_id, WL_SHM_POOL_CREATE_BUFFER, &payload)?;
|
||||
|
||||
let mut payload = Vec::new();
|
||||
push_u32(&mut payload, buffer_id);
|
||||
push_i32(&mut payload, 0);
|
||||
push_i32(&mut payload, 0);
|
||||
probe.send_message(surface_id, WL_SURFACE_ATTACH, &payload)?;
|
||||
probe.send_message(surface_id, WL_SURFACE_COMMIT, &[])?;
|
||||
|
||||
let callback_id = probe.sync()?;
|
||||
let (object_id, opcode, payload) = probe.read_message()?;
|
||||
let _ = std::fs::remove_file(&temp_path);
|
||||
|
||||
if object_id != callback_id || opcode != WL_CALLBACK_DONE || payload.len() != 4 {
|
||||
return Err(format!(
|
||||
"unexpected callback response after SHM commit: object={} opcode={} payload_len={}",
|
||||
object_id,
|
||||
opcode,
|
||||
payload.len()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_wayland_socket() -> Result<(), String> {
|
||||
let runtime_dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp/run/redbear-greeter".into());
|
||||
let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".into());
|
||||
let socket_path = format!("{}/{}", runtime_dir, display);
|
||||
|
||||
let mut probe = WaylandProbe::connect(&socket_path)?;
|
||||
let globals = collect_globals(&mut probe)?;
|
||||
|
||||
let registry_id = 2;
|
||||
let compositor_name = *globals
|
||||
.get("wl_compositor")
|
||||
.ok_or_else(|| String::from("wl_compositor global missing"))?;
|
||||
let shm_name = *globals
|
||||
.get("wl_shm")
|
||||
.ok_or_else(|| String::from("wl_shm global missing"))?;
|
||||
let xdg_name = *globals
|
||||
.get("xdg_wm_base")
|
||||
.ok_or_else(|| String::from("xdg_wm_base global missing"))?;
|
||||
|
||||
let compositor_id = probe.bind(registry_id, compositor_name, "wl_compositor", 4)?;
|
||||
let shm_id = probe.bind(registry_id, shm_name, "wl_shm", 1)?;
|
||||
let xdg_wm_base_id = probe.bind(registry_id, xdg_name, "xdg_wm_base", 1)?;
|
||||
|
||||
expect_shm_formats(&mut probe, shm_id)?;
|
||||
|
||||
let surface_id = probe.alloc_id();
|
||||
probe.send_message(compositor_id, WL_COMPOSITOR_CREATE_SURFACE, &surface_id.to_le_bytes())?;
|
||||
|
||||
let xdg_surface_id = probe.alloc_id();
|
||||
let mut payload = Vec::new();
|
||||
push_u32(&mut payload, xdg_surface_id);
|
||||
push_u32(&mut payload, surface_id);
|
||||
probe.send_message(xdg_wm_base_id, XDG_WM_BASE_GET_XDG_SURFACE, &payload)?;
|
||||
|
||||
let toplevel_id = probe.alloc_id();
|
||||
probe.send_message(xdg_surface_id, XDG_SURFACE_GET_TOPLEVEL, &toplevel_id.to_le_bytes())?;
|
||||
let serial = expect_xdg_configure(&mut probe, toplevel_id, xdg_surface_id)?;
|
||||
probe.send_message(xdg_surface_id, XDG_SURFACE_ACK_CONFIGURE, &serial.to_le_bytes())?;
|
||||
|
||||
exercise_shm_pool(&mut probe, shm_id, surface_id)
|
||||
}
|
||||
|
||||
fn check_binaries() -> Result<(), Vec<String>> {
|
||||
let mut missing = Vec::new();
|
||||
for bin in &["/usr/bin/redbear-compositor", "/usr/bin/redbear-greeterd",
|
||||
"/usr/bin/redbear-greeter-ui", "/usr/bin/redbear-authd",
|
||||
"/usr/bin/kwin_wayland_wrapper"] {
|
||||
for bin in &[
|
||||
"/usr/bin/redbear-compositor",
|
||||
"/usr/bin/redbear-greeterd",
|
||||
"/usr/bin/redbear-greeter-ui",
|
||||
"/usr/bin/redbear-authd",
|
||||
"/usr/bin/kwin_wayland_wrapper",
|
||||
] {
|
||||
if !std::path::Path::new(bin).exists() {
|
||||
missing.push(bin.to_string());
|
||||
}
|
||||
}
|
||||
if missing.is_empty() { Ok(()) } else { Err(missing) }
|
||||
if missing.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(missing)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_framebuffer() -> Result<(), String> {
|
||||
@@ -89,7 +456,11 @@ fn check_services() -> Result<(), Vec<String>> {
|
||||
issues.push(format!("{} not found at {}", name, path));
|
||||
}
|
||||
}
|
||||
if issues.is_empty() { Ok(()) } else { Err(issues) }
|
||||
if issues.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(issues)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@@ -113,7 +484,9 @@ fn main() {
|
||||
($label:expr, $check:expr, vec) => {
|
||||
match $check {
|
||||
Ok(()) => {
|
||||
if verbose { println!(" PASS {}", $label); }
|
||||
if verbose {
|
||||
println!(" PASS {}", $label);
|
||||
}
|
||||
}
|
||||
Err(errs) => {
|
||||
for e in errs {
|
||||
@@ -143,14 +516,17 @@ fn main() {
|
||||
check!("runtime services", check_services(), vec);
|
||||
|
||||
if verbose {
|
||||
println!(" Checking Wayland socket...");
|
||||
println!(" Checking Wayland protocol features...");
|
||||
}
|
||||
check!("Wayland compositor socket", check_wayland_socket());
|
||||
check!("Wayland protocol surface", check_wayland_socket());
|
||||
|
||||
if exit == 0 {
|
||||
println!("redbear-compositor-check: all checks passed");
|
||||
} else {
|
||||
eprintln!("redbear-compositor-check: {} check(s) failed", if exit == 1 { "1" } else { "some" });
|
||||
eprintln!(
|
||||
"redbear-compositor-check: {} check(s) failed",
|
||||
if exit == 1 { "1" } else { "some" }
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::mem;
|
||||
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::net::{UnixListener, UnixStream};
|
||||
use std::sync::{atomic::{AtomicU32, Ordering}, Mutex};
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
};
|
||||
|
||||
fn map_framebuffer(_phys: usize, size: usize) -> Vec<u8> {
|
||||
vec![0u8; size]
|
||||
@@ -75,11 +78,16 @@ fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result<String, String
|
||||
return Ok(String::new());
|
||||
}
|
||||
if *cursor + length > data.len() {
|
||||
return Err(String::from("unexpected end of message while reading string"));
|
||||
return Err(String::from(
|
||||
"unexpected end of message while reading string",
|
||||
));
|
||||
}
|
||||
|
||||
let bytes = &data[*cursor..*cursor + length];
|
||||
let string_len = bytes.iter().position(|byte| *byte == 0).unwrap_or(bytes.len());
|
||||
let string_len = bytes
|
||||
.iter()
|
||||
.position(|byte| *byte == 0)
|
||||
.unwrap_or(bytes.len());
|
||||
*cursor += length;
|
||||
while *cursor % 4 != 0 {
|
||||
*cursor += 1;
|
||||
@@ -90,7 +98,10 @@ fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result<String, String
|
||||
.map_err(|err| format!("invalid UTF-8 in Wayland string: {err}"))
|
||||
}
|
||||
|
||||
fn recv_with_rights(stream: &mut UnixStream, data: &mut [u8]) -> std::io::Result<(usize, VecDeque<RawFd>)> {
|
||||
fn recv_with_rights(
|
||||
stream: &mut UnixStream,
|
||||
data: &mut [u8],
|
||||
) -> std::io::Result<(usize, VecDeque<RawFd>)> {
|
||||
let mut iov = libc::iovec {
|
||||
iov_base: data.as_mut_ptr().cast(),
|
||||
iov_len: data.len(),
|
||||
@@ -118,7 +129,8 @@ fn recv_with_rights(stream: &mut UnixStream, data: &mut [u8]) -> std::io::Result
|
||||
(*cmsg).cmsg_level == libc::SOL_SOCKET && (*cmsg).cmsg_type == libc::SCM_RIGHTS
|
||||
};
|
||||
if is_rights {
|
||||
let data_len = unsafe { (*cmsg).cmsg_len as usize }.saturating_sub(mem::size_of::<libc::cmsghdr>());
|
||||
let data_len = unsafe { (*cmsg).cmsg_len as usize }
|
||||
.saturating_sub(mem::size_of::<libc::cmsghdr>());
|
||||
let fd_count = data_len / mem::size_of::<RawFd>();
|
||||
let data_ptr = unsafe { libc::CMSG_DATA(cmsg).cast::<RawFd>() };
|
||||
for index in 0..fd_count {
|
||||
@@ -292,23 +304,49 @@ impl Compositor {
|
||||
let _ = std::fs::remove_file(socket_path);
|
||||
let listener = UnixListener::bind(socket_path)?;
|
||||
|
||||
let runtime_dir = std::path::Path::new(socket_path).parent()
|
||||
let runtime_dir = std::path::Path::new(socket_path)
|
||||
.parent()
|
||||
.unwrap_or(std::path::Path::new("/tmp"));
|
||||
std::fs::write(
|
||||
runtime_dir.join("compositor.pid"),
|
||||
format!("{}\n", std::process::id()),
|
||||
).ok();
|
||||
)
|
||||
.ok();
|
||||
|
||||
let fb_size = (fb_height as usize) * (fb_stride as usize);
|
||||
let fb_data = map_framebuffer(fb_phys, fb_size);
|
||||
|
||||
let globals = vec![
|
||||
Global { name: 1, interface: "wl_compositor".into(), version: 4 },
|
||||
Global { name: 2, interface: "wl_shm".into(), version: 1 },
|
||||
Global { name: 3, interface: "wl_shell".into(), version: 1 },
|
||||
Global { name: 4, interface: "wl_seat".into(), version: 5 },
|
||||
Global { name: 5, interface: "wl_output".into(), version: 3 },
|
||||
Global { name: 6, interface: "xdg_wm_base".into(), version: 1 },
|
||||
Global {
|
||||
name: 1,
|
||||
interface: "wl_compositor".into(),
|
||||
version: 4,
|
||||
},
|
||||
Global {
|
||||
name: 2,
|
||||
interface: "wl_shm".into(),
|
||||
version: 1,
|
||||
},
|
||||
Global {
|
||||
name: 3,
|
||||
interface: "wl_shell".into(),
|
||||
version: 1,
|
||||
},
|
||||
Global {
|
||||
name: 4,
|
||||
interface: "wl_seat".into(),
|
||||
version: 5,
|
||||
},
|
||||
Global {
|
||||
name: 5,
|
||||
interface: "wl_output".into(),
|
||||
version: 3,
|
||||
},
|
||||
Global {
|
||||
name: 6,
|
||||
interface: "xdg_wm_base".into(),
|
||||
version: 1,
|
||||
},
|
||||
];
|
||||
|
||||
Ok(Self {
|
||||
@@ -335,7 +373,8 @@ impl Compositor {
|
||||
pub fn run(&mut self) -> std::io::Result<()> {
|
||||
eprintln!("redbear-compositor: listening on Wayland socket");
|
||||
let _ = std::fs::write(
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".into()) + "/compositor.status",
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".into())
|
||||
+ "/compositor.status",
|
||||
"ready\n",
|
||||
);
|
||||
for stream in self.listener.incoming() {
|
||||
@@ -343,13 +382,16 @@ impl Compositor {
|
||||
Ok(stream) => {
|
||||
let client_id = self.alloc_id();
|
||||
eprintln!("redbear-compositor: client {} connected", client_id);
|
||||
self.clients.lock().unwrap().insert(client_id, ClientState {
|
||||
objects: HashMap::new(),
|
||||
surfaces: HashMap::new(),
|
||||
buffers: HashMap::new(),
|
||||
shm_pools: HashMap::new(),
|
||||
_next_id: 1,
|
||||
});
|
||||
self.clients.lock().unwrap().insert(
|
||||
client_id,
|
||||
ClientState {
|
||||
objects: HashMap::new(),
|
||||
surfaces: HashMap::new(),
|
||||
buffers: HashMap::new(),
|
||||
shm_pools: HashMap::new(),
|
||||
_next_id: 1,
|
||||
},
|
||||
);
|
||||
self.handle_client(client_id, stream);
|
||||
}
|
||||
Err(e) => eprintln!("redbear-compositor: accept error: {}", e),
|
||||
@@ -415,17 +457,30 @@ impl Compositor {
|
||||
) -> Result<(), String> {
|
||||
let mut offset = 0;
|
||||
while offset + 8 <= data.len() {
|
||||
let object_id = u32::from_le_bytes([data[offset], data[offset + 1], data[offset + 2], data[offset + 3]]);
|
||||
let object_id = u32::from_le_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
]);
|
||||
// Wayland wire format: [object_id:u32][size:u16][opcode:u16]
|
||||
let size_opcode = u32::from_le_bytes([data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7]]);
|
||||
let size_opcode = u32::from_le_bytes([
|
||||
data[offset + 4],
|
||||
data[offset + 5],
|
||||
data[offset + 6],
|
||||
data[offset + 7],
|
||||
]);
|
||||
let msg_size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
||||
let opcode = (size_opcode & 0xFFFF) as u16;
|
||||
|
||||
|
||||
if msg_size < 8 || offset + msg_size > data.len() {
|
||||
return Err(format!("malformed message: object={} opcode={} size={}", object_id, opcode, msg_size));
|
||||
return Err(format!(
|
||||
"malformed message: object={} opcode={} size={}",
|
||||
object_id, opcode, msg_size
|
||||
));
|
||||
}
|
||||
|
||||
let payload = &data[offset+8..offset+msg_size];
|
||||
let payload = &data[offset + 8..offset + msg_size];
|
||||
let object_type = if object_id == 1 {
|
||||
OBJECT_TYPE_WL_DISPLAY
|
||||
} else {
|
||||
@@ -436,18 +491,22 @@ impl Compositor {
|
||||
.and_then(|client| client.objects.get(&object_id).copied())
|
||||
.unwrap_or(0)
|
||||
};
|
||||
|
||||
|
||||
match object_type {
|
||||
OBJECT_TYPE_WL_DISPLAY => match opcode {
|
||||
WL_DISPLAY_SYNC => {
|
||||
let callback_id = if payload.len() >= 4 {
|
||||
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]])
|
||||
} else { self.alloc_id() };
|
||||
} else {
|
||||
self.alloc_id()
|
||||
};
|
||||
self.send_callback_done(stream, callback_id, 0);
|
||||
}
|
||||
WL_DISPLAY_DELETE_ID => {
|
||||
if payload.len() >= 4 {
|
||||
let obj_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let obj_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.remove(&obj_id);
|
||||
@@ -459,7 +518,9 @@ impl Compositor {
|
||||
}
|
||||
WL_DISPLAY_GET_REGISTRY => {
|
||||
if payload.len() >= 4 {
|
||||
let registry_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let registry_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
let mut send_globals = false;
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
@@ -473,7 +534,10 @@ impl Compositor {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_REGISTRY => match opcode {
|
||||
@@ -484,69 +548,91 @@ impl Compositor {
|
||||
let _version = read_u32(payload, &mut cursor)?;
|
||||
let new_id = read_u32(payload, &mut cursor)?;
|
||||
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
let type_id = match iface.as_str() {
|
||||
"wl_compositor" => OBJECT_TYPE_WL_COMPOSITOR,
|
||||
"wl_shm" => OBJECT_TYPE_WL_SHM,
|
||||
"wl_shell" => OBJECT_TYPE_WL_SHELL,
|
||||
"wl_seat" => OBJECT_TYPE_WL_SEAT,
|
||||
"wl_output" => OBJECT_TYPE_WL_OUTPUT,
|
||||
"xdg_wm_base" => OBJECT_TYPE_XDG_WM_BASE,
|
||||
_ => 0,
|
||||
};
|
||||
client.objects.insert(new_id, type_id);
|
||||
if iface == "wl_shm" {
|
||||
self.send_shm_format(stream, new_id, WL_SHM_FORMAT_ARGB8888);
|
||||
self.send_shm_format(stream, new_id, WL_SHM_FORMAT_XRGB8888);
|
||||
}
|
||||
if iface == "wl_output" {
|
||||
self.send_output_info(stream, new_id);
|
||||
}
|
||||
if iface == "wl_seat" {
|
||||
self.send_seat_capabilities(stream, new_id);
|
||||
}
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
let type_id = match iface.as_str() {
|
||||
"wl_compositor" => OBJECT_TYPE_WL_COMPOSITOR,
|
||||
"wl_shm" => OBJECT_TYPE_WL_SHM,
|
||||
"wl_shell" => OBJECT_TYPE_WL_SHELL,
|
||||
"wl_seat" => OBJECT_TYPE_WL_SEAT,
|
||||
"wl_output" => OBJECT_TYPE_WL_OUTPUT,
|
||||
"xdg_wm_base" => OBJECT_TYPE_XDG_WM_BASE,
|
||||
_ => 0,
|
||||
};
|
||||
client.objects.insert(new_id, type_id);
|
||||
if iface == "wl_shm" {
|
||||
self.send_shm_format(stream, new_id, WL_SHM_FORMAT_ARGB8888);
|
||||
self.send_shm_format(stream, new_id, WL_SHM_FORMAT_XRGB8888);
|
||||
}
|
||||
if iface == "wl_output" {
|
||||
self.send_output_info(stream, new_id);
|
||||
}
|
||||
if iface == "wl_seat" {
|
||||
self.send_seat_capabilities(stream, new_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_COMPOSITOR => match opcode {
|
||||
WL_COMPOSITOR_CREATE_SURFACE => {
|
||||
if payload.len() >= 4 {
|
||||
let surface_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let surface_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.insert(surface_id, OBJECT_TYPE_WL_SURFACE);
|
||||
client.surfaces.insert(surface_id, Surface {
|
||||
buffer: None,
|
||||
committed_buffer_id: None,
|
||||
x: 0, y: 0,
|
||||
_width: self.fb_width,
|
||||
_height: self.fb_height,
|
||||
});
|
||||
client.surfaces.insert(
|
||||
surface_id,
|
||||
Surface {
|
||||
buffer: None,
|
||||
committed_buffer_id: None,
|
||||
x: 0,
|
||||
y: 0,
|
||||
_width: self.fb_width,
|
||||
_height: self.fb_height,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_SHM => match opcode {
|
||||
WL_SHM_CREATE_POOL => {
|
||||
if payload.len() >= 8 {
|
||||
let pool_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let size = i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let fd_val = fds
|
||||
.pop_front()
|
||||
.ok_or_else(|| String::from("wl_shm.create_pool missing SCM_RIGHTS fd"))?;
|
||||
let pool_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let size = i32::from_le_bytes([
|
||||
payload[4], payload[5], payload[6], payload[7],
|
||||
]);
|
||||
let fd_val = fds.pop_front().ok_or_else(|| {
|
||||
String::from("wl_shm.create_pool missing SCM_RIGHTS fd")
|
||||
})?;
|
||||
if size > 0 {
|
||||
let file = unsafe { std::fs::File::from_raw_fd(fd_val) };
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.insert(pool_id, OBJECT_TYPE_WL_SHM_POOL);
|
||||
client.shm_pools.insert(pool_id, ShmPool { file, size: size as usize });
|
||||
client.shm_pools.insert(
|
||||
pool_id,
|
||||
ShmPool {
|
||||
file,
|
||||
size: size as usize,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let _ = unsafe { libc::close(fd_val) };
|
||||
@@ -554,54 +640,100 @@ impl Compositor {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_SHM_POOL => match opcode {
|
||||
WL_SHM_POOL_CREATE_BUFFER => {
|
||||
if payload.len() >= 20 {
|
||||
let buffer_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let offset = u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let width = u32::from_le_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
||||
let height = u32::from_le_bytes([payload[12], payload[13], payload[14], payload[15]]);
|
||||
let stride = u32::from_le_bytes([payload[16], payload[17], payload[18], payload[19]]);
|
||||
let buffer_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let offset = u32::from_le_bytes([
|
||||
payload[4], payload[5], payload[6], payload[7],
|
||||
]);
|
||||
let width = u32::from_le_bytes([
|
||||
payload[8],
|
||||
payload[9],
|
||||
payload[10],
|
||||
payload[11],
|
||||
]);
|
||||
let height = u32::from_le_bytes([
|
||||
payload[12],
|
||||
payload[13],
|
||||
payload[14],
|
||||
payload[15],
|
||||
]);
|
||||
let stride = u32::from_le_bytes([
|
||||
payload[16],
|
||||
payload[17],
|
||||
payload[18],
|
||||
payload[19],
|
||||
]);
|
||||
let format = if payload.len() >= 24 {
|
||||
u32::from_le_bytes([payload[20], payload[21], payload[22], payload[23]])
|
||||
} else { WL_SHM_FORMAT_ARGB8888 };
|
||||
u32::from_le_bytes([
|
||||
payload[20],
|
||||
payload[21],
|
||||
payload[22],
|
||||
payload[23],
|
||||
])
|
||||
} else {
|
||||
WL_SHM_FORMAT_ARGB8888
|
||||
};
|
||||
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.insert(buffer_id, OBJECT_TYPE_WL_BUFFER);
|
||||
client.buffers.insert(buffer_id, (object_id, Buffer {
|
||||
pool_id: object_id,
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
_format: format,
|
||||
}));
|
||||
client.buffers.insert(
|
||||
buffer_id,
|
||||
(
|
||||
object_id,
|
||||
Buffer {
|
||||
pool_id: object_id,
|
||||
offset,
|
||||
width,
|
||||
height,
|
||||
stride,
|
||||
_format: format,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_SURFACE => match opcode {
|
||||
WL_SURFACE_ATTACH => {
|
||||
if payload.len() >= 12 {
|
||||
let buffer_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let _x = i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let _y = i32::from_le_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
||||
let buffer_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let _x = i32::from_le_bytes([
|
||||
payload[4], payload[5], payload[6], payload[7],
|
||||
]);
|
||||
let _y = i32::from_le_bytes([
|
||||
payload[8],
|
||||
payload[9],
|
||||
payload[10],
|
||||
payload[11],
|
||||
]);
|
||||
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
if let Some((pool_id, buffer)) = client.buffers.get(&buffer_id).cloned() {
|
||||
if let Some((pool_id, buffer)) =
|
||||
client.buffers.get(&buffer_id).cloned()
|
||||
{
|
||||
if let Some(surface) = client.surfaces.get_mut(&object_id) {
|
||||
surface.buffer = Some(Buffer {
|
||||
pool_id,
|
||||
..buffer
|
||||
});
|
||||
surface.buffer = Some(Buffer { pool_id, ..buffer });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -613,22 +745,33 @@ impl Compositor {
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
if let Some(surface) = client.surfaces.get_mut(&object_id) {
|
||||
let old_buffer = surface.committed_buffer_id.take();
|
||||
surface.committed_buffer_id = surface.buffer.as_ref().map(|b| {
|
||||
client.buffers.iter()
|
||||
.find(|(_, (_, buf))| buf.offset == b.offset && buf.width == b.width)
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap_or(0)
|
||||
});
|
||||
surface.committed_buffer_id =
|
||||
surface.buffer.as_ref().map(|b| {
|
||||
client
|
||||
.buffers
|
||||
.iter()
|
||||
.find(|(_, (_, buf))| {
|
||||
buf.offset == b.offset && buf.width == b.width
|
||||
})
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap_or(0)
|
||||
});
|
||||
let surface_snapshot = surface.clone();
|
||||
|
||||
if let Some(ref buffer) = surface_snapshot.buffer {
|
||||
if let Some(pool) = client.shm_pools.get_mut(&buffer.pool_id) {
|
||||
if let Some(pool) =
|
||||
client.shm_pools.get_mut(&buffer.pool_id)
|
||||
{
|
||||
self.composite_buffer(pool, buffer, &surface_snapshot);
|
||||
}
|
||||
}
|
||||
old_buffer
|
||||
} else { None }
|
||||
} else { None }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(buf_id) = release_id {
|
||||
@@ -641,13 +784,18 @@ impl Compositor {
|
||||
// No-op — we don't need damage tracking for a single-client greeter.
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_SHELL => match opcode {
|
||||
WL_SHELL_GET_SHELL_SURFACE => {
|
||||
if payload.len() >= 4 {
|
||||
let new_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let new_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.insert(new_id, OBJECT_TYPE_WL_SHELL_SURFACE);
|
||||
@@ -655,7 +803,10 @@ impl Compositor {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_SHELL_SURFACE => match opcode {
|
||||
@@ -663,13 +814,18 @@ impl Compositor {
|
||||
// No-op — we don't need window management for a single-client greeter.
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_SEAT => match opcode {
|
||||
WL_SEAT_GET_POINTER | WL_SEAT_GET_KEYBOARD => {
|
||||
if payload.len() >= 4 {
|
||||
let new_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let new_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let object_type = match opcode {
|
||||
WL_SEAT_GET_POINTER => OBJECT_TYPE_WL_POINTER,
|
||||
WL_SEAT_GET_KEYBOARD => OBJECT_TYPE_WL_KEYBOARD,
|
||||
@@ -682,13 +838,18 @@ impl Compositor {
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_XDG_WM_BASE => match opcode {
|
||||
XDG_WM_BASE_GET_XDG_SURFACE => {
|
||||
if payload.len() >= 4 {
|
||||
let new_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let new_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.insert(new_id, OBJECT_TYPE_XDG_SURFACE);
|
||||
@@ -699,7 +860,10 @@ impl Compositor {
|
||||
// No-op — the greeter keeps the shell global alive for the client lifetime.
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_XDG_SURFACE => match opcode {
|
||||
@@ -711,7 +875,9 @@ impl Compositor {
|
||||
}
|
||||
XDG_SURFACE_GET_TOPLEVEL => {
|
||||
if payload.len() >= 4 {
|
||||
let toplevel_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let toplevel_id = u32::from_le_bytes([
|
||||
payload[0], payload[1], payload[2], payload[3],
|
||||
]);
|
||||
let mut clients = self.clients.lock().unwrap();
|
||||
if let Some(client) = clients.get_mut(&client_id) {
|
||||
client.objects.insert(toplevel_id, OBJECT_TYPE_XDG_TOPLEVEL);
|
||||
@@ -726,7 +892,10 @@ impl Compositor {
|
||||
// Client acknowledged — ready for first commit.
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
},
|
||||
OBJECT_TYPE_WL_OUTPUT
|
||||
@@ -734,10 +903,16 @@ impl Compositor {
|
||||
| OBJECT_TYPE_XDG_TOPLEVEL
|
||||
| OBJECT_TYPE_WL_POINTER
|
||||
| OBJECT_TYPE_WL_KEYBOARD => {
|
||||
eprintln!("redbear-compositor: unhandled opcode {} on object {}", opcode, object_id);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled opcode {} on object {}",
|
||||
opcode, object_id
|
||||
);
|
||||
}
|
||||
_ => {
|
||||
eprintln!("redbear-compositor: unhandled object {} opcode {}", object_id, opcode);
|
||||
eprintln!(
|
||||
"redbear-compositor: unhandled object {} opcode {}",
|
||||
object_id, opcode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,7 +931,10 @@ impl Compositor {
|
||||
}
|
||||
|
||||
let mut src = vec![0u8; byte_count];
|
||||
if pool.file.seek(SeekFrom::Start(buffer.offset as u64)).is_err()
|
||||
if pool
|
||||
.file
|
||||
.seek(SeekFrom::Start(buffer.offset as u64))
|
||||
.is_err()
|
||||
|| pool.file.read_exact(&mut src).is_err()
|
||||
{
|
||||
return;
|
||||
@@ -856,7 +1034,8 @@ impl Compositor {
|
||||
|
||||
fn main() {
|
||||
let wayland_display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".into());
|
||||
let runtime_dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp/run/redbear-greeter".into());
|
||||
let runtime_dir =
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp/run/redbear-greeter".into());
|
||||
let socket_path = format!("{}/{}", runtime_dir, wayland_display);
|
||||
|
||||
// Read framebuffer parameters from environment (set by bootloader → vesad)
|
||||
@@ -872,10 +1051,9 @@ fn main() {
|
||||
.unwrap_or_else(|_| (fb_width * 4).to_string())
|
||||
.parse()
|
||||
.unwrap_or(fb_width * 4);
|
||||
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR")
|
||||
.unwrap_or_else(|_| "0x80000000".into());
|
||||
let fb_phys = usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16)
|
||||
.unwrap_or(0x80000000);
|
||||
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| "0x80000000".into());
|
||||
let fb_phys =
|
||||
usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16).unwrap_or(0x80000000);
|
||||
|
||||
eprintln!(
|
||||
"redbear-compositor: fb {}x{} stride {} phys 0x{:X}",
|
||||
@@ -893,9 +1071,9 @@ fn main() {
|
||||
eprintln!("redbear-compositor: failed to start: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let _ = std::fs::remove_file(&socket_path_clone);
|
||||
let _ = std::fs::remove_file(
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".into()) + "/compositor.status"
|
||||
std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".into()) + "/compositor.status",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,43 @@
|
||||
// Integration test: verifies the compositor's Wayland protocol implementation
|
||||
// by starting a real compositor instance and connecting as a client.
|
||||
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::io::{Read, Write};
|
||||
use std::process::{Command, Child};
|
||||
use std::time::Duration;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::process::{Child, Command};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn push_wayland_string(buf: &mut Vec<u8>, value: &str) {
|
||||
let bytes = value.as_bytes();
|
||||
buf.extend_from_slice(&((bytes.len() + 1) as u32).to_le_bytes());
|
||||
buf.extend_from_slice(bytes);
|
||||
buf.push(0);
|
||||
while buf.len() % 4 != 0 {
|
||||
buf.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_wayland_string(payload: &[u8], cursor: &mut usize) -> String {
|
||||
let length = u32::from_le_bytes([
|
||||
payload[*cursor],
|
||||
payload[*cursor + 1],
|
||||
payload[*cursor + 2],
|
||||
payload[*cursor + 3],
|
||||
]) as usize;
|
||||
*cursor += 4;
|
||||
let bytes = &payload[*cursor..*cursor + length];
|
||||
let string_len = bytes
|
||||
.iter()
|
||||
.position(|byte| *byte == 0)
|
||||
.unwrap_or(bytes.len());
|
||||
*cursor += length;
|
||||
while *cursor % 4 != 0 {
|
||||
*cursor += 1;
|
||||
}
|
||||
std::str::from_utf8(&bytes[..string_len])
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
struct WaylandClient {
|
||||
stream: UnixStream,
|
||||
@@ -22,7 +54,10 @@ impl WaylandClient {
|
||||
}
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
Err(std::io::Error::new(std::io::ErrorKind::NotFound, "compositor socket did not appear"))
|
||||
Err(std::io::Error::new(
|
||||
std::io::ErrorKind::NotFound,
|
||||
"compositor socket did not appear",
|
||||
))
|
||||
}
|
||||
|
||||
fn alloc_id(&mut self) -> u32 {
|
||||
@@ -34,9 +69,9 @@ impl WaylandClient {
|
||||
fn send_message(&mut self, object_id: u32, opcode: u16, payload: &[u8]) -> std::io::Result<()> {
|
||||
let size = 8 + payload.len();
|
||||
let mut msg = Vec::with_capacity(size);
|
||||
msg.extend_from_slice(&object_id.to_ne_bytes());
|
||||
msg.extend_from_slice(&object_id.to_le_bytes());
|
||||
let header = ((size as u32) << 16) | opcode as u32;
|
||||
msg.extend_from_slice(&header.to_ne_bytes());
|
||||
msg.extend_from_slice(&header.to_le_bytes());
|
||||
msg.extend_from_slice(payload);
|
||||
self.stream.write_all(&msg)
|
||||
}
|
||||
@@ -44,8 +79,8 @@ impl WaylandClient {
|
||||
fn read_message(&mut self) -> std::io::Result<(u32, u16, Vec<u8>)> {
|
||||
let mut header = [0u8; 8];
|
||||
self.stream.read_exact(&mut header)?;
|
||||
let object_id = u32::from_ne_bytes([header[0], header[1], header[2], header[3]]);
|
||||
let size_opcode = u32::from_ne_bytes([header[4], header[5], header[6], header[7]]);
|
||||
let object_id = u32::from_le_bytes([header[0], header[1], header[2], header[3]]);
|
||||
let size_opcode = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
|
||||
let size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
||||
let opcode = (size_opcode & 0xFFFF) as u16;
|
||||
let mut payload = vec![0u8; size - 8];
|
||||
@@ -57,25 +92,29 @@ impl WaylandClient {
|
||||
|
||||
fn sync(&mut self) -> std::io::Result<u32> {
|
||||
let callback_id = self.alloc_id();
|
||||
self.send_message(1, 0, &callback_id.to_ne_bytes())?; // wl_display.sync
|
||||
self.send_message(1, 0, &callback_id.to_le_bytes())?; // wl_display.sync
|
||||
Ok(callback_id)
|
||||
}
|
||||
|
||||
fn get_registry(&mut self) -> std::io::Result<u32> {
|
||||
let registry_id = self.alloc_id();
|
||||
self.send_message(1, 1, ®istry_id.to_ne_bytes())?; // wl_display.get_registry
|
||||
self.send_message(1, 1, ®istry_id.to_le_bytes())?; // wl_display.get_registry
|
||||
Ok(registry_id)
|
||||
}
|
||||
|
||||
fn bind(&mut self, registry_id: u32, name: u32, iface: &str, version: u32) -> std::io::Result<u32> {
|
||||
fn bind(
|
||||
&mut self,
|
||||
registry_id: u32,
|
||||
name: u32,
|
||||
iface: &str,
|
||||
version: u32,
|
||||
) -> std::io::Result<u32> {
|
||||
let new_id = self.alloc_id();
|
||||
let iface_bytes = iface.as_bytes();
|
||||
let mut payload = Vec::with_capacity(4 + iface_bytes.len() + 1 + 4 + 4);
|
||||
payload.extend_from_slice(&name.to_ne_bytes());
|
||||
payload.extend_from_slice(iface_bytes);
|
||||
payload.push(0);
|
||||
payload.extend_from_slice(&version.to_ne_bytes());
|
||||
payload.extend_from_slice(&new_id.to_ne_bytes());
|
||||
let mut payload = Vec::new();
|
||||
payload.extend_from_slice(&name.to_le_bytes());
|
||||
push_wayland_string(&mut payload, iface);
|
||||
payload.extend_from_slice(&version.to_le_bytes());
|
||||
payload.extend_from_slice(&new_id.to_le_bytes());
|
||||
self.send_message(registry_id, 0, &payload)?; // wl_registry.bind
|
||||
Ok(new_id)
|
||||
}
|
||||
@@ -84,18 +123,21 @@ impl WaylandClient {
|
||||
fn start_compositor(socket_path: &str) -> Child {
|
||||
let compositor_bin = std::env::var("COMPOSITOR_BIN")
|
||||
.unwrap_or_else(|_| "target/debug/redbear-compositor".into());
|
||||
|
||||
|
||||
let runtime_dir = std::path::Path::new(socket_path).parent().unwrap();
|
||||
std::fs::create_dir_all(runtime_dir).ok();
|
||||
|
||||
|
||||
let mut cmd = Command::new(&compositor_bin);
|
||||
cmd.env("WAYLAND_DISPLAY", socket_path.rsplit('/').next().unwrap_or("wayland-0"))
|
||||
.env("XDG_RUNTIME_DIR", runtime_dir)
|
||||
.env("FRAMEBUFFER_WIDTH", "1280")
|
||||
.env("FRAMEBUFFER_HEIGHT", "720")
|
||||
.env("FRAMEBUFFER_STRIDE", "5120")
|
||||
.env("FRAMEBUFFER_ADDR", "0x80000000");
|
||||
|
||||
cmd.env(
|
||||
"WAYLAND_DISPLAY",
|
||||
socket_path.rsplit('/').next().unwrap_or("wayland-0"),
|
||||
)
|
||||
.env("XDG_RUNTIME_DIR", runtime_dir)
|
||||
.env("FRAMEBUFFER_WIDTH", "1280")
|
||||
.env("FRAMEBUFFER_HEIGHT", "720")
|
||||
.env("FRAMEBUFFER_STRIDE", "5120")
|
||||
.env("FRAMEBUFFER_ADDR", "0x80000000");
|
||||
|
||||
cmd.spawn().expect("failed to start compositor")
|
||||
}
|
||||
|
||||
@@ -103,24 +145,24 @@ fn start_compositor(socket_path: &str) -> Child {
|
||||
fn test_compositor_globals() {
|
||||
let socket = "/tmp/test-redbear-compositor.sock";
|
||||
let _ = std::fs::remove_file(socket);
|
||||
|
||||
|
||||
let mut compositor = start_compositor(socket);
|
||||
|
||||
|
||||
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
||||
|
||||
|
||||
// Get registry
|
||||
let _registry = client.get_registry().expect("get_registry failed");
|
||||
|
||||
|
||||
// Read global events
|
||||
let mut globals = Vec::new();
|
||||
for _ in 0..6 {
|
||||
match client.read_message() {
|
||||
Ok((_obj_id, opcode, payload)) => {
|
||||
assert_eq!(opcode, 0); // wl_registry.global
|
||||
let name = u32::from_ne_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let iface_end = payload[4..].iter().position(|&b| b == 0).unwrap_or(0);
|
||||
let iface = std::str::from_utf8(&payload[4..4+iface_end]).unwrap();
|
||||
globals.push((name, iface.to_string()));
|
||||
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let mut cursor = 4;
|
||||
let iface = read_wayland_string(&payload, &mut cursor);
|
||||
globals.push((name, iface));
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("read error: {}", e);
|
||||
@@ -128,14 +170,29 @@ fn test_compositor_globals() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(globals.iter().any(|(_, i)| i == "wl_compositor"), "wl_compositor missing");
|
||||
|
||||
assert!(
|
||||
globals.iter().any(|(_, i)| i == "wl_compositor"),
|
||||
"wl_compositor missing"
|
||||
);
|
||||
assert!(globals.iter().any(|(_, i)| i == "wl_shm"), "wl_shm missing");
|
||||
assert!(globals.iter().any(|(_, i)| i == "wl_shell"), "wl_shell missing");
|
||||
assert!(globals.iter().any(|(_, i)| i == "wl_seat"), "wl_seat missing");
|
||||
assert!(globals.iter().any(|(_, i)| i == "wl_output"), "wl_output missing");
|
||||
assert!(globals.iter().any(|(_, i)| i == "xdg_wm_base"), "xdg_wm_base missing");
|
||||
|
||||
assert!(
|
||||
globals.iter().any(|(_, i)| i == "wl_shell"),
|
||||
"wl_shell missing"
|
||||
);
|
||||
assert!(
|
||||
globals.iter().any(|(_, i)| i == "wl_seat"),
|
||||
"wl_seat missing"
|
||||
);
|
||||
assert!(
|
||||
globals.iter().any(|(_, i)| i == "wl_output"),
|
||||
"wl_output missing"
|
||||
);
|
||||
assert!(
|
||||
globals.iter().any(|(_, i)| i == "xdg_wm_base"),
|
||||
"xdg_wm_base missing"
|
||||
);
|
||||
|
||||
compositor.kill().ok();
|
||||
let _ = std::fs::remove_file(socket);
|
||||
}
|
||||
@@ -144,43 +201,49 @@ fn test_compositor_globals() {
|
||||
fn test_compositor_shm_formats() {
|
||||
let socket = "/tmp/test-redbear-compositor-shm.sock";
|
||||
let _ = std::fs::remove_file(socket);
|
||||
|
||||
|
||||
let mut compositor = start_compositor(socket);
|
||||
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
||||
|
||||
|
||||
let registry = client.get_registry().expect("get_registry failed");
|
||||
|
||||
|
||||
// Read globals to find wl_shm name
|
||||
let mut shm_name = 0u32;
|
||||
for _ in 0..6 {
|
||||
let (_, _, payload) = client.read_message().expect("read failed");
|
||||
let name = u32::from_ne_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let iface_end = payload[4..].iter().position(|&b| b == 0).unwrap_or(0);
|
||||
let iface = std::str::from_utf8(&payload[4..4+iface_end]).unwrap();
|
||||
if iface == "wl_shm" { shm_name = name; break; }
|
||||
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let mut cursor = 4;
|
||||
let iface = read_wayland_string(&payload, &mut cursor);
|
||||
if iface == "wl_shm" {
|
||||
shm_name = name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assert_ne!(shm_name, 0, "wl_shm global not found");
|
||||
|
||||
|
||||
// Bind wl_shm
|
||||
let _shm = client.bind(registry, shm_name, "wl_shm", 1).expect("bind shm failed");
|
||||
|
||||
let _shm = client
|
||||
.bind(registry, shm_name, "wl_shm", 1)
|
||||
.expect("bind shm failed");
|
||||
|
||||
// Should receive format events
|
||||
let mut formats = Vec::new();
|
||||
for _ in 0..3 {
|
||||
match client.read_message() {
|
||||
Ok((_, opcode, payload)) => {
|
||||
if opcode == 0 && payload.len() >= 4 {
|
||||
let format = u32::from_ne_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let format =
|
||||
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
formats.push(format);
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
assert!(!formats.is_empty(), "no wl_shm.format events received");
|
||||
|
||||
|
||||
compositor.kill().ok();
|
||||
let _ = std::fs::remove_file(socket);
|
||||
}
|
||||
@@ -189,18 +252,18 @@ fn test_compositor_shm_formats() {
|
||||
fn test_compositor_sync_roundtrip() {
|
||||
let socket = "/tmp/test-redbear-compositor-sync.sock";
|
||||
let _ = std::fs::remove_file(socket);
|
||||
|
||||
|
||||
let mut compositor = start_compositor(socket);
|
||||
let mut client = WaylandClient::connect(socket).expect("failed to connect");
|
||||
|
||||
|
||||
let callback_id = client.sync().expect("sync failed");
|
||||
|
||||
|
||||
// Should receive callback.done
|
||||
let (obj_id, opcode, payload) = client.read_message().expect("read failed");
|
||||
assert_eq!(obj_id, callback_id, "callback id mismatch");
|
||||
assert_eq!(opcode, 0, "expected callback.done (opcode 0)");
|
||||
assert_eq!(payload.len(), 4, "callback.done payload should be 4 bytes");
|
||||
|
||||
|
||||
compositor.kill().ok();
|
||||
let _ = std::fs::remove_file(socket);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user