diff --git a/base/device/console/console_helper.cpp b/base/device/console/console_helper.cpp index 5d2fad923..555ddee5f 100644 --- a/base/device/console/console_helper.cpp +++ b/base/device/console/console_helper.cpp @@ -9,7 +9,18 @@ std::optional ConsoleHelper::size() const { if (!size_querier) { return {}; } - return size_querier->execute(); + auto result = size_querier->execute(); + if (!result) { + return {}; + } + // A degenerate size (non-positive width or height) means the console size + // is unknown -- e.g. a serial console that reports no window size. Treat it + // as unknown so callers fall back to fallback_size() instead of rendering + // into a zero-sized buffer (which throws std::length_error downstream). + if (result->first <= 0 || result->second <= 0) { + return {}; + } + return result; } bool ConsoleHelper::query_property(const std::string_view property, std::any* value) { diff --git a/desktop/ui/CFDesktopEntity.cpp b/desktop/ui/CFDesktopEntity.cpp index f3c0f9730..b558c0ce3 100644 --- a/desktop/ui/CFDesktopEntity.cpp +++ b/desktop/ui/CFDesktopEntity.cpp @@ -10,6 +10,7 @@ #include "components/IDisplayServerBackend.h" #include "components/PanelManager.h" #include "components/WindowManager.h" +#include "components/builtin_apps/about_panel.h" #include "components/launcher/app_launch_service.h" #include "components/launcher/app_launcher.h" #include "components/statusbar/status_bar.h" @@ -19,12 +20,49 @@ #include "platform/display_backend_helper.h" #include "platform/shell_layer_helper.h" #include "qt_format.h" +#include +#include #include +#include +#include +#include #include #include namespace cf::desktop { +namespace { +/// Loads the per-board app list from /settings/apps.json. Falls back to +/// defaultApps() when the file is missing, unreadable, empty, or has no valid +/// entries -- so a board only needs to drop an apps.json to customize the +/// launcher/taskbar without a recompile. +QList loadAppsConfig() { + // Read from the desktop root (/../apps.json). Kept out of the + // board-written settings/ dir so it stays owner-writable on the NFS root. + const QString path = + QCoreApplication::applicationDirPath() + QStringLiteral("/../apps.json"); + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + return desktop_component::defaultApps(); + } + const QJsonDocument doc = QJsonDocument::fromJson(file.readAll()); + const auto arr = doc.object().value(QStringLiteral("apps")).toArray(); + QList apps; + for (const auto& value : arr) { + const auto o = value.toObject(); + desktop_component::AppEntry entry; + entry.app_id = o.value(QStringLiteral("app_id")).toString(); + entry.display_name = o.value(QStringLiteral("display_name")).toString(); + entry.icon_path = o.value(QStringLiteral("icon_path")).toString(); + entry.exec_command = o.value(QStringLiteral("exec_command")).toString(); + if (!entry.app_id.isEmpty() && !entry.exec_command.isEmpty()) { + apps.append(entry); + } + } + return apps.isEmpty() ? desktop_component::defaultApps() : apps; +} +} // namespace + std::unique_ptr CFDesktopEntity::global_instance_; CFDesktopEntity& CFDesktopEntity::instance() { @@ -220,8 +258,8 @@ CFDesktopEntity::RunsSetupResult CFDesktopEntity::run_init(RunsSetupMethod m) { // ── Taskbar: bottom-edge panel (centered app icons) ── // apps is captured by the click handler to resolve app_id -> exec_command. - const QList apps = - cf::desktop::desktop_component::defaultApps(); + // Loaded from settings/apps.json (per-board app list) if present. + const QList apps = loadAppsConfig(); auto* taskbar = new cf::desktop::desktop_component::CenteredTaskbar(desktop_entity_); taskbar->setApps(apps); taskbar->setBackdropSource(shell); @@ -229,24 +267,43 @@ CFDesktopEntity::RunsSetupResult CFDesktopEntity::run_init(RunsSetupMethod m) { // Shared launch path: resolve app_id -> exec, launch, capture PID. Used by // both the taskbar tile click and the launcher popup so the running-state // indicator lights for either entry point. - std::function launch_app = [apps, app_pid](const QString& app_id) { - QString exec; - for (const auto& app : apps) { - if (app.app_id == app_id) { - exec = app.exec_command; - break; + // Builtin in-process apps live as hidden child widgets of the desktop and + // are shown when their "builtin:*" exec_command is launched (no QProcess, + // so no framebuffer fight with the desktop on linuxfb). + auto* about_panel = new cf::desktop::desktop_component::AboutPanel(desktop_entity_); + + std::function launch_app = + [apps, app_pid, about_panel, panel_mgr](const QString& app_id) { + QString exec; + for (const auto& app : apps) { + if (app.app_id == app_id) { + exec = app.exec_command; + break; + } } - } - if (exec.isEmpty()) { - cf::log::warningftag("CFDesktopEntity", "No exec for app_id '{}'", - app_id.toStdString()); - return; - } - const auto launched = cf::desktop::desktop_component::AppLaunchService::launch(exec); - if (launched.has_value()) { - (*app_pid)[app_id] = *launched; - } - }; + if (exec.isEmpty()) { + cf::log::warningftag("CFDesktopEntity", "No exec for app_id '{}'", + app_id.toStdString()); + return; + } + // Builtin apps render in-process. External apps are launched + // detached (Stage 2 will add hide-desktop + managed-QProcess for + // GUI apps that need the full framebuffer). + if (exec.startsWith(QStringLiteral("builtin:"))) { + const auto id = exec.mid(QStringLiteral("builtin:").size()); + if (id == QStringLiteral("about") && about_panel != nullptr) { + about_panel->popup(panel_mgr->availableGeometry()); + } else { + cf::log::warningftag("CFDesktopEntity", "Unknown builtin app '{}'", + id.toStdString()); + } + return; + } + const auto launched = cf::desktop::desktop_component::AppLaunchService::launch(exec); + if (launched.has_value()) { + (*app_pid)[app_id] = *launched; + } + }; QObject::connect(taskbar, &cf::desktop::desktop_component::CenteredTaskbar::appClicked, this, launch_app); @@ -257,7 +314,17 @@ CFDesktopEntity::RunsSetupResult CFDesktopEntity::run_init(RunsSetupMethod m) { launch_app); QObject::connect( taskbar, &cf::desktop::desktop_component::CenteredTaskbar::launcherRequested, this, - [app_launcher, panel_mgr]() { app_launcher->popup(panel_mgr->availableGeometry()); }); + [app_launcher, panel_mgr]() { + // Toggle: clicking Start while the launcher is open dismisses it, + // otherwise pop it up. The start button used to only ever call + // popup(), so a second click was a no-op while the menu was already + // visible. + if (app_launcher->isShowing()) { + app_launcher->hideLauncher(); + } else { + app_launcher->popup(panel_mgr->availableGeometry()); + } + }); taskbar->show(); panel_mgr->relayout(); diff --git a/desktop/ui/components/CMakeLists.txt b/desktop/ui/components/CMakeLists.txt index f459ea3d9..3abdfe423 100644 --- a/desktop/ui/components/CMakeLists.txt +++ b/desktop/ui/components/CMakeLists.txt @@ -10,6 +10,7 @@ add_subdirectory(window_placement) add_subdirectory(statusbar) add_subdirectory(taskbar) add_subdirectory(launcher) +add_subdirectory(builtin_apps) # Create interface library for convenience add_library(cf_desktop_components STATIC) @@ -38,6 +39,7 @@ PRIVATE cfdesktop_statusbar cfdesktop_taskbar cfdesktop_launcher + cfdesktop_builtin_apps cfdesktop_window_placement Qt6::Core Qt6::Gui Qt6::Widgets ) diff --git a/desktop/ui/components/builtin_apps/CMakeLists.txt b/desktop/ui/components/builtin_apps/CMakeLists.txt new file mode 100644 index 000000000..c66492895 --- /dev/null +++ b/desktop/ui/components/builtin_apps/CMakeLists.txt @@ -0,0 +1,17 @@ +# Builtin in-process apps (child widgets over the desktop, e.g. About). +# These are NOT launched via QProcess; CFDesktop shows them directly, which +# suits single-framebuffer targets (linuxfb) where an external GUI app would +# fight the desktop for /dev/fb0. +add_library(cfdesktop_builtin_apps STATIC + about_panel.cpp +) + +target_include_directories(cfdesktop_builtin_apps PUBLIC + $ + $ +) + +target_link_libraries(cfdesktop_builtin_apps +PRIVATE + Qt6::Core Qt6::Gui Qt6::Widgets +) diff --git a/desktop/ui/components/builtin_apps/about_panel.cpp b/desktop/ui/components/builtin_apps/about_panel.cpp new file mode 100644 index 000000000..424c310bf --- /dev/null +++ b/desktop/ui/components/builtin_apps/about_panel.cpp @@ -0,0 +1,110 @@ +/** + * @file about_panel.cpp + * @brief In-process "About CFDesktop" panel implementation. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-30 + * @version 0.1 + * @since 0.20 + * @ingroup components + */ + +#include "about_panel.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace cf::desktop::desktop_component { + +namespace { +constexpr qreal kCornerRadius = 16.0; ///< Card corner radius (px). +constexpr qreal kWidthRatio = 0.42; ///< Card width as a fraction of available. +constexpr qreal kHeightRatio = 0.36; ///< Card height as a fraction of available. +constexpr int kMinWidth = 360; ///< Minimum card width (px). +constexpr int kMaxWidth = 560; ///< Maximum card width (px). +constexpr int kMinHeight = 220; ///< Minimum card height (px). +constexpr int kMaxHeight = 340; ///< Maximum card height (px). +} // namespace + +AboutPanel::AboutPanel(QWidget* parent) + : QWidget(parent), + version_text_(QStringLiteral("CFDesktop v0.19.0")), + target_text_(QStringLiteral("Running on i.MX6ULL (Qt linuxfb)")) { + setWindowFlags(Qt::FramelessWindowHint); + setAttribute(Qt::WA_TranslucentBackground); + setAttribute(Qt::WA_OpaquePaintEvent, false); + setAutoFillBackground(false); + // Stay hidden until popup(). As a child of the desktop, Qt would otherwise + // auto-show this widget when the desktop becomes visible. + hide(); +} + +AboutPanel::~AboutPanel() = default; + +void AboutPanel::popup(const QRect& available) { + QRect avail = available; + if (!avail.isValid() || avail.width() <= 0 || avail.height() <= 0) { + if (const auto* screen = QGuiApplication::primaryScreen()) { + avail = screen->availableGeometry(); + } + } + + const int w = std::clamp(static_cast(avail.width() * kWidthRatio), kMinWidth, kMaxWidth); + const int h = std::clamp(static_cast(avail.height() * kHeightRatio), kMinHeight, kMaxHeight); + const int x = avail.center().x() - w / 2; + const int y = avail.center().y() - h / 2; + + setGeometry(x, y, w, h); + show(); + raise(); +} + +void AboutPanel::hidePanel() { + hide(); +} + +void AboutPanel::paintEvent(QPaintEvent* /*event*/) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing, true); + + // Hardcoded Material-ish palette; this demo panel does not track the theme. + const QColor surface_color(0xF7, 0xF5, 0xF3); + const QColor text_color(0x1C, 0x1B, 0x1F); + const QColor hint_color(0x49, 0x45, 0x4E); + + QPainterPath surface; + surface.addRoundedRect(QRectF(rect()), kCornerRadius, kCornerRadius); + p.fillPath(surface, surface_color); + + // Version (title), centered in the upper portion. + QFont title_font = font(); + title_font.setPointSize(18); + title_font.setBold(true); + p.setFont(title_font); + p.setPen(text_color); + p.drawText(QRect(0, 0, width(), height() * 2 / 3), Qt::AlignCenter, version_text_); + + // Target line + dismiss hint, centered in the lower portion. + QFont body_font = font(); + body_font.setPointSize(11); + p.setFont(body_font); + p.setPen(hint_color); + p.drawText(QRect(0, height() / 2, width(), height() / 4), Qt::AlignCenter, target_text_); + p.drawText(QRect(0, height() * 3 / 4, width(), height() / 6), Qt::AlignCenter, + QStringLiteral("(tap to close)")); +} + +void AboutPanel::mouseReleaseEvent(QMouseEvent* event) { + if (event->button() == Qt::LeftButton) { + hidePanel(); + } + QWidget::mouseReleaseEvent(event); +} + +} // namespace cf::desktop::desktop_component diff --git a/desktop/ui/components/builtin_apps/about_panel.h b/desktop/ui/components/builtin_apps/about_panel.h new file mode 100644 index 000000000..5ffd3e3fb --- /dev/null +++ b/desktop/ui/components/builtin_apps/about_panel.h @@ -0,0 +1,121 @@ +/** + * @file about_panel.h + * @brief In-process "About CFDesktop" panel (builtin app). + * + * AboutPanel is a frameless child widget shown over the desktop when the + * "builtin:about" app entry is launched. It renders a rounded Material card + * with the CFDesktop version and the running target, and dismisses on a tap. + * As a child of the desktop it renders inside the single desktop window, so it + * does not fight an external app for the framebuffer on linuxfb. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-30 + * @version 0.1 + * @since 0.20 + * @ingroup components + */ + +#pragma once + +#include +#include + +class QMouseEvent; +class QPaintEvent; +class QRect; + +namespace cf::desktop::desktop_component { + +/** + * @brief Builtin "About" panel shown over the desktop. + * + * Demonstrates an in-process (non-QProcess) app: clicking the matching launcher + * tile shows this panel; a tap anywhere hides it. No external binary is needed, + * which suits the single-framebuffer linuxfb target. + * + * @ingroup components + */ +class AboutPanel final : public QWidget { + Q_OBJECT + public: + /** + * @brief Constructs the About panel. + * + * @param[in] parent Owning widget (the desktop surface). + * + * @throws None + * @note The panel starts hidden; call popup() to show it. + * @warning None + * @since 0.20 + * @ingroup components + */ + explicit AboutPanel(QWidget* parent = nullptr); + + /** + * @brief Destructs the panel. + * + * @throws None + * @note None + * @warning None + * @since 0.20 + * @ingroup components + */ + ~AboutPanel() override; + + /** + * @brief Sizes, centers over the available area, and shows the panel. + * + * @param[in] available The free screen geometry (excludes docked panels). + * + * @throws None + * @note A null/empty rect centers on the primary screen. + * @warning None + * @since 0.20 + * @ingroup components + */ + void popup(const QRect& available); + + /** + * @brief Hides the panel. + * + * @throws None + * @note None + * @warning None + * @since 0.20 + * @ingroup components + */ + void hidePanel(); + + protected: + /** + * @brief Paints the rounded card with the version and target text. + * + * @param[in] event The paint event descriptor. + * + * @throws None + * @note None + * @warning None + * @since 0.20 + * @ingroup components + */ + void paintEvent(QPaintEvent* event) override; + + /** + * @brief Dismisses the panel on a left-button release. + * + * @param[in] event The mouse event descriptor. + * + * @throws None + * @note None + * @warning None + * @since 0.20 + * @ingroup components + */ + void mouseReleaseEvent(QMouseEvent* event) override; + + private: + QString version_text_; ///< First line (version). + QString target_text_; ///< Second line (running target). +}; + +} // namespace cf::desktop::desktop_component diff --git a/desktop/ui/components/launcher/app_launcher.cpp b/desktop/ui/components/launcher/app_launcher.cpp index 4e057f81b..4ac9d0036 100644 --- a/desktop/ui/components/launcher/app_launcher.cpp +++ b/desktop/ui/components/launcher/app_launcher.cpp @@ -49,12 +49,24 @@ constexpr int kMaxHeight = 540; ///< Maximum popup height (px). } // namespace AppLauncher::AppLauncher(QWidget* parent) : QWidget(parent) { - setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); + // Frameless CHILD widget -- deliberately NOT Qt::Popup. On windowless + // targets (linuxfb, no compositor / window manager) a Qt::Popup is a + // separate top-level window: show() activates it briefly, then the platform + // deactivates it (~0.5s later) and Qt::Popup auto-closes on deactivation, so + // the start menu flashed and vanished. As a child of the desktop it renders + // inside the single desktop window -- no activation fight, no auto-close. + // Dismissal is via the start-button toggle, ESC, and tile clicks. + setWindowFlags(Qt::FramelessWindowHint); setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_OpaquePaintEvent, false); setAutoFillBackground(false); setupUi(); applyTheme(); + // Stay hidden until popup() is called. As a child of the desktop (not a + // top-level Qt::Popup), Qt would otherwise auto-show this widget when the + // desktop becomes visible -- rendering the tiles at the default (0,0) + // geometry on boot, before the user ever clicks Start. + hide(); // Follow live theme switches (ThemeManager is the canonical source). connect(&qw::core::ThemeManager::instance(), &qw::core::ThemeManager::themeChanged, this, @@ -82,11 +94,13 @@ void AppLauncher::popup(const QRect& available) { const int x = avail.center().x() - w / 2; const int y = avail.bottom() - h; // Bottom-aligned: sits just above the taskbar. - setFixedSize(w, h); - move(x, y); + // Apply geometry atomically and force the grid to lay tiles out within it. + setGeometry(x, y, w, h); + if (grid_ != nullptr) { + grid_->activate(); + } show(); raise(); - activateWindow(); } void AppLauncher::hideLauncher() { diff --git a/desktop/ui/platform/CMakeLists.txt b/desktop/ui/platform/CMakeLists.txt index f3cf0302c..415c53b7f 100644 --- a/desktop/ui/platform/CMakeLists.txt +++ b/desktop/ui/platform/CMakeLists.txt @@ -19,8 +19,17 @@ set(PLATFORM_HEADERS shell_layer_helper.h ) -# Add platform-specific subdirectories -if(UNIX AND NOT APPLE) +# Add platform-specific subdirectories. +# CFDESKTOP_EMBEDDED selects the DirectRender backend (Qt linuxfb / EGLFS) for +# targets without a windowing system (e.g. i.MX6ULL). It is mutually exclusive +# with the linux_wsl X11 backend and the Windows backend. +option(CFDESKTOP_EMBEDDED "Build the embedded DirectRender platform backend (linuxfb/EGLFS, no X11/compositor)" OFF) + +if(CFDESKTOP_EMBEDDED) + # Embedded DirectRender platform -- no X11, no compositor. + file(GLOB embedded_sources "${CMAKE_CURRENT_SOURCE_DIR}/embedded/*.cpp") + list(APPEND PLATFORM_SOURCES ${embedded_sources}) +elseif(UNIX AND NOT APPLE) # Linux/WSL platform if(IS_WSL OR EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/linux_wsl") file(GLOB linux_wsl_sources "${CMAKE_CURRENT_SOURCE_DIR}/linux_wsl/*.cpp") @@ -54,7 +63,11 @@ target_include_directories(cf_desktop_ui_platform PUBLIC ) # Add platform-specific subdirectory includes -if(UNIX AND NOT APPLE) +if(CFDESKTOP_EMBEDDED) + target_include_directories(cf_desktop_ui_platform PUBLIC + $ + ) +elseif(UNIX AND NOT APPLE) if(IS_WSL OR EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/linux_wsl") target_include_directories(cf_desktop_ui_platform PUBLIC $ @@ -81,7 +94,9 @@ PRIVATE QuarkWidgets::quarkwidgets # For qt_format.h (std::formatter) ) -if(UNIX AND NOT APPLE) +if(CFDESKTOP_EMBEDDED) + log_info("UI Backend" "Using Embedded DirectRender Backend (linuxfb/EGLFS).") +elseif(UNIX AND NOT APPLE) if(XCB_FOUND) log_info("UI Backend" "Using XCB Backend as the Desktop UI Render Backend.") target_link_libraries(cf_desktop_ui_platform PRIVATE PkgConfig::XCB) diff --git a/desktop/ui/platform/embedded/embedded_display_server_backend.cpp b/desktop/ui/platform/embedded/embedded_display_server_backend.cpp new file mode 100644 index 000000000..2a868f929 --- /dev/null +++ b/desktop/ui/platform/embedded/embedded_display_server_backend.cpp @@ -0,0 +1,71 @@ +/** + * @file embedded_display_server_backend.cpp + * @brief DirectRender IDisplayServerBackend implementation for embedded targets. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#include "embedded_display_server_backend.h" + +#include "cflog.h" + +#include +#include + +namespace cf::desktop::backend::embedded { + +EmbeddedDisplayServerBackend::EmbeddedDisplayServerBackend(QObject* parent) + : IDisplayServerBackend(parent), + window_backend_(std::make_unique(this)) {} + +EmbeddedDisplayServerBackend::~EmbeddedDisplayServerBackend() = default; + +DisplayServerRole EmbeddedDisplayServerBackend::role() const { + return DisplayServerRole::DirectRender; +} + +DisplayServerCapabilities EmbeddedDisplayServerBackend::capabilities() const { + DisplayServerCapabilities caps; + caps.role = DisplayServerRole::DirectRender; + caps.canManageExternalWindows = false; + caps.needsOwnCompositor = false; + caps.supportsWaylandProtocol = false; + caps.supportsX11Protocol = false; + return caps; +} + +bool EmbeddedDisplayServerBackend::initialize(int /*argc*/, char** /*argv*/) { + initialized_ = true; + cf::log::traceftag("EmbeddedBackend", "Initialized - DirectRender (linuxfb/EGLFS)"); + return true; +} + +void EmbeddedDisplayServerBackend::shutdown() { + initialized_ = false; +} + +int EmbeddedDisplayServerBackend::runEventLoop() { + // The Qt event loop is driven by the desktop session (QApplication::exec()). + return 0; +} + +aex::WeakPtr EmbeddedDisplayServerBackend::windowBackend() { + if (!window_backend_) { + return {}; + } + return window_backend_->make_weak(); +} + +QList EmbeddedDisplayServerBackend::outputs() const { + QList result; + if (auto* screen = QGuiApplication::primaryScreen()) { + result.append(screen->geometry()); + } + return result; +} + +} // namespace cf::desktop::backend::embedded diff --git a/desktop/ui/platform/embedded/embedded_display_server_backend.h b/desktop/ui/platform/embedded/embedded_display_server_backend.h new file mode 100644 index 000000000..911e0e679 --- /dev/null +++ b/desktop/ui/platform/embedded/embedded_display_server_backend.h @@ -0,0 +1,150 @@ +/** + * @file embedded_display_server_backend.h + * @brief DirectRender IDisplayServerBackend for embedded framebuffer targets. + * + * EmbeddedDisplayServerBackend is the display server backend used when + * CFDesktop renders directly to the framebuffer (Qt linuxfb / EGLFS) without + * a windowing system. It reports the DirectRender role, exposes the primary + * screen as the single output, and provides a NullWindowBackend so the shell + * boots standalone. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#pragma once + +#include "../../components/IDisplayServerBackend.h" +#include "null_window_backend.h" + +#include + +namespace cf::desktop::backend::embedded { + +/** + * @brief DirectRender display server backend for embedded framebuffer targets. + * + * Operates without an external window manager: the Qt platform plugin + * (linuxfb/EGLFS) owns the framebuffer, and this backend only reports the + * primary output geometry and a no-op window backend. + * + * @ingroup platform_embedded + */ +class EmbeddedDisplayServerBackend : public IDisplayServerBackend { + Q_OBJECT + public: + /** + * @brief Constructs the embedded DirectRender backend. + * @param[in] parent Optional Qt parent. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + explicit EmbeddedDisplayServerBackend(QObject* parent = nullptr); + + /** + * @brief Destroys the embedded DirectRender backend. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + ~EmbeddedDisplayServerBackend() override; + + /** + * @brief Returns the DirectRender role. + * @return DisplayServerRole::DirectRender. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + DisplayServerRole role() const override; + + /** + * @brief Returns capabilities for a standalone framebuffer target. + * @return DisplayServerCapabilities with no external-window or + * protocol support. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + DisplayServerCapabilities capabilities() const override; + + /** + * @brief Initializes the backend (the QPA plugin owns the framebuffer). + * @param[in] argc Argument count from the application entry point (unused). + * @param[in] argv Argument vector from the application entry point (unused). + * @return True unconditionally; initialization cannot fail. + * @throws None + * @note The Qt platform plugin (linuxfb/EGLFS) is selected via + * QT_QPA_PLATFORM before the QApplication is created. + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + bool initialize(int argc, char** argv) override; + + /** + * @brief Releases resources held by the backend. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + void shutdown() override; + + /** + * @brief Runs the backend event loop. + * @return Always zero; the Qt event loop is driven by the desktop + * session via QApplication::exec(). + * @throws None + * @note Not invoked on the Linux boot path. + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + int runEventLoop() override; + + /** + * @brief Returns the no-op window backend. + * @return Weak pointer to the NullWindowBackend, or empty before + * initialization. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + aex::WeakPtr windowBackend() override; + + /** + * @brief Returns the primary screen geometry as the single output. + * @return List containing the primary screen rectangle, or empty when + * no screen is available yet. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + QList outputs() const override; + + private: + /// No-op window backend. Ownership: this. + std::unique_ptr window_backend_; + /// Whether initialize() has succeeded. + bool initialized_{false}; +}; + +} // namespace cf::desktop::backend::embedded diff --git a/desktop/ui/platform/embedded/embedded_display_size_policy.cpp b/desktop/ui/platform/embedded/embedded_display_size_policy.cpp new file mode 100644 index 000000000..3a92ebca2 --- /dev/null +++ b/desktop/ui/platform/embedded/embedded_display_size_policy.cpp @@ -0,0 +1,41 @@ +/** + * @file embedded_display_size_policy.cpp + * @brief Embedded framebuffer display size policy implementation. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#include "embedded_display_size_policy.h" + +#include +#include + +namespace cf::desktop::platform_strategy::embedded { + +EmbeddedDisplaySizePolicy::EmbeddedDisplaySizePolicy() = default; + +EmbeddedDisplaySizePolicy::~EmbeddedDisplaySizePolicy() = default; + +const char* EmbeddedDisplaySizePolicy::name() const noexcept { + return "Embedded linuxfb Size Policy"; +} + +bool EmbeddedDisplaySizePolicy::action(QWidget* widget_data) { + if (widget_data == nullptr) { + return false; + } + // Embedded targets render directly to the framebuffer; drop any window + // decorations that a compositor would otherwise provide. + widget_data->setWindowFlag(Qt::FramelessWindowHint, true); + return true; +} + +DesktopBehaviors EmbeddedDisplaySizePolicy::query() const { + return DesktopBehaviorFlag::Fullscreen | DesktopBehaviorFlag::Frameless; +} + +} // namespace cf::desktop::platform_strategy::embedded diff --git a/desktop/ui/platform/embedded/embedded_display_size_policy.h b/desktop/ui/platform/embedded/embedded_display_size_policy.h new file mode 100644 index 000000000..0b4db7794 --- /dev/null +++ b/desktop/ui/platform/embedded/embedded_display_size_policy.h @@ -0,0 +1,92 @@ +/** + * @file embedded_display_size_policy.h + * @brief Embedded (linuxfb / EGLFS) display size policy strategy. + * + * Provides a minimal display size strategy for embedded targets that render + * directly to the framebuffer without a window manager. Unlike the WSL X11 + * policy, it does not probe for X11/Wayland and simply requests a frameless + * fullscreen surface. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#pragma once + +#include "../IDesktopDisplaySizeStrategy.h" + +class QWidget; + +namespace cf::desktop::platform_strategy::embedded { + +/** + * @brief Display size policy for embedded framebuffer targets. + * + * Configures the desktop widget for frameless fullscreen rendering on a + * single framebuffer output (Qt linuxfb / EGLFS). No window-manager + * interaction is performed. + * + * @ingroup platform_embedded + */ +class EmbeddedDisplaySizePolicy : public IDesktopDisplaySizeStrategy { + public: + /** + * @brief Constructs the embedded display size policy. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + EmbeddedDisplaySizePolicy(); + + /** + * @brief Destroys the embedded display size policy. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + ~EmbeddedDisplaySizePolicy() override; + + /** + * @brief Returns the ABI-friendly name of this strategy. + * @return Null-terminated static string identifier. + * @throws None + * @note The returned string must not be freed. + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + const char* name() const noexcept override; + + /** + * @brief Applies frameless fullscreen configuration to the widget. + * @param[in] widget_data Pointer to the QWidget to configure. May be nullptr. + * @return True when the widget was configured, false on nullptr. + * @throws None + * @note The widget is shown fullscreen by the desktop entity; + * this call only clears window decorations. + * @warning Passing nullptr results in an immediate false return. + * @since 0.19.0 + * @ingroup platform_embedded + */ + bool action(QWidget* widget_data) override; + + /** + * @brief Queries the desktop behaviors supported by this strategy. + * @return DesktopBehaviors with Fullscreen and Frameless flags set. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + DesktopBehaviors query() const override; +}; + +} // namespace cf::desktop::platform_strategy::embedded diff --git a/desktop/ui/platform/embedded/embedded_platform.cpp b/desktop/ui/platform/embedded/embedded_platform.cpp new file mode 100644 index 000000000..31150dafa --- /dev/null +++ b/desktop/ui/platform/embedded/embedded_platform.cpp @@ -0,0 +1,76 @@ +/** + * @file embedded_platform.cpp + * @brief Native platform factory hooks for the embedded DirectRender backend. + * + * Provides the per-platform factory entry points (native_impl, + * native_display_impl, native_shell_layer_impl) consumed by the platform + * helpers when CFDESKTOP_EMBEDDED is enabled. The display backend is the + * EmbeddedDisplayServerBackend; the shell layer reuses the QWidget-based + * WidgetShellLayer and wallpaper strategy. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#include "embedded_display_size_policy.h" +#include "embedded_display_server_backend.h" + +#include "IDesktopPropertyStrategy.h" +#include "components/IShellLayerStrategy.h" +#include "components/shell_layer_impl/WidgetShellLayer.h" +#include "components/shell_layer_impl/wallpaper_setup.h" +#include "display_backend_helper.h" +#include "platform_helper.h" +#include "shell_layer_helper.h" + +#include + +namespace cf::desktop::platform_strategy { + +PlatformFactoryAPI native_impl() { + PlatformFactoryAPI api; + // The embedded target owns a single shared size policy (function-local + // static). create() lends a borrowed pointer; release() is a no-op. + api.creator_func = [](IDesktopPropertyStrategy::StrategyType t) + -> IDesktopPropertyStrategy* { + if (t != IDesktopPropertyStrategy::StrategyType::DisplaySizePolicy) { + return nullptr; + } + static auto policy = std::make_unique(); + return policy.get(); + }; + api.release_func = [](IDesktopPropertyStrategy* /*policy*/) { + // Factory-owned; nothing to release. + }; + return api; +} + +} // namespace cf::desktop::platform_strategy + +namespace cf::desktop::platform { + +DisplayBackendFactoryAPI native_display_impl() { + DisplayBackendFactoryAPI api; + api.creator_func = []() -> IDisplayServerBackend* { + return new backend::embedded::EmbeddedDisplayServerBackend(); + }; + api.release_func = [](IDisplayServerBackend* p) { delete p; }; + return api; +} + +ShellLayerFactoryAPI native_shell_layer_impl() { + ShellLayerFactoryAPI api; + api.shell_creator = [](QWidget* parent) -> IShellLayer* { + return new WidgetShellLayer(parent); + }; + api.shell_releaser = [](IShellLayer* p) { delete p; }; + api.strategy_creator = []() -> std::unique_ptr { + return wallpaper::create_wallpaper_strategy(); + }; + return api; +} + +} // namespace cf::desktop::platform diff --git a/desktop/ui/platform/embedded/null_window_backend.cpp b/desktop/ui/platform/embedded/null_window_backend.cpp new file mode 100644 index 000000000..437d106fa --- /dev/null +++ b/desktop/ui/platform/embedded/null_window_backend.cpp @@ -0,0 +1,42 @@ +/** + * @file null_window_backend.cpp + * @brief No-op IWindowBackend implementation for embedded DirectRender mode. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#include "null_window_backend.h" + +namespace cf::desktop::backend::embedded { + +NullWindowBackend::NullWindowBackend(QObject* parent) : IWindowBackend(parent) {} + +NullWindowBackend::~NullWindowBackend() = default; + +aex::WeakPtr NullWindowBackend::createWindow(const QString& /*appId*/) { + return {}; +} + +void NullWindowBackend::destroyWindow(aex::WeakPtr /*window*/) {} + +QList> NullWindowBackend::windows() const { + return {}; +} + +render::BackendCapabilities NullWindowBackend::capabilities() const { + // The framebuffer target has no GPU and no multi-window support. + render::BackendCapabilities caps; + caps.supportsMultiWindow = false; + caps.supportsTransparency = false; + caps.hasHardwareAcceleration = false; + caps.supportsVSync = false; + caps.supportsScreenshot = false; + caps.maxTextureSize = 0; + return caps; +} + +} // namespace cf::desktop::backend::embedded diff --git a/desktop/ui/platform/embedded/null_window_backend.h b/desktop/ui/platform/embedded/null_window_backend.h new file mode 100644 index 000000000..213082db8 --- /dev/null +++ b/desktop/ui/platform/embedded/null_window_backend.h @@ -0,0 +1,101 @@ +/** + * @file null_window_backend.h + * @brief No-op IWindowBackend for embedded DirectRender mode. + * + * In DirectRender mode CFDesktop renders its own shell and does not manage + * external application windows. NullWindowBackend satisfies the IWindowBackend + * contract with empty implementations so the shell boots without a windowing + * system. + * + * @author Charliechen114514 (chengh1922@mails.jlu.edu.cn) + * @date 2026-06-29 + * @version 0.1 + * @since 0.19.0 + * @ingroup platform_embedded + */ + +#pragma once + +#include "../../components/IWindowBackend.h" + +namespace cf::desktop::backend::embedded { + +/** + * @brief No-op window backend for embedded DirectRender mode. + * + * Reports no managed windows and limited capabilities. createWindow() returns + * an empty reference and no signals are emitted. + * + * @ingroup platform_embedded + */ +class NullWindowBackend : public IWindowBackend { + Q_OBJECT + public: + /** + * @brief Constructs the null window backend. + * @param[in] parent Optional Qt parent. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + explicit NullWindowBackend(QObject* parent = nullptr); + + /** + * @brief Destroys the null window backend. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + ~NullWindowBackend() override; + + /** + * @brief Returns an empty reference; no windows are created. + * @param[in] appId Logical identifier of the application (unused). + * @return Empty weak reference (no window is created). + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + aex::WeakPtr createWindow(const QString& appId) override; + + /** + * @brief No-op destruction; nothing is tracked. + * @param[in] window Weak reference to the window (unused). + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + void destroyWindow(aex::WeakPtr window) override; + + /** + * @brief Returns an empty list; no windows are tracked. + * @return Empty list of weak window references. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + QList> windows() const override; + + /** + * @brief Returns limited capabilities (no multi-window, no GPU). + * @return BackendCapabilities describing the embedded framebuffer. + * @throws None + * @note None + * @warning None + * @since 0.19.0 + * @ingroup platform_embedded + */ + render::BackendCapabilities capabilities() const override; +}; + +} // namespace cf::desktop::backend::embedded