# Nebula Desktop Production shell for the mouse-and-keyboard NebulaOS desktop. This is Nebula Desktop **0.1**: a usable session foundation, not only a visual prototype. The user can enter the development session, use the full output, open the Nebula launcher, and start real installed applications (including Foot) without Sway keyboard shortcuts. ## Architecture Nebula Desktop is a **Qt Quick / QML** Wayland shell. It draws system chrome only. It does not host application or game windows. ```text Ubuntu ↓ Wayland ↓ Sway temporary compositor ↓ Nebula Shell C++20 + Qt 6 + Qt Quick/QML + LayerShellQt ``` Eventual target: ```text Ubuntu/Linux ↓ Wayland ↓ Nebula compositor ↓ Nebula Shell Qt Quick/QML ``` Normal applications remain ordinary Wayland / XWayland clients of the compositor: ```text Nebula Launcher ↓ ApplicationService ↓ application process ↓ Wayland / XWayland ↓ Sway ``` Games, browsers, terminals, and other apps never pass through Qt Quick. ## Production stack | Piece | Technology | |-------|------------| | Language | C++20 | | UI | Qt 6, Qt Quick, QML | | Layer surfaces | LayerShellQt | | Installed apps | `core/applications` (`import Nebula.Applications`) | | Build | CMake | | Shared design system | `packages/nebula-ui` (`import Nebula.UI`) | The executable is `nebula-shell`. QML module URI: `Nebula.Shell`. ## Surfaces Independent Wayland layer-shell windows: | Surface | Layer | Role | |---------|-------|------| | Desktop | `BACKGROUND` | Fills the current output, no exclusive zone, no keyboard focus | | Top Bar | `TOP` | 40 logical pixels, exclusive zone, spans the output width | | Launcher | `OVERLAY` | Covers the output below the top bar while open; hidden otherwise | Stacking: ```text OVERLAY Nebula Launcher TOP Nebula Top Bar NORMAL WAYLAND WINDOWS Foot, Firefox, applications, games, … BACKGROUND Nebula Desktop ``` Surfaces follow the live Wayland output geometry from Qt (`OutputTracker`). They do not assume 1920×1080 or other fixed desktop sizes. If VMware resizes the display, the shell updates. ## Applications `core/applications` discovers freedesktop `.desktop` entries from XDG data directories: 1. `$XDG_DATA_HOME/applications` (default `~/.local/share/applications`) 2. `$XDG_DATA_DIRS/applications` (default `/usr/local/share:/usr/share`) Launcher-visible apps respect `Hidden=true`, `NoDisplay=true`, `OnlyShowIn` / `NotShowIn`, and `TryExec` when present. Localized `Name` values are used when available. Launching uses `QProcess::startDetached` after `QProcess::splitCommand`. Exec field codes (`%f`, `%F`, `%u`, `%U`, `%i`, `%c`, `%k`, `%%`, …) are handled for launches without file arguments. Desktop-entry strings are never passed to a shell. Search matches name, generic name, comment, and categories. Searching `term` should find Foot (`GenericName=Terminal`). Searching `firefox` should find Firefox if it is installed. Application icons use the system icon theme through Qt (`QIcon::fromTheme`). Unresolved icons use a Nebula fallback glyph, not a broken-image placeholder. The top-bar context text stays **Desktop** in this milestone. Launching an app is not treated as focus; real window tracking comes next. Mock launcher data remains only as a fallback when no installed applications are discovered. ## Layout ```text shells/desktop/ ├── README.md this file ├── shell/ production Qt/QML shell │ ├── CMakeLists.txt │ ├── src/ │ └── qml/ └── prototype-react/ archived design reference only ``` Shared visual language lives in `packages/nebula-ui`. Application discovery lives in `core/applications`. ## Linux build This project will not compile on Windows. LayerShellQt is a Wayland API and is not stubbed. On the Ubuntu NebulaOS VM: ```bash sudo apt install \ qt6-base-dev \ qt6-declarative-dev \ qt6-wayland \ liblayershellqtinterface-dev \ qml6-module-org-kde-layershell \ qml6-module-qtquick \ qml6-module-qtquick-window \ adwaita-icon-theme \ cmake \ build-essential ``` Qt 6.4 or newer is required. `loadFromModule` is used on Qt 6.5+. ```bash cd shells/desktop/shell rm -rf build cmake -S . -B build cmake --build build -j$(nproc) ``` The production binary is `shells/desktop/shell/build/nebula-shell`. It does **not** force `QT_QUICK_BACKEND=software`. GPU-accelerated Qt Quick is the production default. ### QML lint If the installed Qt provides `qmllint`: ```bash qmllint \ shells/desktop/shell/qml/*.qml \ shells/desktop/shell/qml/surfaces/*.qml \ shells/desktop/shell/qml/components/*.qml \ shells/desktop/shell/qml/mock/*.qml \ packages/nebula-ui/qml/Nebula/UI/*.qml ``` Do not use ESLint or Oxlint on the production Qt shell. ## Development session (VMware) From the repository root, the intended flow is: ```bash sway -c compositor/sway/nebula-sway.conf ``` or, recommended on the VM (sets cursor env and starts Sway from the repo root): ```bash sh compositor/sway/dev/run-sway-vm.sh ``` That should: 1. start Sway without a Sway bar 2. autostart `nebula-shell` through `scripts/run-nebula-vm.sh` 3. fill the current output 4. show a normal Adwaita 24px cursor 5. let the Nebula launcher start Foot and other installed apps Skip autostart while debugging the compositor: ```bash NEBULA_SHELL_AUTOSTART=0 sway -c compositor/sway/nebula-sway.conf ``` ### VMware software rendering The VMware virtual GPU currently cannot run the Qt Quick accelerated path correctly. The **development wrapper** therefore sets: ```bash QT_QUICK_BACKEND=software ``` That variable is not a production default. `scripts/run-nebula-vm.sh` is the only place that sets it automatically. Production `nebula-session` / `nebula-shell` must run GPU-accelerated. Manual equivalent, if you start the shell yourself: ```bash QT_QUICK_BACKEND=software ./build/nebula-shell ``` ### Output / resolution The development Sway config requests `1920x1080` at scale `1` when that mode exists. If the VM does not advertise it, Sway keeps the preferred mode. This is a development default, not a production requirement. Inspect outputs and modes: ```bash swaymsg -t get_outputs ``` Set a mode explicitly if needed: ```bash swaymsg output mode 1920x1080 swaymsg output scale 1 ``` In VMware, also set the guest display (or Autofit Guest) so the advertised modes match the host window. ### Cursor Sway owns the cursor: ```text XCURSOR_THEME=Adwaita XCURSOR_SIZE=24 seat * xcursor_theme Adwaita 24 ``` Install `adwaita-icon-theme` if the theme is missing. A branded Nebula cursor is a later task. `WLR_NO_HARDWARE_CURSORS=1` is set only by the VMware Sway wrapper. ### Emergency shortcuts These remain for development fallback. They are not the normal UX once the GUI launcher works. | Shortcut | Action | |----------|--------| | Super+Enter | open Foot | | Super+Shift+E | exit Sway | | Super+Shift+C | reload Sway config | ## Design reference `prototype-react/` is an archived React/Vite UI. It is **not** the production shell. Keep it until the Qt shell reaches visual and behavioural parity, then remove it in a later cleanup. ## Out of scope Not implemented in this milestone: - Sway IPC / focused-window tracking - workspaces, window overview, dock - PipeWire, NetworkManager, BlueZ, UPower - notifications, XDG portals, Polkit - login/lock screen, greetd - a custom Nebula compositor - Nebula Bigscreen - Gamescope Those come later. Bigscreen is expected to reuse `Nebula.UI` and `Nebula.Applications`.