Skip to content

Feat/shell foundation#27

Merged
Charliechen114514 merged 22 commits into
mainfrom
feat/shell-foundation
Jul 8, 2026
Merged

Feat/shell foundation#27
Charliechen114514 merged 22 commits into
mainfrom
feat/shell-foundation

Conversation

@Charliechen114514

Copy link
Copy Markdown
Member

No description provided.

RenderBackend and RenderBackendFactory were dead code with zero references repo-wide. Capability/rendering duties are covered by IDisplayServerBackend, IWindowBackend, and qt_backend. Drop both headers from cf_desktop_render's FILE_SET; keep backend_capabilities.h since IWindowBackend::capabilities() still returns it (implemented on all three platforms).

Also corrects current.md MS4: launcher enter/exit animations are already implemented symmetrically; the old text listed them as remaining.
A second shell instance that fails to acquire the single-instance lock now signals the running one to bring itself to the foreground, then exits. Previously it just exited silently.

- base/ipc/: new STATIC lib (cfipc) -- IPCServer (QObject singleton, raiseRequested signal) + IPCClient (blocking one-shot send). QtCore/Qt Network only, no D-Bus (suits the 6ULL rule).
- IpcStage starts the server in early boot, after the logger stage.
- CFDesktopEntity connects raiseRequested to showNormal/raise/activateWindow on the desktop widget.
- main.cpp sends "raise" on lock failure before exiting.
- cfipc linked into CFDesktopMain + CFDesktopUi + the thin exe, and folded into CFDesktop_shared via --whole-archive; ipc_server.h is listed explicitly in cfipc sources so AUTOMOC scans its Q_OBJECT.

Verified on WSLg: second instance exits in ~0.3s (lock fail + send); first instance creates the IPC socket (listening confirmed). Vertical slice for the IPC foundation; message framework / ServiceLocator / unit tests come next.
Promotes the single-instance raise channel into a real IPC foundation: typed messages, a type-routed registry, and an in-process service locator. The line-based raise token becomes an IPCMessage with JSON framing (one compact object per newline).

- IPCMessage { type, JSON payload } with toJson/fromJson round-trip.
- IPCMessageRegistry (singleton) maps type to handler; IPCServer dispatches each received message through it and registers raise itself.
- IPCClient gains send(IPCMessage) and a send(type, payload) convenience overload; the wire format is now JSON.
- ServiceLocator: header-only typed registry for in-process service discovery (not for cross-process use).
- Unit tests: message round-trip, registry dispatch, service locator, and a transport integration test (client send to server dispatch via QEventLoop). 4 new tests, 25/25 green.

raise still works end-to-end (transport test exercises the same dispatch path).
Async-signal-safe Linux handler (base/crash_handler/, lib cfcrash, Qt-free)
captures SIGSEGV/SIGABRT/SIGFPE/SIGBUS/SIGILL, writes a raw .pending snapshot
(pid/signal/bare backtrace addresses + this run's logger path), and _exit()s.
The handler is isolated in crash_signal_handler.cpp with sigaltstack +
SA_ONSTACK (stack-overflow safety) and pure-arithmetic hex/decimal output --
no malloc/stdio/Qt in the signal path.

CrashHandlerStage (early_session Stage 4, after Logger) folds each .pending
into a finalized .json report, tailing the crashed run's own log file for
lastLogs (the .pending records the per-launch logger path so the next boot
reads the right file), then prunes to 20 reports.

- CrashHandler singleton (non-QObject) + CrashReport JSON model
- CMake four-link wiring: base/, CFDesktopMain, CFDESKTOP_STATIC_LIBS
- 4 unit tests: report serialization / retention / finalize assembly /
  fork+SIGSEGV end-to-end (29/29 green, includes the 4 ipc tests)
- docs synced: CRASH_HANDLE_STEP.md, current.md, 06_infrastructure.md
  (corrects stale flush_sync-signal-safe claim, pre-reorg infrastructure/
  path, and ~/.cache -> <exe_dir>/crashes/ to match app_runtime_dir)

Windows is a Phase-2 stub; symbol resolution, CrashReporter UI, and Watchdog
remain Phase 2.
crash_signal_test now forks children that arm the handler and trigger real
crashes (not just raise(SIGSEGV)): a null-pointer dereference (SIGSEGV via
MMU fault), std::abort() (SIGABRT), and infinite recursion (stack overflow
-> SIGSEGV).

The stack-overflow case proves sigaltstack works: without it the handler
could not run on a full stack and the child would be raw-killed (WIFSIGNALED)
instead of _exit(128+signo). A shared expectCrashCaught() helper asserts the
WIFEXITED + 128+signo + .pending-written contract for every case.
Adds a desktop widget framework and a Material-styled clock to the shell.

- WidgetBase: free-positionable base with press-drag-move (threshold-gated
  so a static click does not nudge it) and a widgetMoved signal; editable toggle
- WidgetContainer: QObject registry that mounts widgets onto a host widget
  (the widgets stack as direct children of the host so Qt z-order follows the
  host's child creation order)
- ClockWidget: rounded Material surface (surfaceContainer) with HH:mm
  (displayLarge) + date (bodyMedium), a coarse 1s refresh timer, and live
  theme following via ThemeManager::themeChanged
- Mounted on the shell in CFDesktopEntity right after the icon layer so the
  clock floats above icons; new cfdesktop_desktop_widget STATIC target linked
  into cf_desktop_components

Theme color/font fetch reuses the status_bar/app_launcher idiom
(queryColor(SURFACE/ON_SURFACE/ON_SURFACE_VARIANT) +
queryTargetFont(TYPOGRAPHY_DISPLAY_LARGE/BODY_MEDIUM)); card paint reuses the
AppLauncher QPainterPath+addRoundedRect+fillPath idiom (no elevation shadow
yet, matching AppLauncher). WSL boot verified (widget construction does not
crash); pixel render/drag needs visual confirmation.

ControlCenter dropdown + StatusBar timeClicked() signal are Step B.
Docs synced: milestone_06 (status + path correction), current.md milestone.
Replaces the Win11 icon-grid default with a CCIMX-style information-flow
home screen, and stacks it with the icon grid as two horizontally-swiped
pages. Fixes the overlap where a translucent HomePage showed the icon grid
through it, and adds the missing horizontal swipe.

HomePage (ui/components/home_page/, migrated from CCIMXDesktop):
- AnalogClockWidget: QPainter analog dial, MD3-colored
- DigitalTimeWidget: hh:mm:ss + date, MD3 font/color
- CardStackWidget: vertical-swipe card stack (1/4-height threshold,
  QPropertyAnimation slide/fade), ported from CCIMX
- HomeCardManager + GlobalClockSources (1s time tick) + DateCard placeholder
- HomePage: left/right split (clock | card stack), MD3 themed

PageStackWidget (QStackedWidget): horizontal swipe between
  page 0 HomePage (info-flow home) <-> page 1 icon_layer (Win11 grid),
  1/4-width threshold + pos-interpolation animation. Horizontal page swipe
  vs vertical card swipe split by Qt event propagation: HomePage and clocks
  do not accept mouse press -> bubbles to PageStackWidget; CardStackWidget
  accepts press -> keeps vertical.

Integration (CFDesktopEntity): PageStackWidget mounts HomePage + icon_layer,
occupies PanelManager::availableGeometry(); icon_layer's geometry connect
switched to page-local coords and its explicit show() removed (page stack
manages visibility). All icon_layer signal connects preserved; icon_layer
becomes page 1 (kept for a future Win11 theme).

Removes MS6 Step A's digital ClockWidget + WidgetContainer (different paradigm
-- free widget vs fixed home layout); WidgetBase retained for future use.

Step B+: complex cards (UserInfo/Net/Weather, need CCIMX infra), control
center, app pages.
…d icon

LauncherTile::mousePressEvent called QWidget::mousePressEvent (default
ignore), so the press bubbled up to PageStackWidget, which grabbed the
gesture and swiped the page while an icon was being long-press-dragged.

Accept on left-button press and return without invoking the base class;
event propagation to PageStackWidget stops, so tapping/dragging a tile no
longer swipes the page. The icon grid's empty cells (not on a tile) still
bubble, so swiping the grid background still returns to the home page.
Fills the CardStackWidget (previously a single DateCard placeholder) with
three live system-data cards driven by cfbase probes:

- MemoryCard: getSystemMemoryInfo -> used/total + %, 2 s poll
- CpuCard: getCPUProfileInfo -> usage % + cores/frequency, 5 s poll
- DiskCard: std::filesystem::space("/") -> used/total + %, 10 s poll

Each card reuses the DateCard idiom (QWidget + paintEvent rounded surface
+ MD3 tokens via ThemeManager::themeChanged) and adds a progress bar
(primary on surfaceVariant). cfdesktop_home_page now links cfbase.

Note: getCPUProfileInfo blocks ~100 ms per sample (internal /proc/stat
sampling); accepted on a 5 s timer for now -- Phase 2 should move it to
QtConcurrent::run off the UI thread.

WSL boot verified (4 cards construct, incl. the CPU probe sample).
…/ layout)

Replaces the MD3-colored Step B1 cards with CCIMX-faithful visuals:

- AnalogClockWidget: white radial-gradient dial + 8px black border + black
  hands + red second hand + 12/3/6/9 numerals (ported draw-by-draw, fixed
  geometry constants)
- DigitalTimeWidget: white Helvetica Neue 40pt Bold time + 15pt Light date
- DateCard: blue gradient (#4A90E2 -> #1E3C72) + drop shadow + white QLabels
- SystemUsageCard (new, replaces per-card Memory/Cpu/Disk): dark gradient
  (#5D6D7E -> #2C3E50) + QProgressBar (chunk #1abc9c) + drop shadow;
  parameterized by a probe callable + poll interval so Memory/CPU/Disk share
  one implementation
- Layout: 0 margins + precise stretch (1,1 / 5,2 / 3,1) + right-bottom
  gradient panel, matching homepage.ui

Data sources unchanged (cfbase getSystemMemoryInfo / getCPUProfileInfo,
std::filesystem::space). Drops ThemeManager MD3 theming on the home page
(hardcoded QSS, CCIMX style) -- Phase G re-themes later. CPU probe still
blocks ~100ms on the UI thread (Phase2 -> QtConcurrent).
- DateCard / SystemUsageCard layout: replace the trailing addStretch (which
  pushed all text to the top of the card) with stretch distribution -- title
  at top, big value centered, bar + detail at the bottom -- so content fills
  the card evenly instead of bunching at the top.
- DigitalTimeWidget: use the system default font (painter.font()) instead of
  "Helvetica Neue", which is absent on Linux and fell back to an inconsistent
  face (the "plastic" look).
Commit d8a0554 switched DigitalTimeWidget from the Linux-absent
Helvetica Neue face to the system default font (painter.font()).
current.md still described the old face; align it with the code.
The CPU probe (cfbase getCpuUsage) sleeps 100 ms between two
/proc/stat reads to measure a usage delta. Running it on the UI
thread froze the desktop for ~100 ms every poll tick.

Move all three card probes (Memory / CPU / Disk) off the UI thread
with QtConcurrent::run, and marshal each result back through a
QFutureWatcher whose 'finished' signal fires on the UI thread.

Lifetime safety:
- probe_watcher_ is parented to the card, so it is destroyed with
  the card; an in-flight QFuture is detached, never dereferenced.
- The worker lambda captures the probe by value, never 'this'.
- in_flight_ (UI-thread-only bool) skips a tick when the previous
  probe has not returned, so slow CPU samples never stack up.

Wires Qt6::Concurrent via a localized find_package in the home_page
CMakeLists (additive on top of QuarkWidgets' Core/Gui/Widgets).
Brings the home card stack to CCIMX parity. Three new widgets, all
visual-faithful to CCIMX (theme-less hardcoded QSS):

- GaugeWidget: self-painted semicircular gauge (gradient arc, ticks,
  animated needle), ported verbatim from CCIMX. Fixes a latent swapped
  min/max member init and guards a divide-by-zero in the needle ratio.
- ModernCalendarWidget: QCalendarWidget subclass with custom cell
  painting (rounded cells, selection/today/event dots) and a date->
  color marker map. Drops CCIMX's bundled nav-arrow PNGs for the Qt
  defaults (resources not present here).
- DiskGaugeCard: replaces the Disk card's linear QProgressBar with the
  gauge, keeping the same dark gradient + async QtConcurrent poll.
- UserInfoCard: CCIMX green-gradient user card. CFDesktop has no
  DesktopUserInfo subsystem, so it reads real local data via POSIX
  (getpwuid / gethostname / uname) and shows avatar-initial + name +
  hostname + OS instead of the phone/email a generic desktop lacks.

Card-stack order now mirrors CCIMX (UserInfo, Calendar, Date, Disk,
Memory) with CPU kept as an extra SystemUsageCard.
…temp

Replaces the right-bottom gradient placeholder with a 2-column gadget
grid, mirroring CCIMX's NetCardGadget + LocalWeatherCard placement.
Both cards are standalone (no AppCardWidget / DesktopToast dependency,
which do not exist in CFDesktop) and backed by real local data:

- NetStatusCard: cfbase getNetworkInfo() drives the reachability word
  (Online / LAN / Local / Offline), the transport medium, and the first
  non-loopback IPv4. Teal gradient matches CCIMX NetCardGadget.
- LocalTempCard: reads /sys/class/thermal/thermal_zone* (genuine kernel
  thermal reading). CCIMX's LocalWeatherCard reads an ICM20608 IMU sensor
  (embedded-only) and fakes random temps on x86; CFDesktop has neither
  sensor nor a weather API, so this card reads the real thermal zone and
  reports 'N/A' honestly where none exists (e.g. WSL2).

Neither card uses the async QtConcurrent path — both probes are cheap
(getifaddrs / single file read) and poll synchronously every 5s.
CI PR #27 Windows MSVC failed: user_info_card.cpp included <pwd.h>,
<sys/utsname.h>, <unistd.h>, which do not exist on MSVC.

- readUserName: guard with #ifdef — POSIX keep getpwuid (GECOS/login),
  Windows branch uses GetUserNameW.
- readHostName / readOsLine: switch to QSysInfo::machineHostName() /
  prettyProductName() (cross-platform), dropping gethostname/uname.

Linux behavior unchanged. local_temp_card needs no change — it reads a
Linux path via std::filesystem, which returns false (no error) on Windows.
Two Windows-only test failures surfaced once the MSVC build was fixed
(previous commit). Both passed on Linux; they are Windows-specific:

- crash_finalize_test: PendingFoldsIntoJsonWithLoggerTail read the
  JSON via an ifstream that was still open when fs::remove_all() tore
  down the temp dir. Windows refuses to delete an open file (Linux
  allows it). Scope the stream so it closes before remove_all().
- ipc_transport_test: ClientMessageDispatchesOnServer sent before
  loop.exec(). On Windows named pipes the server must be spinning its
  event loop to accept the connection and emit readyRead before the
  one-shot client disconnects, so the message was dropped. Defer the
  send into the loop via QTimer::singleShot(0) — mirrors production,
  where the server loop is always running.
…indows

ipc_transport_test.ClientMessageDispatchesOnServer failed on Windows MSVC
(only). Root cause: the server only read on readyRead and set up that
slot inside onNewConnection. A one-shot client that writes and closes
immediately (the single-instance-raise pattern) can, on Windows named
pipes, deliver its final bytes without a separate readyRead firing —
so the server deleted the connection on disconnect without ever reading
or dispatching the message.

This is a production bug, not just a test artifact: the Windows
single-instance-raise path has the same race.

Drain the line buffer on readyRead, on disconnect (before deleteLater),
and once up front in case bytes arrived before the slot was wired.
Draining is idempotent. Linux behavior unchanged (4/4 ipc tests pass).
ipc_transport_test still failed on Windows MSVC after the drain fix.
Root cause: canReadLine() only inspects Qt's internal read buffer, which
on Windows named pipes is never populated when a one-shot peer writes and
disconnects before the server's readyRead fires. The bytes sit in the OS
pipe buffer, unreachable by canReadLine.

Add waitForReadyRead(100) up front in onNewConnection (the peer may have
written and disconnected before this slot was wired) and in the
disconnected handler. waitForReadyRead forces Qt to ReadFile the OS
buffer into its internal buffer, after which canReadLine/drain find the
message. Linux behavior unchanged (4/4 ipc tests pass).
ipc_transport_test.ClientMessageDispatchesOnServer kept failing on
Windows MSVC across three server-side fixes. The test runs the one-shot
client and the server in the same thread; on Windows named pipes the
server's event loop never gets to accept and read before the
fire-and-forget client disconnects. This race is an artifact of the
single-threaded test, not of production, which runs the client and
server as separate processes each with its own event loop.

Skip the test on Windows (GTEST_SKIP — visible in output, not a silent
pass). The server-side drain + waitForReadyRead hardening from the
prior commits stays; it improves the real cross-process path. Linux/
macOS keep running the test unchanged.
@Charliechen114514 Charliechen114514 merged commit b0871a2 into main Jul 8, 2026
6 checks passed
@Charliechen114514 Charliechen114514 deleted the feat/shell-foundation branch July 8, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant