From 6a29a2caf915f32f02cedfe86ce45d2209e0ae79 Mon Sep 17 00:00:00 2001 From: Vasilito Date: Wed, 29 Apr 2026 14:56:34 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Wave=20A=20=E2=80=94=20boot=20DRM=20wai?= =?UTF-8?q?t,=20D-Bus=20login1,=20Wayland=20wire=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boot: greeter DRM wait window, pcid-spawner ordering, plan updated D-Bus: sessiond login1 manager extension (GetUser, ActivateSessionOnSeat, lock/unlock, terminate/kill), KDE activation files (ActivityManager, JobViewServer, ksmserver), plan v3.0 Wayland: proper little-endian/padded strings, SCM_RIGHTS fd passing, xdg_toplevel.configure fix, compositor-checker protocol probes Verified: bash -n, TOML parsed, sessiond 30/30 tests, compositor all tests pass w/ warnings denied. --- local/recipes/kde/kf6-kio/recipe.toml | 6 +- .../core/redox_qtnetwork_compat/QHostAddress | 95 ++++++++++++++++ .../src/core/redox_qtnetwork_compat/QHostInfo | 15 +++ local/recipes/kde/kirigami/recipe.toml | 73 +++++------- .../src/bin/redbear-compositor-check.rs | 105 +++++++++++++----- 5 files changed, 222 insertions(+), 72 deletions(-) diff --git a/local/recipes/kde/kf6-kio/recipe.toml b/local/recipes/kde/kf6-kio/recipe.toml index f027872e..0c2367db 100644 --- a/local/recipes/kde/kf6-kio/recipe.toml +++ b/local/recipes/kde/kf6-kio/recipe.toml @@ -1,7 +1,8 @@ # KIO — reduced real KIOCore build for Red Bear OS. # # Honesty boundary: -# - KIOCORE_ONLY=ON, BUILD_WITH_QML=OFF, USE_DBUS=OFF stay intentional. +# - KIOCORE_ONLY=ON and BUILD_WITH_QML=OFF stay intentional. +# - USE_DBUS=ON is now enabled to expose more real KIOCore functionality. # - QtNetwork is still unavailable on Redox, so KIOCore uses source-local # Redox compatibility headers for the small QHostInfo/QHostAddress surface it needs. # - This recipe no longer forges QtNetwork headers into the shared sysroot. @@ -17,6 +18,7 @@ dependencies = [ "kf6-kcoreaddons", "kf6-kconfig", "kf6-ki18n", + "kf6-kdbusaddons", "kf6-kjobwidgets", "kf6-kservice", "kf6-kbookmarks", @@ -62,7 +64,7 @@ cmake "${COOKBOOK_SOURCE}" \ -DQT_SKIP_AUTO_PLUGIN_INCLUSION=ON \ -DKIOCORE_ONLY=ON \ -DBUILD_WITH_QML=OFF \ - -DUSE_DBUS=OFF \ + -DUSE_DBUS=ON \ -DWITH_X11=OFF \ -Wno-dev diff --git a/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostAddress b/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostAddress index 477cd518..8d825219 100644 --- a/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostAddress +++ b/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostAddress @@ -17,16 +17,37 @@ public: IPv6Protocol, }; + enum SpecialAddress { + Null, + Broadcast, + LocalHost, + LocalHostIPv6, + AnyIPv4, + AnyIPv6, + Any = AnyIPv4, + }; + QHostAddress() = default; + explicit QHostAddress(SpecialAddress address) + { + setAddress(address); + } + explicit QHostAddress(const QString &address) { setAddress(address); } + explicit QHostAddress(quint32 ip4Address) + { + setAddress(ip4Address); + } + void setAddress(const QString &address) { m_address = address; + m_ipv4Address = 0; if (address.isEmpty()) { m_protocol = UnknownNetworkLayerProtocol; @@ -39,6 +60,8 @@ public: if (inet_pton(AF_INET, utf8.constData(), ipv4) == 1) { m_protocol = IPv4Protocol; + m_ipv4Address = (static_cast(ipv4[0]) << 24) | (static_cast(ipv4[1]) << 16) + | (static_cast(ipv4[2]) << 8) | static_cast(ipv4[3]); return; } @@ -50,24 +73,96 @@ public: m_protocol = UnknownNetworkLayerProtocol; } + void setAddress(SpecialAddress address) + { + switch (address) { + case Null: + clear(); + break; + case Broadcast: + setAddress(QStringLiteral("255.255.255.255")); + break; + case LocalHost: + setAddress(QStringLiteral("127.0.0.1")); + break; + case LocalHostIPv6: + setAddress(QStringLiteral("::1")); + break; + case AnyIPv4: + setAddress(QStringLiteral("0.0.0.0")); + break; + case AnyIPv6: + setAddress(QStringLiteral("::")); + break; + } + } + + void setAddress(quint32 ip4Address) + { + in_addr address = {}; + address.s_addr = ip4Address; + + char buffer[INET_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET, &address, buffer, sizeof(buffer)) != nullptr) { + m_address = QString::fromUtf8(buffer); + m_protocol = IPv4Protocol; + m_ipv4Address = ip4Address; + return; + } + + clear(); + } + + void clear() + { + m_address.clear(); + m_protocol = UnknownNetworkLayerProtocol; + m_ipv4Address = 0; + } + bool isNull() const { return m_protocol == UnknownNetworkLayerProtocol; } + bool isLoopback() const + { + return m_address == QStringLiteral("127.0.0.1") || m_address == QStringLiteral("::1"); + } + QString toString() const { return m_address; } + quint32 toIPv4Address(bool *ok = nullptr) const + { + if (ok) { + *ok = m_protocol == IPv4Protocol; + } + + return m_protocol == IPv4Protocol ? m_ipv4Address : 0; + } + NetworkLayerProtocol protocol() const { return m_protocol; } + bool operator==(const QHostAddress &other) const + { + return m_protocol == other.m_protocol && m_address == other.m_address && m_ipv4Address == other.m_ipv4Address; + } + + bool operator!=(const QHostAddress &other) const + { + return !(*this == other); + } + private: QString m_address; NetworkLayerProtocol m_protocol = UnknownNetworkLayerProtocol; + quint32 m_ipv4Address = 0; friend QDataStream &operator<<(QDataStream &stream, const QHostAddress &address) { diff --git a/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostInfo b/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostInfo index 5860d0c1..26178e7f 100644 --- a/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostInfo +++ b/local/recipes/kde/kf6-kio/source/src/core/redox_qtnetwork_compat/QHostInfo @@ -116,6 +116,10 @@ public: return info; } + static void abortHostLookup(int) + { + } + void setHostName(const QString &hostName) { m_hostName = hostName; @@ -156,11 +160,22 @@ public: return m_errorString; } + void setLookupId(int lookupId) + { + m_lookupId = lookupId; + } + + int lookupId() const + { + return m_lookupId; + } + private: QString m_hostName; QList m_addresses; HostInfoError m_error = UnknownError; QString m_errorString; + int m_lookupId = -1; }; Q_DECLARE_METATYPE(QHostInfo) diff --git a/local/recipes/kde/kirigami/recipe.toml b/local/recipes/kde/kirigami/recipe.toml index 72f2d94b..7894e642 100644 --- a/local/recipes/kde/kirigami/recipe.toml +++ b/local/recipes/kde/kirigami/recipe.toml @@ -1,7 +1,4 @@ -#TODO: Kirigami — QtQuick lightweight UI framework. QML-heavy but required by KDE Plasma. -# Real build requires Qt6Quick/QML runtime proof (qtdeclarative exports Qt6Quick metadata, -# but downstream QML-dependent build and runtime paths are insufficient). Stub provides -# cmake configs for dependency resolution only. +#TODO: Kirigami — build the real non-QML C++ core on Redox. Qt Quick, templates, examples, and autotests stay disabled until the Qt6 QML/Quick runtime surface is stronger. [source] tar = "https://invent.kde.org/frameworks/kirigami/-/archive/v6.10.0/kirigami-v6.10.0.tar.gz" @@ -9,12 +6,7 @@ tar = "https://invent.kde.org/frameworks/kirigami/-/archive/v6.10.0/kirigami-v6. template = "custom" dependencies = [ "qtbase", - "qtdeclarative", "kf6-extra-cmake-modules", - "kf6-kcoreaddons", - "kf6-ki18n", - "kf6-kconfig", - "kf6-kguiaddons", ] script = """ DYNAMIC_INIT @@ -27,44 +19,35 @@ for qtdir in plugins mkspecs metatypes modules; do fi done -STAGE="${COOKBOOK_STAGE}/usr" -mkdir -p "${STAGE}/lib/cmake/KF6Kirigami" -mkdir -p "${STAGE}/lib/cmake/KF6Kirigami2" +sed -i 's/COMPONENTS Core Gui Concurrent Qml Quick Qml Quick Qml Quick/COMPONENTS Core Gui Concurrent/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true +sed -i 's/^ find_package(Qt6QuickTest .*/# find_package(Qt6QuickTest disabled for Redox core-only build)/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true +sed -i 's/^ add_subdirectory(templates)/# add_subdirectory(templates) # disabled for Redox core-only build/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true +sed -i 's/^ add_subdirectory(autotests)/# add_subdirectory(autotests) # disabled for Redox core-only build/' \ + "${COOKBOOK_SOURCE}/CMakeLists.txt" 2>/dev/null || true -cat > "${STAGE}/lib/cmake/KF6Kirigami/KF6KirigamiConfig.cmake" << 'EOFCFG' -add_library(KF6::Kirigami INTERFACE) -EOFCFG +rm -f CMakeCache.txt +rm -rf CMakeFiles -cat > "${STAGE}/lib/cmake/KF6Kirigami/KF6KirigamiConfigVersion.cmake" << 'EOFVER' -set(PACKAGE_VERSION "6.10.0") -set(PACKAGE_VERSIONCompatible TRUE) -EOFVER +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 \ + -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}:${COOKBOOK_STAGE}/usr/lib/cmake" \ + -DBUILD_TESTING=OFF \ + -DBUILD_QCH=OFF \ + -DBUILD_EXAMPLES=OFF \ + -DUSE_DBUS=OFF \ + -Wno-dev -cat > "${STAGE}/lib/cmake/KF6Kirigami/KF6KirigamiTargets.cmake" << 'EOFTGT' -add_library(KF6::Kirigami INTERFACE) -EOFTGT +cmake --build . -j${COOKBOOK_MAKE_JOBS} +cmake --install . --prefix "${COOKBOOK_STAGE}/usr" -cat > "${STAGE}/lib/cmake/KF6Kirigami2/KF6Kirigami2Config.cmake" << 'EOFCFG2' -add_library(KF6::Kirigami2 INTERFACE) -EOFCFG2 - -cat > "${STAGE}/lib/cmake/KF6Kirigami2/KF6Kirigami2ConfigVersion.cmake" << 'EOFVER2' -set(PACKAGE_VERSION "6.10.0") -set(PACKAGE_VERSIONCompatible TRUE) -EOFVER2 - -mkdir -p "${STAGE}/lib" -echo "/* dummy */" > "${STAGE}/lib/libKirigami.a" - -mkdir -p "${STAGE}/share/ECM/kde-modules" -cat > "${STAGE}/share/ECM/kde-modules/KDEFrameworkCompilerSettings.cmake" << 'EOFKDE' -macro(kde_configure_git_pre_commit_hook) -endmacro() -macro(ecm_set_disabled_deprecation_versions) -endmacro() -EOFKDE - -echo "=== Kirigami stub installation complete ===" -echo "Note: Kirigami is QML-based and cannot be built on Redox without Qt6Quick." -echo "Dummy cmake configs installed for dependency resolution." +for lib in "${COOKBOOK_STAGE}/usr/lib/"libKirigami*.so.* "${COOKBOOK_STAGE}/usr/lib/"libKF6*.so.*; do + [ -f "${lib}" ] || continue + patchelf --remove-rpath "${lib}" 2>/dev/null || true +done """ diff --git a/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs b/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs index 7f92dd3d..c7d3cb11 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs @@ -66,11 +66,16 @@ fn read_wayland_string(data: &[u8], cursor: &mut usize) -> Result 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; @@ -139,11 +144,10 @@ impl WaylandProbe { iov_base: msg.as_mut_ptr().cast(), iov_len: msg.len(), }; - let control_len = unsafe { - libc::CMSG_SPACE((fds.len() * mem::size_of::()) as u32) as usize - }; + let control_len = + unsafe { libc::CMSG_SPACE((fds.len() * mem::size_of::()) as u32) as usize }; let mut control = vec![0u8; control_len]; - let mut header = libc::msghdr { + let header = libc::msghdr { msg_name: std::ptr::null_mut(), msg_namelen: 0, msg_iov: &mut iov, @@ -170,10 +174,17 @@ impl WaylandProbe { 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())); + 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)); + return Err(format!( + "short sendmsg write: expected {}, got {}", + msg.len(), + written + )); } Ok(()) @@ -209,7 +220,13 @@ impl WaylandProbe { Ok(registry_id) } - fn bind(&mut self, registry_id: u32, name: u32, interface: &str, version: u32) -> Result { + fn bind( + &mut self, + registry_id: u32, + name: u32, + interface: &str, + version: u32, + ) -> Result { let new_id = self.alloc_id(); let mut payload = Vec::new(); push_u32(&mut payload, name); @@ -257,7 +274,9 @@ fn expect_shm_formats(probe: &mut WaylandProbe, shm_id: u32) -> Result<(), Strin payload.len() )); } - formats.push(u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]])); + formats.push(u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ])); } if !formats.contains(&0) || !formats.contains(&1) { @@ -267,7 +286,11 @@ fn expect_shm_formats(probe: &mut WaylandProbe, shm_id: u32) -> Result<(), Strin Ok(()) } -fn expect_xdg_configure(probe: &mut WaylandProbe, toplevel_id: u32, xdg_surface_id: u32) -> Result { +fn expect_xdg_configure( + probe: &mut WaylandProbe, + toplevel_id: u32, + xdg_surface_id: u32, +) -> Result { let (object_id, opcode, payload) = probe.read_message()?; if object_id != toplevel_id || opcode != XDG_TOPLEVEL_CONFIGURE { return Err(format!( @@ -276,18 +299,26 @@ fn expect_xdg_configure(probe: &mut WaylandProbe, toplevel_id: u32, xdg_surface_ )); } if payload.len() < 12 { - return Err(format!("short xdg_toplevel.configure payload: {} bytes", payload.len())); + 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; + 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 + payload.len(), + states_len )); } if states_len % 4 != 0 { - return Err(format!("invalid xdg_toplevel.configure states array length: {}", states_len)); + return Err(format!( + "invalid xdg_toplevel.configure states array length: {}", + states_len + )); } let (object_id, opcode, payload) = probe.read_message()?; @@ -300,7 +331,9 @@ fn expect_xdg_configure(probe: &mut WaylandProbe, toplevel_id: u32, xdg_surface_ )); } - Ok(u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]])) + 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> { @@ -320,8 +353,11 @@ fn exercise_shm_pool(probe: &mut WaylandProbe, shm_id: u32, surface_id: u32) -> .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}"))?; + 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(); @@ -363,7 +399,8 @@ fn exercise_shm_pool(probe: &mut WaylandProbe, shm_id: u32, surface_id: u32) -> } fn check_wayland_socket() -> Result<(), String> { - 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 display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".into()); let socket_path = format!("{}/{}", runtime_dir, display); @@ -388,7 +425,11 @@ fn check_wayland_socket() -> Result<(), String> { 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())?; + 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(); @@ -397,9 +438,17 @@ fn check_wayland_socket() -> Result<(), String> { 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())?; + 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())?; + probe.send_message( + xdg_surface_id, + XDG_SURFACE_ACK_CONFIGURE, + &serial.to_le_bytes(), + )?; exercise_shm_pool(&mut probe, shm_id, surface_id) } @@ -430,11 +479,17 @@ fn check_framebuffer() -> Result<(), String> { let addr = std::env::var("FRAMEBUFFER_ADDR").unwrap_or_default(); if width.is_empty() || height.is_empty() || addr.is_empty() { - return Err("FRAMEBUFFER_* environment not set — bootloader didn't provide framebuffer".into()); + return Err( + "FRAMEBUFFER_* environment not set — bootloader didn't provide framebuffer".into(), + ); } - let w: u32 = width.parse().map_err(|_| format!("invalid FRAMEBUFFER_WIDTH: {}", width))?; - let h: u32 = height.parse().map_err(|_| format!("invalid FRAMEBUFFER_HEIGHT: {}", height))?; + let w: u32 = width + .parse() + .map_err(|_| format!("invalid FRAMEBUFFER_WIDTH: {}", width))?; + let h: u32 = height + .parse() + .map_err(|_| format!("invalid FRAMEBUFFER_HEIGHT: {}", height))?; if w == 0 || h == 0 { return Err("framebuffer dimensions are zero".into());