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).
|
#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.
|
||||||
# Full NewStuffQuick/NewStuffWidgets require Qt6Quick which is not yet QML-proven.
|
|
||||||
[source]
|
[source]
|
||||||
tar = "https://invent.kde.org/frameworks/knewstuff/-/archive/v6.10.0/knewstuff-v6.10.0.tar.gz"
|
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"
|
template = "custom"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"qtbase",
|
"qtbase",
|
||||||
"qtdeclarative",
|
|
||||||
"kf6-extra-cmake-modules",
|
"kf6-extra-cmake-modules",
|
||||||
"kf6-kcoreaddons",
|
"kf6-kcoreaddons",
|
||||||
"kf6-ki18n",
|
"kf6-ki18n",
|
||||||
"kf6-kconfig",
|
"kf6-kconfig",
|
||||||
|
"kf6-karchive",
|
||||||
|
"kf6-kpackage",
|
||||||
]
|
]
|
||||||
script = """
|
script = """
|
||||||
DYNAMIC_INIT
|
DYNAMIC_INIT
|
||||||
@@ -24,67 +24,40 @@ for qtdir in plugins mkspecs metatypes modules; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
BUILD_DIR="${COOKBOOK_SOURCE}/redox_build"
|
sed -i 's/^include(ECMQmlModule)/# include(ECMQmlModule) # disabled for Redox core-only build/' \
|
||||||
mkdir -p "${BUILD_DIR}"
|
"${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}" \
|
rm -f CMakeCache.txt
|
||||||
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_SYSROOT}/usr/share/cmake/redox.cmake" \
|
rm -rf CMakeFiles
|
||||||
-DCMAKE_INSTALL_PREFIX="${COOKBOOK_STAGE}/usr" \
|
|
||||||
-DCMAKE_PREFIX_PATH="${COOKBOOK_STAGE}/usr;${COOKBOOK_SYSROOT}/usr;${HOST_BUILD}" \
|
cmake "${COOKBOOK_SOURCE}" \
|
||||||
-DBUILD_SHARED_LIBS=OFF \
|
-DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \
|
||||||
-DBUILD_TESTING=OFF \
|
-DQT_HOST_PATH="${HOST_BUILD}" \
|
||||||
-DKF6_HOST_TOOLING="${HOST_BUILD}/lib/cmake" \
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
-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 \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|| {
|
-DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}:${COOKBOOK_STAGE}/usr/lib/cmake" \
|
||||||
echo "=== KNewStuff cmake configure failed — falling back to stub configs ==="
|
-DBUILD_TESTING=OFF \
|
||||||
STAGE="${COOKBOOK_STAGE}/usr"
|
-DBUILD_QCH=OFF \
|
||||||
mkdir -p "${STAGE}/lib/cmake/KF6NewStuff"
|
-DBUILD_DESIGNERPLUGIN=OFF \
|
||||||
cat > "${STAGE}/lib/cmake/KF6NewStuff/KF6NewStuffConfig.cmake" << 'EOFCFG'
|
-Wno-dev
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
cmake --build "${BUILD_DIR}" -j "${COOKBOOK_MAKE_JOBS}" || {
|
cmake --build . -j${COOKBOOK_MAKE_JOBS}
|
||||||
echo "=== KNewStuff build failed — falling back to stub ==="
|
cmake --install . --prefix "${COOKBOOK_STAGE}/usr"
|
||||||
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 --install "${BUILD_DIR}"
|
for lib in "${COOKBOOK_STAGE}/usr/lib/"libKF6*.so.*; do
|
||||||
echo "=== KNewStuff real build (Core only, QML disabled) ==="
|
[ -f "${lib}" ] || continue
|
||||||
|
patchelf --remove-rpath "${lib}" 2>/dev/null || true
|
||||||
|
done
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# Build only C++ core, no QML modules
|
# Build only the non-QML C++ core for now.
|
||||||
add_library(Kirigami STATIC)
|
add_library(Kirigami)
|
||||||
add_library(KF6::Kirigami ALIAS Kirigami)
|
add_library(KF6::Kirigami ALIAS Kirigami)
|
||||||
|
|
||||||
# Core C++ sources that don't require QML/QtQuick
|
# 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.
|
// Red Bear Compositor Runtime Check — verifies the compositor and greeter surface are healthy.
|
||||||
// Usage: redbear-compositor-check [--verbose]
|
// 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::io::{Read, Write};
|
||||||
|
use std::mem;
|
||||||
|
use std::os::fd::AsRawFd;
|
||||||
|
use std::os::unix::net::UnixStream;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
fn check_wayland_socket() -> Result<(), String> {
|
const WL_DISPLAY_SYNC: u16 = 0;
|
||||||
let runtime_dir = std::env::var("XDG_RUNTIME_DIR")
|
const WL_DISPLAY_GET_REGISTRY: u16 = 1;
|
||||||
.unwrap_or_else(|_| "/tmp/run/redbear-greeter".into());
|
const WL_REGISTRY_BIND: u16 = 0;
|
||||||
let display = std::env::var("WAYLAND_DISPLAY")
|
const WL_REGISTRY_GLOBAL: u16 = 0;
|
||||||
.unwrap_or_else(|_| "wayland-0".into());
|
const WL_COMPOSITOR_CREATE_SURFACE: u16 = 0;
|
||||||
let socket_path = format!("{}/{}", runtime_dir, display);
|
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() {
|
fn push_u32(buf: &mut Vec<u8>, value: u32) {
|
||||||
return Err(format!("Wayland socket {} does not exist", socket_path));
|
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)
|
let value = u32::from_le_bytes([
|
||||||
.map_err(|e| format!("failed to connect to {}: {}", socket_path, e))?;
|
data[*cursor],
|
||||||
stream.set_read_timeout(Some(Duration::from_secs(2)))
|
data[*cursor + 1],
|
||||||
.map_err(|e| format!("failed to set timeout: {}", e))?;
|
data[*cursor + 2],
|
||||||
|
data[*cursor + 3],
|
||||||
|
]);
|
||||||
|
*cursor += 4;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
// Send wl_display.sync request to verify protocol
|
fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result<String, String> {
|
||||||
let display_id = 1u32;
|
let length = read_u32(data, cursor)? as usize;
|
||||||
let callback_id = 2u32;
|
if length == 0 {
|
||||||
let mut msg = Vec::new();
|
return Ok(String::new());
|
||||||
msg.extend_from_slice(&display_id.to_ne_bytes());
|
}
|
||||||
let size = 12u32;
|
if *cursor + length > data.len() {
|
||||||
let opcode = 0u16; // wl_display.sync
|
return Err(String::from("unexpected end of message while reading string"));
|
||||||
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))?;
|
|
||||||
|
|
||||||
// Read response
|
let bytes = &data[*cursor..*cursor + length];
|
||||||
let mut buf = [0u8; 256];
|
let string_len = bytes.iter().position(|byte| *byte == 0).unwrap_or(bytes.len());
|
||||||
let n = stream.read(&mut buf)
|
*cursor += length;
|
||||||
.map_err(|e| format!("read failed: {}", e))?;
|
while *cursor % 4 != 0 {
|
||||||
|
*cursor += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if n < 8 {
|
std::str::from_utf8(&bytes[..string_len])
|
||||||
return Err(format!("short response: {} bytes", n));
|
.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(())
|
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>> {
|
fn check_binaries() -> Result<(), Vec<String>> {
|
||||||
let mut missing = Vec::new();
|
let mut missing = Vec::new();
|
||||||
for bin in &["/usr/bin/redbear-compositor", "/usr/bin/redbear-greeterd",
|
for bin in &[
|
||||||
"/usr/bin/redbear-greeter-ui", "/usr/bin/redbear-authd",
|
"/usr/bin/redbear-compositor",
|
||||||
"/usr/bin/kwin_wayland_wrapper"] {
|
"/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() {
|
if !std::path::Path::new(bin).exists() {
|
||||||
missing.push(bin.to_string());
|
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> {
|
fn check_framebuffer() -> Result<(), String> {
|
||||||
@@ -89,7 +456,11 @@ fn check_services() -> Result<(), Vec<String>> {
|
|||||||
issues.push(format!("{} not found at {}", name, path));
|
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() {
|
fn main() {
|
||||||
@@ -113,7 +484,9 @@ fn main() {
|
|||||||
($label:expr, $check:expr, vec) => {
|
($label:expr, $check:expr, vec) => {
|
||||||
match $check {
|
match $check {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if verbose { println!(" PASS {}", $label); }
|
if verbose {
|
||||||
|
println!(" PASS {}", $label);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(errs) => {
|
Err(errs) => {
|
||||||
for e in errs {
|
for e in errs {
|
||||||
@@ -143,14 +516,17 @@ fn main() {
|
|||||||
check!("runtime services", check_services(), vec);
|
check!("runtime services", check_services(), vec);
|
||||||
|
|
||||||
if verbose {
|
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 {
|
if exit == 0 {
|
||||||
println!("redbear-compositor-check: all checks passed");
|
println!("redbear-compositor-check: all checks passed");
|
||||||
} else {
|
} 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);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ use std::io::{Read, Seek, SeekFrom, Write};
|
|||||||
use std::mem;
|
use std::mem;
|
||||||
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::os::unix::net::{UnixListener, UnixStream};
|
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> {
|
fn map_framebuffer(_phys: usize, size: usize) -> Vec<u8> {
|
||||||
vec![0u8; size]
|
vec![0u8; size]
|
||||||
@@ -75,11 +78,16 @@ fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result<String, String
|
|||||||
return Ok(String::new());
|
return Ok(String::new());
|
||||||
}
|
}
|
||||||
if *cursor + length > data.len() {
|
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 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;
|
*cursor += length;
|
||||||
while *cursor % 4 != 0 {
|
while *cursor % 4 != 0 {
|
||||||
*cursor += 1;
|
*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}"))
|
.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 {
|
let mut iov = libc::iovec {
|
||||||
iov_base: data.as_mut_ptr().cast(),
|
iov_base: data.as_mut_ptr().cast(),
|
||||||
iov_len: data.len(),
|
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
|
(*cmsg).cmsg_level == libc::SOL_SOCKET && (*cmsg).cmsg_type == libc::SCM_RIGHTS
|
||||||
};
|
};
|
||||||
if is_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 fd_count = data_len / mem::size_of::<RawFd>();
|
||||||
let data_ptr = unsafe { libc::CMSG_DATA(cmsg).cast::<RawFd>() };
|
let data_ptr = unsafe { libc::CMSG_DATA(cmsg).cast::<RawFd>() };
|
||||||
for index in 0..fd_count {
|
for index in 0..fd_count {
|
||||||
@@ -292,23 +304,49 @@ impl Compositor {
|
|||||||
let _ = std::fs::remove_file(socket_path);
|
let _ = std::fs::remove_file(socket_path);
|
||||||
let listener = UnixListener::bind(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"));
|
.unwrap_or(std::path::Path::new("/tmp"));
|
||||||
std::fs::write(
|
std::fs::write(
|
||||||
runtime_dir.join("compositor.pid"),
|
runtime_dir.join("compositor.pid"),
|
||||||
format!("{}\n", std::process::id()),
|
format!("{}\n", std::process::id()),
|
||||||
).ok();
|
)
|
||||||
|
.ok();
|
||||||
|
|
||||||
let fb_size = (fb_height as usize) * (fb_stride as usize);
|
let fb_size = (fb_height as usize) * (fb_stride as usize);
|
||||||
let fb_data = map_framebuffer(fb_phys, fb_size);
|
let fb_data = map_framebuffer(fb_phys, fb_size);
|
||||||
|
|
||||||
let globals = vec![
|
let globals = vec![
|
||||||
Global { name: 1, interface: "wl_compositor".into(), version: 4 },
|
Global {
|
||||||
Global { name: 2, interface: "wl_shm".into(), version: 1 },
|
name: 1,
|
||||||
Global { name: 3, interface: "wl_shell".into(), version: 1 },
|
interface: "wl_compositor".into(),
|
||||||
Global { name: 4, interface: "wl_seat".into(), version: 5 },
|
version: 4,
|
||||||
Global { name: 5, interface: "wl_output".into(), version: 3 },
|
},
|
||||||
Global { name: 6, interface: "xdg_wm_base".into(), version: 1 },
|
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 {
|
Ok(Self {
|
||||||
@@ -335,7 +373,8 @@ impl Compositor {
|
|||||||
pub fn run(&mut self) -> std::io::Result<()> {
|
pub fn run(&mut self) -> std::io::Result<()> {
|
||||||
eprintln!("redbear-compositor: listening on Wayland socket");
|
eprintln!("redbear-compositor: listening on Wayland socket");
|
||||||
let _ = std::fs::write(
|
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",
|
"ready\n",
|
||||||
);
|
);
|
||||||
for stream in self.listener.incoming() {
|
for stream in self.listener.incoming() {
|
||||||
@@ -343,13 +382,16 @@ impl Compositor {
|
|||||||
Ok(stream) => {
|
Ok(stream) => {
|
||||||
let client_id = self.alloc_id();
|
let client_id = self.alloc_id();
|
||||||
eprintln!("redbear-compositor: client {} connected", client_id);
|
eprintln!("redbear-compositor: client {} connected", client_id);
|
||||||
self.clients.lock().unwrap().insert(client_id, ClientState {
|
self.clients.lock().unwrap().insert(
|
||||||
objects: HashMap::new(),
|
client_id,
|
||||||
surfaces: HashMap::new(),
|
ClientState {
|
||||||
buffers: HashMap::new(),
|
objects: HashMap::new(),
|
||||||
shm_pools: HashMap::new(),
|
surfaces: HashMap::new(),
|
||||||
_next_id: 1,
|
buffers: HashMap::new(),
|
||||||
});
|
shm_pools: HashMap::new(),
|
||||||
|
_next_id: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
self.handle_client(client_id, stream);
|
self.handle_client(client_id, stream);
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("redbear-compositor: accept error: {}", e),
|
Err(e) => eprintln!("redbear-compositor: accept error: {}", e),
|
||||||
@@ -415,17 +457,30 @@ impl Compositor {
|
|||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
while offset + 8 <= data.len() {
|
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]
|
// 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 msg_size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
||||||
let opcode = (size_opcode & 0xFFFF) as u16;
|
let opcode = (size_opcode & 0xFFFF) as u16;
|
||||||
|
|
||||||
if msg_size < 8 || offset + msg_size > data.len() {
|
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 {
|
let object_type = if object_id == 1 {
|
||||||
OBJECT_TYPE_WL_DISPLAY
|
OBJECT_TYPE_WL_DISPLAY
|
||||||
} else {
|
} else {
|
||||||
@@ -442,12 +497,16 @@ impl Compositor {
|
|||||||
WL_DISPLAY_SYNC => {
|
WL_DISPLAY_SYNC => {
|
||||||
let callback_id = if payload.len() >= 4 {
|
let callback_id = if payload.len() >= 4 {
|
||||||
u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]])
|
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);
|
self.send_callback_done(stream, callback_id, 0);
|
||||||
}
|
}
|
||||||
WL_DISPLAY_DELETE_ID => {
|
WL_DISPLAY_DELETE_ID => {
|
||||||
if payload.len() >= 4 {
|
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();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.remove(&obj_id);
|
client.objects.remove(&obj_id);
|
||||||
@@ -459,7 +518,9 @@ impl Compositor {
|
|||||||
}
|
}
|
||||||
WL_DISPLAY_GET_REGISTRY => {
|
WL_DISPLAY_GET_REGISTRY => {
|
||||||
if payload.len() >= 4 {
|
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 clients = self.clients.lock().unwrap();
|
||||||
let mut send_globals = false;
|
let mut send_globals = false;
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
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 {
|
OBJECT_TYPE_WL_REGISTRY => match opcode {
|
||||||
@@ -484,69 +548,91 @@ impl Compositor {
|
|||||||
let _version = read_u32(payload, &mut cursor)?;
|
let _version = read_u32(payload, &mut cursor)?;
|
||||||
let new_id = read_u32(payload, &mut cursor)?;
|
let new_id = read_u32(payload, &mut cursor)?;
|
||||||
|
|
||||||
let mut clients = self.clients.lock().unwrap();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
let type_id = match iface.as_str() {
|
let type_id = match iface.as_str() {
|
||||||
"wl_compositor" => OBJECT_TYPE_WL_COMPOSITOR,
|
"wl_compositor" => OBJECT_TYPE_WL_COMPOSITOR,
|
||||||
"wl_shm" => OBJECT_TYPE_WL_SHM,
|
"wl_shm" => OBJECT_TYPE_WL_SHM,
|
||||||
"wl_shell" => OBJECT_TYPE_WL_SHELL,
|
"wl_shell" => OBJECT_TYPE_WL_SHELL,
|
||||||
"wl_seat" => OBJECT_TYPE_WL_SEAT,
|
"wl_seat" => OBJECT_TYPE_WL_SEAT,
|
||||||
"wl_output" => OBJECT_TYPE_WL_OUTPUT,
|
"wl_output" => OBJECT_TYPE_WL_OUTPUT,
|
||||||
"xdg_wm_base" => OBJECT_TYPE_XDG_WM_BASE,
|
"xdg_wm_base" => OBJECT_TYPE_XDG_WM_BASE,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
client.objects.insert(new_id, type_id);
|
client.objects.insert(new_id, type_id);
|
||||||
if iface == "wl_shm" {
|
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_ARGB8888);
|
||||||
self.send_shm_format(stream, new_id, WL_SHM_FORMAT_XRGB8888);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
OBJECT_TYPE_WL_COMPOSITOR => match opcode {
|
||||||
WL_COMPOSITOR_CREATE_SURFACE => {
|
WL_COMPOSITOR_CREATE_SURFACE => {
|
||||||
if payload.len() >= 4 {
|
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();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.insert(surface_id, OBJECT_TYPE_WL_SURFACE);
|
client.objects.insert(surface_id, OBJECT_TYPE_WL_SURFACE);
|
||||||
client.surfaces.insert(surface_id, Surface {
|
client.surfaces.insert(
|
||||||
buffer: None,
|
surface_id,
|
||||||
committed_buffer_id: None,
|
Surface {
|
||||||
x: 0, y: 0,
|
buffer: None,
|
||||||
_width: self.fb_width,
|
committed_buffer_id: None,
|
||||||
_height: self.fb_height,
|
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 {
|
OBJECT_TYPE_WL_SHM => match opcode {
|
||||||
WL_SHM_CREATE_POOL => {
|
WL_SHM_CREATE_POOL => {
|
||||||
if payload.len() >= 8 {
|
if payload.len() >= 8 {
|
||||||
let pool_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
let pool_id = u32::from_le_bytes([
|
||||||
let size = i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
payload[0], payload[1], payload[2], payload[3],
|
||||||
let fd_val = fds
|
]);
|
||||||
.pop_front()
|
let size = i32::from_le_bytes([
|
||||||
.ok_or_else(|| String::from("wl_shm.create_pool missing SCM_RIGHTS fd"))?;
|
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 {
|
if size > 0 {
|
||||||
let file = unsafe { std::fs::File::from_raw_fd(fd_val) };
|
let file = unsafe { std::fs::File::from_raw_fd(fd_val) };
|
||||||
let mut clients = self.clients.lock().unwrap();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.insert(pool_id, OBJECT_TYPE_WL_SHM_POOL);
|
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 {
|
} else {
|
||||||
let _ = unsafe { libc::close(fd_val) };
|
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 {
|
OBJECT_TYPE_WL_SHM_POOL => match opcode {
|
||||||
WL_SHM_POOL_CREATE_BUFFER => {
|
WL_SHM_POOL_CREATE_BUFFER => {
|
||||||
if payload.len() >= 20 {
|
if payload.len() >= 20 {
|
||||||
let buffer_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
let buffer_id = u32::from_le_bytes([
|
||||||
let offset = u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
payload[0], payload[1], payload[2], payload[3],
|
||||||
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 offset = u32::from_le_bytes([
|
||||||
let stride = u32::from_le_bytes([payload[16], payload[17], payload[18], payload[19]]);
|
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 {
|
let format = if payload.len() >= 24 {
|
||||||
u32::from_le_bytes([payload[20], payload[21], payload[22], payload[23]])
|
u32::from_le_bytes([
|
||||||
} else { WL_SHM_FORMAT_ARGB8888 };
|
payload[20],
|
||||||
|
payload[21],
|
||||||
|
payload[22],
|
||||||
|
payload[23],
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
WL_SHM_FORMAT_ARGB8888
|
||||||
|
};
|
||||||
|
|
||||||
let mut clients = self.clients.lock().unwrap();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.insert(buffer_id, OBJECT_TYPE_WL_BUFFER);
|
client.objects.insert(buffer_id, OBJECT_TYPE_WL_BUFFER);
|
||||||
client.buffers.insert(buffer_id, (object_id, Buffer {
|
client.buffers.insert(
|
||||||
pool_id: object_id,
|
buffer_id,
|
||||||
offset,
|
(
|
||||||
width,
|
object_id,
|
||||||
height,
|
Buffer {
|
||||||
stride,
|
pool_id: object_id,
|
||||||
_format: format,
|
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 {
|
OBJECT_TYPE_WL_SURFACE => match opcode {
|
||||||
WL_SURFACE_ATTACH => {
|
WL_SURFACE_ATTACH => {
|
||||||
if payload.len() >= 12 {
|
if payload.len() >= 12 {
|
||||||
let buffer_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
let buffer_id = u32::from_le_bytes([
|
||||||
let _x = i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
payload[0], payload[1], payload[2], payload[3],
|
||||||
let _y = i32::from_le_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
]);
|
||||||
|
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();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
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) {
|
if let Some(surface) = client.surfaces.get_mut(&object_id) {
|
||||||
surface.buffer = Some(Buffer {
|
surface.buffer = Some(Buffer { pool_id, ..buffer });
|
||||||
pool_id,
|
|
||||||
..buffer
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -613,22 +745,33 @@ impl Compositor {
|
|||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
if let Some(surface) = client.surfaces.get_mut(&object_id) {
|
if let Some(surface) = client.surfaces.get_mut(&object_id) {
|
||||||
let old_buffer = surface.committed_buffer_id.take();
|
let old_buffer = surface.committed_buffer_id.take();
|
||||||
surface.committed_buffer_id = surface.buffer.as_ref().map(|b| {
|
surface.committed_buffer_id =
|
||||||
client.buffers.iter()
|
surface.buffer.as_ref().map(|b| {
|
||||||
.find(|(_, (_, buf))| buf.offset == b.offset && buf.width == b.width)
|
client
|
||||||
.map(|(id, _)| *id)
|
.buffers
|
||||||
.unwrap_or(0)
|
.iter()
|
||||||
});
|
.find(|(_, (_, buf))| {
|
||||||
|
buf.offset == b.offset && buf.width == b.width
|
||||||
|
})
|
||||||
|
.map(|(id, _)| *id)
|
||||||
|
.unwrap_or(0)
|
||||||
|
});
|
||||||
let surface_snapshot = surface.clone();
|
let surface_snapshot = surface.clone();
|
||||||
|
|
||||||
if let Some(ref buffer) = surface_snapshot.buffer {
|
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);
|
self.composite_buffer(pool, buffer, &surface_snapshot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
old_buffer
|
old_buffer
|
||||||
} else { None }
|
} else {
|
||||||
} else { None }
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(buf_id) = release_id {
|
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.
|
// 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 {
|
OBJECT_TYPE_WL_SHELL => match opcode {
|
||||||
WL_SHELL_GET_SHELL_SURFACE => {
|
WL_SHELL_GET_SHELL_SURFACE => {
|
||||||
if payload.len() >= 4 {
|
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();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.insert(new_id, OBJECT_TYPE_WL_SHELL_SURFACE);
|
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 {
|
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.
|
// 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 {
|
OBJECT_TYPE_WL_SEAT => match opcode {
|
||||||
WL_SEAT_GET_POINTER | WL_SEAT_GET_KEYBOARD => {
|
WL_SEAT_GET_POINTER | WL_SEAT_GET_KEYBOARD => {
|
||||||
if payload.len() >= 4 {
|
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 {
|
let object_type = match opcode {
|
||||||
WL_SEAT_GET_POINTER => OBJECT_TYPE_WL_POINTER,
|
WL_SEAT_GET_POINTER => OBJECT_TYPE_WL_POINTER,
|
||||||
WL_SEAT_GET_KEYBOARD => OBJECT_TYPE_WL_KEYBOARD,
|
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 {
|
OBJECT_TYPE_XDG_WM_BASE => match opcode {
|
||||||
XDG_WM_BASE_GET_XDG_SURFACE => {
|
XDG_WM_BASE_GET_XDG_SURFACE => {
|
||||||
if payload.len() >= 4 {
|
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();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.insert(new_id, OBJECT_TYPE_XDG_SURFACE);
|
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.
|
// 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 {
|
OBJECT_TYPE_XDG_SURFACE => match opcode {
|
||||||
@@ -711,7 +875,9 @@ impl Compositor {
|
|||||||
}
|
}
|
||||||
XDG_SURFACE_GET_TOPLEVEL => {
|
XDG_SURFACE_GET_TOPLEVEL => {
|
||||||
if payload.len() >= 4 {
|
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();
|
let mut clients = self.clients.lock().unwrap();
|
||||||
if let Some(client) = clients.get_mut(&client_id) {
|
if let Some(client) = clients.get_mut(&client_id) {
|
||||||
client.objects.insert(toplevel_id, OBJECT_TYPE_XDG_TOPLEVEL);
|
client.objects.insert(toplevel_id, OBJECT_TYPE_XDG_TOPLEVEL);
|
||||||
@@ -726,7 +892,10 @@ impl Compositor {
|
|||||||
// Client acknowledged — ready for first commit.
|
// 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
|
OBJECT_TYPE_WL_OUTPUT
|
||||||
@@ -734,10 +903,16 @@ impl Compositor {
|
|||||||
| OBJECT_TYPE_XDG_TOPLEVEL
|
| OBJECT_TYPE_XDG_TOPLEVEL
|
||||||
| OBJECT_TYPE_WL_POINTER
|
| OBJECT_TYPE_WL_POINTER
|
||||||
| OBJECT_TYPE_WL_KEYBOARD => {
|
| 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];
|
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()
|
|| pool.file.read_exact(&mut src).is_err()
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -856,7 +1034,8 @@ impl Compositor {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let wayland_display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".into());
|
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);
|
let socket_path = format!("{}/{}", runtime_dir, wayland_display);
|
||||||
|
|
||||||
// Read framebuffer parameters from environment (set by bootloader → vesad)
|
// Read framebuffer parameters from environment (set by bootloader → vesad)
|
||||||
@@ -872,10 +1051,9 @@ fn main() {
|
|||||||
.unwrap_or_else(|_| (fb_width * 4).to_string())
|
.unwrap_or_else(|_| (fb_width * 4).to_string())
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or(fb_width * 4);
|
.unwrap_or(fb_width * 4);
|
||||||
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR")
|
let fb_phys_str = std::env::var("FRAMEBUFFER_ADDR").unwrap_or_else(|_| "0x80000000".into());
|
||||||
.unwrap_or_else(|_| "0x80000000".into());
|
let fb_phys =
|
||||||
let fb_phys = usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16)
|
usize::from_str_radix(fb_phys_str.trim_start_matches("0x"), 16).unwrap_or(0x80000000);
|
||||||
.unwrap_or(0x80000000);
|
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"redbear-compositor: fb {}x{} stride {} phys 0x{:X}",
|
"redbear-compositor: fb {}x{} stride {} phys 0x{:X}",
|
||||||
@@ -896,6 +1074,6 @@ fn main() {
|
|||||||
|
|
||||||
let _ = std::fs::remove_file(&socket_path_clone);
|
let _ = std::fs::remove_file(&socket_path_clone);
|
||||||
let _ = std::fs::remove_file(
|
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
|
// Integration test: verifies the compositor's Wayland protocol implementation
|
||||||
// by starting a real compositor instance and connecting as a client.
|
// by starting a real compositor instance and connecting as a client.
|
||||||
|
|
||||||
use std::os::unix::net::UnixStream;
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::process::{Command, Child};
|
use std::os::unix::net::UnixStream;
|
||||||
use std::time::Duration;
|
use std::process::{Child, Command};
|
||||||
use std::thread;
|
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 {
|
struct WaylandClient {
|
||||||
stream: UnixStream,
|
stream: UnixStream,
|
||||||
@@ -22,7 +54,10 @@ impl WaylandClient {
|
|||||||
}
|
}
|
||||||
thread::sleep(Duration::from_millis(100));
|
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 {
|
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<()> {
|
fn send_message(&mut self, object_id: u32, opcode: u16, payload: &[u8]) -> std::io::Result<()> {
|
||||||
let size = 8 + payload.len();
|
let size = 8 + payload.len();
|
||||||
let mut msg = Vec::with_capacity(size);
|
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;
|
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);
|
msg.extend_from_slice(payload);
|
||||||
self.stream.write_all(&msg)
|
self.stream.write_all(&msg)
|
||||||
}
|
}
|
||||||
@@ -44,8 +79,8 @@ impl WaylandClient {
|
|||||||
fn read_message(&mut self) -> std::io::Result<(u32, u16, Vec<u8>)> {
|
fn read_message(&mut self) -> std::io::Result<(u32, u16, Vec<u8>)> {
|
||||||
let mut header = [0u8; 8];
|
let mut header = [0u8; 8];
|
||||||
self.stream.read_exact(&mut header)?;
|
self.stream.read_exact(&mut header)?;
|
||||||
let object_id = u32::from_ne_bytes([header[0], header[1], header[2], header[3]]);
|
let object_id = u32::from_le_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 size_opcode = u32::from_le_bytes([header[4], header[5], header[6], header[7]]);
|
||||||
let size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
let size = ((size_opcode >> 16) & 0xFFFF) as usize;
|
||||||
let opcode = (size_opcode & 0xFFFF) as u16;
|
let opcode = (size_opcode & 0xFFFF) as u16;
|
||||||
let mut payload = vec![0u8; size - 8];
|
let mut payload = vec![0u8; size - 8];
|
||||||
@@ -57,25 +92,29 @@ impl WaylandClient {
|
|||||||
|
|
||||||
fn sync(&mut self) -> std::io::Result<u32> {
|
fn sync(&mut self) -> std::io::Result<u32> {
|
||||||
let callback_id = self.alloc_id();
|
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)
|
Ok(callback_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_registry(&mut self) -> std::io::Result<u32> {
|
fn get_registry(&mut self) -> std::io::Result<u32> {
|
||||||
let registry_id = self.alloc_id();
|
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)
|
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 new_id = self.alloc_id();
|
||||||
let iface_bytes = iface.as_bytes();
|
let mut payload = Vec::new();
|
||||||
let mut payload = Vec::with_capacity(4 + iface_bytes.len() + 1 + 4 + 4);
|
payload.extend_from_slice(&name.to_le_bytes());
|
||||||
payload.extend_from_slice(&name.to_ne_bytes());
|
push_wayland_string(&mut payload, iface);
|
||||||
payload.extend_from_slice(iface_bytes);
|
payload.extend_from_slice(&version.to_le_bytes());
|
||||||
payload.push(0);
|
payload.extend_from_slice(&new_id.to_le_bytes());
|
||||||
payload.extend_from_slice(&version.to_ne_bytes());
|
|
||||||
payload.extend_from_slice(&new_id.to_ne_bytes());
|
|
||||||
self.send_message(registry_id, 0, &payload)?; // wl_registry.bind
|
self.send_message(registry_id, 0, &payload)?; // wl_registry.bind
|
||||||
Ok(new_id)
|
Ok(new_id)
|
||||||
}
|
}
|
||||||
@@ -89,12 +128,15 @@ fn start_compositor(socket_path: &str) -> Child {
|
|||||||
std::fs::create_dir_all(runtime_dir).ok();
|
std::fs::create_dir_all(runtime_dir).ok();
|
||||||
|
|
||||||
let mut cmd = Command::new(&compositor_bin);
|
let mut cmd = Command::new(&compositor_bin);
|
||||||
cmd.env("WAYLAND_DISPLAY", socket_path.rsplit('/').next().unwrap_or("wayland-0"))
|
cmd.env(
|
||||||
.env("XDG_RUNTIME_DIR", runtime_dir)
|
"WAYLAND_DISPLAY",
|
||||||
.env("FRAMEBUFFER_WIDTH", "1280")
|
socket_path.rsplit('/').next().unwrap_or("wayland-0"),
|
||||||
.env("FRAMEBUFFER_HEIGHT", "720")
|
)
|
||||||
.env("FRAMEBUFFER_STRIDE", "5120")
|
.env("XDG_RUNTIME_DIR", runtime_dir)
|
||||||
.env("FRAMEBUFFER_ADDR", "0x80000000");
|
.env("FRAMEBUFFER_WIDTH", "1280")
|
||||||
|
.env("FRAMEBUFFER_HEIGHT", "720")
|
||||||
|
.env("FRAMEBUFFER_STRIDE", "5120")
|
||||||
|
.env("FRAMEBUFFER_ADDR", "0x80000000");
|
||||||
|
|
||||||
cmd.spawn().expect("failed to start compositor")
|
cmd.spawn().expect("failed to start compositor")
|
||||||
}
|
}
|
||||||
@@ -117,10 +159,10 @@ fn test_compositor_globals() {
|
|||||||
match client.read_message() {
|
match client.read_message() {
|
||||||
Ok((_obj_id, opcode, payload)) => {
|
Ok((_obj_id, opcode, payload)) => {
|
||||||
assert_eq!(opcode, 0); // wl_registry.global
|
assert_eq!(opcode, 0); // wl_registry.global
|
||||||
let name = u32::from_ne_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||||
let iface_end = payload[4..].iter().position(|&b| b == 0).unwrap_or(0);
|
let mut cursor = 4;
|
||||||
let iface = std::str::from_utf8(&payload[4..4+iface_end]).unwrap();
|
let iface = read_wayland_string(&payload, &mut cursor);
|
||||||
globals.push((name, iface.to_string()));
|
globals.push((name, iface));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("read error: {}", e);
|
eprintln!("read error: {}", e);
|
||||||
@@ -129,12 +171,27 @@ 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_shm"), "wl_shm missing");
|
||||||
assert!(globals.iter().any(|(_, i)| i == "wl_shell"), "wl_shell missing");
|
assert!(
|
||||||
assert!(globals.iter().any(|(_, i)| i == "wl_seat"), "wl_seat missing");
|
globals.iter().any(|(_, i)| i == "wl_shell"),
|
||||||
assert!(globals.iter().any(|(_, i)| i == "wl_output"), "wl_output missing");
|
"wl_shell missing"
|
||||||
assert!(globals.iter().any(|(_, i)| i == "xdg_wm_base"), "xdg_wm_base 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();
|
compositor.kill().ok();
|
||||||
let _ = std::fs::remove_file(socket);
|
let _ = std::fs::remove_file(socket);
|
||||||
@@ -154,16 +211,21 @@ fn test_compositor_shm_formats() {
|
|||||||
let mut shm_name = 0u32;
|
let mut shm_name = 0u32;
|
||||||
for _ in 0..6 {
|
for _ in 0..6 {
|
||||||
let (_, _, payload) = client.read_message().expect("read failed");
|
let (_, _, payload) = client.read_message().expect("read failed");
|
||||||
let name = u32::from_ne_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||||
let iface_end = payload[4..].iter().position(|&b| b == 0).unwrap_or(0);
|
let mut cursor = 4;
|
||||||
let iface = std::str::from_utf8(&payload[4..4+iface_end]).unwrap();
|
let iface = read_wayland_string(&payload, &mut cursor);
|
||||||
if iface == "wl_shm" { shm_name = name; break; }
|
if iface == "wl_shm" {
|
||||||
|
shm_name = name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_ne!(shm_name, 0, "wl_shm global not found");
|
assert_ne!(shm_name, 0, "wl_shm global not found");
|
||||||
|
|
||||||
// Bind wl_shm
|
// 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
|
// Should receive format events
|
||||||
let mut formats = Vec::new();
|
let mut formats = Vec::new();
|
||||||
@@ -171,7 +233,8 @@ fn test_compositor_shm_formats() {
|
|||||||
match client.read_message() {
|
match client.read_message() {
|
||||||
Ok((_, opcode, payload)) => {
|
Ok((_, opcode, payload)) => {
|
||||||
if opcode == 0 && payload.len() >= 4 {
|
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);
|
formats.push(format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user