Compare commits

...
2 Commits
Author SHA1 Message Date
andrew dd9b43bd27 Bootstrap NebulaOS repo and desktop shell UI
Initialize the NebulaOS monorepo layout with top-level documentation, ignore rules, placeholder service/package directories, compositor docs plus a baseline Sway config, and session startup/desktop entry files. Add the first desktop shell prototype under `shells/desktop/ui` using React + Vite, including themed surface components (desktop, top bar, launcher, preview), mock Nebula APIs and shell bridge state wiring, reusable UI controls/icons, and supporting styles/config needed for local development.
2026-08-26 15:31:54 +12:00
andrew bf5686cebf removed all 2026-08-26 14:57:55 +12:00
340 changed files with 3462 additions and 4413 deletions
-2
View File
@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
+28 -22
View File
@@ -1,25 +1,31 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
Bigscreen/build/
dist
dist-ssr
# Node
node_modules/
dist/
.vite/
dist-ssr/
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
# CMake
build/
cmake-build-*/
# Compiled objects
*.o
*.so
*.a
# IDE
.idea/
# OS
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Thumbs.db
# Logs
*.log
logs/
# Environment
.env
.env.*
!.env.example
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
}
-1
View File
@@ -1 +0,0 @@
build/
-108
View File
@@ -1,108 +0,0 @@
cmake_minimum_required(VERSION 3.21)
project(NebulaBigscreen VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(Qt6 6.5 REQUIRED COMPONENTS Quick Gui)
find_package(SDL3 CONFIG QUIET)
if(NOT SDL3_FOUND)
include(FetchContent)
set(SDL_SHARED ON CACHE BOOL "" FORCE)
set(SDL_STATIC OFF CACHE BOOL "" FORCE)
set(SDL_TEST_LIBRARY OFF CACHE BOOL "" FORCE)
set(SDL_TESTS OFF CACHE BOOL "" FORCE)
set(SDL_EXAMPLES OFF CACHE BOOL "" FORCE)
FetchContent_Declare(SDL3
URL https://github.com/libsdl-org/SDL/releases/download/release-3.2.30/SDL3-3.2.30.tar.gz
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
)
FetchContent_MakeAvailable(SDL3)
endif()
if(TARGET SDL3::SDL3)
set(BIGSCREEN_SDL_TARGET SDL3::SDL3)
elseif(TARGET SDL3::SDL3-shared)
set(BIGSCREEN_SDL_TARGET SDL3::SDL3-shared)
else()
message(FATAL_ERROR "SDL3 target was not found after package lookup/fetch")
endif()
qt_standard_project_setup(REQUIRES 6.5)
qt_policy(SET QTP0004 NEW)
set(BIGSCREEN_QML_FILES
qml/Main.qml
qml/ShellWindow.qml
qml/Theme.qml
qml/components/NebulaBackground.qml
qml/components/TopBar.qml
qml/components/AppTile.qml
qml/components/TileRail.qml
qml/components/PowerOverlay.qml
qml/views/HomeView.qml
qml/views/LibraryView.qml
qml/views/SettingsView.qml
)
set_source_files_properties(qml/Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE)
qt_add_executable(nebula-bigscreen
src/main.cpp
src/ControllerInputService.cpp
src/ControllerInputService.h
src/InputRouter.cpp
src/InputRouter.h
)
qt_add_qml_module(nebula-bigscreen
URI Nebula.Bigscreen
VERSION 1.0
QML_FILES ${BIGSCREEN_QML_FILES}
)
target_link_libraries(nebula-bigscreen
PRIVATE
Qt6::Quick
Qt6::Gui
${BIGSCREEN_SDL_TARGET}
)
if(WIN32)
add_custom_command(
TARGET nebula-bigscreen
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
$<TARGET_RUNTIME_DLLS:nebula-bigscreen>
$<TARGET_FILE_DIR:nebula-bigscreen>
COMMAND_EXPAND_LISTS
COMMENT "Copying runtime DLLs next to nebula-bigscreen"
)
endif()
if(WIN32)
set_target_properties(nebula-bigscreen PROPERTIES WIN32_EXECUTABLE TRUE)
get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION)
get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY)
find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}" REQUIRED)
add_custom_command(
TARGET nebula-bigscreen
POST_BUILD
COMMAND "${WINDEPLOYQT_EXECUTABLE}"
$<$<CONFIG:Debug>:--debug>
$<$<NOT:$<CONFIG:Debug>>:--release>
--qmldir "${CMAKE_CURRENT_SOURCE_DIR}/qml"
"$<TARGET_FILE:nebula-bigscreen>"
COMMENT "Deploying Qt runtime DLLs next to nebula-bigscreen"
)
endif()
install(TARGETS nebula-bigscreen RUNTIME DESTINATION bin)
-407
View File
@@ -1,407 +0,0 @@
# NebulaOS Bigscreen Mode
Bigscreen Mode is the controller-first console interface for NebulaOS.
It is designed for gaming PCs, handhelds, TVs, and couch-based setups where the primary input is a controller rather than a mouse and keyboard.
Bigscreen Mode should feel like the main dashboard of a console, but with the freedom of a Linux PC underneath.
---
## Purpose
Bigscreen Mode exists to make NebulaOS feel native on gaming hardware.
It provides:
* A controller-first home screen
* A game and app library
* System settings
* Power controls
* Profile status and quick user actions (not OS login)
* Store and launcher integrations
* A fullscreen interface for TV and handheld use
Account selection, sign-in, and first-time setup live on the **OS login screen** (see [Plan](#plan)), not inside Bigscreen.
The goal is not to copy Steam Big Picture or Xbox exactly.
The goal is to create an open, polished, flexible OS shell that feels good to use with a controller.
---
## Current Features
The current v0 **Tauri prototype** includes:
* Lock screen with PIN keypad (prototype only; not part of the target Qt shell)
* Controller and keyboard input support
* Home dashboard
* Library stub
* Settings stub
* Power overlay
* Controller focus management
* Keyboard mirror controls for development
* Xbox-inspired horizontal tile layout
* Dynamic nebula background stack
* Smooth focus transitions
* Shared top bar with profile and time display
---
## Input Model
Bigscreen Mode uses unified input actions instead of raw button handling.
Core actions:
```text
up
down
left
right
accept
back
menu
```
These actions can come from:
* Controller D-pad
* Controller left stick
* Controller face buttons
* Keyboard arrow keys
* Enter
* Escape
* Backspace
This keeps the UI consistent across controllers, keyboards, and future input devices.
---
## Controller Mapping
```text
D-pad / left stick: Navigate
A: Accept
B: Back
Start: Menu
```
Future mappings may include:
```text
LB / RB: Switch tabs or rails
LT / RT: Page navigation
Guide button: Nebula overlay
Y: Search
X: Context action
```
---
## Design Goals
Bigscreen Mode should be:
* Fast
* Clear
* Controller-first
* Readable from a distance
* Comfortable on TVs
* Comfortable on handheld screens
* Smooth and polished
* Visually distinct from desktop Linux
* Easy to navigate without a mouse
---
## UI Structure
Bigscreen Mode is planned around the following major areas:
```text
Bigscreen Mode (after OS login)
├── Home
├── Library
├── Store
├── Apps
├── Settings
├── Downloads
├── Notifications
├── Power Menu
└── Profile Menu
```
OS-level (Desktop Mode / KDE work, not inside Bigscreen):
```text
NebulaOS login (SDDM)
├── Controller-friendly account picker
├── Sign in
├── Session choice (Bigscreen vs Desktop)
└── First-run / account setup when needed
```
---
## Home
The Home screen is the main landing area.
It should include:
* Recent games
* Installed games
* Featured apps
* Continue playing section
* Quick settings
* Profile status
* Time and network status
* Download status
* Power menu access
The current prototype uses a horizontal app tile rail with Library, Settings, and Power tiles.
---
## Library
The Library is the game and app management area.
Initial v0 scope:
* Display mock entries
* Display locally added games
* Launch local executables
* Store metadata locally
* Support controller navigation
* Separate games from apps
Future scope:
* Steam integration
* GOG integration
* Epic Games integration
* Proton and Wine configuration
* Per-game settings
* Save sync support
* Mod manager integration
* Metadata and artwork scraping
---
## Settings
Settings should expose system controls in a controller-friendly way.
Planned categories:
* Network
* Bluetooth
* Audio
* Display
* Controller
* Storage
* Signed-in user (read-only; account changes via OS login / Desktop)
* Appearance
* Updates
* System
* Desktop Mode
The settings UI should avoid feeling like a desktop control panel.
It should feel like a native console settings app.
---
## Login and sessions
Bigscreen Mode does **not** own account creation, authentication, or session selection.
Those belong to a **controller-friendly NebulaOS login experience** built as part of [Desktop Mode](../Desktop/README-Desktop.md): KDE Plasma customization, SDDM theming, and session entries for Nebula Bigscreen and Nebula Desktop. Users pick an account and session at boot; Bigscreen starts already signed in.
The Tauri v0 **lock screen with PIN keypad** (`1234` in the prototype) was only for early UI testing. It will not ship in the Qt shell. If the OS needs a lock or suspend screen later, that should stay at the session/login layer or use the desktop stack—not a second login flow inside Bigscreen.
Bigscreen may still show **who is signed in** (avatar, name) and offer **Log out** / **Switch user** via the power menu, which returns to the OS login screen.
---
## Plan
NebulaOS is no longer “Bigscreen first, desktop later.” **Desktop Mode is being built in parallel** on KDE Plasma, and that work unlocks a proper OS login so Bigscreen can stay a game shell.
### Phase 1 — Desktop Mode foundation (in progress)
Handled in `Desktop/`, not in Bigscreen:
* KDE Plasma theme and look-and-feel
* Nebula visual identity on panels, widgets, and dialogs
* Repo-based apply/reload workflow for Plasma assets
### Phase 2 — OS login and sessions
Also primarily Desktop / system integration:
* Controller-friendly **SDDM** login theme
* Account picker and sign-in usable with a gamepad
* Session entries: **Nebula Bigscreen** and **Nebula Desktop**
* First-run or account setup on the login/desktop side when needed
* Log out / switch user returns here
Bigscreen does not implement PIN keypads, local account linking, or onboarding wizards for new users.
### Phase 3 — Bigscreen Qt shell
* Native Qt/QML shell (see [Technology](#technology))
* Home, Library, Settings, Power menu
* Controller-first navigation and focus model
* Launch games and apps into the signed-in user session
* Switch to Desktop Mode; restart shell; no embedded login
### Phase 4 — Library and integrations
* Local library database and scanners
* Store and launcher integrations
* Proton/Wine and per-game settings (later)
### Prototype note
The Tauri/HTML v0 under `Bigscreen/` remains useful for layout and input experiments until the Qt shell replaces it. Treat its lock screen and onboarding flows as **throwaway**, not roadmap deliverables.
---
## Power Menu
The power overlay should support:
* Sleep
* Restart
* Shut down
* Log out
* Switch to Desktop Mode
* Return to login
* Restart Bigscreen shell
Current v0 scope includes a power overlay that traps focus and closes with Back.
---
## Technology
Bigscreen Mode is a **native Qt application**, not a web shell.
Target stack:
* Qt 6
* Qt Quick (QML) for the controller-first UI
* C++ for system integration, library scanning, and Nebula Core adapters
* Qt input APIs for keyboard and gamepad navigation
* CMake for builds
The repo still contains an early **Tauri/HTML v0 prototype** under `Bigscreen/` (HTML, CSS, JavaScript, and a Rust backend). That prototype validated layout, focus flow, and input actions. New Bigscreen work targets the Qt shell; the Tauri stack is legacy and will be removed as the Qt app reaches parity.
---
## Run
### Qt shell
From `Bigscreen/` (Qt 6.5+ with Quick modules installed):
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build --config Debug
```
On Windows, the build runs `windeployqt` automatically so Qt DLLs sit next to the executable. If you see missing `Qt6*.dll` errors, rebuild once from `Bigscreen/`:
```powershell
cmake --build build --config Debug
```
From the repo root instead:
```bash
cmake -S Bigscreen -B Bigscreen/build -DCMAKE_BUILD_TYPE=Debug
cmake --build Bigscreen/build --config Debug
```
Run the binary:
```bash
# Linux (from Bigscreen/)
./build/nebula-bigscreen
# Windows (from Bigscreen/)
build\Debug\nebula-bigscreen.exe
```
To run without redeploying, you can instead put Qt on `PATH` for that session (adjust the kit path if needed):
```powershell
$env:PATH = "C:\Qt\6.10.2\msvc2022_64\bin;" + $env:PATH
.\build\Debug\nebula-bigscreen.exe
```
**Controls (v0):** arrow keys to navigate, Enter to accept, Escape/Backspace for back, Menu key (or Start when mapped later) for the power overlay. Gamepad mapping is planned next.
### Legacy Tauri prototype
To run the existing v0 web prototype while migration is in progress, from `Bigscreen/`:
```bash
npm install
npm run dev
```
---
## Build
### Qt shell
Same CMake workflow as above with `-DCMAKE_BUILD_TYPE=Release`.
### Legacy Tauri prototype
```bash
npm run build
```
Run from `Bigscreen/`.
---
## Development Notes
Bigscreen Mode is moving to Qt for a native fullscreen shell, lower overhead, and tighter OS integration (sessions, launching, power controls).
Login, SDDM, and Plasma session work happen under **Desktop Mode**; Bigscreen assumes the user already chose the Bigscreen session at the OS login screen.
The Tauri prototype was developed primarily on Windows for fast UI iteration. Qt development should be validated on both Windows and Linux early.
Linux testing is still required for:
* Fullscreen behavior
* Session integration
* Controller detection
* System commands
* App launching
* Proton and Wine workflows
* Power controls
---
## Success Criteria
Bigscreen Mode is successful when:
* The entire interface works with a controller
* Users arrive signed in via the OS login screen (Bigscreen does not gate access)
* Users can navigate Home
* Users can open Library
* Users can open Settings
* Users can launch games or apps
* Users can access power controls
* Users can switch to Desktop Mode
* The UI feels like a native OS shell, not a web app
-4
View File
@@ -1,4 +0,0 @@
import QtQuick
ShellWindow {
}
-140
View File
@@ -1,140 +0,0 @@
import QtQuick
import QtQuick.Controls
import Nebula.Bigscreen
ApplicationWindow {
id: window
width: 1280
height: 720
visible: true
title: "Nebula Bigscreen"
color: Theme.backgroundDeep
property string currentView: "home"
property bool powerOverlayVisible: false
property string signedInUser: "Player"
NebulaBackground {
anchors.fill: parent
}
Column {
anchors.fill: parent
spacing: 0
TopBar {
id: topBar
width: parent.width
userName: window.signedInUser
accentFocus: contentLoader.item && contentLoader.item.accentFocus !== undefined
? contentLoader.item.accentFocus()
: 0
}
Loader {
id: contentLoader
width: parent.width
height: parent.height - topBar.height
sourceComponent: viewComponentFor(window.currentView)
}
}
Connections {
target: contentLoader.item
ignoreUnknownSignals: true
function onNavigate(route) {
window.onChildNavigate(route)
}
function onGoBack() {
window.onChildGoBack()
}
}
PowerOverlay {
id: powerOverlay
visible: window.powerOverlayVisible
z: 10
onDismissed: window.powerOverlayVisible = false
onActionChosen: function(actionId) {
console.log("Power action:", actionId)
window.powerOverlayVisible = false
}
}
Connections {
target: InputRouter
function onActionTriggered(action) {
if (window.powerOverlayVisible) {
window.handlePowerInput(action)
return
}
if (action === 7) {
window.powerOverlayVisible = true
return
}
if (contentLoader.item && contentLoader.item.handleInput) {
contentLoader.item.handleInput(action)
}
}
}
Component {
id: homeComponent
HomeView {}
}
Component {
id: libraryComponent
LibraryView {}
}
Component {
id: settingsComponent
SettingsView {}
}
function viewComponentFor(viewId) {
switch (viewId) {
case "library":
return libraryComponent
case "settings":
return settingsComponent
default:
return homeComponent
}
}
function onChildNavigate(route) {
if (route === "power") {
window.powerOverlayVisible = true
return
}
window.currentView = route
}
function onChildGoBack() {
window.currentView = "home"
}
function handlePowerInput(action) {
switch (action) {
case 1:
powerOverlay.moveFocus(-1)
break
case 2:
powerOverlay.moveFocus(1)
break
case 5:
powerOverlay.activateFocused()
break
case 6:
window.powerOverlayVisible = false
break
default:
break
}
}
}
-45
View File
@@ -1,45 +0,0 @@
pragma Singleton
import QtQuick
QtObject {
readonly property color backgroundDeep: "#050A17"
readonly property color backgroundMid: "#111321"
readonly property color backgroundPanel: "#181A2E"
readonly property color accentCyan: "#4FD8FF"
readonly property color accentPurple: "#6F5CFF"
readonly property color accentLine: "#30325A"
readonly property color textPrimary: "#F2F7FF"
readonly property color textMuted: "#A8BDD8"
readonly property int tileWidth: 280
readonly property int tileHeight: 160
readonly property int tileGap: 20
readonly property int topBarHeight: 72
readonly property int focusScaleDuration: 180
readonly property real tileFocusScale: 1.06
readonly property real tilePressScale: 0.96
readonly property font brandFont: Qt.font({
family: "Segoe UI",
pixelSize: 28,
weight: Font.DemiBold
})
readonly property font titleFont: Qt.font({
family: "Segoe UI",
pixelSize: 22,
weight: Font.DemiBold
})
readonly property font bodyFont: Qt.font({
family: "Segoe UI",
pixelSize: 16,
weight: Font.Normal
})
readonly property font metaFont: Qt.font({
family: "Segoe UI",
pixelSize: 13,
weight: Font.Normal
})
}
-89
View File
@@ -1,89 +0,0 @@
import QtQuick
import Nebula.Bigscreen
Item {
id: root
property string label: ""
property string meta: ""
property string iconText: ""
property bool focused: false
property bool pressed: false
signal activated()
implicitWidth: Theme.tileWidth
implicitHeight: Theme.tileHeight
scale: pressed ? Theme.tilePressScale : (focused ? Theme.tileFocusScale : 1.0)
Behavior on scale {
NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic }
}
Rectangle {
id: card
anchors.fill: parent
radius: 14
color: Theme.backgroundPanel
border.width: focused ? 2 : 1
border.color: focused ? Theme.accentCyan : Theme.accentLine
Rectangle {
anchors.fill: parent
radius: parent.radius
visible: focused
gradient: Gradient {
GradientStop { position: 0.0; color: "#224FD8FF" }
GradientStop { position: 1.0; color: "transparent" }
}
}
Column {
anchors.fill: parent
anchors.margins: 20
spacing: 10
Text {
text: root.iconText
font.pixelSize: 36
}
Text {
text: root.label
font: Theme.titleFont
color: Theme.textPrimary
}
Text {
text: root.meta
font: Theme.metaFont
color: Theme.textMuted
}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 12
height: 3
radius: 2
color: Theme.accentCyan
opacity: focused ? 1.0 : 0.0
Behavior on opacity {
NumberAnimation { duration: Theme.focusScaleDuration }
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: root.activated()
onPressed: root.pressed = true
onReleased: root.pressed = false
onCanceled: root.pressed = false
}
}
@@ -1,53 +0,0 @@
import QtQuick
Item {
id: root
anchors.fill: parent
Rectangle {
anchors.fill: parent
gradient: Gradient {
GradientStop { position: 0.0; color: "#050A17" }
GradientStop { position: 0.55; color: "#0E1630" }
GradientStop { position: 1.0; color: "#1A1342" }
}
}
Repeater {
model: 48
Rectangle {
width: (index % 5) + 1
height: width
radius: width / 2
color: index % 3 === 0 ? "#88FFFFFF" : "#44B8D4FF"
x: (index * 137) % root.width
y: (index * 89) % root.height
opacity: 0.35 + (index % 7) * 0.05
SequentialAnimation on opacity {
loops: Animation.Infinite
running: true
NumberAnimation {
from: 0.2
to: 0.7
duration: 2000 + (index % 5) * 400
}
NumberAnimation {
from: 0.7
to: 0.2
duration: 2000 + (index % 5) * 400
}
}
}
}
Rectangle {
anchors.fill: parent
opacity: 0.35
gradient: Gradient {
orientation: Gradient.Vertical
GradientStop { position: 0.0; color: "transparent" }
GradientStop { position: 1.0; color: "#CC000000" }
}
}
}
-92
View File
@@ -1,92 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
Rectangle {
id: root
property int focusIndex: 0
readonly property var actions: [
{ label: "Sleep", meta: "Suspend system" },
{ label: "Restart", meta: "Reboot NebulaOS" },
{ label: "Shut down", meta: "Power off" },
{ label: "Log out", meta: "Return to login" },
{ label: "Desktop Mode", meta: "Switch session" },
{ label: "Close", meta: "Back to Home" }
]
signal dismissed()
signal actionChosen(string actionId)
anchors.fill: parent
color: "#CC050A17"
Keys.onPressed: function(event) { event.accepted = true }
ColumnLayout {
anchors.centerIn: parent
width: Math.min(parent.width - 80, 520)
spacing: 12
Text {
text: "Power"
font: Theme.brandFont
color: Theme.textPrimary
Layout.alignment: Qt.AlignHCenter
}
Repeater {
model: root.actions
Rectangle {
Layout.fillWidth: true
height: 56
radius: 10
color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel
border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine
border.width: index === root.focusIndex ? 2 : 1
RowLayout {
anchors.fill: parent
anchors.margins: 16
Text {
text: modelData.label
font: Theme.titleFont
color: Theme.textPrimary
}
Item { Layout.fillWidth: true }
Text {
text: modelData.meta
font: Theme.metaFont
color: Theme.textMuted
}
}
MouseArea {
anchors.fill: parent
onClicked: {
root.focusIndex = index
root.activateFocused()
}
}
}
}
}
function moveFocus(delta) {
focusIndex = Math.max(0, Math.min(actions.length - 1, focusIndex + delta))
}
function activateFocused() {
const labels = ["sleep", "restart", "shutdown", "logout", "desktop", "close"]
const id = labels[focusIndex]
if (id === "close") {
dismissed()
} else {
actionChosen(id)
}
}
}
-52
View File
@@ -1,52 +0,0 @@
import QtQuick
import Nebula.Bigscreen
Item {
id: root
property var tiles: []
property int focusIndex: 0
property real accentFocus: 0
signal tileActivated(int index)
implicitHeight: Theme.tileHeight + 24
onFocusIndexChanged: root.accentFocus = focusIndex / Math.max(1, tiles.length - 1)
Row {
id: rail
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 40
spacing: Theme.tileGap
Repeater {
model: root.tiles
AppTile {
label: modelData.label
meta: modelData.meta
iconText: modelData.icon
focused: index === root.focusIndex
onActivated: root.tileActivated(index)
}
}
}
function moveFocus(delta) {
if (tiles.length === 0) {
return
}
const next = Math.max(0, Math.min(tiles.length - 1, focusIndex + delta))
focusIndex = next
}
function activateFocused() {
if (tiles.length === 0) {
return
}
tileActivated(focusIndex)
}
}
-80
View File
@@ -1,80 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
Item {
id: root
property string userName: "Player"
property real accentFocus: 0
implicitHeight: Theme.topBarHeight
RowLayout {
anchors.fill: parent
anchors.leftMargin: 40
anchors.rightMargin: 40
spacing: 16
Text {
text: "Nebula OS"
font: Theme.brandFont
color: Theme.textPrimary
style: Text.Outline
styleColor: Theme.accentCyan
Layout.alignment: Qt.AlignVCenter
}
Item { Layout.fillWidth: true }
RowLayout {
spacing: 14
Layout.alignment: Qt.AlignVCenter
Rectangle {
width: 36
height: 36
radius: 18
color: Theme.backgroundPanel
border.color: Theme.accentCyan
border.width: 2
Text {
anchors.centerIn: parent
text: root.userName.length > 0 ? root.userName.charAt(0).toUpperCase() : "?"
font: Theme.titleFont
color: Theme.textPrimary
}
}
Text {
id: clockLabel
font: Theme.bodyFont
color: Theme.textMuted
}
}
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: 40 + root.accentFocus * 320
width: 120
height: 2
radius: 1
color: Theme.accentCyan
opacity: 0.85
Behavior on anchors.leftMargin {
NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic }
}
}
Timer {
interval: 1000
running: true
repeat: true
triggeredOnStart: true
onTriggered: clockLabel.text = Qt.formatTime(new Date(), "hh:mm AP")
}
}
-62
View File
@@ -1,62 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
ColumnLayout {
id: root
property var homeTiles: [
{ label: "Library", meta: "Games and apps", icon: "📚", route: "library" },
{ label: "Settings", meta: "System controls", icon: "⚙", route: "settings" },
{ label: "Power", meta: "Sleep and sessions", icon: "⏻", route: "power" }
]
signal navigate(string route)
spacing: 28
Text {
text: "Home"
font: Theme.brandFont
color: Theme.textPrimary
Layout.leftMargin: 40
}
TileRail {
id: rail
Layout.fillWidth: true
tiles: root.homeTiles
onTileActivated: function(index) {
const route = root.homeTiles[index].route
if (route === "power") {
root.navigate("power")
} else {
root.navigate(route)
}
}
}
function handleInput(action) {
switch (action) {
case 3:
rail.moveFocus(-1)
break
case 4:
rail.moveFocus(1)
break
case 5:
rail.activateFocused()
break
case 6:
root.navigate("power")
break
default:
break
}
}
function accentFocus() {
return rail.accentFocus
}
}
-101
View File
@@ -1,101 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
ColumnLayout {
id: root
signal goBack()
readonly property var entries: [
{ title: "Nebula Demo", meta: "Local • Ready", icon: "🎮" },
{ title: "Retro Runner", meta: "Wine • Installed", icon: "👾" },
{ title: "Media Hub", meta: "App • Installed", icon: "🎬" }
]
property int focusIndex: 0
property string statusText: "Mock entries for v0 — scanners and launchers come later."
spacing: 20
Text {
text: "Library"
font: Theme.brandFont
color: Theme.textPrimary
Layout.leftMargin: 40
}
Text {
text: root.statusText
font: Theme.metaFont
color: Theme.textMuted
Layout.leftMargin: 40
Layout.bottomMargin: 8
}
ListView {
id: list
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 40
Layout.rightMargin: 40
clip: true
spacing: 12
model: root.entries
delegate: Rectangle {
width: list.width
height: 72
radius: 12
color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel
border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine
border.width: index === root.focusIndex ? 2 : 1
RowLayout {
anchors.fill: parent
anchors.margins: 16
spacing: 16
Text {
text: modelData.icon
font.pixelSize: 28
}
ColumnLayout {
spacing: 2
Text {
text: modelData.title
font: Theme.titleFont
color: Theme.textPrimary
}
Text {
text: modelData.meta
font: Theme.metaFont
color: Theme.textMuted
}
}
}
}
}
function handleInput(action) {
switch (action) {
case 1:
focusIndex = Math.max(0, focusIndex - 1)
list.currentIndex = focusIndex
break
case 2:
focusIndex = Math.min(entries.length - 1, focusIndex + 1)
list.currentIndex = focusIndex
break
case 6:
root.goBack()
break
case 5:
statusText = entries[focusIndex].title + " selected — launcher integration comes later."
break
default:
break
}
}
}
-133
View File
@@ -1,133 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Nebula.Bigscreen
ColumnLayout {
id: root
signal goBack()
readonly property var categories: [
"Network",
"Audio",
"Display",
"Controller",
"System"
]
property int categoryIndex: 0
property int itemIndex: 0
readonly property int settingCount: 3
property string statusText: "Choose a setting with Accept. Detailed controls come later."
spacing: 20
Text {
text: "Settings"
font: Theme.brandFont
color: Theme.textPrimary
Layout.leftMargin: 40
}
Row {
Layout.leftMargin: 40
spacing: 16
Repeater {
model: root.categories
Rectangle {
height: 40
width: categoryLabel.implicitWidth + 24
radius: 8
color: index === root.categoryIndex ? "#22304A66" : "transparent"
border.color: index === root.categoryIndex ? Theme.accentCyan : "transparent"
border.width: index === root.categoryIndex ? 2 : 0
Text {
id: categoryLabel
anchors.centerIn: parent
text: modelData
font: Theme.bodyFont
color: index === root.categoryIndex ? Theme.accentCyan : Theme.textMuted
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: 40
radius: 16
color: Theme.backgroundPanel
border.color: Theme.accentLine
ColumnLayout {
anchors.fill: parent
anchors.margins: 24
spacing: 12
Text {
text: root.categories[root.categoryIndex]
font: Theme.titleFont
color: Theme.textPrimary
}
Repeater {
model: root.settingCount
Rectangle {
Layout.fillWidth: true
height: 52
radius: 10
color: index === root.itemIndex ? "#22304A66" : Theme.backgroundMid
border.color: index === root.itemIndex ? Theme.accentCyan : Theme.accentLine
border.width: index === root.itemIndex ? 2 : 1
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 16
text: "Placeholder setting " + (index + 1)
font: Theme.bodyFont
color: Theme.textPrimary
}
}
}
Text {
text: root.statusText
font: Theme.metaFont
color: Theme.textMuted
}
}
}
function handleInput(action) {
switch (action) {
case 3:
categoryIndex = Math.max(0, categoryIndex - 1)
itemIndex = 0
break
case 4:
categoryIndex = Math.min(categories.length - 1, categoryIndex + 1)
itemIndex = 0
break
case 1:
itemIndex = Math.max(0, itemIndex - 1)
break
case 2:
itemIndex = Math.min(settingCount - 1, itemIndex + 1)
break
case 6:
root.goBack()
break
case 5:
statusText = categories[categoryIndex] + " setting " + (itemIndex + 1) + " selected."
break
default:
break
}
}
}
-301
View File
@@ -1,301 +0,0 @@
#include "ControllerInputService.h"
#include <QDebug>
#include <utility>
namespace {
constexpr int PollIntervalMs = 8;
constexpr int AxisDeadzone = 4000;
constexpr int AxisReleaseDeadzone = 2000;
constexpr qint64 InitialRepeatDelayMs = 260;
constexpr qint64 RepeatIntervalMs = 95;
constexpr qint64 DiscoveryIntervalMs = 1000;
constexpr int AxisControlOffset = 100;
}
ControllerInputService::ControllerInputService(InputRouter *router, QObject *parent)
: QObject(parent)
, m_router(router)
{
if (!m_router) {
qWarning() << "ControllerInputService disabled: missing InputRouter";
return;
}
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD)) {
qWarning() << "ControllerInputService disabled:" << SDL_GetError();
return;
}
m_sdlReady = true;
m_clock.start();
m_nextDiscoveryMs = DiscoveryIntervalMs;
discoverControllers();
connect(&m_pollTimer, &QTimer::timeout, this, &ControllerInputService::pollEvents);
m_pollTimer.start(PollIntervalMs);
}
ControllerInputService::~ControllerInputService()
{
m_pollTimer.stop();
for (SDL_Gamepad *gamepad : std::as_const(m_gamepads)) {
SDL_CloseGamepad(gamepad);
}
m_gamepads.clear();
if (m_sdlReady) {
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
}
}
void ControllerInputService::pollEvents()
{
if (!m_sdlReady) {
return;
}
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_EVENT_GAMEPAD_ADDED:
openController(event.gdevice.which);
break;
case SDL_EVENT_GAMEPAD_REMOVED:
closeController(event.gdevice.which);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
handleButtonDown(event.gbutton);
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
handleAxisMotion(event.gaxis);
break;
default:
break;
}
}
const qint64 now = m_clock.elapsed();
if (now >= m_nextDiscoveryMs) {
discoverControllers();
m_nextDiscoveryMs = now + DiscoveryIntervalMs;
}
pollControllerState();
updateAxisRepeats();
}
void ControllerInputService::discoverControllers()
{
int gamepadCount = 0;
SDL_JoystickID *gamepads = SDL_GetGamepads(&gamepadCount);
if (!gamepads) {
return;
}
for (int i = 0; i < gamepadCount; ++i) {
openController(gamepads[i]);
}
SDL_free(gamepads);
}
void ControllerInputService::pollControllerState()
{
QHash<SDL_JoystickID, SDL_Gamepad *>::iterator it = m_gamepads.begin();
while (it != m_gamepads.end()) {
const SDL_JoystickID instanceId = it.key();
SDL_Gamepad *gamepad = it.value();
if (!gamepad) {
++it;
continue;
}
for (int button = 0; button < SDL_GAMEPAD_BUTTON_COUNT; ++button) {
const qint64 key = stateKey(instanceId, button);
const bool pressed = SDL_GetGamepadButton(gamepad, static_cast<SDL_GamepadButton>(button));
const bool wasPressed = m_pressedButtons.contains(key);
if (pressed && !wasPressed) {
m_pressedButtons.insert(key);
dispatchAction(actionForButton(static_cast<Uint8>(button)));
} else if (!pressed && wasPressed) {
m_pressedButtons.remove(key);
}
}
for (int axis = 0; axis < SDL_GAMEPAD_AXIS_COUNT; ++axis) {
const Sint16 value = SDL_GetGamepadAxis(gamepad, static_cast<SDL_GamepadAxis>(axis));
updateAxisHold(stateKey(instanceId, AxisControlOffset + axis),
actionForAxis(static_cast<Uint8>(axis), value));
}
++it;
}
}
void ControllerInputService::updateAxisRepeats()
{
const qint64 now = m_clock.elapsed();
for (auto it = m_axisHolds.begin(); it != m_axisHolds.end(); ++it) {
AxisHold &hold = it.value();
if (!hold.active || hold.action == InputRouter::Action::None || now < hold.nextRepeatMs) {
continue;
}
dispatchAction(hold.action);
hold.nextRepeatMs = now + RepeatIntervalMs;
}
}
void ControllerInputService::openController(SDL_JoystickID instanceId)
{
if (m_gamepads.contains(instanceId) || !SDL_IsGamepad(instanceId)) {
return;
}
SDL_Gamepad *gamepad = SDL_OpenGamepad(instanceId);
if (!gamepad) {
qWarning() << "Failed to open gamepad" << instanceId << SDL_GetError();
return;
}
m_gamepads.insert(instanceId, gamepad);
qInfo() << "Opened gamepad" << instanceId << SDL_GetGamepadName(gamepad);
}
void ControllerInputService::closeController(SDL_JoystickID instanceId)
{
SDL_Gamepad *gamepad = m_gamepads.take(instanceId);
if (!gamepad) {
return;
}
SDL_CloseGamepad(gamepad);
m_axisHolds.clear();
m_pressedButtons.clear();
qInfo() << "Closed gamepad" << instanceId;
}
void ControllerInputService::handleButtonDown(const SDL_GamepadButtonEvent &event)
{
const qint64 key = stateKey(event.which, event.button);
if (m_pressedButtons.contains(key)) {
return;
}
m_pressedButtons.insert(key);
dispatchAction(actionForButton(event.button));
}
void ControllerInputService::handleAxisMotion(const SDL_GamepadAxisEvent &event)
{
updateAxisHold(stateKey(event.which, AxisControlOffset + event.axis),
actionForAxis(event.axis, event.value));
}
void ControllerInputService::updateAxisHold(qint64 axisKey, InputRouter::Action action)
{
if (action == InputRouter::Action::None) {
releaseAxisHold(axisKey);
return;
}
AxisHold &hold = m_axisHolds[axisKey];
if (hold.active && hold.action == action) {
return;
}
hold.active = true;
hold.action = action;
hold.nextRepeatMs = m_clock.elapsed() + InitialRepeatDelayMs;
dispatchAction(action);
}
void ControllerInputService::releaseAxisHold(qint64 axisKey)
{
AxisHold &hold = m_axisHolds[axisKey];
hold.active = false;
hold.action = InputRouter::Action::None;
hold.nextRepeatMs = 0;
}
void ControllerInputService::dispatchAction(InputRouter::Action action)
{
if (m_router && action != InputRouter::Action::None) {
m_router->triggerAction(action);
}
}
InputRouter::Action ControllerInputService::actionForButton(Uint8 button)
{
const SDL_GamepadButton buttonEnum = static_cast<SDL_GamepadButton>(button);
switch (buttonEnum) {
case SDL_GAMEPAD_BUTTON_SOUTH:
return InputRouter::Action::Back;
case SDL_GAMEPAD_BUTTON_EAST:
return InputRouter::Action::Accept;
case SDL_GAMEPAD_BUTTON_WEST:
return InputRouter::Action::None;
case SDL_GAMEPAD_BUTTON_NORTH:
return InputRouter::Action::None;
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return InputRouter::Action::Up;
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return InputRouter::Action::Down;
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return InputRouter::Action::Left;
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return InputRouter::Action::Right;
case SDL_GAMEPAD_BUTTON_START:
case SDL_GAMEPAD_BUTTON_BACK:
case SDL_GAMEPAD_BUTTON_GUIDE:
return InputRouter::Action::Menu;
default:
return InputRouter::Action::None;
}
}
InputRouter::Action ControllerInputService::actionForAxis(Uint8 axis, Sint16 value)
{
if (value > -AxisReleaseDeadzone && value < AxisReleaseDeadzone) {
return InputRouter::Action::None;
}
const SDL_GamepadAxis axisEnum = static_cast<SDL_GamepadAxis>(axis);
switch (axisEnum) {
case SDL_GAMEPAD_AXIS_LEFTX:
if (value <= -AxisDeadzone) {
return InputRouter::Action::Left;
}
if (value >= AxisDeadzone) {
return InputRouter::Action::Right;
}
break;
case SDL_GAMEPAD_AXIS_LEFTY:
if (value <= -AxisDeadzone) {
return InputRouter::Action::Up;
}
if (value >= AxisDeadzone) {
return InputRouter::Action::Down;
}
break;
default:
break;
}
return InputRouter::Action::None;
}
qint64 ControllerInputService::stateKey(SDL_JoystickID instanceId, int control)
{
return (static_cast<qint64>(instanceId) << 16) | control;
}
-52
View File
@@ -1,52 +0,0 @@
#pragma once
#include "InputRouter.h"
#include <QElapsedTimer>
#include <QHash>
#include <QObject>
#include <QSet>
#include <QTimer>
#include <SDL3/SDL.h>
class ControllerInputService : public QObject
{
Q_OBJECT
public:
explicit ControllerInputService(InputRouter *router, QObject *parent = nullptr);
~ControllerInputService() override;
private:
struct AxisHold {
InputRouter::Action action = InputRouter::Action::None;
qint64 nextRepeatMs = 0;
bool active = false;
};
void pollEvents();
void discoverControllers();
void pollControllerState();
void updateAxisRepeats();
void openController(SDL_JoystickID instanceId);
void closeController(SDL_JoystickID instanceId);
void handleButtonDown(const SDL_GamepadButtonEvent &event);
void handleAxisMotion(const SDL_GamepadAxisEvent &event);
void updateAxisHold(qint64 axisKey, InputRouter::Action action);
void releaseAxisHold(qint64 axisKey);
void dispatchAction(InputRouter::Action action);
static InputRouter::Action actionForButton(Uint8 button);
static InputRouter::Action actionForAxis(Uint8 axis, Sint16 value);
static qint64 stateKey(SDL_JoystickID instanceId, int control);
InputRouter *m_router = nullptr;
QTimer m_pollTimer;
QElapsedTimer m_clock;
QHash<SDL_JoystickID, SDL_Gamepad *> m_gamepads;
QSet<qint64> m_pressedButtons;
QHash<qint64, AxisHold> m_axisHolds;
qint64 m_nextDiscoveryMs = 0;
bool m_sdlReady = false;
};
-58
View File
@@ -1,58 +0,0 @@
#include "InputRouter.h"
#include <QKeyEvent>
InputRouter::InputRouter(QObject *parent)
: QObject(parent)
{
}
bool InputRouter::handleKeyPress(QKeyEvent *event)
{
if (!event || event->isAutoRepeat()) {
return false;
}
const Action action = mapKey(event->key());
if (action == Action::None) {
return false;
}
triggerAction(action);
event->accept();
return true;
}
void InputRouter::triggerAction(Action action)
{
if (action == Action::None) {
return;
}
emit actionTriggered(action);
}
InputRouter::Action InputRouter::mapKey(int key)
{
switch (key) {
case Qt::Key_Up:
return Action::Up;
case Qt::Key_Down:
return Action::Down;
case Qt::Key_Left:
return Action::Left;
case Qt::Key_Right:
return Action::Right;
case Qt::Key_Return:
case Qt::Key_Enter:
case Qt::Key_Space:
return Action::Accept;
case Qt::Key_Escape:
case Qt::Key_Backspace:
return Action::Back;
case Qt::Key_Menu:
return Action::Menu;
default:
return Action::None;
}
}
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include <QObject>
class QKeyEvent;
class InputRouter : public QObject
{
Q_OBJECT
public:
enum class Action {
None = 0,
Up,
Down,
Left,
Right,
Accept,
Back,
Menu,
};
Q_ENUM(Action)
explicit InputRouter(QObject *parent = nullptr);
bool handleKeyPress(QKeyEvent *event);
void triggerAction(Action action);
signals:
void actionTriggered(InputRouter::Action action);
private:
static Action mapKey(int key);
};
-102
View File
@@ -1,102 +0,0 @@
#include "ControllerInputService.h"
#include "InputRouter.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QFile>
#include <QGuiApplication>
#include <QMessageLogContext>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickWindow>
#include <QTextStream>
#include <QUrl>
namespace {
void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
QFile logFile(QStringLiteral("nebula-bigscreen.log"));
if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
return;
}
QTextStream stream(&logFile);
stream << QDateTime::currentDateTime().toString(Qt::ISODate) << " ";
switch (type) {
case QtDebugMsg:
stream << "DEBUG";
break;
case QtInfoMsg:
stream << "INFO";
break;
case QtWarningMsg:
stream << "WARN";
break;
case QtCriticalMsg:
stream << "CRITICAL";
break;
case QtFatalMsg:
stream << "FATAL";
break;
}
stream << ": " << message;
if (context.file) {
stream << " (" << context.file << ":" << context.line << ")";
}
stream << "\n";
}
}
class InputFilter : public QObject
{
public:
explicit InputFilter(InputRouter *router, QObject *parent = nullptr)
: QObject(parent)
, m_router(router)
{
}
protected:
bool eventFilter(QObject *watched, QEvent *event) override
{
Q_UNUSED(watched);
if (event->type() == QEvent::KeyPress) {
return m_router->handleKeyPress(static_cast<QKeyEvent *>(event));
}
return false;
}
private:
InputRouter *m_router = nullptr;
};
int main(int argc, char *argv[])
{
qInstallMessageHandler(logMessageHandler);
QGuiApplication app(argc, argv);
QGuiApplication::setApplicationName(QStringLiteral("Nebula Bigscreen"));
QGuiApplication::setOrganizationName(QStringLiteral("NebulaOS"));
QGuiApplication::setOrganizationDomain(QStringLiteral("nebulaos.local"));
InputRouter inputRouter;
InputFilter inputFilter(&inputRouter);
ControllerInputService controllerInputService(&inputRouter);
Q_UNUSED(controllerInputService);
app.installEventFilter(&inputFilter);
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("InputRouter"), &inputRouter);
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(QUrl(QStringLiteral("qrc:/qt/qml/Nebula/Bigscreen/qml/Main.qml")));
return app.exec();
}
-612
View File
@@ -1,612 +0,0 @@
# NebulaOS Desktop Mode
NebulaOS Desktop Mode is the mouse-and-keyboard desktop layer for NebulaOS. It provides the full Linux desktop experience for users who need file management, development tools, modding workflows, browser access, productivity apps, system settings, and advanced troubleshooting.
Desktop Mode is built on KDE Plasma as its starting foundation. KDE gives NebulaOS a mature, flexible, themeable, and gaming-friendly Linux desktop while the Nebula-specific shell identity, desktop apps, launcher ideas, and visual language continue to develop.
This README only covers **Desktop Mode**. It does not cover the full NebulaOS project or Bigscreen Mode. Those have their own separate README files.
---
## Purpose
Desktop Mode exists because NebulaOS should not only be a controller-first or console-style interface. It should also be a capable desktop operating system when the user needs one.
Desktop Mode is designed for:
* File management
* Web browsing
* Productivity
* Development
* Modding
* Troubleshooting
* Advanced settings
* Desktop gaming
* App management
* System maintenance
* Mouse and keyboard workflows
NebulaOS should feel simple in Bigscreen Mode, but never locked down. Desktop Mode is what keeps NebulaOS flexible, useful, and powerful.
---
## Design Vision
NebulaOS Desktop Mode should feel like a sleek modern OS UI that combines the strongest parts of Windows, macOS, and Linux while still becoming its own distinct interface.
The goal is not to copy any one operating system. The goal is to borrow the best ideas and reinterpret them through the NebulaOS identity.
### Windows Influence
Desktop Mode should take inspiration from Windows for:
* Familiar taskbar-style productivity
* Clear window management
* App launching habits users already understand
* System tray concepts
* Right-click and power-user workflows
* Desktop gaming familiarity
### macOS Influence
Desktop Mode should take inspiration from macOS for:
* Visual polish
* Calm spacing
* Soft blur and translucency
* Clean typography
* Smooth animation
* Premium-feeling system surfaces
* Control Centre-style quick settings
### Linux / KDE Influence
Desktop Mode should take inspiration from Linux and KDE for:
* Customization
* Openness
* User control
* Themeability
* Widgets
* Scripting
* Session flexibility
* Power-user tooling
### NebulaOS Identity
NebulaOS Desktop Mode should add its own identity through:
* Deep blue and purple tones
* Glass-like dark surfaces
* Soft glow
* Rounded panels
* Space-inspired wallpapers
* Clean icon language
* Gaming-aware shortcuts
* Bigscreen access from the desktop
* Nebula Browser and Nebula Library integration
---
## Current Development Status
Desktop Mode is currently in the early KDE customization stage.
### Working / Set Up
* [x] KDE Plasma selected as the Desktop Mode foundation
* [x] Desktop Mode repo structure created
* [x] Starter KDE theme files copied into the repo
* [x] Theme files are being edited directly from the repo
* [x] Symlink-based development workflow started
* [x] Apply script created or started
* [x] Plasma reload workflow started
* [x] Desktop Mode separated from Bigscreen Mode conceptually
* [x] Current focus is KDE theme and shell identity
### Being Worked On
* [ ] NebulaOS Plasma theme
* [ ] NebulaOS color scheme
* [ ] NebulaOS look-and-feel package
* [ ] NebulaOS wallpaper set
* [ ] Floating/glass panel styling
* [ ] Launcher/menu styling
* [ ] Popup/dialog styling
* [ ] Notification styling
* [ ] Task manager active indicators
* [ ] System tray styling
* [ ] Bigscreen Mode desktop shortcut
* [ ] Nebula Browser desktop shortcut
* [ ] Cleaner apply and reload scripts
### Parallel track: OS login (controller-friendly)
Desktop Mode work now includes a **controller-friendly OS login** so Bigscreen Mode does not need its own account creation, PIN lock, or sign-in flows.
Planned at the system layer (SDDM + sessions):
* Nebula SDDM theme navigable with a gamepad
* Account picker and sign-in
* Session choice: Nebula Bigscreen vs Nebula Desktop
* First-run / account setup when needed (desktop or login side, not inside Bigscreen)
This can proceed alongside Plasma theme work; it does not need to wait until every panel pixel is final.
### Not the Focus Yet
The following are planned, but should not block the base Desktop theme or login track:
* Full Nebula Control Center
* Nebula Store
* Nebula Library desktop app
* Nebula Mod Manager
* Deep KDE source patches
* Full ISO packaging
* First-run setup / OOBE
---
## Recommended Foundation
The recommended Desktop Mode foundation is:
* Linux
* KDE Plasma
* SDDM
* Wayland where possible
* X11 fallback where needed
* Custom Nebula Plasma theme
* Custom Nebula color scheme
* Custom Nebula look-and-feel package
* Custom Nebula wallpaper set
* Custom Nebula session entries later
KDE Plasma is recommended because it is:
* Highly customizable
* Friendly to Linux gaming workflows
* Themeable
* Mature
* Flexible
* Suitable for both desktop and handheld layouts
* Easier to prototype with than writing a whole desktop shell from scratch
---
## Repository & Development
This repository is the source of truth for Desktop Mode.
Local KDE folders under `~/.local/share` are runtime and test targets only. During development, symlinks can point KDE at the files in this repo. For packaging and install testing, the apply script should copy the same repo files into KDE's local package locations.
This means:
* Edit files in the repo
* Test through symlinks where possible
* Use scripts to apply or reload changes
* Avoid manually editing copied files in KDE's local folders
* Keep all Desktop Mode work version-controlled
---
## Desktop Mode Responsibilities
Desktop Mode should provide:
* File manager access
* Browser access
* Terminal access
* App launcher
* System settings
* Development tools
* Game modding tools
* Desktop game launching
* Access to Bigscreen Mode
* Advanced troubleshooting tools
* App management
* System maintenance
Desktop Mode should feel like a proper computer desktop, not just a fallback mode.
---
## KDE Customization Goals
Desktop Mode customization may include:
* Nebula global theme
* Nebula Plasma theme
* Nebula color scheme
* Nebula icon theme
* Nebula cursor theme
* Nebula wallpaper set
* Custom SDDM theme later
* Custom splash screen later
* Custom panel layout
* Custom application launcher direction
* Custom desktop widgets
* Preinstalled Nebula apps later
* Desktop shortcut for Bigscreen Mode
* Desktop shortcut for Nebula Browser
* Desktop shortcut for Nebula Library later
Starter KDE Plasma customization currently happens through:
* Plasma desktop themes for shell surfaces such as panels, widgets, popups, dialogs, and SVG assets
* Look-and-feel packages for grouping the desktop experience under a Nebula identity
* Color schemes for the Nebula Dark palette
* Wallpapers and branding assets
* Plasmoids for prototype Nebula desktop widgets such as the launcher
* Later, KWin scripts and deeper KDE source patches after the prototype layer works
This layer should stay small and safe for now. Avoid deep KDE source changes until the Plasma theme, color scheme, look-and-feel package, and launcher prototype are easy to test and iterate.
---
## Suggested Desktop Layout
A possible default Desktop Mode layout:
```text
Floating bottom panel
├── Nebula launcher
├── Search
├── Pinned apps
├── Open apps
├── Bigscreen shortcut
├── System tray
├── Network
├── Audio
├── Battery
├── Clock
└── Quick settings
Desktop
├── Nebula wallpaper
├── Minimal icons
└── Optional widgets
Launcher
├── Search
├── Pinned apps
├── Recent apps
├── Games
├── Files
├── Settings
├── Power
└── Switch to Bigscreen Mode
```
The default desktop should feel like a mix of a Windows taskbar, macOS Dock polish, and KDE flexibility.
Suggested panel style:
* Floating rounded panel
* Dark translucent glass surface
* Subtle border
* Soft shadow
* Centered app icons
* Small glow under active apps
* System tray grouped cleanly on the right
* Nebula launcher button on the left
---
## Visual Identity
Desktop Mode should share visual DNA with the wider NebulaOS project while remaining calmer and more practical than a controller-first interface.
Shared elements may include:
* Nebula gradients
* Deep blue and purple tones
* Soft glass panels
* Rounded corners
* Subtle glow
* Clean typography
* Space-inspired wallpapers
* Calm animation
* Consistent icon language
### Visual Keywords
```text
Glass
Depth
Glow
Blur
Soft shadows
Rounded panels
Dark translucent surfaces
Blue/purple nebula gradients
Clean typography
Minimal clutter
Gaming polish
Desktop productivity
```
---
## Plasma vs Native Desktop Apps
Desktop Mode should not try to force everything into KDE theme files. Some parts should remain KDE/Plasma, while larger Nebula tools should become separate native apps.
### KDE Plasma should handle:
* Desktop shell
* Panels
* Widgets
* Notifications
* System tray
* Window management
* Theme integration
* Shortcuts
* Power menu
* Basic desktop settings
### Native Nebula apps should handle later:
* Nebula Welcome
* Nebula Control Center
* Nebula Settings
* Nebula Library
* Nebula Mod Manager
* Nebula Store
* Nebula Update Manager
* Nebula account/profile manager
* Nebula diagnostics
These apps should likely be native Qt apps where deep desktop integration is useful.
---
# Milestones
## Milestone 0: KDE Prototype Setup
Status: Mostly complete
Goal: Get KDE ready for safe Desktop Mode theme development.
Tasks:
* [x] Choose KDE Plasma as the Desktop Mode foundation
* [x] Set up the repo structure
* [x] Copy starter KDE theme files into the repo
* [x] Set up symlinks for development
* [x] Begin editing the Plasma theme
* [x] Add or start apply script
* [x] Add or start reload workflow
* [ ] Confirm theme metadata is clean
* [ ] Confirm theme appears correctly in KDE settings
* [ ] Document the development workflow
Definition of done:
* KDE can load the NebulaOS theme from the repo or from copied local files
* The theme can be refreshed without reinstalling the OS
* The repo is the source of truth
---
## Milestone 1: NebulaOS Desktop Theme v0.1
Status: Current priority
Goal: Make the desktop visually stop looking like stock KDE.
Tasks:
* [ ] Create NebulaOS wallpaper
* [ ] Create NebulaOS color scheme
* [ ] Rename and clean theme metadata
* [ ] Style the main Plasma panel
* [ ] Style launcher/menu surfaces
* [ ] Style popups and dialogs
* [ ] Style tooltips
* [ ] Style notifications
* [ ] Style task manager active indicators
* [ ] Add Nebula launcher icon
* [ ] Add Bigscreen Mode shortcut
* [ ] Add Nebula Browser shortcut
* [ ] Improve apply script
* [ ] Improve reload script
* [ ] Add screenshots to this README later
Definition of done:
* Wallpaper applies correctly
* Color scheme applies correctly
* Panel looks like NebulaOS
* Menus and popups match the theme
* Notifications match the theme
* Bigscreen Mode can be launched from Desktop Mode
* The desktop has a clear NebulaOS identity
---
## Milestone 2: NebulaOS Desktop Shell v0.2
Status: Planned
Goal: Improve the default layout and desktop workflow.
Tasks:
* [ ] Create default panel layout
* [ ] Add pinned app defaults
* [ ] Add clean system tray layout
* [ ] Add search-focused workflow
* [ ] Add power/user shortcuts
* [ ] Add better desktop widgets if needed
* [ ] Add default shortcuts for browser, files, terminal, settings, and Bigscreen Mode
* [ ] Improve launcher direction
Definition of done:
* A fresh Desktop Mode session has a clear default layout
* Users can launch core apps easily
* Users can switch to Bigscreen Mode easily
* The desktop feels intentionally designed instead of manually assembled
---
## Milestone 3: NebulaOS Desktop Apps v0.3
Status: Planned
Goal: Add the first native Nebula desktop tools.
Recommended first app:
```text
Nebula Welcome
```
Nebula Welcome should allow users to:
* Learn what Desktop Mode is
* Open KDE/System Settings
* Launch Bigscreen Mode
* Launch Nebula Browser
* Open project/about information
* Access basic setup links
Future desktop apps may include:
* Nebula Control Center
* Nebula Library
* Nebula Mod Manager
* Nebula Update Manager
* Nebula Store
Definition of done:
* At least one native Nebula desktop app exists
* It matches the Desktop Mode visual identity
* It helps connect the desktop experience to the wider NebulaOS ecosystem
---
## Milestone 4: Login, Session, and System Polish v0.4
Status: Planned (parallel with theme milestones)
Goal: Provide a **controller-friendly OS login** so users pick account and session before Bigscreen or Desktop starts. Bigscreen Mode should not implement account creation or login.
Tasks:
* [ ] Create controller-friendly SDDM theme
* [ ] Account picker and sign-in usable with gamepad and keyboard
* [ ] Session entries: Nebula Bigscreen and Nebula Desktop
* [ ] First-run / account setup flow (OS or desktop side, not in Bigscreen shell)
* [ ] Create splash screen
* [ ] Optional lock/suspend styling consistent with SDDM (session layer, not Bigscreen PIN)
* [ ] Add desktop-to-bigscreen switch flow
* [ ] Add bigscreen-to-desktop switch flow
* [ ] Log out / switch user returns to SDDM
* [ ] Improve power menu integration in Bigscreen (return to login only)
Definition of done:
* Users can sign in and choose Bigscreen or Desktop from the couch with a controller
* Bigscreen Qt shell starts already authenticated—no in-shell login or account wizard
* Desktop Mode feels connected from login to desktop
* NebulaOS feels less like a theme and more like an operating system experience
---
## Milestone 5: Packaging and Distribution v0.5
Status: Future
Goal: Make Desktop Mode installable and repeatable.
Tasks:
* [ ] Clean install script
* [ ] Clean uninstall/reset script
* [ ] Package theme files properly
* [ ] Package wallpapers properly
* [ ] Package desktop shortcuts properly
* [ ] Prepare Arch/EndeavourOS install steps
* [ ] Prepare ISO integration later
* [ ] Add screenshots and setup instructions
Definition of done:
* Desktop Mode can be applied consistently on a clean KDE install
* Installation does not rely on manual file copying
* The README clearly explains how to test and apply the desktop theme
---
## Useful Test Commands
```bash
kbuildsycoca6
plasmoidviewer -a org.nebula.launcher
./Desktop/scripts/reload-plasma.sh
./Desktop/scripts/apply-nebula-desktop.sh
```
`reload-plasma.sh` refreshes Plasma after editing symlinked repo files.
`apply-nebula-desktop.sh` copies the repo packages into local KDE folders for install and packaging tests.
---
## What Desktop Mode Should Avoid
Desktop Mode should avoid:
* Removing normal Linux functionality
* Hiding important system tools
* Making KDE harder to use
* Becoming too console-like
* Depending on Bigscreen Mode to function
* Locking users into one workflow
* Deep KDE source changes too early
* Rebuilding things KDE already handles well
Desktop Mode should be powerful, normal, and clean.
---
## Success Criteria
Desktop Mode is successful when:
* Users can use NebulaOS like a normal Linux desktop
* KDE feels visually integrated with NebulaOS
* Desktop Mode feels polished instead of temporary
* Bigscreen Mode can be launched easily
* Advanced settings are accessible
* Development and troubleshooting are possible
* Gaming and modding workflows are supported
* The desktop has its own identity without breaking Linux flexibility
---
## Current Priority
The current priority is:
```text
Milestone 1: NebulaOS Desktop Theme v0.1
```
Focus on:
* Wallpaper
* Color scheme
* Panel styling
* Menu and popup styling
* Notifications
* Task manager active indicators
* Bigscreen shortcut
* Nebula Browser shortcut
* Clean apply/reload scripts
Do not focus on the full login system, custom store, mod manager, or ISO packaging yet. The base Desktop Mode identity should come first.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-157
View File
@@ -1,157 +0,0 @@
{
"KPlugin": {
"Authors": [
{
"Email": "",
"Name": "Nebula",
"Name[ar]": "Nebula",
"Name[az]": "Nebula",
"Name[be]": "Nebula",
"Name[bg]": "Nebula",
"Name[ca@valencia]": "Nebula",
"Name[ca]": "Nebula",
"Name[cs]": "Nebula",
"Name[da]": "Nebula",
"Name[de]": "Nebula",
"Name[el]": "Nebula",
"Name[en_GB]": "Nebula",
"Name[eo]": "Nebula",
"Name[es]": "Nebula",
"Name[eu]": "Nebula",
"Name[fi]": "Nebula",
"Name[fr]": "Nebula",
"Name[gl]": "Nebula",
"Name[he]": "Nebula",
"Name[hi]": "Nebula",
"Name[hu]": "Nebula",
"Name[ia]": "Nebula",
"Name[id]": "Nebula",
"Name[is]": "Nebula",
"Name[it]": "Nebula",
"Name[ka]": "Nebula",
"Name[ko]": "Nebula",
"Name[lt]": "Nebula",
"Name[lv]": "Nebula",
"Name[nb]": "Nebula",
"Name[nl]": "Nebula",
"Name[nn]": "Nebula",
"Name[pl]": "Nebula",
"Name[pt]": "Nebula",
"Name[pt_BR]": "Nebula",
"Name[ro]": "Nebula",
"Name[ru]": "Nebula",
"Name[sa]": "Nebula",
"Name[sk]": "Nebula",
"Name[sl]": "Nebula",
"Name[sv]": "Nebula",
"Name[ta]": "Nebula",
"Name[tr]": "Nebula",
"Name[uk]": "Nebula",
"Name[vi]": "Nebula",
"Name[zh_CN]": "Nebula",
"Name[zh_TW]": "Nebula"
}
],
"Category": "",
"Description": "Nebula Plasma desktop theme for NebulaOS",
"Description[ar]": "سمة سطح مكتب Nebula Plasma لـ NebulaOS",
"Description[az]": "Nebula Plasma desktop theme for NebulaOS",
"Description[be]": "Nebula Plasma desktop theme for NebulaOS",
"Description[bg]": "Nebula Plasma desktop theme for NebulaOS",
"Description[ca@valencia]": "Nebula Plasma desktop theme for NebulaOS",
"Description[ca]": "Nebula Plasma desktop theme for NebulaOS",
"Description[cs]": "Nebula Plasma desktop theme for NebulaOS",
"Description[da]": "Nebula Plasma desktop theme for NebulaOS",
"Description[de]": "Nebula Plasma-Desktopthema für NebulaOS",
"Description[el]": "Nebula Plasma desktop theme for NebulaOS",
"Description[en_GB]": "Nebula Plasma desktop theme for NebulaOS",
"Description[eo]": "Nebula Plasma desktop theme for NebulaOS",
"Description[es]": "Tema de escritorio Nebula Plasma para NebulaOS",
"Description[eu]": "Nebula Plasma desktop theme for NebulaOS",
"Description[fi]": "Nebula Plasma desktop theme for NebulaOS",
"Description[fr]": "Thème de bureau Nebula Plasma pour NebulaOS",
"Description[gl]": "Nebula Plasma desktop theme for NebulaOS",
"Description[he]": "Nebula Plasma desktop theme for NebulaOS",
"Description[hi]": "Nebula Plasma desktop theme for NebulaOS",
"Description[hu]": "Nebula Plasma desktop theme for NebulaOS",
"Description[ia]": "Nebula Plasma desktop theme for NebulaOS",
"Description[id]": "Nebula Plasma desktop theme for NebulaOS",
"Description[is]": "Nebula Plasma desktop theme for NebulaOS",
"Description[it]": "Tema desktop Nebula Plasma per NebulaOS",
"Description[ka]": "Nebula Plasma desktop theme for NebulaOS",
"Description[ko]": "NebulaOS용 Nebula Plasma 데스크톱 테마",
"Description[lt]": "Nebula Plasma desktop theme for NebulaOS",
"Description[lv]": "Nebula Plasma desktop theme for NebulaOS",
"Description[nb]": "Nebula Plasma desktop theme for NebulaOS",
"Description[nl]": "Nebula Plasma desktop theme for NebulaOS",
"Description[nn]": "Nebula Plasma desktop theme for NebulaOS",
"Description[pl]": "Nebula Plasma desktop theme for NebulaOS",
"Description[pt]": "Tema de ambiente de trabalho Nebula Plasma para NebulaOS",
"Description[pt_BR]": "Tema de área de trabalho Nebula Plasma para o NebulaOS",
"Description[ro]": "Nebula Plasma desktop theme for NebulaOS",
"Description[ru]": "Тема рабочего стола Nebula Plasma для NebulaOS",
"Description[sa]": "Nebula Plasma desktop theme for NebulaOS",
"Description[sk]": "Nebula Plasma desktop theme for NebulaOS",
"Description[sl]": "Nebula Plasma desktop theme for NebulaOS",
"Description[sv]": "Nebula Plasma desktop theme for NebulaOS",
"Description[ta]": "Nebula Plasma desktop theme for NebulaOS",
"Description[tr]": "Nebula Plasma desktop theme for NebulaOS",
"Description[uk]": "Nebula Plasma desktop theme for NebulaOS",
"Description[vi]": "Nebula Plasma desktop theme for NebulaOS",
"Description[zh_CN]": "NebulaOS 的 Nebula Plasma 桌面主题",
"Description[zh_TW]": "NebulaOS 的 Nebula Plasma 桌面主題",
"EnabledByDefault": true,
"Id": "Nebula",
"License": "LGPL",
"Name": "Nebula Plasma",
"Name[ar]": "Nebula Plasma",
"Name[az]": "Nebula Plasma",
"Name[be]": "Nebula Plasma",
"Name[bg]": "Nebula Plasma",
"Name[ca@valencia]": "Nebula Plasma",
"Name[ca]": "Nebula Plasma",
"Name[cs]": "Nebula Plasma",
"Name[da]": "Nebula Plasma",
"Name[de]": "Nebula Plasma",
"Name[el]": "Nebula Plasma",
"Name[en_GB]": "Nebula Plasma",
"Name[eo]": "Nebula Plasma",
"Name[es]": "Nebula Plasma",
"Name[eu]": "Nebula Plasma",
"Name[fi]": "Nebula Plasma",
"Name[fr]": "Nebula Plasma",
"Name[gl]": "Nebula Plasma",
"Name[he]": "Nebula Plasma",
"Name[hi]": "Nebula Plasma",
"Name[hu]": "Nebula Plasma",
"Name[ia]": "Nebula Plasma",
"Name[id]": "Nebula Plasma",
"Name[is]": "Nebula Plasma",
"Name[it]": "Nebula Plasma",
"Name[ka]": "Nebula Plasma",
"Name[ko]": "Nebula Plasma",
"Name[lt]": "Nebula Plasma",
"Name[lv]": "Nebula Plasma",
"Name[nb]": "Nebula Plasma",
"Name[nl]": "Nebula Plasma",
"Name[nn]": "Nebula Plasma",
"Name[pl]": "Nebula Plasma",
"Name[pt]": "Nebula Plasma",
"Name[pt_BR]": "Nebula Plasma",
"Name[ro]": "Nebula Plasma",
"Name[ru]": "Nebula Plasma",
"Name[sa]": "Nebula Plasma",
"Name[sk]": "Nebula Plasma",
"Name[sl]": "Nebula Plasma",
"Name[sv]": "Nebula Plasma",
"Name[ta]": "Nebula Plasma",
"Name[tr]": "Nebula Plasma",
"Name[uk]": "Nebula Plasma",
"Name[vi]": "Nebula Plasma",
"Name[zh_CN]": "Nebula Plasma",
"Name[zh_TW]": "Nebula Plasma",
"Version": "6.22.0",
"Website": "https://git.zambazosmedia.group/nebula-project/Nebula-OS"
},
"X-Plasma-API": "5.0"
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,119 +0,0 @@
[ColorEffects:Disabled]
Color=56,56,56
ColorAmount=0
ColorEffect=0
ContrastAmount=0.65
ContrastEffect=1
IntensityAmount=0.1
IntensityEffect=2
[ColorEffects:Inactive]
ChangeSelectionColor=true
Color=112,111,110
ColorAmount=0.025
ColorEffect=2
ContrastAmount=0.1
ContrastEffect=2
Enable=false
IntensityAmount=0
IntensityEffect=0
[Colors:Button]
BackgroundAlternate=25,28,42
BackgroundNormal=18,21,33
DecorationFocus=111,92,255
DecorationHover=104,92,255
ForegroundActive=143,132,255
ForegroundInactive=140,145,165
ForegroundLink=136,170,255
ForegroundNegative=255,98,118
ForegroundNeutral=255,194,102
ForegroundNormal=236,240,255
ForegroundPositive=92,230,170
ForegroundVisited=180,142,255
[Colors:Complementary]
BackgroundAlternate=20,24,38
BackgroundNormal=10,13,24
DecorationFocus=111,92,255
DecorationHover=104,92,255
ForegroundActive=143,132,255
ForegroundInactive=138,144,166
ForegroundLink=136,170,255
ForegroundNegative=255,98,118
ForegroundNeutral=255,194,102
ForegroundNormal=238,242,255
ForegroundPositive=92,230,170
ForegroundVisited=180,142,255
[Colors:Selection]
BackgroundAlternate=111,92,255
BackgroundNormal=104,92,255
DecorationFocus=143,132,255
DecorationHover=143,132,255
ForegroundActive=255,255,255
ForegroundInactive=228,230,245
ForegroundLink=255,255,255
ForegroundNegative=255,220,225
ForegroundNeutral=255,245,214
ForegroundNormal=255,255,255
ForegroundPositive=220,255,242
ForegroundVisited=245,238,255
[Colors:Tooltip]
BackgroundAlternate=22,25,40
BackgroundNormal=13,16,28
DecorationFocus=111,92,255
DecorationHover=104,92,255
ForegroundActive=143,132,255
ForegroundInactive=150,156,178
ForegroundLink=136,170,255
ForegroundNegative=255,98,118
ForegroundNeutral=255,194,102
ForegroundNormal=238,242,255
ForegroundPositive=92,230,170
ForegroundVisited=180,142,255
[Colors:View]
BackgroundAlternate=13,17,31
BackgroundNormal=8,11,22
DecorationFocus=111,92,255
DecorationHover=104,92,255
ForegroundActive=143,132,255
ForegroundInactive=137,143,165
ForegroundLink=136,170,255
ForegroundNegative=255,98,118
ForegroundNeutral=255,194,102
ForegroundNormal=232,237,252
ForegroundPositive=92,230,170
ForegroundVisited=180,142,255
[Colors:Window]
BackgroundAlternate=16,20,34
BackgroundNormal=10,13,24
DecorationFocus=111,92,255
DecorationHover=104,92,255
ForegroundActive=143,132,255
ForegroundInactive=144,150,172
ForegroundLink=136,170,255
ForegroundNegative=255,98,118
ForegroundNeutral=255,194,102
ForegroundNormal=235,240,255
ForegroundPositive=92,230,170
ForegroundVisited=180,142,255
[General]
ColorScheme=Nebula Dark
Name=Nebula Dark
shadeSortColumn=true
[KDE]
contrast=4
[WM]
activeBackground=10,13,24
activeBlend=10,13,24
activeForeground=235,240,255
inactiveBackground=13,17,31
inactiveBlend=13,17,31
inactiveForeground=145,151,173
@@ -1,271 +0,0 @@
# NebulaOS Desktop Mode
Desktop Mode is the full Linux desktop experience for NebulaOS.
It exists because NebulaOS should not only be a console-style interface.
It should also be a capable desktop operating system when the user needs one.
Bigscreen Mode is for controller-first gaming and media.
Desktop Mode is for everything else.
---
## Purpose
Desktop Mode gives users access to a normal computer environment.
It is designed for:
* File management
* Web browsing
* Productivity
* Development
* Modding
* Troubleshooting
* Advanced settings
* Desktop gaming
* App management
* System maintenance
NebulaOS should feel simple in Bigscreen Mode, but never locked down.
Desktop Mode is what keeps NebulaOS flexible.
---
## Recommended Foundation
The recommended Desktop Mode foundation is:
* Linux
* KDE Plasma
* SDDM
* Wayland where possible
* X11 fallback where needed
* Custom Nebula theme
* Custom Nebula session entries
KDE Plasma is recommended because it is:
* Highly customizable
* Friendly to Linux gaming workflows
* Themeable
* Mature
* Flexible
* Suitable for both desktop and handheld layouts
---
## Design Direction
Desktop Mode should feel:
* Clean
* Smooth
* Modern
* Minimal
* Polished
* Fast
* Familiar
* Slightly futuristic
The design should take inspiration from the clarity and polish of macOS while keeping the flexibility and openness of Linux.
NebulaOS Desktop Mode should not look like a generic KDE install forever.
The long-term goal is to make it feel like a distinct Nebula desktop experience.
---
## Desktop Mode Responsibilities
Desktop Mode should provide:
* File manager access
* Browser access
* Terminal access
* App launcher
* System settings
* Development tools
* Game modding tools
* Desktop game launching
* Access to Bigscreen Mode
* Advanced troubleshooting tools
---
## Relationship to Bigscreen Mode
NebulaOS has two main sessions:
```text
Nebula Bigscreen
Nebula Desktop
```
### Bigscreen Mode
Used for:
* Controller navigation
* Game launching
* TV use
* Handheld use
* Couch workflows
* Simple settings
* Console-like experience
### Desktop Mode
Used for:
* Mouse and keyboard use
* Advanced configuration
* Development
* File browsing
* Modding
* Productivity
* Troubleshooting
Both modes should feel connected, but they should not try to do the same job.
---
## Session Switching
Long-term, NebulaOS should allow users to switch between modes easily.
Possible flows:
```text
Bigscreen Mode → Power Menu → Switch to Desktop Mode
Desktop Mode → App Launcher → Return to Bigscreen Mode
Login Screen → Choose Nebula Bigscreen or Nebula Desktop
```
The user should never feel trapped in either mode.
---
## KDE Customization Goals
Desktop Mode customization may include:
* Nebula global theme
* Nebula icon theme
* Nebula cursor theme
* Nebula wallpaper set
* Custom SDDM theme
* Custom splash screen
* Custom panel layout
* Custom application launcher
* Custom desktop widgets
* Preinstalled Nebula apps
* Desktop shortcut for Bigscreen Mode
---
## Suggested Desktop Layout
A possible default layout:
```text
Top panel
├── App launcher
├── Active window title
├── System tray
├── Network
├── Audio
├── Battery
└── Clock
Desktop
├── Nebula wallpaper
├── Minimal icons
└── Optional widgets
Dock or bottom panel
├── Browser
├── Files
├── Terminal
├── Settings
├── Nebula Bigscreen
└── Nebula Library
```
---
## Visual Identity
Desktop Mode should share visual DNA with Bigscreen Mode.
Shared elements may include:
* Nebula gradients
* Deep blue and purple tones
* Soft glass panels
* Rounded corners
* Subtle glow
* Clean typography
* Space-inspired wallpapers
* Calm animation
* Consistent icon language
Desktop Mode should be calmer and more practical than Bigscreen Mode, but still recognizably Nebula.
---
## System Tools
Desktop Mode may eventually include Nebula-specific tools such as:
* Nebula Control Center
* Nebula Update Manager
* Nebula Library desktop app
* Nebula Browser
* Nebula Store
* Nebula Settings bridge
* Nebula account/profile manager
* Nebula system diagnostics
---
## Development Goals
Initial Desktop Mode goals:
* Pick KDE Plasma as the base
* Create a Nebula desktop theme
* Create a Nebula SDDM login theme
* Add Bigscreen and Desktop sessions
* Add a launcher shortcut back into Bigscreen Mode
* Keep normal Linux tools available
* Avoid breaking standard KDE functionality
---
## What Desktop Mode Should Avoid
Desktop Mode should avoid:
* Removing normal Linux functionality
* Hiding important system tools
* Making KDE harder to use
* Becoming too console-like
* Depending on Bigscreen Mode to function
* Locking users into one workflow
Desktop Mode should be powerful, normal, and clean.
---
## Success Criteria
Desktop Mode is successful when:
* Users can use NebulaOS like a normal Linux desktop
* KDE feels visually integrated with NebulaOS
* Bigscreen Mode can be launched easily
* Advanced settings are accessible
* Development and troubleshooting are possible
* The desktop feels polished instead of temporary
* The system still feels like one unified OS
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More