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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions clients/deck/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
cmake_minimum_required(VERSION 3.24)

project(NovaDeck LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_library(nova_deck_core
src/deck_gamepad.cpp
src/deck_layout.cpp
src/polaris_game_fixture.cpp
)
target_include_directories(nova_deck_core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_compile_definitions(nova_deck_core
PUBLIC
NOVA_DECK_SAMPLE_GAME_FIXTURE=\"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/sample_polaris_game.json\"
NOVA_DECK_SAMPLE_LIBRARY_FIXTURE=\"${CMAKE_CURRENT_SOURCE_DIR}/fixtures/sample_polaris_library.json\"
)

include(CTest)
if(BUILD_TESTING)
add_executable(nova_deck_layout_test
tests/deck_layout_test.cpp
)
target_link_libraries(nova_deck_layout_test PRIVATE nova_deck_core)
target_compile_definitions(nova_deck_layout_test
PRIVATE
NOVA_DECK_MAIN_QML_SOURCE="${CMAKE_CURRENT_SOURCE_DIR}/qml/Main.qml"
)
add_test(NAME nova_deck_controller_library_smoke COMMAND nova_deck_layout_test)
endif()

option(NOVA_DECK_BUILD_QT_SHELL "Build the experimental Qt/QML Steam Deck shell" ON)

if(NOVA_DECK_BUILD_QT_SHELL)
find_package(Qt6 QUIET COMPONENTS Core Gui Qml Quick QuickControls2)

if(Qt6_FOUND)
qt_standard_project_setup(REQUIRES 6.5)
if(COMMAND qt_policy AND Qt6_VERSION VERSION_GREATER_EQUAL 6.8)
qt_policy(SET QTP0004 NEW)
endif()

qt_add_executable(nova-deck
src/main.cpp
)

qt_add_qml_module(nova-deck
URI Nova.Deck
VERSION 0.1
QML_FILES
qml/Main.qml
)

target_link_libraries(nova-deck
PRIVATE
nova_deck_core
Qt6::Core
Qt6::Gui
Qt6::Qml
Qt6::Quick
Qt6::QuickControls2
)

if(BUILD_TESTING)
add_test(NAME nova_deck_qt_shell_smoke COMMAND nova-deck --smoke-exit)
set_tests_properties(nova_deck_qt_shell_smoke PROPERTIES
ENVIRONMENT "QT_QPA_PLATFORM=offscreen"
TIMEOUT 10
)
endif()
else()
message(WARNING "Qt6 Quick/QuickControls2 not found; building nova_deck_core and tests only. On Fedora install cmake gcc-c++ qt6-qtbase-devel qt6-qtdeclarative-devel; qt6-qtdeclarative-devel provides cmake(Qt6QuickControls2).")
endif()
endif()
51 changes: 44 additions & 7 deletions clients/deck/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
# Nova Deck Client

This directory is reserved for the native Steam Deck client.
This directory is the first native Steam Deck client slice for Nova. It is intentionally a scaffold, not the streamer port.

Current status: scaffold only.
Current status:

- CMake builds a small native core library on Linux/SteamOS-capable development hosts.
- Qt 6/QML shell builds when Qt Quick and QuickControls2 development packages are installed.
- Fallback build path keeps the core/controller/library smoke runnable without Qt.
- The shell consumes a generated sample Polaris game fixture shaped after shared/polaris/model/src/commonMain/kotlin/com/papi/nova/shared/polaris/model/PolarisGame.kt.

## Current preview smoke scope

This slice is a preview-only Deck smoke shell. It validates the native window, 1280x800 controller-first layout, fake host list states, an inert launch preview, local clipboard copy feedback, and Steam Input primary-action routing for the copy-preview flow.

It intentionally does **not** validate or perform backend launch, Moonlight streaming, host discovery, pairing, HostStore persistence, network calls, shell/process execution, or real game launch behavior. Keep that boundary visible until the next vertical slice wires a real read-only data source or typed launch-intent contract.

Planned role:

Expand All @@ -11,15 +22,41 @@ Planned role:
- controller-first fullscreen handheld UX
- built on top of the shared Nova backend layers rather than the Android shell

Expected dependencies:
## Runnable smoke paths

Fallback native core and controller/library placeholder, no Qt required:

cmake -S clients/deck -B build/deck-smoke-core -DNOVA_DECK_BUILD_QT_SHELL=OFF
cmake --build build/deck-smoke-core
ctest --test-dir build/deck-smoke-core --output-on-failure

Full Qt shell smoke, when Qt deps are present:

cmake -S clients/deck -B build/deck-smoke-qt
cmake --build build/deck-smoke-qt
ctest --test-dir build/deck-smoke-qt --output-on-failure

The Qt smoke runs nova-deck --smoke-exit with QT_QPA_PLATFORM=offscreen, so it verifies QML object creation and sample library-card data binding without launching a visible desktop window. It does not verify real D-pad focus or game launch behavior yet.

## Shared Polaris DTO boundary

Native C++ cannot include Kotlin source directly. For this first slice, fixtures/sample_polaris_game.json is a generated/shared-contract sample using the same snake_case keys covered by the Kotlin shared DTO tests. src/polaris_game_fixture.h and src/polaris_game_fixture.cpp load that fixture into a tiny native projection so the Deck shell can exercise a real library-card shape while the actual native Polaris API/client bridge is still future work.

Keep this boundary explicit until the shared contract is exported through a real native-consumable API. Do not fake Kotlin/C++ interop by including .kt files.

## Fedora or SteamOS dependency notes

The fallback smoke only needs CMake and a C++20 compiler.

For the Qt shell on Fedora, install the Qt 6 development packages if CMake warns that Qt6 Quick or QuickControls2 is missing:

sudo dnf install cmake gcc-c++ qt6-qtbase-devel qt6-qtdeclarative-devel

- `../../shared/models/`
- `../../shared/polaris/`
- `../../shared/stream-core/`
On Fedora, qt6-qtdeclarative-devel provides cmake(Qt6QuickControls2). SteamOS package names may differ; the required CMake components are Qt6 Core, Qt6 Gui, Qt6 Qml, Qt6 Quick, and Qt6 QuickControls2.

Primary design reference:

- [`../../docs/steam_deck_native_port_study.md`](../../docs/steam_deck_native_port_study.md)
- ../../docs/steam_deck_native_port_study.md

Guardrails:

Expand Down
33 changes: 33 additions & 0 deletions clients/deck/fixtures/sample_polaris_game.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"id": "game-123",
"app_id": 456,
"name": "Portal 2",
"source": "steam",
"launcher_source": "steam",
"launcher_detail": "library",
"platform": "linux",
"runtime": "proton",
"platform_label": "Linux",
"runtime_label": "Proton",
"steam_appid": "620",
"category": "fast_action",
"installed": true,
"cover_url": "/polaris/v1/games/game-123/cover",
"genres": ["Action", "Puzzle"],
"last_launched": 1718187600000,
"mangohud": true,
"hdr_supported": true,
"launch_mode": {
"preferred_mode": "virtual_display",
"recommended_mode": "headless",
"allowed_modes": ["headless", "virtual_display"],
"mode_reason": "Host default is headless."
},
"steam_launch": {
"available": true,
"mode": "big-picture",
"recommended_mode": "direct",
"allowed_modes": ["direct", "big-picture"],
"mode_reason": "Steam Input fallback."
}
}
72 changes: 72 additions & 0 deletions clients/deck/fixtures/sample_polaris_library.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"fixture_source": "Shared Polaris contract fixture",
"read_only": true,
"games": [
{
"id": "game-123",
"app_id": 456,
"name": "Portal 2",
"source": "steam",
"launcher_source": "steam",
"launcher_detail": "library",
"platform": "linux",
"runtime": "proton",
"platform_label": "Linux",
"runtime_label": "Proton",
"steam_appid": "620",
"category": "fast_action",
"installed": true,
"cover_url": "/polaris/v1/games/game-123/cover",
"genres": ["Action", "Puzzle"],
"last_launched": 1718187600000,
"mangohud": true,
"hdr_supported": true,
"launch_mode": {
"preferred_mode": "virtual_display",
"recommended_mode": "headless",
"allowed_modes": ["headless", "virtual_display"],
"mode_reason": "Host default is headless."
},
"steam_launch": {
"available": true,
"mode": "big-picture",
"recommended_mode": "direct",
"allowed_modes": ["direct", "big-picture"],
"mode_reason": "Steam Input fallback."
}
},
{
"id": "game-456",
"app_id": 789,
"name": "Hades",
"source": "steam",
"launcher_source": "steam",
"launcher_detail": "library",
"platform": "linux",
"runtime": "proton",
"platform_label": "Linux",
"runtime_label": "Proton",
"steam_appid": "1145360",
"category": "fast_action",
"installed": true,
"cover_url": "/polaris/v1/games/game-456/cover",
"genres": ["Action", "Roguelike"],
"last_launched": 1718191200000,
"mangohud": true,
"hdr_supported": false,
"launch_mode": {
"preferred_mode": "headless",
"recommended_mode": "virtual_display",
"allowed_modes": ["headless", "virtual_display"],
"mode_reason": "Virtual display is available for this host."
},
"steam_launch": {
"available": true,
"mode": "direct",
"recommended_mode": "big-picture",
"allowed_modes": ["direct", "big-picture"],
"mode_reason": "Big Picture is controller-friendly."
}
}
]
}
Loading
Loading