Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions document/status/current.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ description: CFDesktop 项目进度的唯一事实来源与全局导航。

# CFDesktop 当前项目状态

> **校准日期**:2026-06-26 | **版本**:0.19.0
> **校准日期**:2026-07-05 | **版本**:0.19.0
> **本文件是项目进度的唯一事实来源(single source of truth)。** 其他位置一律指向此处,勿另行手抄。

## 项目导航(各信息去哪看)
Expand Down Expand Up @@ -50,7 +50,7 @@ description: CFDesktop 项目进度的唯一事实来源与全局导航。
1. **MS2 状态栏**(顶部时间 + 系统图标)— ✅ 功能落地(`StatusBar` 实现 + 注册 PanelManager + 主题跟随 + MD3 美化;offscreen 启动通过,待真机视觉确认)
2. **MS3 任务栏**(底部居中图标条 + hover 动画)— ✅ 最小切片完成(`CenteredTaskbar` 注册 Bottom 面板 + `TaskbarIcon` 居中图标/hover 放大/自绘 ripple/运行指示器;`appClicked` 已接 `AppLaunchService::launch` 真启动并记 PID,运行指示器经 `WindowManager` 窗口追踪联动——见 `desktop/ui/CFDesktopEntity.cpp:133-180`)
3. **MS4 应用启动器**(网格 + 搜索 + QProcess + 双态)— 🚧 进行中(双态框架 + 网格 + 搜索框落地:`LaunchKind`/`IBuiltinPanel`/`BuiltinPanelRegistry` 把 `builtin:` hack 正式化、消灭 if 链,calculator 双态,`HardwareTier::prefer_inprocess_apps` 按 Low/High 裁决;`AppLauncher` 网格 + 搜索 QLineEdit 实时过滤;`DesktopEntryIndex` 扫 XDG `.desktop`(firefox 等);Noter 移植成第二个独立 App。仅余入场/退场动画。详见 [milestone_04](../todo/desktop/milestone_04_app_launcher.md))
4. **MS5 窗口管理**(窗口装饰 + 任务栏联动)— 🚧 进行中(追踪+联动切片跑通:`WindowManager` 追踪外部窗口 + `IWindow::pid()` + Taskbar 运行指示器联动;靠 PID 匹配,直接启动(xterm)可靠、间接启动(xdg-open)受限;**窗口装饰已定策→overlay 渲染**〔策略 A:CFDesktop 自绘 overlay 层呈现标题栏+控制按钮作纯视觉指示,因 WSL X11 客户端模式下外部窗口由 XWayland 管理无法直接装饰;X11 WM〔B〕/ Wayland Compositor〔C〕延后到 EGLFS/Wayland 后端,详见 [milestone_05](../todo/desktop/milestone_05_window_management.md):157-160〕
4. **MS5 窗口管理**(窗口状态机 + 任务栏联动)— 🚧 进行中(追踪+联动切片跑通:`WindowManager` 追踪外部窗口 + `IWindow::pid()` + Taskbar 运行指示器联动;靠 PID 匹配,直接启动(xterm)可靠、间接启动(xdg-open)受限。**2026-07-05 状态机 + 联动 + FloatingPolicy 落地**:`WindowState` 状态机 (`isValidTransition` + `applyState`)、`IWindow` 加 `minimize/maximize/restore` 非纯虚默认 no-op(Windows 三态全实现 `ShowWindow`;WSL X11 minimize+restore 走 ICCCM `WM_CHANGE_STATE` ClientMessage、maximize 留 no-op 因 XWayland EWMH 不可靠)、WindowManager 查询 (`getWindowInfo`/`getAllWindowInfos`/`findWindowByPid`) + `windowStateChanged`/`windowInfoUpdated` 信号、Taskbar 点击运行图标走 raise/minimize toggle(Normal→minimize、Minimized→restore+raise、其他→仅 raise,不再二次启动)、`FloatingPolicy` 新窗口初始位置(居中+24px 级联+缩进 work area)、WindowInfo 补 `icon_hint`/`z_index`/`is_always_on_top`/`created_at`;FloatingPolicy 13 例 + WindowManager 14 例(FakeWindow+QSignalSpy)单测全绿。**装饰 overlay (策略 A) DEFERRED 到 EGLFS/Wayland-compositor 阶段**——WSL X11/Win32 外部窗口自带原生标题栏、overlay 重影;EGLFS 后端将原生持有装饰权。详见 [milestone_05](../todo/desktop/milestone_05_window_management.md))

闭环达成后按需推进:CrashHandler、IPC、EGLFS 嵌入式后端、输入抽象层、P2/P3 控件。

Expand All @@ -67,6 +67,7 @@ description: CFDesktop 项目进度的唯一事实来源与全局导航。
- **2026-07(并发迁移 3 App)**:SystemState/AlarmyClock/CCCalendar 三 App 用 Agent **并行迁移**(参照 noter 范式,验证批量可复制):**SystemState**(`apps/system_state/`)复用 `cfbase` 的 CPU/memory probe(`getCPUProfileInfo`/`getCPUBonusInfo`/`getSystemMemoryInfo`)+ QTimer 刷新,展示 base 层能力;**AlarmyClock**(`apps/alarm_clock/`)1s 轮询 + QSpinBox 编辑器 + QListWidget 已设列表 + QMessageBox 响铃(音频 TODO);**CCCalendar**(`apps/calendar/`)`QCalendarWidget` + 日期笔记(内存 QMap,持久化 TODO)。集成时修:alarm_clock 的 `Q_DECLARE_METATYPE` 移到全局命名空间(moc 要求)、calendar 的 Qt6 API(`setVerticalGridLineVisible` 不存在、`DefaultLocaleLongDate` Qt6 删除→`Qt::TextDate`)。三 App 全 build 绿 + Doxygen 过。
- **2026-07(App 抽离到 CFDeskit + 发现机制重构)**:5 个独立 app(calculator/noter/alarm_clock/calendar/system_state)抽到独立仓 [CFDeskit](https://github.com/Awesome-Embedded-Learning-Studio/CFDeskit);主仓 `apps/` 源码全删(PR #22),改**运行时部署模式**——app 由 CFDeskit build + install 到 `<active_root>/apps/<id>/`,`AppDiscoverer` 改扫 `active_root/apps`(`Apps` PathType 小写,原 `<bin>/../apps/` 已弃),空目录 → `log_info` 不报错。system_state **vendoring cfbase probe**(STATIC `mini_system_probe` lib,零 `libcfbase.so` 依赖,完全独立演化)。**ABI 部署隔离**:app rpath `$ORIGIN/..` 找 `apps/libquarkwidgets.so`(独立于 desktop `bin/` 那份),QuarkWidgets 加版本导出(PR #2 `quarkwidgets_runtime_abi_version`)+ CFDeskit `abi_check.hpp` 启动自检(mismatch `qFatal`)。源码开发停放 `third_party/apps/`(gitignore,clone 任意 app 仓一视同仁)。calculator parser 单测随源码迁 CFDeskit(15 TEST 全过)。主仓零 app 代码,desktop 本体纯净。注:`LoadKind`/`IBuiltinPanel`/`BuiltinPanelRegistry` 双态机制保留(builtin 仅 about)。
- **2026-07(主仓重组:cfbase + desktop/base 合并 + desktop/ 平铺)**:删 `desktop/` 包装层——`base/`(cfbase) + 原 `desktop/base/`(logger/config/path/filesystem/fundamental/ascii_art) 合并成新顶层 `base/`(cfbase 子目录化,desktop/base 6 模块同居);`desktop/ui` → `ui/`、`desktop/main` → `main/`(升顶层);`desktop/export.h` + `desktop/main.cpp` → 顶层;`CFDesktop_shared` 组装搬到顶层 CMakeLists。源码**零改动**(内部全相对 include,平移后路径不变,实测 `#include "desktop/"` = 0);CMake 重组(base/ 合并 + 顶层 `add_subdirectory(ui/main)` + CFDesktop_shared + exe,补 `enable_testing()` 顶层)。三层架构图 `base → ui → desktop` 改 `base → ui → main`。cfbase 名副其实(真正的 base 层,含探针 + 桌面基础设施)。验证:build 全绿 + ctest 全过 + desktop 启动正常。
- **2026-07(MS5 窗口状态机 + 任务栏 raise/minimize + FloatingPolicy)**:补齐 milestone_05 除装饰外的全部验收项。`WindowState` 状态机(`isValidTransition`+`applyState`,Normal↔Minimized/Maximized、Closed 终态)+ WindowManager 查询/信号(`getWindowInfo`/`getAllWindowInfos`/`findWindowByPid` + `windowStateChanged`/`windowInfoUpdated`);`IWindow` 加 `minimize/maximize/restore` 非纯虚默认 no-op——Windows 三态全实现(`ShowWindow`),WSL X11 实现 minimize+restore(ICCCM 4.1.4 `WM_CHANGE_STATE` ClientMessage → root + SubstructureRedirectMask,即 `XIconifyWindow`/xdotool 路径)、maximize 留默认 no-op(XWayland 下 EWMH `_NET_WM_STATE` 不可靠);`onWindowCame` 同步填 `windows_` map(原来只填 `window_infos_` 致 `find_window` 对外部窗口失效) + destroyed lambda 先发 Closed 再 erase;Taskbar 点击运行图标走 raise/minimize toggle(不再二次启动);`FloatingPolicy` 新窗口初始位置(居中+24px 级联+缩进 work area,镜像 `WindowPlacementPolicy`);WindowInfo 补 `icon_hint`/`z_index`/`is_always_on_top`/`created_at`。单测:FloatingPolicy 13 例 + WindowManager 14 例(FakeWindow+QSignalSpy)。**装饰 overlay (策略 A) DEFERRED 到 EGLFS 阶段**(WSL X11/Win32 外部窗口自带原生标题栏、overlay 重影;EGLFS/Wayland-compositor 后端将原生持有装饰权)。详见 [milestone_05](../todo/desktop/milestone_05_window_management.md)。
- **已达成**:Milestone 1「桌面骨架可见」;Phase 0 / 1 / 2 / A(CI) / 6 / G(Widget) / H(显示后端)(详见 [SUMMARY.md](../todo/done/SUMMARY.md))

## 新人入门
Expand Down
43 changes: 32 additions & 11 deletions document/todo/desktop/milestone_05_window_management.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ description: "预计周期: 7-10 天,前置依赖: Milestone 3: 任务栏 (任

# Milestone 5: 窗口管理可见

> **状态**: 🚧 进行中(追踪 + 任务栏联动切片跑通;窗口装饰走 overlay 渲染〔策略 A〕待实现
> **状态**: 🚧 进行中(窗口状态机 + 任务栏 raise/minimize 联动 + FloatingPolicy 落地〔2026-07-05〕;装饰 overlay 渲染 DEFERRED 到 EGLFS 阶段
> **预计周期**: 7-10 天
> **前置依赖**: [Milestone 3: 任务栏](milestone_03_taskbar.md) (任务栏联动)
> **目标**: 外部应用窗口能被管理——有窗口装饰,支持最小化/最大化/关闭,任务栏同步显示运行中应用
> **目标**: 外部应用窗口能被管理——支持最小化/最大化/恢复/关闭,任务栏同步显示运行中应用,点击运行图标切换 raise/minimize
>
> **决策(2026-06-26)**:窗口装饰采用**策略 A — overlay 渲染**为初期路径(CFDesktop 自绘层呈现标题栏 + 控制按钮,纯视觉指示;WSL X11 客户端模式下外部窗口由 XWayland 管理无法直接装饰)。策略 B(X11 WM)/ 策略 C(Wayland Compositor)延后到 EGLFS/Wayland 后端阶段。
>
> **决策(2026-07-05)**:**装饰 overlay(策略 A)DEFERRED 到 EGLFS/Wayland-compositor 阶段**。详见文末「装饰 DEFERRED 决策」。本切片先行落地状态机 + 任务栏联动 + 浮动布局策略,不依赖装饰。

---

Expand Down Expand Up @@ -257,15 +259,34 @@ description: "预计周期: 7-10 天,前置依赖: Milestone 3: 任务栏 (任

## 五、验收标准

- [ ] 启动外部应用后 WindowManager 能追踪到新窗口
- [ ] WindowManager 维护 WindowInfo 数据 (标题、PID、状态)
- [ ] 窗口状态可在 Normal/Minimized/Maximized 之间转换
- [ ] Taskbar 同步显示运行中应用图标
- [ ] 点击 Taskbar 图标可 raise/minimize 对应窗口
- [ ] 窗口有自定义装饰 (至少标题 + 关闭按钮的视觉呈现)
- [ ] 窗口关闭后 Taskbar 图标自动消失
- [ ] Light/Dark 主题切换时装饰正确变色
- [x] 启动外部应用后 WindowManager 能追踪到新窗口
- [x] WindowManager 维护 WindowInfo 数据 (标题、PID、状态、icon_hint/z_index/is_always_on_top/created_at)
- [x] 窗口状态可在 Normal/Minimized/Maximized 之间转换(IWindow minimize/maximize/restore + WindowManager 状态机;X11 上 maximize 留默认 no-op,详见 Step B6)
- [x] Taskbar 同步显示运行中应用图标
- [x] 点击 Taskbar 图标可 raise/minimize 对应窗口(Normal→minimize、Minimized→restore+raise、其他→仅 raise)
- [ ] 窗口有自定义装饰 (至少标题 + 关闭按钮的视觉呈现) — **DEFERRED 到 EGLFS 阶段**(见下)
- [x] 窗口关闭后 Taskbar 图标自动消失(destroyed 观察发 Closed 迁移 + windowDisappeared)
- [ ] Light/Dark 主题切换时装饰正确变色 — 随装饰一同 DEFERRED

---

## 装饰 DEFERRED 决策(2026-07-05)

WindowDecoration(策略 A overlay)推迟到 EGLFS/Wayland-compositor 后端阶段。理由:

1. **WSL X11 客户端模式无法直接装饰外部窗口**——外部窗口由 XWayland 管理,自带原生标题栏;CFDesktop 只能做纯视觉 overlay 指示,叠加上去会与原生标题栏重影。
2. **Win32 后端同理**——外部窗口自带原生标题栏,overlay 重影。
3. **EGLFS/Wayland-compositor 后端将原生持有窗口装饰权**——那时 CFDesktop 是 compositor,装饰才有真实价值;纯视觉 overlay 在没有 compositor 权限下收益有限,且会在后端切换时被重写。

本切片先行落地、且**不依赖装饰**的成果:

- **WindowState 状态机**——`isValidTransition`(Normal↔Minimized、Normal↔Maximized;Maximized↔Minimized 须 restore 中转;Closed 终态)+ `applyState`(校验→派发 IWindow op→回写 info→发信号)。
- **IWindow minimize/maximize/restore**——非纯虚默认 no-op;Windows 三态全实现(`ShowWindow`);WSL X11 实现 minimize+restore(ICCCM 4.1.4 `WM_CHANGE_STATE` ClientMessage → root + SubstructureRedirectMask,即 `XIconifyWindow`/xdotool 路径),maximize 留默认 no-op(XWayland 下 EWMH `_NET_WM_STATE` 不可靠)。
- **WindowManager 查询/信号**——`getWindowInfo`/`getAllWindowInfos`/`findWindowByPid` + `windowStateChanged`/`windowInfoUpdated`。
- **Taskbar raise/minimize toggle**——点击运行图标不再二次启动,而是按状态切换。
- **FloatingPolicy**——新窗口初始位置(居中 + 24px 级联 + 缩进 work area)。
- **单测**——FloatingPolicy 13 例 + WindowManager 14 例(FakeWindow + QSignalSpy)全绿。

---

*最后更新: 2026-06-29(核对代码现状:`window_info.h` 骨架已存在待补字段、`WindowPlacementPolicy` 已落地补录)*
*最后更新: 2026-07-05(MS5 状态机 + 任务栏 raise/minimize 联动 + FloatingPolicy 落地;装饰 overlay DEFERRED 到 EGLFS)*
3 changes: 3 additions & 0 deletions test/desktop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ add_subdirectory(frosted_backdrop)
# Add window placement tests subdirectory
add_subdirectory(window_placement)

# Add window manager (state machine) tests subdirectory
add_subdirectory(window_manager)

# Add wallpaper animation tests subdirectory
add_subdirectory(wallpaper_animation)

Expand Down
42 changes: 42 additions & 0 deletions test/desktop/window_manager/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# WindowManager state-machine unit tests.
# Drives minimize/maximize/restore via an in-memory FakeWindow (no backend, no
# display). Pure logic + signal emission — Qt6::Widgets pulled in for the
# QObject/QSignalSpy machinery.
add_executable(window_manager_test
window_manager_test.cpp
)

target_include_directories(window_manager_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/ui/components
)

target_link_libraries(window_manager_test PRIVATE
cf_desktop_components
Qt6::Core
Qt6::Widgets
GTest::gtest
GTest::gtest_main
)

# Qt6::Test enables QSignalSpy-based signal verification when available.
if(Qt6Test_FOUND)
target_link_libraries(window_manager_test PRIVATE Qt6::Test)
target_compile_definitions(window_manager_test PRIVATE QT_TEST_AVAILABLE)
log_info("window_manager_tests" " - Qt6::Test available, signal tests enabled")
else()
log_info("window_manager_tests" " - Qt6::Test not available, signal tests disabled")
endif()

set_target_properties(window_manager_test PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/test/bin"
)

add_test(NAME window_manager_test COMMAND window_manager_test)
set_tests_properties(window_manager_test PROPERTIES
LABELS "desktop;unit;components"
TIMEOUT 60
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/test/bin"
)

log_info("window_manager_tests" " - window_manager_test")
57 changes: 57 additions & 0 deletions test/desktop/window_manager/fake_window.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @file fake_window.h
* @brief Fake IWindow for WindowManager unit tests.
*
* FakeWindow implements the IWindow contract entirely in-memory: every query
* returns canned data and every operation increments a counter. This lets the
* WindowManager state-machine tests assert behavior with no platform backend
* and no display. It inherits IWindow's Q_OBJECT meta-object (so QObject::
* destroyed fires normally) without declaring its own Q_OBJECT.
*
* @author Charliechen114514 (chengh1922@mails.jlu.edu.cn)
* @date 2026-07-05
* @version 0.1
* @since 0.21
* @ingroup components
*/

#pragma once

#include "IWindow.h"

#include <QRect>
#include <QString>

namespace cf::desktop::test {

/// @brief In-memory IWindow double that records every operation.
class FakeWindow : public IWindow {
public:
FakeWindow(QString id, QString title, QRect geo, qint64 pid, QObject* parent = nullptr)
: IWindow(parent), id_(std::move(id)), title_(std::move(title)), geo_(geo), pid_(pid) {}

win_id_t windowID() const override { return id_; }
QString title() const override { return title_; }
QRect geometry() const override { return geo_; }
void set_geometry(const QRect& r) override { geo_ = r; }
void requestClose() override { ++close_calls; }
void raise() override { ++raise_calls; }
void minimize() override { ++minimize_calls; }
void maximize() override { ++maximize_calls; }
void restore() override { ++restore_calls; }
qint64 pid() const override { return pid_; }

int close_calls = 0;
int raise_calls = 0;
int minimize_calls = 0;
int maximize_calls = 0;
int restore_calls = 0;

private:
QString id_;
QString title_;
QRect geo_;
qint64 pid_;
};

} // namespace cf::desktop::test
Loading
Loading