Skip to content

Add Input Forwarding (X11 app)#11

Merged
K1ngst0m merged 17 commits intomainfrom
dev/kingstom/add-input-forwarding
Jan 4, 2026
Merged

Add Input Forwarding (X11 app)#11
K1ngst0m merged 17 commits intomainfrom
dev/kingstom/add-input-forwarding

Conversation

@K1ngst0m
Copy link
Copy Markdown
Collaborator

@K1ngst0m K1ngst0m commented Dec 24, 2025

Proposal: Add Input Forwarding

Why

When Goggles captures frames from a Vulkan application via the layer, users cannot control the captured application by pressing keys in the Goggles viewer window. Input events go to the Goggles window instead of the captured application.

Standard input forwarding approaches fail:

  1. Synthetic input injection (uinput, XTest to host DISPLAY) is filtered by many applications, especially Wine/DXVK
  2. Focusing the captured app window breaks the seamless viewing experience and may not work if the app is headless

Users currently have no way to interact with captured applications while viewing through Goggles.

Proposed Solution

Introduce an input forwarding module (src/input/) that creates a nested XWayland server for captured applications and forwards key + mouse events from the Goggles SDL window via XTest injection into the nested server.

Architecture

┌─────────────────────────────────────────┐
│  Goggles (DISPLAY=:0)                   │
│                                         │
│  ┌────────────┐      ┌──────────────┐  │
│  │ SDL Window │─────▶│ InputForwarder│  │
│  │  (viewer)  │ keys │   (src/input/) │  │
│  └────────────┘      └────────┬──────┘  │
│                               │         │
│                  ┌────────────▼──────┐  │
│                  │ XWaylandServer    │  │
│                  │ (headless wlroots)│  │
│                  └────────────┬──────┘  │
│                               │         │
│                    Creates DISPLAY=:N   │
│                               │         │
│                  ┌────────────▼──────┐  │
│                  │ X11 connection    │  │
│                  │ XTestFakeKeyEvent │  │
│                  └────────────┬──────┘  │
└──────────────────────────────┼─────────┘
                                │ XTest
                                ▼
┌─────────────────────────────────────────┐
│  Captured App (DISPLAY=:N)              │
│  Receives real X11 KeyPress events      │
└─────────────────────────────────────────┘

Key insight: XTest injection into XWayland generates real X11 protocol events (KeyPress/KeyRelease), indistinguishable from physical keyboard, bypassing synthetic event filters.

Components

  1. src/input/xwayland_server.cpp/hpp: Manages headless Wayland compositor (wlroots) and spawns XWayland process
  2. src/input/input_forwarder.cpp/hpp: Public API class (PIMPL pattern), forwards SDL key + mouse events via XTest
  3. Configuration/CLI: Input forwarding is opt-in in the viewer; the target app must be launched inside the nested XWayland session (DISPLAY=:N)

Integration Points

  • src/app/main.cpp: Optionally initialize InputForwarder (via create()) and forward SDL input events via XTest
  • docs/input_forwarding.md: Document how to launch the target inside the nested XWayland session (DISPLAY=:N)

Benefits

  • Seamless UX: Users control captured apps by pressing keys in viewer window
  • Wine/DXVK compatible: XTest → XWayland generates real X11 events, not filtered
  • Opt-in: Disabled by default; enable via config or --input-forwarding
  • Minimal deps: System packages only (wlroots, wayland-server, xkbcommon, libX11, libXtst)
  • Basic mouse support: Button/motion/wheel events are forwarded (coordinate mapping is currently 1:1 with the viewer window)

Non-Goals

  • Accurate mouse coordinate mapping / scaling between viewer and captured app
  • Pointer confinement / relative mouse mode support
  • Wayland native app support (X11-only for now)
  • Display/composition (XWayland used only as input server)
  • Multiple app focus management

Alternatives Considered

uinput Injection

Rejected: Requires root/uinput group, filtered by Wine

Cage Compositor

Rejected: Full compositor with display output we don't need, complex integration

Direct SDL Input Forwarding

Rejected: No way to inject into different DISPLAY without XTest

What Changes

Configuration / CLI

  • New config option: input.forwarding (default: false)
  • New CLI flag: --input-forwarding (overrides config to enable)
  • Input forwarding only works for targets launched with DISPLAY=:N (nested XWayland)
  • Wayland input forwarding is not supported yet

Public API

  • New class goggles::input::InputForwarder (PIMPL pattern)
    • create() -> ResultPtr<InputForwarder>
    • forward_key(const SDL_KeyboardEvent&) -> Result<void>
    • forward_mouse_button(const SDL_MouseButtonEvent&) -> Result<void>
    • forward_mouse_motion(const SDL_MouseMotionEvent&) -> Result<void>
    • forward_mouse_wheel(const SDL_MouseWheelEvent&) -> Result<void>
    • display_number() -> int
  • New class goggles::input::XWaylandServer (internal)
    • start() -> Result<int>
    • stop() -> void

Error Codes

  • ErrorCode::input_init_failed - XWayland/compositor startup failure
  • ErrorCode::input_socket_send_failed - IPC message send failure

Files Added

  • src/input/input_forwarder.cpp/hpp
  • src/input/xwayland_server.cpp/hpp

Files Modified

  • src/app/main.cpp - InputForwarder instantiation
  • src/app/cli.hpp - --input-forwarding flag
  • src/util/config.* - input.forwarding config option
  • docs/input_forwarding.md - usage instructions

Dependencies

All system packages (no CPM changes required):

Package Version Purpose
wlroots 0.18 Headless Wayland compositor
wayland-server Latest Wayland protocol server
xkbcommon Latest Keyboard keymap handling
libX11 Latest X11 client (connect to nested XWayland)
libXtst Latest XTest extension for input injection

SDL3 already in project.

Risks and Mitigations

Risk Mitigation
DISPLAY conflict (socket already bound) Auto-select :1, :2, :3... until successful
XWayland startup failure Propagate error via Result<void>, log failure, disable input forwarding
Memory leak in compositor thread RAII wrappers for all wlroots objects, explicit cleanup
Target launched outside nested XWayland Document requirement: target must be launched with DISPLAY=:N

Success Criteria

  • User presses W/A/S/D in Goggles window → captured app receives KeyPress events
  • Works with Wine/DXVK applications (tested with RE4)
  • Enabled explicitly (config/CLI); default behavior remains unchanged when disabled
  • Clean shutdown (no segfaults, proper resource cleanup)
  • Passes openspec validate add-input-forwarding --strict

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 24, 2025

📝 Walkthrough

Walkthrough

This PR introduces a complete input forwarding subsystem that enables SDL input event forwarding into captured applications via a nested XWayland server. It adds a new goggles::input module with InputForwarder (PImpl-based public API) and XWaylandServer (wlroots-backed Wayland/XWayland compositor), integrates event dispatching into the main loop, and provides opt-in configuration and CLI support.

Changes

Cohort / File(s) Summary
Input Forwarding Core Module
src/input/input_forwarder.hpp, src/input/input_forwarder.cpp, src/input/xwayland_server.hpp, src/input/xwayland_server.cpp, src/input/CMakeLists.txt
Implements InputForwarder (factory-based, non-copyable/non-movable PImpl) and XWaylandServer (headless wlroots compositor with XWayland and compositor thread). Handles SDL-to-X11 keycode translation, XTest event injection for keys/mouse/wheel, and graceful resource cleanup with staged error handling. Links against wlroots, wayland-server, xkbcommon, X11, and XTest libraries.
Main Application Integration
src/app/main.cpp, src/app/cli.hpp, src/app/CMakeLists.txt, src/util/config.hpp, src/util/config.cpp
Extends run_main_loop signature to accept optional InputForwarder pointer; routes SDL key, mouse button, motion, and wheel events to forwarder during main loop. Adds --input-forwarding CLI flag and [input] config section with forwarding boolean (default false). Initializes forwarder conditionally in run_app with logging.
Build System & Dependencies
cmake/Dependencies.cmake, src/CMakeLists.txt, pixi.toml, packages/wlroots_0_18/pixi.toml, packages/wlroots_0_18/recipe.yaml
Adds PkgConfig requirement for wlroots-0.18, wayland-server, xkbcommon; requires X11 and libXtst. Creates local wlroots-0-18 package with prebuilt headers/libs via pixi build. Adds pixman and xcb-util-wm dependencies. Inserts input subdirectory into src/CMakeLists.txt.
Error Handling
src/util/error.hpp
Adds two new error codes: input_init_failed and input_socket_send_failed with corresponding string mappings in error_code_name().
Testing
tests/CMakeLists.txt, tests/input/goggles_input_test.cpp
Adds goggles_input_test executable with RAII Vulkan/SDL3 harness, init functions, and an interactive input event logger (ESC to exit). Links SDL3, Vulkan, and goggles_util.
Documentation & Design Specs
docs/input_forwarding.md, openspec/changes/archive/2026-01-04-add-input-forwarding-x11/*
New design doc explaining architecture (nested XWayland, headless wlroots, XTest injection), data flow, dependencies, launch instructions, and testing. Openspec directory contains proposal, design, tasks, and spec detailing public API surface, lifecycle, keycode translation, error handling, and threading model.
Capture & Minor Updates
src/capture/capture_protocol.hpp, src/capture/capture_receiver.hpp, src/capture/capture_receiver.cpp, src/capture/vk_layer/ipc_socket.cpp, config/goggles.toml, README.md
Removes static_assert on CaptureFrameMetadata size; minor formatting in CaptureReceiver; adds #include <poll.h> to ipc_socket.cpp; adds [input] section to config; whitespace adjustment to README.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant IF as InputForwarder
    participant XWS as XWaylandServer
    participant Comp as Compositor Thread
    participant Wayland as Wayland Display
    participant XWayland as XWayland
    
    Note over App,XWayland: Initialization Phase
    App->>IF: create()
    activate IF
    IF->>XWS: start()
    activate XWS
    XWS->>Wayland: wl_display_create()
    XWS->>Wayland: wlr_backend_create(headless)
    XWS->>Wayland: bind_socket(DISPLAY:1-9)
    XWS->>XWayland: wlr_xwayland_create()
    XWS->>Comp: spawn compositor thread
    activate Comp
    Comp->>Wayland: drive event loop
    XWS-->>IF: return DISPLAY number
    deactivate XWS
    IF->>IF: XOpenDisplay(DISPLAY:N)
    IF-->>App: return InputForwarder
    deactivate IF
    
    Note over App,XWayland: Runtime Event Forwarding Phase
    App->>App: SDL event received
    App->>IF: forward_key(SDL_KeyboardEvent)
    activate IF
    IF->>IF: translate SDL scancode → Linux keycode
    IF->>IF: convert Linux keycode → X11 keycode
    IF->>XWayland: XTest keyboard event
    IF->>XWayland: XSync(display)
    IF-->>App: Result<void>
    deactivate IF
    XWayland->>Wayland: deliver to target app
    
    Note over App,XWayland: Shutdown Phase
    App->>IF: ~InputForwarder()
    activate IF
    IF->>IF: XCloseDisplay()
    IF->>XWS: stop()
    activate XWS
    XWS->>Wayland: wl_display_terminate()
    XWS->>Comp: join compositor thread
    deactivate Comp
    Comp-->>XWS: thread exits
    XWS->>XWayland: destroy
    XWS->>Wayland: wlr_* cleanup
    deactivate XWS
    deactivate IF
Loading
sequenceDiagram
    participant SDL as SDL Event Loop
    participant Main as main.cpp
    participant IF as InputForwarder
    participant X11 as X11/XTest
    participant XWL as XWayland
    participant App as Target App
    
    Note over SDL,App: Mouse Button Event Flow
    SDL->>SDL: SDL_MOUSEBUTTONDOWN
    SDL->>Main: poll event
    Main->>Main: check input_forwarder != nullptr
    rect rgb(200, 220, 240)
    Note over Main,IF: Event Forwarding
    Main->>IF: forward_mouse_button(SDL_MouseButtonEvent)
    activate IF
    IF->>IF: translate button & state
    IF->>X11: XTestFakeButtonEvent(button, is_pressed)
    IF->>X11: XSync(display)
    IF-->>Main: Result::ok()
    deactivate IF
    end
    Main->>Main: log if error
    
    rect rgb(240, 200, 200)
    Note over X11,App: X11 Delivery
    X11->>XWL: synthetic button event
    XWL->>App: deliver to window
    App->>App: handle button press
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The review requires careful attention to wlroots integration (compositor lifecycle, resource cleanup order, thread safety), X11/XTest interaction (keycode translation, display management), SDL-to-X11 event mapping, and integration points in the main event loop. Multiple files with interconnected responsibilities and some unfamiliar domain-specific code (wlroots API) increase complexity despite clean organization.

Possibly related PRs

Poem

🐰 A Tale of Keys and Mice

From SDL's swift streams to X's ancient halls,
A rabbit routes each click through nested walls,
Where wlroots weave and XTest threads align—
Now distant hands and captured apps entwine! ✨🖱️
Hop, forward, sync, repeat!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.27% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add Input Forwarding (X11 app)' directly and clearly summarizes the main change: adding input forwarding functionality for X11 applications.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@K1ngst0m K1ngst0m marked this pull request as draft December 24, 2025 03:15
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
docs/inputtino_evaluation.md (2)

90-90: Add language identifiers to fenced code blocks and format bare URLs.

For markdown best practices:

  • Line 90: Format the bare URL as [games-on-whales/inputtino](https://github.com/games-on-whales/inputtino)
  • Lines 23, 48, 129, 169, 328: Add language specifiers to code blocks (e.g., ```shell, ```cpp, ```json)

These improve readability and syntax highlighting in rendered markdown.

Also applies to: 23-23, 48-48, 129-129, 169-169, 328-328


125-125: Consider using markdown headings for verdict/verdict-like sections instead of bold emphasis.

Lines 125 and 162 use **Verdict: ...** formatting. For consistency with the rest of the document structure and better markdown semantics, consider promoting these to heading level (e.g., #### Verdict: Fully Agnostic). This improves document navigation and outline generation.

Also applies to: 162-162

openspec/changes/add-input-forwarding/proposal.md (2)

25-25: Format the bare URL as a markdown link.

Line 25 should be: [Inputtino repository](https://github.com/games-on-whales/inputtino)

This improves markdown rendering and follows best practices used elsewhere in the PR documentation.


7-20: Consider more explicit specification of spec deltas.

The proposal lists code/module additions but could be clearer about the associated spec changes. The "What Changes" section could explicitly reference the new spec file or structure entries to indicate both code and spec changes:

For example:

## What Changes

**Spec:**
- **ADDED:** New `input-forwarding` capability specification (see `specs/input-forwarding/spec.md`)

**Code & Dependencies:**
- **ADDED:** New `src/input/` module for input handling
- ... (etc)

This makes it explicit to readers that both specs and code are being added, which helps with compliance tracking and proposal comprehensiveness.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11c62bc and a838e77.

📒 Files selected for processing (5)
  • docs/inputtino_evaluation.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/tasks.md
🧰 Additional context used
📓 Path-based instructions (3)
openspec/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/*.md: Use #### Scenario: format (4 hashtags) for scenario headers in requirements, not bullets or bold text, with at least one scenario per requirement
Use format - **WHEN** [condition] - **THEN** [expected result] for scenario steps in requirements

Files:

  • openspec/changes/add-input-forwarding/tasks.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
openspec/changes/**/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

List spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING

Files:

  • openspec/changes/add-input-forwarding/proposal.md
openspec/changes/**/specs/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/*.md: Write spec deltas using ## ADDED|MODIFIED|REMOVED|RENAMED Requirements format with at least one #### Scenario: per requirement in spec files
When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement
Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Files:

  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
🧠 Learnings (3)
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Read `proposal.md`, `design.md` (if exists), and `tasks.md` in order, then implement tasks sequentially before updating checklist status to completed

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Create a change proposal with `proposal.md`, `tasks.md`, and optional `design.md` under `openspec/changes/<change-id>/` before implementing changes to add features, make breaking changes, or update architecture

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
🪛 LanguageTool
docs/inputtino_evaluation.md

[grammar] ~1-~1: Ensure spelling is correct
Context: # Inputtino Evaluation for Goggles Input Forwarding...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~7-~7: Ensure spelling is correct
Context: ...adding input forwarding to Goggles. Its uinput-based approach provides true desktop en...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~81-~81: Ensure spelling is correct
Context: ... display server (X11/Wayland). --- ## Inputtino Overview ### What is Inputtino? Input...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~111-~111: Ensure spelling is correct
Context: ...yleft restrictions. Compatible with any Goggles licensing model. ### Production Usage ...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~320-~320: Ensure spelling is correct
Context: ...sing a tested library Verdict: Use inputtino rather than reinventing the wheel. ---...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~363-~363: Consider an alternative for the overused word “exactly”.
Context: ...erialize, inject via inputtino This is exactly how Wolf/Sunshine work today. ### Inpu...

(EXACTLY_PRECISELY)


[grammar] ~413-~413: Ensure spelling is correct
Context: ...omplex deployments --- ## Conclusion Inputtino is the right choice for Goggles input f...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

openspec/changes/add-input-forwarding/design.md

[grammar] ~83-~83: Use a hyphen to join words.
Context: ...v translation ``` ### Decision 4: Error handling strategy What: Return `tl:...

(QB_NEW_EN_HYPHEN)

🪛 markdownlint-cli2 (0.18.1)
docs/inputtino_evaluation.md

23-23: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


48-48: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


90-90: Bare URL used

(MD034, no-bare-urls)


125-125: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


129-129: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


162-162: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


169-169: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


328-328: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

openspec/changes/add-input-forwarding/proposal.md

25-25: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (5)
openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md (1)

1-102: Spec format and structure look excellent.

The specification correctly follows all coding guidelines: ## ADDED Requirements header, #### Scenario: sections (4 hashtags), and proper - **WHEN** [condition] - **THEN** [result] format throughout. Requirements are clear, testable, and cover success, failure, and focus-based behavior scenarios comprehensively.

docs/inputtino_evaluation.md (1)

1-425: Comprehensive and well-structured evaluation document.

The evaluation provides excellent context for the inputtino integration decision, covering technical fit, license compatibility, privilege models, and risk assessment. Architecture diagrams are particularly helpful for understanding the kernel-level injection approach. The comparison with alternatives (libei, ydotool, xdotool) clearly justifies the choice. This document should serve well as reference for future maintainers.

openspec/changes/add-input-forwarding/tasks.md (1)

1-47: Task checklist is well-organized and aligned with design decisions.

The 26 tasks are sequenced logically from dependencies through integration to documentation and testing. Tasks directly map to the architecture decisions in design.md (inputtino integration, input module structure, SDL event flow) and requirements in spec.md (configuration, privilege management, focus-based behavior). The optional deferral of gamepad support (task 2.5) appropriately reflects design non-goals. This serves as a solid project plan for implementation.

openspec/changes/add-input-forwarding/proposal.md (1)

1-25: Proposal clearly articulates motivation, scope, and impact.

The proposal effectively communicates the gap (lack of input forwarding) and the solution scope. The "Impact" section is well-organized with clear sub-sections for affected specs, code, external dependencies, and privilege requirements. The references to evaluation documentation and inputtino repository are helpful for decision justification.

openspec/changes/add-input-forwarding/design.md (1)

1-158: Excellent architectural decision documentation.

The design document provides clear context, well-reasoned decisions with alternatives considered, and appropriate risk/mitigation analysis. The five key decisions are well-justified and the code examples (especially the error handling pattern) serve as good implementation guidance. The migration plan provides a clear incremental path with rollback strategy. The open questions demonstrate thoughtful consideration of scope boundaries.

This design effectively bridges from the evaluation (why inputtino) to the specification (what requirements) and tasks (how to implement).

@K1ngst0m K1ngst0m force-pushed the dev/kingstom/add-input-forwarding branch 3 times, most recently from fbf469c to 3b7d231 Compare December 25, 2025 02:23
@K1ngst0m K1ngst0m changed the title Add Input Forwarding via Inputtino Change: Add Layer-Side Input Injection Dec 25, 2025
@K1ngst0m K1ngst0m changed the title Change: Add Layer-Side Input Injection Add Layer-Side Input Injection Dec 25, 2025
@K1ngst0m K1ngst0m force-pushed the dev/kingstom/add-input-forwarding branch from 3b7d231 to ada473a Compare December 27, 2025 02:54
@K1ngst0m K1ngst0m changed the title Add Layer-Side Input Injection Add Input Forwarding Dec 27, 2025
@K1ngst0m K1ngst0m marked this pull request as ready for review December 27, 2025 15:14
@K1ngst0m
Copy link
Copy Markdown
Collaborator Author

@coderabbitai help

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 27, 2025

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
    • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
    • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@K1ngst0m
Copy link
Copy Markdown
Collaborator Author

@coderabbitai summary

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 27, 2025

✅ Actions performed

Summary regeneration triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 27, 2025

✅ Actions performed

Sequence diagram generated.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/util/error.hpp (1)

30-31: Stray semicolon after enum closing brace.

There's an extra semicolon after the enum definition that should be removed.

 };
-;
🧹 Nitpick comments (14)
openspec/changes/add-input-forwarding/proposal.md (2)

1-120: Missing 'What Changes' section per coding guidelines.

As per the coding guidelines for openspec/changes/**/proposal.md files, proposals should include a "What Changes" section listing spec deltas, with breaking changes marked with BREAKING. This document is missing that section.

Consider adding a section like:

## What Changes

- **NEW**: `goggles::input::InputForwarder` class with `init()`, `forward_key()`, `display_number()` APIs
- **NEW**: `goggles::input::XWaylandServer` class with `start()` and `stop()` APIs
- **NEW**: `CaptureMessageType::config_request` (6) and `input_display_ready` (7) enum values
- **NEW**: `CaptureConfigRequest` and `CaptureInputDisplayReady` structs (16-byte wire format)
- **NEW**: `layer_early_init()` constructor function for early DISPLAY setup
- **NEW**: `CaptureReceiver::set_input_display(int)` and `handle_config_request(int)` methods

20-20: Consider adding language identifier to fenced code block.

The static analysis tool flagged this code block as missing a language identifier. For ASCII art diagrams, you can use text or leave it empty if the tool is overly pedantic for this use case.

-```
+```text
src/capture/vk_layer/vk_capture.cpp (2)

28-46: Socket connection logic duplicates LayerSocketClient::connect().

This code duplicates the socket creation and connection logic from LayerSocketClient::connect() (as shown in the relevant code snippets). Consider extracting a shared helper or adding a blocking connect mode to LayerSocketClient to reduce duplication.

That said, the use of a blocking socket (no SOCK_NONBLOCK) for the config handshake is intentional and appropriate here since we need synchronous communication before the app starts.


60-70: 100ms timeout is reasonable but consider making it configurable.

The 100ms timeout should be sufficient for local IPC. However, on heavily loaded systems, this might occasionally be too short. Consider allowing configuration via an environment variable for debugging purposes, or at minimum document this timeout in the design docs.

src/capture/vk_layer/ipc_socket.hpp (1)

27-29: Consider handling EINTR and partial reads.

The implementation has two potential edge cases:

  1. EINTR handling: poll() can return -1 with errno == EINTR if interrupted by a signal. This should be retried.

  2. Partial reads: If recv() returns a value between 0 and sizeof(T), the message is lost and the socket state becomes inconsistent. For fixed-size IPC messages over a local Unix socket, this is unlikely but not impossible under heavy load.

🔎 Suggested improvement for robustness
 template<typename T>
 bool LayerSocketClient::receive_with_timeout(T& msg, int timeout_ms) {
     std::lock_guard lock(mutex_);

     if (socket_fd_ < 0) {
         return false;
     }

     // Use poll() to wait for data with timeout
     pollfd pfd{};
     pfd.fd = socket_fd_;
     pfd.events = POLLIN;

-    int poll_result = poll(&pfd, 1, timeout_ms);
-    if (poll_result <= 0) {
-        // Timeout or error
-        return false;
+    int poll_result;
+    do {
+        poll_result = poll(&pfd, 1, timeout_ms);
+    } while (poll_result < 0 && errno == EINTR);
+
+    if (poll_result <= 0) {
+        return false;
     }

     // Data available, read it
     ssize_t received = recv(socket_fd_, &msg, sizeof(T), MSG_DONTWAIT);
     return received == sizeof(T);
 }

Also applies to: 40-63

tests/CMakeLists.txt (1)

86-104: Missing link to goggles_input library.

The test is named goggles_input_test but doesn't link against goggles_input. Looking at the test source, it currently only exercises SDL/Vulkan initialization without actually testing the InputForwarder. If the intent is to eventually test input forwarding, consider linking the library now.

Also, consider adding target_compile_features(goggles_input_test PRIVATE cxx_std_20) for consistency with the main test target.

🔎 Proposed fix
 target_link_libraries(goggles_input_test PRIVATE
     SDL3::SDL3-static
     Vulkan::Vulkan
     goggles_util
+    goggles_input
 )
+
+target_compile_features(goggles_input_test PRIVATE cxx_std_20)
src/capture/capture_receiver.cpp (1)

231-258: Missing version validation.

The summary mentions validating the version, but the code only validates the message type. Consider adding version validation to ensure protocol compatibility.

🔎 Proposed fix
     if (request.type != CaptureMessageType::config_request) {
         GOGGLES_LOG_WARN("Config request: unexpected message type");
         return;
     }

+    if (request.version != CAPTURE_PROTOCOL_VERSION) {
+        GOGGLES_LOG_WARN("Config request: version mismatch (got {}, expected {})",
+                         request.version, CAPTURE_PROTOCOL_VERSION);
+        return;
+    }
+
     // Send response with input display number
openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md (1)

78-82: Clarify return value for display_number() edge cases.

The scenario states display_number() "SHALL return a positive integer N" after successful init, but it's worth clarifying what happens if init() wasn't called or failed. Based on the implementation (m_display_number starts at -1), consider adding this clarification.

 - **WHEN** `InputForwarder::init()` succeeds
 - **THEN** `InputForwarder::display_number()` SHALL return a positive integer N
 - **AND** callers MAY pass N to other subsystems (e.g. capture config handshakes)
+- **WHEN** `InputForwarder::init()` was not called or failed
+- **THEN** `InputForwarder::display_number()` SHALL return -1
src/input/xwayland_server.cpp (2)

89-89: Return value from wlr_xdg_shell_create is ignored.

The XDG shell creation could fail (return NULL), but the result isn't checked. While this may be non-critical for XWayland operation, it's good practice to validate.

🔎 Proposed fix
-    wlr_xdg_shell_create(m_display, 3);
+    if (!wlr_xdg_shell_create(m_display, 3)) {
+        GOGGLES_LOG_WARN("Failed to create XDG shell (non-fatal for XWayland)");
+    }

46-168: Consider reducing cleanup code repetition.

The repeated cleanup blocks on each failure path are correct but verbose. A scope guard or RAII wrapper could simplify this while maintaining safety.

This is optional since the current explicit cleanup is clear and correct. If you'd like to reduce repetition, a scope guard pattern could help:

auto cleanup_on_failure = [&](bool success) {
    if (success) return;
    // cleanup code here
};
docs/input_forwarding.md (2)

19-49: Add language specifier to fenced code block.

Per the static analysis hint, fenced code blocks should have a language specified. For ASCII diagrams, use text or ``` (blank) to satisfy the linter.

-```
+```text
 ┌────────────────────────────────────────────────────────────┐

157-177: Add language specifier to remaining diagram blocks.

Lines 157, 165, and 173 have the same issue. Add text language specifier for consistency.

src/input/xwayland_server.hpp (1)

32-32: Consider thread safety for display_number() accessor.

The display_number() accessor reads m_display_number without synchronization. If start() modifies this value on the compositor thread while the main thread reads it, there could be a data race. Consider using std::atomic<int> for m_display_number or documenting that display_number() should only be called after start() completes.

🔎 Proposed fix using atomic
 private:
     struct wl_display* m_display = nullptr;
     struct wl_event_loop* m_event_loop = nullptr;
     struct wlr_backend* m_backend = nullptr;
     struct wlr_renderer* m_renderer = nullptr;
     struct wlr_allocator* m_allocator = nullptr;
     struct wlr_compositor* m_compositor = nullptr;
     struct wlr_seat* m_seat = nullptr;
     struct wlr_xwayland* m_xwayland = nullptr;
     std::jthread m_compositor_thread;
-    int m_display_number = -1;
+    std::atomic<int> m_display_number = -1;
 };

Also applies to: 44-44

openspec/changes/add-input-forwarding/design.md (1)

120-120: Optional: Use markdown headings instead of bold for subsections.

Lines 120 and 127 use bold emphasis for "Phase 1" and "Phase 2" subsection headers. Consider using proper markdown heading syntax (#### or #####) for better document structure and accessibility.

🔎 Proposed fix
-**Phase 1: Config Handshake (Layer Constructor)**
+#### Phase 1: Config Handshake (Layer Constructor)
 1. Layer constructor connects to socket
 2. Sends `config_request` message
 3. Goggles responds with `input_display_ready` containing DISPLAY number
 4. Layer sets `DISPLAY=:N` environment variable
 5. Layer closes config connection
 
-**Phase 2: Frame Capture (on_present)**
+#### Phase 2: Frame Capture (on_present)
 6. Layer reconnects on first `vkQueuePresentKHR`

Also applies to: 127-127

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a838e77 and aaee578.

📒 Files selected for processing (23)
  • cmake/Dependencies.cmake
  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/tasks.md
  • src/CMakeLists.txt
  • src/app/CMakeLists.txt
  • src/app/main.cpp
  • src/capture/capture_protocol.hpp
  • src/capture/capture_receiver.cpp
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.hpp
  • src/capture/vk_layer/vk_capture.cpp
  • src/input/CMakeLists.txt
  • src/input/input_forwarder.cpp
  • src/input/input_forwarder.hpp
  • src/input/xwayland_server.cpp
  • src/input/xwayland_server.hpp
  • src/util/error.hpp
  • tests/CMakeLists.txt
  • tests/input/goggles_input_test.cpp
✅ Files skipped from review due to trivial changes (1)
  • cmake/Dependencies.cmake
🚧 Files skipped from review as they are similar to previous changes (1)
  • openspec/changes/add-input-forwarding/tasks.md
🧰 Additional context used
📓 Path-based instructions (3)
openspec/changes/**/specs/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/*.md: Write spec deltas using ## ADDED|MODIFIED|REMOVED|RENAMED Requirements format with at least one #### Scenario: per requirement in spec files
When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement
Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Files:

  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
openspec/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/*.md: Use #### Scenario: format (4 hashtags) for scenario headers in requirements, not bullets or bold text, with at least one scenario per requirement
Use format - **WHEN** [condition] - **THEN** [expected result] for scenario steps in requirements

Files:

  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
openspec/changes/**/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

List spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING

Files:

  • openspec/changes/add-input-forwarding/proposal.md
🧬 Code graph analysis (8)
src/capture/capture_receiver.cpp (2)
src/capture/capture_receiver.hpp (1)
  • fd (42-42)
src/util/unique_fd.hpp (1)
  • fd (34-34)
src/input/input_forwarder.hpp (2)
src/input/input_forwarder.cpp (2)
  • InputForwarder (91-91)
  • InputForwarder (93-93)
src/input/xwayland_server.hpp (1)
  • nodiscard (32-32)
src/input/xwayland_server.hpp (2)
src/input/xwayland_server.cpp (2)
  • XWaylandServer (27-27)
  • XWaylandServer (29-31)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
src/input/xwayland_server.cpp (3)
src/input/xwayland_server.hpp (4)
  • XWaylandServer (22-22)
  • XWaylandServer (23-23)
  • XWaylandServer (25-25)
  • XWaylandServer (27-27)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.hpp (2)
  • display_num (36-36)
  • display_num (36-36)
src/input/input_forwarder.cpp (2)
src/input/input_forwarder.hpp (8)
  • InputForwarder (11-11)
  • InputForwarder (12-12)
  • InputForwarder (14-14)
  • InputForwarder (16-16)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/vk_layer/vk_capture.cpp (1)
src/capture/vk_layer/ipc_socket.cpp (2)
  • connect (25-78)
  • connect (25-25)
src/app/main.cpp (2)
src/input/input_forwarder.hpp (4)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/capture/capture_receiver.hpp (2)
  • display_num (36-36)
  • display_num (36-36)
tests/input/goggles_input_test.cpp (1)
src/input/input_forwarder.hpp (4)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
🪛 markdownlint-cli2 (0.18.1)
docs/input_forwarding.md

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


157-157: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


165-165: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


173-173: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

openspec/changes/add-input-forwarding/design.md

11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


178-178: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

openspec/changes/add-input-forwarding/proposal.md

20-20: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (37)
src/CMakeLists.txt (1)

6-6: LGTM!

The addition of the input subdirectory follows the existing pattern and is appropriately placed.

src/app/CMakeLists.txt (1)

15-15: LGTM!

The goggles_input library is correctly linked to the main application target.

src/util/error.hpp (1)

26-27: LGTM!

The new error codes input_init_failed and input_socket_send_failed are appropriately added and follow the existing naming conventions. The corresponding error_code_name switch cases are correctly implemented.

Also applies to: 79-82

src/capture/capture_protocol.hpp (2)

18-20: LGTM on protocol enum additions.

The new config_request and input_display_ready message types are appropriately added with clear documentation about reserved values for PR #16.


52-66: LGTM on new wire-format structures.

The CaptureConfigRequest and CaptureInputDisplayReady structures are well-designed with:

  • Default initializers for safety
  • Reserved arrays for forward compatibility
  • Static assertions ensuring 16-byte wire format stability
src/capture/vk_layer/vk_capture.cpp (3)

17-22: Good use of constructor priority for early initialization.

Using __attribute__((constructor(101))) ensures this runs before the application's main(), allowing the DISPLAY environment variable to be set early. The logging of the original DISPLAY value is helpful for debugging.


72-85: LGTM on response handling.

Good validation of the response:

  • Checks received bytes match expected size
  • Validates message type matches expected input_display_ready
  • Falls back gracefully to original DISPLAY on any failure

87-101: LGTM on DISPLAY environment variable handling.

The validation of display_number >= 0 before calling setenv is correct. The buffer size of 32 bytes is more than sufficient for the :N format even with large display numbers.

src/capture/vk_layer/ipc_socket.hpp (1)

6-8: LGTM on header includes.

The additional system headers are necessary for the poll() and recv() functionality in the template method.

src/capture/capture_receiver.hpp (3)

35-36: LGTM on set_input_display API.

The inline setter is clean and appropriate for configuring the input display number before clients connect.


42-42: LGTM on handle_config_request declaration.

The method signature appropriately takes the file descriptor for the config connection to send the response.


46-46: LGTM on member variable.

Using -1 as the default/sentinel value for an unset display number is appropriate and consistent with how DISPLAY numbers work (valid values are non-negative).

tests/input/goggles_input_test.cpp (1)

164-182: LGTM!

The main function correctly initializes Vulkan first to trigger layer loading before SDL, then logs the DISPLAY value to verify the layer's effect. Good test structure for manual validation of input forwarding.

src/capture/capture_receiver.cpp (1)

100-109: LGTM!

Good approach using MSG_PEEK to differentiate config requests from normal capture connections without consuming the data. The early detection allows proper routing of different connection types.

openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md (1)

1-200: LGTM!

The specification is well-structured with clear requirements and scenarios following the prescribed format. Good coverage of the XWayland lifecycle, input forwarding, error handling, and integration patterns.

src/input/CMakeLists.txt (1)

1-30: LGTM!

The CMake configuration properly separates public and private dependencies, keeping wlroots/Wayland/X11 details private to support the PIMPL pattern in the public API. The WLR_USE_UNSTABLE define is required for accessing wlroots APIs.

src/input/xwayland_server.cpp (1)

177-226: LGTM!

The shutdown sequence correctly terminates the display loop, joins the compositor thread before destroying resources, and follows the proper destruction order to prevent use-after-free. Good RAII-style cleanup with clear comments explaining the constraints.

docs/input_forwarding.md (1)

108-114: Documentation outdated regarding mouse support.

The limitations state "Keyboard only: Mouse forwarding not yet implemented" and "Mouse forwarding" as a future enhancement, but the InputForwarder API includes forward_mouse_button(), forward_mouse_motion(), and forward_mouse_wheel() methods. Consider updating this section to reflect the current implementation.

src/input/input_forwarder.hpp (1)

1-31: LGTM!

Clean PIMPL implementation that correctly hides wlroots/X11 details from the public header. The [[nodiscard]] attributes ensure callers handle Result return values, and deleted copy/move operations prevent accidental ownership issues with the underlying X11 connection and XWayland server.

src/input/xwayland_server.hpp (1)

1-33: LGTM! Clean header design.

The forward declarations, API structure, and copy/move semantics are well-designed. The NOLINT suppressions are appropriate for external wlroots types.

src/input/input_forwarder.cpp (8)

14-75: Approve keycode translation logic with note on coverage.

The SDL to Linux to X11 keycode translation is correct for the covered keys. Returning 0 for unmapped keys is handled properly in forward_key(). Note that this covers basic alphanumeric and modifier keys but excludes function keys, numpad, and other extended keys—acceptable for an MVP.


80-89: LGTM! Proper RAII cleanup.

The Impl destructor correctly closes the X11 display before the XWaylandServer destructor runs. The cleanup order is appropriate.


151-167: Coordinate mapping TODO is documented.

The TODO at Lines 159-162 correctly identifies the missing coordinate mapping. This aligns with the PR's documented non-goal of accurate mouse coordinate mapping. The current implementation forwards raw SDL coordinates, which will be incorrect when viewer and target window sizes differ.


169-194: Mouse wheel implementation is correct.

The wheel-to-button mapping (4=up, 5=down, 6=left, 7=right) follows standard X11 protocol, and the press-release sequence is appropriate for wheel events.


196-198: Thread safety depends on XWaylandServer.

This accessor delegates to XWaylandServer::display_number(). Ensure the thread safety concern raised for that method is addressed.


95-115: Code is correct; TODO comment is stale.

The init logic is correct with proper error handling and cleanup. The TODO on line 112 about sending DISPLAY to the layer via IPC is misleading—this functionality is already implemented. The DISPLAY number is transmitted to the layer through the config handshake: the receiver stores it via set_input_display(), then sends it back in the CaptureInputDisplayReady response message when the layer initiates the config_request. Consider removing or updating the TODO comment since Task 16 is complete.


117-134: No action needed. SDL3's SDL_KeyboardEvent structure includes a bool down field, which is correctly used at line 128. The code event.down ? True : False is valid SDL3 API usage.

Likely an incorrect or invalid review comment.


136-149: No issue. SDL3 SDL_MouseButtonEvent has a down field (type bool), and line 143 correctly uses event.down ? True : False to convert it to X11's Bool type.

src/app/main.cpp (2)

197-214: LGTM! Proper graceful degradation.

The InputForwarder initialization correctly implements graceful degradation: on failure, logs a warning and continues without input forwarding. On success, integrates with the capture receiver and enables forwarding. The stack allocation with pointer approach is clean.


35-66: Event forwarding integration is correct.

The main loop properly checks for input_forwarder availability before forwarding and logs errors without disrupting the render loop. All major event types (keyboard, mouse button, motion, wheel) are covered.

openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md (3)

5-40: LGTM! Requirement format and content are correct.

The Config Handshake Protocol requirement properly uses the OpenSpec format with #### Scenario: headers and WHEN/THEN/AND structure. The scenarios are clear, testable, and align with the implementation design.


42-59: Protocol message types are well-specified.

The requirement properly documents the new message types and their structure. The use of GIVEN for preconditions (Lines 47, 52, 57) is acceptable, though WHEN is preferred per coding guidelines—this is a minor style point.


61-76: CaptureReceiver requirements are clear and testable.

The config handling scenarios properly specify the two-phase protocol and connection lifecycle. The requirements align with the design documentation.

openspec/changes/add-input-forwarding/design.md (4)

1-51: LGTM! Overview and Layer 1-2 documentation is accurate.

The architectural overview and InputForwarder module description accurately reflect the implementation in src/input/input_forwarder.hpp and .cpp.


53-199: Architecture layers and data flow documentation is accurate.

Layers 3-5 descriptions match the implementation in xwayland_server.hpp, the protocol specs in spec.md, and the expected layer integration. The data flow trace and latency estimate are technically sound.


215-318: Error handling and resource management patterns are sound.

The documented error handling (graceful degradation, error propagation) and RAII patterns match the actual implementation in input_forwarder.cpp and align with best practices.


319-384: Performance and dependency documentation looks reasonable.

The performance estimates (memory, CPU, latency), testing strategy, and dependency justification appear sound based on the architecture described.

@goggles-dev goggles-dev deleted a comment from coderabbitai bot Dec 27, 2025
@K1ngst0m
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 27, 2025

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (4)
src/capture/vk_layer/vk_capture.cpp (1)

24-25: Unused variable still present.

The LayerSocketClient config_socket is declared but never used; the socket operations are performed manually with raw file descriptors. This was flagged in a previous review.

src/capture/capture_protocol.hpp (1)

21-22: Stray semicolon already flagged in previous review.

There's an extra semicolon after the enum closing brace that should be removed.

tests/input/goggles_input_test.cpp (2)

8-23: Defaulted move operations cause double-free risk.

The defaulted move constructor and assignment operator do not transfer ownership properly. After a move, both the source and destination objects will have the same handle value, leading to double-free when both destructors run.

🔎 Proposed fix
-    VulkanInstance(VulkanInstance&&) = default;
-    VulkanInstance& operator=(VulkanInstance&&) = default;
+    VulkanInstance(VulkanInstance&& other) noexcept : handle(other.handle) {
+        other.handle = VK_NULL_HANDLE;
+    }
+    VulkanInstance& operator=(VulkanInstance&& other) noexcept {
+        if (this != &other) {
+            if (handle != VK_NULL_HANDLE) {
+                vkDestroyInstance(handle, nullptr);
+            }
+            handle = other.handle;
+            other.handle = VK_NULL_HANDLE;
+        }
+        return *this;
+    }

25-45: Same double-free risk in SDLContext, plus unconditional SDL_Quit.

The defaulted move operations cause the same double-free issue as VulkanInstance. Additionally, SDL_Quit() is called unconditionally in the destructor, which is problematic if SDL_Init failed or the object was moved-from.

🔎 Proposed fix
+    bool initialized = false;
+
     SDLContext() = default;

     ~SDLContext() {
         if (renderer) {
             SDL_DestroyRenderer(renderer);
         }
         if (window) {
             SDL_DestroyWindow(window);
         }
-        SDL_Quit();
+        if (initialized) {
+            SDL_Quit();
+        }
     }

     SDLContext(const SDLContext&) = delete;
     SDLContext& operator=(const SDLContext&) = delete;
-    SDLContext(SDLContext&&) = default;
-    SDLContext& operator=(SDLContext&&) = default;
+    SDLContext(SDLContext&& other) noexcept
+        : window(other.window), renderer(other.renderer), initialized(other.initialized) {
+        other.window = nullptr;
+        other.renderer = nullptr;
+        other.initialized = false;
+    }
+    SDLContext& operator=(SDLContext&& other) noexcept {
+        if (this != &other) {
+            if (renderer) SDL_DestroyRenderer(renderer);
+            if (window) SDL_DestroyWindow(window);
+            if (initialized) SDL_Quit();
+            window = other.window;
+            renderer = other.renderer;
+            initialized = other.initialized;
+            other.window = nullptr;
+            other.renderer = nullptr;
+            other.initialized = false;
+        }
+        return *this;
+    }

Then set initialized = true after SDL_Init succeeds in init_sdl() (line 76).

🧹 Nitpick comments (3)
openspec/changes/add-input-forwarding/proposal.md (1)

1-120: Missing "What Changes" section per coding guidelines.

The proposal document should include a "What Changes" section listing spec deltas, with any breaking changes marked with BREAKING. This helps reviewers understand which specifications are affected by this proposal.

Consider adding a section like:

## What Changes

- **NEW** `openspec/specs/input-forwarding/` - Input forwarding module specification
- **MODIFIED** `src/capture/capture_protocol.hpp` - Extended IPC protocol with `config_request` and `input_display_ready` message types
- **MODIFIED** `src/capture/capture_receiver` - Added config handshake handling

As per coding guidelines for openspec/changes/**/proposal.md.

src/app/main.cpp (1)

51-58: Consider rate-limiting error logs for high-frequency events.

Mouse motion events can fire hundreds of times per second. If forwarding fails persistently (e.g., XWayland crashes), this will flood the logs with error messages. Consider logging the first failure and then suppressing subsequent failures for the same event type, or using LOG_WARN with rate limiting.

// Example approach: log once per failure state transition
static bool motion_error_logged = false;
if (!forward_result) {
    if (!motion_error_logged) {
        GOGGLES_LOG_WARN("Mouse motion forwarding failed: {}", 
                         forward_result.error().message);
        motion_error_logged = true;
    }
} else {
    motion_error_logged = false;  // Reset on success
}
src/input/input_forwarder.cpp (1)

112-112: Remove obsolete TODO comment.

This TODO references "Task 16" for sending the DISPLAY number to the layer via IPC. However, the PR already implements this functionality through the config handshake (CaptureConfigRequest/CaptureInputDisplayReady) and the display_number() method that's used by CaptureReceiver. This comment should be removed.

🔎 Proposed fix
-    // TODO: Send DISPLAY number to layer via IPC (Task 16)
-
     return {};
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6677db and aaee578.

📒 Files selected for processing (23)
  • cmake/Dependencies.cmake
  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/tasks.md
  • src/CMakeLists.txt
  • src/app/CMakeLists.txt
  • src/app/main.cpp
  • src/capture/capture_protocol.hpp
  • src/capture/capture_receiver.cpp
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.hpp
  • src/capture/vk_layer/vk_capture.cpp
  • src/input/CMakeLists.txt
  • src/input/input_forwarder.cpp
  • src/input/input_forwarder.hpp
  • src/input/xwayland_server.cpp
  • src/input/xwayland_server.hpp
  • src/util/error.hpp
  • tests/CMakeLists.txt
  • tests/input/goggles_input_test.cpp
🧰 Additional context used
📓 Path-based instructions (3)
openspec/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/*.md: Use #### Scenario: format (4 hashtags) for scenario headers in requirements, not bullets or bold text, with at least one scenario per requirement
Use format - **WHEN** [condition] - **THEN** [expected result] for scenario steps in requirements

Files:

  • openspec/changes/add-input-forwarding/tasks.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/proposal.md
openspec/changes/**/specs/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/*.md: Write spec deltas using ## ADDED|MODIFIED|REMOVED|RENAMED Requirements format with at least one #### Scenario: per requirement in spec files
When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement
Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Files:

  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
openspec/changes/**/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

List spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING

Files:

  • openspec/changes/add-input-forwarding/proposal.md
🧠 Learnings (2)
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Create a change proposal with `proposal.md`, `tasks.md`, and optional `design.md` under `openspec/changes/<change-id>/` before implementing changes to add features, make breaking changes, or update architecture

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
🧬 Code graph analysis (9)
src/input/input_forwarder.hpp (3)
src/input/input_forwarder.cpp (2)
  • InputForwarder (91-91)
  • InputForwarder (93-93)
src/input/xwayland_server.hpp (1)
  • nodiscard (32-32)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
src/capture/capture_receiver.hpp (1)
src/util/unique_fd.hpp (1)
  • fd (34-34)
tests/input/goggles_input_test.cpp (1)
src/input/input_forwarder.hpp (4)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/input/xwayland_server.cpp (2)
src/input/xwayland_server.hpp (4)
  • XWaylandServer (22-22)
  • XWaylandServer (23-23)
  • XWaylandServer (25-25)
  • XWaylandServer (27-27)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.cpp (2)
src/capture/capture_receiver.hpp (1)
  • fd (42-42)
src/util/unique_fd.hpp (1)
  • fd (34-34)
src/input/xwayland_server.hpp (2)
src/input/xwayland_server.cpp (2)
  • XWaylandServer (27-27)
  • XWaylandServer (29-31)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
src/input/input_forwarder.cpp (3)
src/input/input_forwarder.hpp (8)
  • InputForwarder (11-11)
  • InputForwarder (12-12)
  • InputForwarder (14-14)
  • InputForwarder (16-16)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.hpp (2)
  • display_num (36-36)
  • display_num (36-36)
src/capture/vk_layer/vk_capture.cpp (1)
src/capture/vk_layer/ipc_socket.cpp (2)
  • connect (25-78)
  • connect (25-25)
src/app/main.cpp (2)
src/input/input_forwarder.hpp (4)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/capture/capture_receiver.hpp (2)
  • display_num (36-36)
  • display_num (36-36)
🔇 Additional comments (30)
openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md (1)

1-200: Spec structure follows coding guidelines correctly.

The specification uses the correct ## ADDED Requirements format, #### Scenario: headers (4 hashtags), and - **WHEN** ... - **THEN** ... format for scenario steps. Each requirement has at least one scenario as required. The requirements comprehensively cover the input forwarding module's lifecycle, event forwarding, error handling, and integration points.

src/CMakeLists.txt (1)

6-6: LGTM!

The add_subdirectory(input) follows the existing pattern for including source subdirectories.

src/app/CMakeLists.txt (1)

15-15: LGTM!

Linking goggles_input privately follows the existing pattern for internal library dependencies.

cmake/Dependencies.cmake (1)

172-182: LGTM!

The input forwarding dependencies are correctly configured using PkgConfig for wlroots/wayland/xkbcommon and find_package for X11. The fatal error for missing libXtst ensures early build failure with a clear message.

Consider documenting these system dependencies in the project README or build documentation, as users will need to install wlroots, wayland-server, xkbcommon, and libXtst development packages.

src/util/error.hpp (1)

26-27: LGTM!

The new error codes input_init_failed and input_socket_send_failed are appropriately named following the existing pattern, and the error_code_name switch correctly maps them to string representations.

Also applies to: 79-82

src/capture/vk_layer/vk_capture.cpp (1)

17-101: Config handshake implementation is well-structured.

The layer_early_init() constructor correctly:

  • Preserves the original DISPLAY on any failure path
  • Uses a blocking socket with poll() timeout for synchronous handshake
  • Validates the response type and display_number before modifying the environment
  • Properly closes the socket on all paths
src/capture/capture_receiver.hpp (1)

35-36: LGTM!

The additions cleanly extend CaptureReceiver for config handshake support:

  • set_input_display() provides a simple setter for the display number from InputForwarder
  • handle_config_request() encapsulates config request handling
  • m_input_display_number with -1 default correctly indicates "not configured"

Also applies to: 42-42, 46-46

openspec/changes/add-input-forwarding/tasks.md (1)

1-17: LGTM!

Task list is well-structured with clear separation between completed implementation work and pending follow-up/validation items.

src/input/CMakeLists.txt (1)

8-14: Consider simplifying include directories.

When using PkgConfig::wlroots as an IMPORTED target, include directories are typically propagated automatically. The explicit ${wlroots_INCLUDE_DIRS} etc. may be redundant if the pkg-config targets are properly defined.

However, this works correctly as-is and ensures backward compatibility with older CMake/pkg-config setups.

src/capture/capture_receiver.cpp (1)

100-109: LGTM on the peek-based config request detection.

The approach of peeking at the message type to distinguish config requests from normal client connections is clean and allows proper routing without consuming the message prematurely.

src/capture/capture_protocol.hpp (1)

52-66: LGTM on the new IPC message types.

The CaptureConfigRequest and CaptureInputDisplayReady structures are well-designed:

  • Fixed 16-byte size with proper alignment
  • int32_t for display_number correctly allows -1 to signal "not ready"
  • Reserved fields provide future extensibility
  • static_assert ensures wire format stability
src/app/main.cpp (1)

197-214: LGTM on InputForwarder initialization and integration.

Good design choices:

  • Graceful degradation when input forwarding fails (logs warning, continues without it)
  • Display number handshake properly wired to CaptureReceiver
  • Stack allocation with pointer passing ensures proper lifetime management
tests/CMakeLists.txt (1)

86-94: The goggles_input_test executable does not need to link against goggles_input. The test file contains only SDL and Vulkan initialization code with a simple input event loop that prints SDL events. It does not include or use any classes/functions from the goggles_input library (no InputForwarder, XWaylandServer, or other module symbols). The current link dependencies are correct.

Likely an incorrect or invalid review comment.

openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md (3)

5-40: LGTM!

The Config Handshake Protocol requirement is well-structured with comprehensive scenarios covering the constructor flow, message format, timeout handling, priority ordering, and connection independence. The specification correctly uses the #### Scenario: format with WHEN/THEN/AND structure as required by the coding guidelines.


42-60: LGTM!

The Protocol Message Types requirement clearly defines the new IPC message structures with explicit size requirements (16 bytes) and proper numbering to avoid conflicts. The scenarios correctly document the wire format and reserved ranges.


62-76: LGTM!

The CaptureReceiver Config Handling requirement properly specifies the receiver's branching logic and response construction. The scenarios clearly describe the connection lifecycle and integration with InputForwarder.

src/input/xwayland_server.cpp (3)

27-31: LGTM!

The constructor and destructor pattern is correct. The destructor safely calls stop(), which is idempotent and handles the case where start() was never called or failed.


33-175: LGTM with one verification note.

The start() method follows a solid initialization pattern with comprehensive error handling. Each failure triggers appropriate partial cleanup in reverse dependency order. The socket binding loop (1-9) is reasonable, and the compositor thread is correctly spawned after all resources are initialized.


177-226: Excellent resource cleanup ordering.

The stop() method demonstrates careful attention to destruction order. The explicit thread join before destroying objects (line 184-187) prevents use-after-free, and the comments clearly document dependencies (e.g., XWayland must be destroyed before compositor, seat before display). All pointers are defensively nulled after destruction.

src/input/input_forwarder.hpp (1)

1-31: LGTM!

The header demonstrates excellent API design with proper PIMPL pattern, appropriate use of [[nodiscard]] on all public methods, and correctly deleted copy/move operations. The interface is clean and well-organized.

tests/input/goggles_input_test.cpp (2)

49-99: LGTM!

The initialization functions are straightforward and appropriate for a test harness. The forced X11 video driver (line 74) is necessary for testing the XWayland integration. Error handling via std::exit(1) is acceptable for a test binary.


101-182: LGTM!

The event logging and input loop are well-structured. The initialization order in main() (Vulkan first to trigger layer loading, then SDL) is correctly documented and necessary for testing the DISPLAY handshake flow.

src/input/input_forwarder.cpp (6)

11-77: LGTM!

The keycode translation tables correctly map SDL scancodes to Linux input event codes and then to X11 keycodes (+8 offset). The coverage is limited to common keys, which is appropriate for the initial implementation. Unmapped keys return 0 and are safely handled by the caller.


80-93: LGTM!

The Impl struct demonstrates proper RAII with correct destruction order: X11 display is closed explicitly, then the XWaylandServer is destroyed automatically. The constructor and destructor implementations are correct.


95-115: LGTM with one cleanup note.

The init() method correctly starts the XWayland server, opens the X11 connection, and properly cleans up on failure. Error propagation is handled well through the Result<void> type. See separate comment about the outdated TODO.


117-149: LGTM!

Both methods properly guard against uninitialized state and correctly use XTest to generate events. The comment on line 141 correctly notes that SDL and X11 use the same button numbering. The XFlush calls ensure events are delivered immediately.


151-167: LGTM - acknowledged limitation.

The mouse motion forwarding has a known limitation (no coordinate scaling) that's documented in both the code (lines 159-162) and the PR objectives. The 1:1 mapping is acceptable for the initial implementation, with proper scaling deferred to Phase 2 as noted in the design doc.


169-199: LGTM!

The mouse wheel forwarding correctly maps scroll directions to X11 button events (4=up, 5=down, 6/7=horizontal). The press+release pair (lines 189-190) is the correct X11 protocol for wheel events. The display_number() method properly delegates to the server.

src/input/xwayland_server.hpp (1)

1-47: LGTM!

The header demonstrates excellent design with minimal dependencies through forward declarations, appropriate NOLINT suppressions for external types, and a clean public interface. The use of std::jthread (line 43) ensures automatic thread joining, and the inline display_number() getter is appropriate for a trivial accessor.

openspec/changes/add-input-forwarding/design.md (1)

1-385: Excellent design documentation.

The design document is comprehensive, well-organized, and accurately reflects the implementation. The architecture layers are clearly explained, the IPC protocol extension is properly documented, and the RAII resource management strategy matches the implementation. The comparisons with alternatives and dependency justifications provide valuable context for the design decisions.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/capture/capture_receiver.cpp (1)

357-360: Duplicate: Distinguish EAGAIN from genuinely incomplete messages.

This recv() error-handling pattern has the same issue flagged in the previous review. The socket is non-blocking, so recv() can return -1 with EAGAIN/EWOULDBLOCK when data isn't ready yet, but the current code logs "incomplete message received" for any return value other than sizeof(request), which is misleading.

Refer to the past review comment on lines 352-365 for the detailed fix. The suggested approach is to explicitly check for recv() == -1 and inspect errno to differentiate between EAGAIN/EWOULDBLOCK (data not ready) versus actual recv errors, and handle partial reads separately.

🧹 Nitpick comments (1)
src/capture/capture_receiver.cpp (1)

103-112: Consider logging peek failures for easier debugging.

When the recv with MSG_PEEK fails (e.g., due to errors other than EAGAIN), the code silently falls through to the normal frame capture flow. While this is functionally safe, adding debug-level logging for unexpected peek failures would help diagnose issues where config requests are missed.

🔎 Optional enhancement
 // Peek at message type to detect config requests
 CaptureMessageType msg_type{};
 ssize_t peeked = recv(new_fd, &msg_type, sizeof(msg_type), MSG_PEEK | MSG_DONTWAIT);

+if (peeked < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
+    GOGGLES_LOG_DEBUG("Peek failed on new connection: {}", strerror(errno));
+}
+
 if (peeked == sizeof(msg_type) && msg_type == CaptureMessageType::config_request) {
     // This is a config request connection - handle and close
     handle_config_request(new_fd);
     close(new_fd);
     return false; // Don't set as client_fd
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aaee578 and c257b8e.

📒 Files selected for processing (9)
  • cmake/Dependencies.cmake
  • src/app/main.cpp
  • src/capture/capture_protocol.hpp
  • src/capture/capture_receiver.cpp
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.hpp
  • src/capture/vk_layer/vk_capture.cpp
  • src/input/CMakeLists.txt
  • tests/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (8)
  • cmake/Dependencies.cmake
  • tests/CMakeLists.txt
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.hpp
  • src/input/CMakeLists.txt
  • src/capture/vk_layer/vk_capture.cpp
  • src/capture/capture_protocol.hpp
  • src/app/main.cpp
🧰 Additional context used
🧬 Code graph analysis (1)
src/capture/capture_receiver.cpp (2)
src/capture/capture_receiver.hpp (1)
  • fd (52-52)
src/util/unique_fd.hpp (1)
  • fd (34-34)
🔇 Additional comments (1)
src/capture/capture_receiver.cpp (1)

368-370: Layer already validates and handles negative display_number gracefully.

The vk_capture layer explicitly validates display_number < 0 in vk_capture.cpp:88-92 and keeps the original DISPLAY setting when the value is invalid, rather than failing or retrying. No action needed.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
docs/input_forwarding.md (2)

19-49: Consider adding language identifiers to fenced code blocks.

The static analysis tool flags code blocks without language specifiers. For ASCII diagrams and text flow diagrams, you can use text or plaintext as the language identifier:

-```
+```text
 ┌────────────────────────────────────────────────────────────┐

This applies to blocks at lines 19, 158, 166, and 174.


140-152: Documentation example differs from actual implementation.

The code example shows a simplified version that hardcodes setenv("DISPLAY", ":1", 1), but the actual implementation in vk_capture.cpp performs an IPC handshake to dynamically obtain the display number. Consider updating the example to reflect the actual behavior or add a note clarifying this is a simplified illustration:

 ```cpp
 __attribute__((constructor)) static void
 layer_early_init()
 {
     const char* old_display = getenv("DISPLAY");
-    setenv("DISPLAY", ":1", 1);
-    // App now connects to XWayland on :1 instead of host X server on :0
+    // Performs IPC handshake with Goggles to get display number
+    // Then sets DISPLAY=:N where N is the negotiated display
+    // (simplified - actual implementation uses socket handshake)
 }

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between c257b8efde0512a2d33ec9fb510b2dae69a17f97 and 4810663152dd0d82a17a76598243155191a86aaf.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `docs/input_forwarding.md`
* `src/capture/vk_layer/vk_capture.cpp`
* `tests/input/goggles_input_test.cpp`

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code graph analysis (2)</summary>

<details>
<summary>src/capture/vk_layer/vk_capture.cpp (1)</summary><blockquote>

<details>
<summary>src/capture/vk_layer/ipc_socket.cpp (2)</summary>

* `connect` (25-78)
* `connect` (25-25)

</details>

</blockquote></details>
<details>
<summary>tests/input/goggles_input_test.cpp (1)</summary><blockquote>

<details>
<summary>src/input/input_forwarder.hpp (4)</summary>

* `event` (20-20)
* `event` (21-21)
* `event` (22-22)
* `event` (23-23)

</details>

</blockquote></details>

</details><details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>

<details>
<summary>docs/input_forwarding.md</summary>

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

158-158: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

166-166: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

---

174-174: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)</summary>

* GitHub Check: Static Analysis (clang-tidy)
* GitHub Check: Build and Test

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>src/capture/vk_layer/vk_capture.cpp (1)</summary><blockquote>

`17-98`: **Well-structured early initialization with proper error handling.**

The `layer_early_init()` constructor correctly:
- Uses a blocking socket (no `SOCK_NONBLOCK`) suitable for synchronous config handshake
- Validates `display_number >= 0` before setting DISPLAY
- Closes the socket on all error paths
- Uses appropriate 100ms timeout for the handshake

The past review comment about the unused `LayerSocketClient config_socket` variable has been addressed — it's no longer present.

</blockquote></details>
<details>
<summary>docs/input_forwarding.md (1)</summary><blockquote>

`106-123`: **Documentation now accurately reflects current mouse support.**

The limitations section correctly states "Basic mouse support: Mouse button, motion, and wheel events are forwarded but coordinate mapping is 1:1", which aligns with the implementation. The past review comment about outdated mouse support documentation has been addressed.

</blockquote></details>
<details>
<summary>tests/input/goggles_input_test.cpp (4)</summary><blockquote>

`8-36`: **Move semantics correctly implemented.**

The `VulkanInstance` now properly implements move operations that null out the source handle, preventing the double-free issue flagged in the past review. The move assignment also correctly destroys any existing handle before taking ownership.

---

`38-92`: **SDLContext move semantics and initialization tracking correctly implemented.**

The past review concerns have been addressed:
- Added `initialized` flag (line 41) that tracks whether `SDL_Init` was called
- Move operations properly transfer all state including `initialized` and null out the source
- Destructor only calls `SDL_Quit()` if `initialized` is true (lines 52-54)

---

`96-148`: **Initialization functions are well-structured.**

Both `init_vulkan()` and `init_sdl()` properly:
- Handle errors with informative messages
- Use RAII wrappers that ensure cleanup even on early exit
- Set `sdl.initialized = true` only after `SDL_Init` succeeds (line 128)

The forced X11 backend via `SDL_VIDEODRIVER=x11` (line 121) is appropriate for testing the XWayland input forwarding path.

---

`213-231`: **Correct initialization order for layer testing.**

The deliberate ordering—Vulkan initialization first (line 217) followed by SDL (line 221)—ensures the Vulkan layer's `layer_early_init()` constructor runs and sets `DISPLAY` before SDL attempts to connect to the X server. The comments clearly document this design intent.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/input/xwayland_server.cpp (1)

120-131: Consider expanding the display number range.

The current range of 1-9 provides only 9 possible display numbers. On systems with many X servers or Wayland compositors, this might be limiting. Consider extending to a larger range (e.g., 1-99) to reduce collision probability.

🔎 Suggested change
-    for (int display_num = 1; display_num < 10; ++display_num) {
+    for (int display_num = 1; display_num < 100; ++display_num) {

And update the error message:

         return make_error<int>(ErrorCode::input_init_failed,
-                               "No available DISPLAY numbers (1-9 all bound)");
+                               "No available DISPLAY numbers (1-99 all bound)");
src/input/xwayland_server.hpp (1)

21-34: Add class documentation.

The class design is sound with proper RAII semantics (deleted copy/move operations) and good error handling (Result return type). However, the class lacks documentation explaining its purpose, lifecycle, and thread safety characteristics.

🔎 Suggested documentation
 namespace goggles::input {
 
+/**
+ * @brief Manages a headless Wayland compositor with nested XWayland server.
+ *
+ * Creates a wlroots-based headless Wayland compositor and spawns an XWayland
+ * server, automatically selecting an available DISPLAY number (1-9).
+ * Runs the compositor in a background thread.
+ *
+ * Lifecycle:
+ * - Construct → start() → use display_number() → stop() or destructor
+ * - start() may be called only once; subsequent calls are undefined
+ * - stop() is idempotent and called automatically by the destructor
+ *
+ * Thread safety:
+ * - The compositor runs in m_compositor_thread
+ * - stop() synchronizes by terminating the display and joining the thread
+ * - Do not call start()/stop() concurrently from multiple threads
+ */
 class XWaylandServer {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4810663 and d3088a9.

📒 Files selected for processing (2)
  • src/input/xwayland_server.cpp
  • src/input/xwayland_server.hpp
🧰 Additional context used
🧬 Code graph analysis (2)
src/input/xwayland_server.cpp (3)
src/input/xwayland_server.hpp (4)
  • XWaylandServer (23-23)
  • XWaylandServer (24-24)
  • XWaylandServer (26-26)
  • XWaylandServer (28-28)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.hpp (2)
  • display_num (38-38)
  • display_num (38-38)
src/input/xwayland_server.hpp (2)
src/input/xwayland_server.cpp (2)
  • XWaylandServer (27-27)
  • XWaylandServer (29-31)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
🔇 Additional comments (9)
src/input/xwayland_server.cpp (7)

1-24: LGTM!

The includes and external declarations are well-organized. The manual forward declarations for wlr_xdg_shell functions and the class keyword workaround for xwayland.h are appropriate solutions for C/C++ interop with wlroots.


27-31: LGTM!

The default constructor and destructor design is sound. The destructor calling stop() ensures proper RAII cleanup of resources.


89-101: Previous issue resolved.

The past review comment about missing error handling for wlr_xdg_shell_create has been addressed. The code now properly checks for NULL and returns an error with appropriate cleanup.


120-146: LGTM!

The socket binding logic correctly tries display numbers 1-9 and returns an appropriate error if none are available. The error handling is thorough.


148-187: LGTM!

The XWayland creation, backend startup, and compositor thread initialization are well-structured with proper error handling and cleanup on failure.


189-243: LGTM!

The stop() method demonstrates excellent resource management:

  • Explicitly joins the compositor thread before destroying objects to prevent use-after-free
  • Destroys objects in the correct order with clear comments explaining dependencies
  • Properly resets all pointers to nullptr
  • Handles the case where stop() is called multiple times

118-118: No error checking needed for wlr_seat_set_capabilities — the function returns void.

wlr_seat_set_capabilities does not return an error code; it has a void return type and cannot fail in a way that produces a checkable return value. The function simply updates the seat's capability flags and sends the update to clients. Responsibility for validity (e.g., ensuring m_seat is non-NULL) rests with the caller.

Likely an incorrect or invalid review comment.

src/input/xwayland_server.hpp (2)

1-18: LGTM!

The header structure is clean with appropriate includes and forward declarations. The NOLINT comments for wlroots types correctly suppress naming convention warnings for external library types.


35-47: LGTM!

The private member variables are well-organized:

  • All resource pointers properly initialized to nullptr
  • m_display_number uses -1 as a sentinel value
  • std::jthread automatically joins on destruction, complementing the RAII design

@K1ngst0m K1ngst0m force-pushed the dev/kingstom/add-input-forwarding branch from d3088a9 to 7782f7a Compare December 28, 2025 00:33
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/input/xwayland_server.cpp (1)

62-63: Add error handling for wlr_renderer_init_wl_display.

The return value of wlr_renderer_init_wl_display is ignored. This function returns bool and can fail (e.g., when wl_display_init_shm() fails or wlr_renderer_get_formats() returns NULL). All other wlroots initialization calls in this function check for errors, making this omission inconsistent and potentially unsafe.

🔎 Proposed fix
-    // XWayland crashes (segfault in wl_proxy_get_version) without protocol init
-    wlr_renderer_init_wl_display(m_renderer, m_display);
+    // XWayland crashes (segfault in wl_proxy_get_version) without protocol init
+    if (!wlr_renderer_init_wl_display(m_renderer, m_display)) {
+        wlr_renderer_destroy(m_renderer);
+        wlr_backend_destroy(m_backend);
+        wl_display_destroy(m_display);
+        m_renderer = nullptr;
+        m_backend = nullptr;
+        m_display = nullptr;
+        return make_error<int>(ErrorCode::input_init_failed, "Failed to initialize renderer for Wayland display");
+    }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3088a9 and 7782f7a.

📒 Files selected for processing (3)
  • src/capture/vk_layer/ipc_socket.hpp
  • src/input/xwayland_server.cpp
  • src/input/xwayland_server.hpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/capture/vk_layer/ipc_socket.hpp
🧰 Additional context used
🧬 Code graph analysis (2)
src/input/xwayland_server.cpp (3)
src/input/xwayland_server.hpp (4)
  • XWaylandServer (23-23)
  • XWaylandServer (24-24)
  • XWaylandServer (26-26)
  • XWaylandServer (28-28)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.hpp (2)
  • display_num (38-38)
  • display_num (38-38)
src/input/xwayland_server.hpp (2)
src/input/xwayland_server.cpp (2)
  • XWaylandServer (27-27)
  • XWaylandServer (29-31)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
🔇 Additional comments (3)
src/input/xwayland_server.hpp (1)

1-49: LGTM: Well-designed RAII header.

The header demonstrates solid design:

  • Forward declarations minimize compilation dependencies.
  • Non-copyable/non-movable semantics are appropriate for managing external C resources.
  • Member initialization (m_display_number = -1, pointers = nullptr) prevents undefined behavior.
  • The inline getter is clean and appropriate.
src/input/xwayland_server.cpp (2)

27-31: LGTM: Proper RAII pattern.

The destructor correctly delegates cleanup to stop(), ensuring resources are released even if the caller forgets to call stop() explicitly.


189-243: LGTM: Correct and well-ordered cleanup.

The stop() method demonstrates careful attention to destruction order:

  • Terminates the display and joins the thread before destroying objects (preventing use-after-free).
  • Destroys XWayland before compositor, seat before display, as required by wlroots lifecycle constraints.
  • Explicitly nulls all pointers for idempotency.

The inline comments documenting ordering constraints are valuable.

@K1ngst0m
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 28, 2025

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
src/app/CMakeLists.txt (1)

15-15: Pipeline failure due to missing wlroots dependency.

The goggles_input linkage is correct, but the build fails because the input subdirectory cannot be built without wlroots-0.18. See the review comment on src/CMakeLists.txt for details on addressing the missing dependency.

src/capture/capture_receiver.cpp (1)

352-379: Address past review comments on recv/send error handling.

The implementation of handle_config_request has the same recv/send error handling issues identified in past review comments:

  1. Lines 355-360: recv() error handling doesn't distinguish between EAGAIN/EWOULDBLOCK (no data ready) and genuine errors or incomplete reads on a non-blocking socket.

  2. Lines 372-376: send() error handling conflates send errors (sent == -1) with partial sends, making diagnostics unclear.

The past review comments provide detailed fixes for both issues. Please implement those suggestions to properly handle non-blocking socket behavior and provide clear error diagnostics.

🧹 Nitpick comments (4)
docs/input_forwarding.md (1)

11-11: Minor markdown formatting issues flagged by linter.

Static analysis identified two formatting issues:

  1. Line 11: Fenced code block missing language specifier
  2. Line 178: **Advantages**: uses emphasis instead of a proper heading
🔎 Proposed markdown fixes

Fix line 11 (add language specifier):

-```
+```text
 ┌────────────────────────────────────────────────────────────┐
 │  Goggles Application (DISPLAY=:0, viewer window)           │

Fix line 178 (use proper heading):

-**Advantages**: No special permissions, works with Wine, minimal dependencies, no display output.
+#### Advantages
+
+No special permissions, works with Wine, minimal dependencies, no display output.

Also applies to: 178-178

openspec/changes/add-input-forwarding/proposal.md (2)

20-47: Add language identifier to architecture diagram.

The fenced code block containing the ASCII architecture diagram should specify a language for proper rendering. Consider using text or mermaid if converting to a proper diagram format.

🔎 Proposed fix
-```
+```text
 ┌─────────────────────────────────────────┐
 │  Goggles (DISPLAY=:0)                   │

Based on static analysis hints.


1-120: Consider adding 'What Changes' section per coding guidelines.

The coding guidelines specify that openspec/changes/**/proposal.md files should list spec deltas under a 'What Changes' section, marking breaking changes with BREAKING. Consider adding this section to document the protocol and API additions (e.g., new message types, InputForwarder API, XWaylandServer).

As per coding guidelines.

src/input/input_forwarder.cpp (1)

95-115: LGTM: Robust initialization with proper cleanup on failure.

The init() method demonstrates good error handling:

  • Starts XWayland server and checks result
  • Opens X11 display connection
  • Stops the server if display connection fails (prevents resource leak)
  • Clear error messages with context

Line 112: The TODO comment appears outdated—the display number is already communicated via display_number() method and used by CaptureReceiver (per the integration in main.cpp).

🔎 Remove outdated TODO
-    // TODO: Send DISPLAY number to layer via IPC (Task 16)
-
     return {};
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cfa24e and cf79c2e.

📒 Files selected for processing (23)
  • cmake/Dependencies.cmake
  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/tasks.md
  • src/CMakeLists.txt
  • src/app/CMakeLists.txt
  • src/app/main.cpp
  • src/capture/capture_protocol.hpp
  • src/capture/capture_receiver.cpp
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.hpp
  • src/capture/vk_layer/vk_capture.cpp
  • src/input/CMakeLists.txt
  • src/input/input_forwarder.cpp
  • src/input/input_forwarder.hpp
  • src/input/xwayland_server.cpp
  • src/input/xwayland_server.hpp
  • src/util/error.hpp
  • tests/CMakeLists.txt
  • tests/input/goggles_input_test.cpp
🧰 Additional context used
📓 Path-based instructions (3)
openspec/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/*.md: Use #### Scenario: format (4 hashtags) for scenario headers in requirements, not bullets or bold text, with at least one scenario per requirement
Use format - **WHEN** [condition] - **THEN** [expected result] for scenario steps in requirements

Files:

  • openspec/changes/add-input-forwarding/tasks.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
openspec/changes/**/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

List spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING

Files:

  • openspec/changes/add-input-forwarding/proposal.md
openspec/changes/**/specs/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/*.md: Write spec deltas using ## ADDED|MODIFIED|REMOVED|RENAMED Requirements format with at least one #### Scenario: per requirement in spec files
When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement
Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Files:

  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
🧠 Learnings (2)
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Create a change proposal with `proposal.md`, `tasks.md`, and optional `design.md` under `openspec/changes/<change-id>/` before implementing changes to add features, make breaking changes, or update architecture

Applied to files:

  • openspec/changes/add-input-forwarding/tasks.md
🧬 Code graph analysis (9)
src/input/input_forwarder.hpp (3)
src/input/input_forwarder.cpp (2)
  • InputForwarder (91-91)
  • InputForwarder (93-93)
src/input/xwayland_server.hpp (1)
  • nodiscard (33-33)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
src/app/main.cpp (2)
src/input/input_forwarder.hpp (4)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/capture/capture_receiver.hpp (2)
  • display_num (38-38)
  • display_num (38-38)
src/input/xwayland_server.cpp (3)
src/input/xwayland_server.hpp (4)
  • XWaylandServer (23-23)
  • XWaylandServer (24-24)
  • XWaylandServer (26-26)
  • XWaylandServer (28-28)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.hpp (2)
  • display_num (38-38)
  • display_num (38-38)
src/capture/vk_layer/vk_capture.cpp (1)
src/capture/vk_layer/ipc_socket.cpp (2)
  • connect (25-78)
  • connect (25-25)
tests/input/goggles_input_test.cpp (1)
src/input/input_forwarder.hpp (4)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/capture/capture_receiver.cpp (1)
src/capture/capture_receiver.hpp (1)
  • fd (52-52)
src/input/input_forwarder.cpp (4)
src/input/input_forwarder.hpp (8)
  • InputForwarder (11-11)
  • InputForwarder (12-12)
  • InputForwarder (14-14)
  • InputForwarder (16-16)
  • event (20-20)
  • event (21-21)
  • event (22-22)
  • event (23-23)
src/capture/capture_receiver.cpp (2)
  • init (22-60)
  • init (22-22)
src/util/error.hpp (1)
  • make_error (47-49)
src/capture/capture_receiver.hpp (2)
  • display_num (38-38)
  • display_num (38-38)
src/input/xwayland_server.hpp (2)
src/input/xwayland_server.cpp (2)
  • XWaylandServer (27-27)
  • XWaylandServer (29-31)
src/util/error.hpp (2)
  • nodiscard (47-51)
  • nodiscard (53-89)
src/capture/capture_receiver.hpp (2)
src/util/unique_fd.hpp (1)
  • fd (34-34)
src/capture/vk_layer/ipc_socket.hpp (1)
  • data (23-23)
🪛 GitHub Actions: CI
src/input/CMakeLists.txt

[error] 15-15: CMake configuration failed. The following required packages were not found: wlroots-0.18.

tests/CMakeLists.txt

[error] 15-15: CMake configuration failed. The following required packages were not found: wlroots-0.18.

src/CMakeLists.txt

[error] 15-15: CMake configuration failed. The following required packages were not found: wlroots-0.18.

src/app/CMakeLists.txt

[error] 15-15: CMake configuration failed. The following required packages were not found: wlroots-0.18.

🪛 markdownlint-cli2 (0.18.1)
openspec/changes/add-input-forwarding/proposal.md

11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)

docs/input_forwarding.md

11-11: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


120-120: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


178-178: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (30)
openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md (1)

1-76: LGTM! Clean separation of config and frame capture protocols.

The spec follows the required format with ## ADDED Requirements and #### Scenario: headers. The two-phase handshake design (config connection in constructor, frame capture on present) elegantly solves the timing problem where DISPLAY must be set before app initialization. The message type numbering (6, 7) appropriately skips 4-5 for future use, and the 100ms timeout for config response is reasonable for local IPC.

openspec/changes/add-input-forwarding/design.md (1)

1-385: Excellent design documentation with comprehensive error handling.

The design document thoroughly covers all aspects of the input forwarding architecture:

  • Clear API surface with PIMPL pattern for InputForwarder
  • Well-defined layer architecture (SDL → InputForwarder → XWaylandServer → IPC → VK Layer)
  • Robust error handling with Result<T> pattern and graceful degradation
  • RAII-based resource management with explicit cleanup order for wlroots objects
  • Performance characteristics documented (~1 MB memory, <1% CPU, ~1-2 ms latency)
  • Comprehensive comparison with rejected alternatives (uinput, Cage)

The two-phase IPC protocol (config handshake in constructor, frame capture on present) elegantly solves the DISPLAY timing requirement.

src/capture/vk_layer/ipc_socket.hpp (1)

6-8: LGTM! Headers required for config handshake.

The added POSIX headers (poll.h, sys/socket.h, unistd.h) are necessary for the config handshake socket operations performed in layer_early_init() (poll timeout, socket creation, connect, send/recv, close).

src/capture/vk_layer/vk_capture.cpp (1)

17-98: Excellent implementation with robust error handling.

The layer_early_init() constructor implements the config handshake exactly as specified:

  • Constructor priority 101 ensures execution before default constructors
  • Clean error handling on every operation with early returns
  • Validates display_number >= 0 before use (line 85)
  • Preserves original DISPLAY on any failure (graceful degradation)
  • 100ms timeout matches spec (line 62)
  • Comprehensive logging for debugging

The implementation correctly uses poll() for the timeout and validates the message type before processing the response.

src/capture/capture_receiver.hpp (3)

37-38: LGTM! Simple setter for config handshake.

The inline setter is appropriate for passing the DISPLAY number from InputForwarder to the capture receiver. The comment clearly documents its purpose.


52-52: LGTM! Private method for config handshake.

The private declaration is appropriate for handling configuration requests separately from the main frame capture flow.


58-58: LGTM! State for input display number.

The default value of -1 serves as a clear sentinel for "not set", and the comment documents the data source.

src/util/error.hpp (2)

26-27: LGTM! Error codes for input forwarding.

The new error codes follow the existing naming convention and cover input initialization and IPC socket failures.


79-82: LGTM! Error code name mappings.

The switch cases correctly map the new error codes to their string representations.

tests/CMakeLists.txt (1)

88-108: LGTM! Test executable setup is correct.

The goggles_input_test executable is properly configured with SDL3, Vulkan, and goggles_util dependencies. The build configuration, output directory, and sanitizer setup follow project conventions.

However, note the pipeline failure indicating missing wlroots-0.18 dependency. This is expected for the new input forwarding feature and should be resolved by installing the required system packages on the CI environment.

Pipeline failure: Missing wlroots dependency.

The CI build fails because wlroots-0.18 is not installed. This is a CI environment issue, not a code issue. Ensure the CI environment is updated to install wlroots-0.18 and its dependencies (wayland-server, xkbcommon, libX11, libXtst) as documented in the PR.

openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md (1)

1-200: LGTM! Comprehensive specification with proper structure.

The specification correctly follows the required format:

  • Uses ## ADDED Requirements for new capabilities
  • Each requirement has at least one #### Scenario: with WHEN/THEN format
  • Covers all key aspects: lifecycle, forwarding, error handling, integration, and conventions
  • Scenarios provide clear acceptance criteria

The spec provides a solid foundation for validating the implementation.

openspec/changes/add-input-forwarding/tasks.md (1)

1-17: LGTM! Clear task tracking with appropriate follow-ups.

The task list effectively documents:

  • Completed implementation items (1.1-1.6)
  • Identified follow-ups for future work (2.1-2.4)
  • Validation steps (3.1-3.2)

The follow-up items appropriately defer non-critical refinements (coordinate mapping, handshake hardening, optional dependencies) while delivering the core functionality.

src/input/CMakeLists.txt (1)

1-30: LGTM! Proper library configuration for input forwarding.

The goggles_input library is correctly set up:

  • Static library with appropriate sources
  • Public/private include directories properly separated
  • Dependencies correctly linked (wlroots, wayland-server, xkbcommon, X11, XTest)
  • Required WLR_USE_UNSTABLE definition for wlroots API access
  • Tooling enabled (clang-tidy, sanitizers, profiling)

The pipeline failure for missing wlroots-0.18 is a CI environment configuration issue, not a code issue. The CMake configuration itself is correct.

Pipeline failure: Missing wlroots dependency (duplicate of tests/CMakeLists.txt issue).

Same CI environment issue as noted in tests/CMakeLists.txt. The build configuration is correct; the CI environment needs wlroots-0.18 and related packages installed.

src/capture/capture_receiver.cpp (1)

103-112: LGTM! Config request detection with MSG_PEEK.

The peek logic correctly routes config_request connections separately from normal frame capture connections:

  • Uses MSG_PEEK | MSG_DONTWAIT to inspect message type without consuming data
  • Delegates to handle_config_request and closes the fd for config connections
  • Returns false to avoid treating config connections as persistent clients

The approach cleanly separates the config handshake from the frame capture protocol.

src/app/main.cpp (3)

19-21: LGTM! Optional InputForwarder parameter for graceful degradation.

The signature change to accept an optional InputForwarder* parameter enables the application to run without input forwarding if initialization fails. This is the right design choice.


36-68: LGTM! Consistent event forwarding with proper error handling.

The SDL event forwarding implementation is well-structured:

  • Each event type (key, mouse button, motion, wheel) is handled consistently
  • Properly guarded with null-check on input_forwarder
  • Errors are logged but don't terminate the application
  • Pattern is clear and maintainable

The per-event error handling prevents cascading failures while providing diagnostic information.


219-234: LGTM! Proper InputForwarder initialization and lifecycle.

The initialization flow correctly implements graceful degradation:

  • Stack-allocated InputForwarder with appropriate lifetime (entire main loop)
  • Error handling logs the failure but allows the app to continue
  • On success, the display number is propagated to capture_receiver via set_input_display
  • Pointer is passed to run_main_loop only when initialization succeeds

The lifecycle management is correct: the forwarder lives for the duration of run_main_loop and is automatically cleaned up on function exit.

src/input/xwayland_server.cpp (2)

33-206: LGTM: Comprehensive initialization with proper error handling.

The start() method demonstrates solid resource management:

  • Sequential initialization with proper error checking at each step
  • Consistent cleanup on all error paths (including m_xdg_shell and m_seat that were missing in earlier commits)
  • RAII thread management with std::jthread
  • Clear separation of concerns

The past review issues regarding resource leaks have been properly addressed.


208-262: LGTM: Well-ordered resource cleanup.

The stop() method properly manages shutdown:

  • Guards against double-cleanup with null check
  • Explicitly joins compositor thread before destroying objects (prevents use-after-free)
  • Destruction order respects wlroots object dependencies
  • All pointers are nulled after destruction

The comment at line 237 correctly notes that compositor is auto-destroyed, matching the implementation.

src/capture/capture_protocol.hpp (2)

20-21: LGTM: Wire protocol extension follows established pattern.

The new message types (config_request and input_display_ready) are properly integrated into the existing protocol enum with sequential numbering and consistent uint32_t backing for wire format stability.


74-88: LGTM: Well-designed protocol structs with forward compatibility.

Both new structs follow the established pattern:

  • Explicit type tag and version fields
  • Reserved space for future extensions
  • Static size assertions for wire format stability
  • display_number as int32_t appropriately allows -1 for error cases
src/input/input_forwarder.hpp (1)

1-31: LGTM: Clean PIMPL-based public API with proper error handling.

The interface design demonstrates good practices:

  • PImpl pattern provides ABI stability and encapsulation
  • Deleted copy/move operations prevent resource management issues
  • [[nodiscard]] attributes enforce error checking on all fallible operations
  • Consistent use of Result<void> for error propagation
  • Clear separation between initialization (init()), forwarding operations, and query (display_number())
tests/input/goggles_input_test.cpp (3)

8-36: LGTM: Proper RAII with correct move semantics.

The VulkanInstance wrapper correctly implements resource management:

  • Move constructor transfers ownership and nulls the source
  • Move assignment performs cleanup before transfer
  • Destructor safely checks for valid handle
  • Uses VK_NULL_HANDLE for moved-from state

The past double-free issue has been properly resolved.


38-92: LGTM: Proper SDL resource management with move semantics.

The SDLContext wrapper correctly handles SDL resources:

  • initialized flag prevents calling SDL_Quit() on moved-from or uninitialized objects
  • Move operations transfer all state (window, renderer, initialized)
  • Destructor conditionally cleans up each resource
  • Move assignment properly cleans up existing resources before transfer

The past issue regarding double-cleanup has been properly resolved.


96-231: LGTM: Well-structured test harness for input forwarding.

The test implementation properly exercises the input forwarding stack:

  • Correct initialization order (Vulkan first to trigger layer, then SDL)
  • Proper RAII with RAII wrappers for resources
  • Clear logging for debugging
  • Simple but effective event loop

Line 121 uses setenv() which is POSIX-specific. This is acceptable for a Linux-focused input forwarding feature (requires X11/Wayland), but note that the test won't compile on Windows.

src/input/input_forwarder.cpp (4)

14-75: LGTM: Correct keycode mappings for basic input.

The keycode translation functions are properly implemented:

  • sdl_to_linux_keycode() correctly maps to Linux input-event-codes values
  • linux_to_x11_keycode() applies the documented X11 protocol offset (+8)
  • Returns 0 for unmapped keys, handled gracefully by callers
  • Coverage includes essential keys (A-Z, 0-9, arrows, modifiers, common special keys)

Limited coverage (no F-keys, numpad, etc.) aligns with the proposal's basic input goals.


117-134: LGTM: Correct keyboard event forwarding via XTest.

The keyboard forwarding implementation is solid:

  • Gracefully handles uninitialized display (early return)
  • Maps SDL scancodes to X11 keycodes
  • Silently ignores unmapped keys (returns success)
  • Uses XTestFakeKeyEvent() to generate real X11 events
  • XFlush() ensures immediate delivery

136-194: LGTM: Mouse event forwarding with documented limitations.

The mouse event forwarding is properly implemented:

  • Button events (lines 136-149): Direct mapping between SDL and X11 button numbers
  • Motion events (lines 151-167): Basic 1:1 coordinate forwarding with TODO at line 159 correctly noting the limitation (coordinate mapping is listed as a non-goal in the proposal)
  • Wheel events (lines 169-194): Correct X11 protocol usage (buttons 4/5/6/7 for wheel directions)

All methods properly check for initialized display and use XFlush() for immediate delivery.


196-198: LGTM: Clean delegation to underlying server.

The display_number() method correctly delegates to the XWayland server.

src/input/xwayland_server.hpp (1)

1-49: LGTM: Well-designed header with proper encapsulation.

The header demonstrates good design practices:

  • Forward declarations minimize compile-time dependencies
  • NOLINT suppressions appropriately document external library naming conventions
  • Deleted copy/move operations prevent resource management issues
  • Raw pointers for wlroots types with manual lifetime management (RAII patterns in .cpp)
  • std::jthread for automatic thread joining
  • Clean public interface with [[nodiscard]] on fallible operations

- Documented cross-process GPU synchronization.
- Revised DMA-BUF sharing narrative and simplified filter-chain diagrams to reflect new headless and sync workflows.
- Add `goggles_input` library with headless wlroots XWayland server
- Inject keyboard/mouse events via X11 XTest
- Wire SDL event loop to `InputForwarder` for key/mouse forwarding
- Extend capture protocol with config messages so the layer can set DISPLAY early
- Add `goggles_input_test` to validate end-to-end forwarding
@K1ngst0m K1ngst0m force-pushed the dev/kingstom/add-input-forwarding branch from 9b9ac28 to da4b842 Compare January 2, 2026 10:05
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (5)
cmake/Dependencies.cmake (1)

64-74: Hard dependency on wlroots-0.18 already flagged in previous review.

The past review comment comprehensively addresses making input forwarding optional to avoid build breakage on systems without wlroots-0.18.

tests/CMakeLists.txt (1)

96-100: Missing goggles_input library link already flagged in previous review.

The past review comment correctly identifies that the test will fail to link without the goggles_input library.

src/capture/vk_layer/vk_capture.cpp (1)

34-34: Fix potential NULL dereference in LAYER_DEBUG after setenv.

Line 34 passes getenv("DISPLAY") directly to fprintf (via LAYER_DEBUG macro) without checking for NULL. While setenv on line 32 should succeed, defensive coding requires a NULL check to prevent a crash if the environment variable is unexpectedly unset. This issue was flagged in a previous review but remains unresolved in the current code.

🔎 Proposed fix
     snprintf(display_str, sizeof(display_str), ":%d", response.display_number);
     setenv("DISPLAY", display_str, 1);
 
-    LAYER_DEBUG("layer_early_init: DISPLAY now '%s'", getenv("DISPLAY"));
+    const char* new_display = getenv("DISPLAY");
+    LAYER_DEBUG("layer_early_init: DISPLAY now '%s'", new_display ? new_display : "(unset)");
 }

Based on static analysis hints and past review comments.

openspec/changes/add-input-forwarding/proposal.md (2)

97-97: Add BREAKING marker to IPC handshake change.

Past review comment (already raised) noted that the new IPC handshake phase breaks backward compatibility:

  • Old layers won't send config_request.
  • Old viewers won't understand new message types.
  • Protocol sequence fundamentally altered.

This change must be marked as BREAKING per coding guidelines for openspec/changes/**/proposal.md.

🔎 Proposed fix
 ### Protocol / IPC
 - New `CaptureMessageType` enum: `config_request`, `input_display_ready`
 - New message struct: `CaptureConfigRequest` (layer → viewer)
 - New message struct: `CaptureInputDisplayReady` (viewer → layer, contains display number)
-- New IPC handshake phase: config negotiation before frame streaming
+- **BREAKING** New IPC handshake phase: config negotiation before frame streaming

102-106: Update method signatures to match implemented API.

Past review comment (already raised) noted that the documented method signatures don't match the actual implementation in src/input/input_forwarder.hpp:

Proposal (lines 102-106):

  • forward_key(uint32_t scancode, bool pressed) -> void
  • forward_mouse_button(uint8_t button, bool pressed) -> void
  • forward_mouse_motion(int32_t x, int32_t y) -> void
  • forward_mouse_wheel(int32_t x, int32_t y) -> void

Actual implementation (from input_forwarder.cpp lines 170-247):

  • forward_key(const SDL_KeyboardEvent& event) -> Result<void>
  • forward_mouse_button(const SDL_MouseButtonEvent& event) -> Result<void>
  • forward_mouse_motion(const SDL_MouseMotionEvent& event) -> Result<void>
  • forward_mouse_wheel(const SDL_MouseWheelEvent& event) -> Result<void>
🔎 Proposed fix
 ### Public API
 - New class `goggles::input::InputForwarder` (PIMPL pattern)
   - `init() -> Result<void>`
-  - `forward_key(uint32_t scancode, bool pressed) -> void`
-  - `forward_mouse_button(uint8_t button, bool pressed) -> void`
-  - `forward_mouse_motion(int32_t x, int32_t y) -> void`
-  - `forward_mouse_wheel(int32_t x, int32_t y) -> void`
+  - `forward_key(const SDL_KeyboardEvent& event) -> Result<void>`
+  - `forward_mouse_button(const SDL_MouseButtonEvent& event) -> Result<void>`
+  - `forward_mouse_motion(const SDL_MouseMotionEvent& event) -> Result<void>`
+  - `forward_mouse_wheel(const SDL_MouseWheelEvent& event) -> Result<void>`
   - `display_number() -> int`
🧹 Nitpick comments (4)
src/input/xwayland_server.hpp (1)

21-47: Consider adding brief documentation comments for public API.

While the implementation is clean, adding minimal documentation for the public methods would clarify:

  • Thread-safety guarantees for display_number()
  • Expected call order (start() before display_number())
  • Resource cleanup guarantees in stop() and destructor
Example documentation style
/// Manages a headless Wayland compositor with nested XWayland server.
/// Not copyable or movable due to native resource ownership.
class XWaylandServer {
public:
    XWaylandServer();
    ~XWaylandServer();

    // ... deleted operations ...

    /// Starts the compositor and XWayland server on an available DISPLAY.
    /// @return Selected display number (e.g., 1 for :1) or error.
    [[nodiscard]] auto start() -> Result<int>;

    /// Stops the compositor thread and cleans up wlroots resources.
    /// Safe to call multiple times. Called automatically by destructor.
    void stop();

    /// Returns the selected display number after start() succeeds.
    /// @return Display number (e.g., 1 for :1), or -1 if not started.
    /// Thread-safe to call after start() completes.
    [[nodiscard]] auto display_number() const -> int { return m_display_number; }

private:
    // ...
};
docs/input_forwarding.md (1)

19-19: Optional: Add language hints to fenced code blocks for markdown linter compliance.

The markdown linter flags these fenced code blocks for missing language specifiers. While the content (ASCII diagrams and pseudo-code flows) doesn't map to a specific language, you can add text to satisfy the linter:

🔎 Example fix for line 19
-```
+```text
 ┌────────────────────────────────────────────────────────────┐
 │  Goggles Application (DISPLAY=:0, viewer window)           │

Apply similar changes to lines 158, 166, and 174.

Based on static analysis hints.

Also applies to: 158-158, 166-166, 174-174

src/input/input_forwarder.cpp (2)

170-187: Consider batching XFlush calls for multiple events.

The current implementation calls XFlush() after every key event (line 184). For rapid key sequences or simultaneous key presses, this may generate unnecessary X11 protocol round-trips.

If input forwarding performance becomes a concern, consider:

  • Batching multiple events before flushing.
  • Flushing once per SDL event loop iteration instead of per event.

For the initial implementation, the current approach is acceptable and ensures immediate event delivery.


204-220: TODO acknowledged: coordinate mapping required for production use.

The TODO comment (lines 212-215) correctly identifies that 1:1 coordinate mapping will be incorrect when viewer and target windows have different sizes. This is documented as a non-goal in the proposal but will be essential for practical use.

The comment references "OpenSpec for coordinate mapping task" — ensure this task is tracked in the appropriate backlog.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b9ac28 and da4b842.

⛔ Files ignored due to path filters (1)
  • pixi.lock is excluded by !**/*.lock
📒 Files selected for processing (28)
  • README.md
  • cmake/Dependencies.cmake
  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/tasks.md
  • packages/wlroots_0_18/pixi.toml
  • packages/wlroots_0_18/recipe.yaml
  • pixi.toml
  • src/CMakeLists.txt
  • src/app/CMakeLists.txt
  • src/app/main.cpp
  • src/capture/capture_protocol.hpp
  • src/capture/capture_receiver.cpp
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.cpp
  • src/capture/vk_layer/ipc_socket.hpp
  • src/capture/vk_layer/vk_capture.cpp
  • src/input/CMakeLists.txt
  • src/input/input_forwarder.cpp
  • src/input/input_forwarder.hpp
  • src/input/xwayland_server.cpp
  • src/input/xwayland_server.hpp
  • src/util/error.hpp
  • tests/CMakeLists.txt
  • tests/input/goggles_input_test.cpp
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/CMakeLists.txt
  • src/capture/vk_layer/ipc_socket.hpp
  • packages/wlroots_0_18/pixi.toml
  • README.md
  • src/app/CMakeLists.txt
  • src/capture/capture_receiver.cpp
  • packages/wlroots_0_18/recipe.yaml
  • pixi.toml
  • tests/input/goggles_input_test.cpp
🧰 Additional context used
📓 Path-based instructions (3)
openspec/changes/**/specs/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/*.md: Write spec deltas using ## ADDED|MODIFIED|REMOVED|RENAMED Requirements format with at least one #### Scenario: per requirement in spec files
When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement
Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Files:

  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
openspec/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/*.md: Use #### Scenario: format (4 hashtags) for scenario headers in requirements, not bullets or bold text, with at least one scenario per requirement
Use format - **WHEN** [condition] - **THEN** [expected result] for scenario steps in requirements

Files:

  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/tasks.md
openspec/changes/**/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

List spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING

Files:

  • openspec/changes/add-input-forwarding/proposal.md
🧠 Learnings (24)
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/config/goggles.toml : Development/testing configuration: `config/goggles.toml` in repository root. Structure should include sections for [capture], [pipeline], [render], and [logging].

Applied to files:

  • docs/input_forwarding.md
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/**/{CMakeLists.txt,conanfile.txt,conanfile.py} : All dependencies must have explicit versions: CPM specifies `VERSION` parameter (Git tag or commit hash), Conan uses versioning in conanfile. Document rationale for version changes in commit messages.

Applied to files:

  • docs/input_forwarding.md
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Applies to openspec/src/capture/vk_layer/**/*.{cpp,hpp} : Do not perform blocking operations or file I/O in capture layer performance-critical paths (e.g., vkQueuePresentKHR)

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • src/capture/vk_layer/ipc_socket.cpp
  • src/app/main.cpp
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • src/capture/vk_layer/vk_capture.cpp
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Vulkan capture layer is the primary frame capture method, intercepting rendered frames before presentation for zero-copy access

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • src/app/main.cpp
  • src/capture/vk_layer/vk_capture.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/src/capture/vk_layer/**/*.{cpp,hpp} : Vulkan layer code in `src/capture/vk_layer/` has strict logging constraints: use only `error` and `critical` levels, no file I/O or blocking operations in hot paths, prefix all logs with `[goggles_vklayer]`, never log in `vkQueuePresentKHR` hot path.

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • tests/CMakeLists.txt
  • src/app/main.cpp
  • openspec/changes/add-input-forwarding/design.md
  • openspec/changes/add-input-forwarding/proposal.md
  • src/capture/vk_layer/vk_capture.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/src/capture/vk_layer/**/*.{cpp,hpp} : Vulkan layer runs in host application threads (no control). Hooks execute in calling thread (usually render thread). No blocking in hooks, especially `vkQueuePresentKHR`. Use atomics or locks for thread-safe shared layer state.

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • src/app/main.cpp
  • src/capture/vk_layer/vk_capture.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/src/capture/vk_layer/**/*.{cpp,hpp} : Exception - Capture Layer Code (`src/capture/vk_layer/`) is exempt and MUST use the raw Vulkan C API because layer dispatch tables require C function pointers, layer must intercept C API calls from host applications, and minimal dependencies required.

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • src/capture/vk_layer/ipc_socket.cpp
  • src/app/main.cpp
  • openspec/changes/add-input-forwarding/design.md
  • src/capture/vk_layer/vk_capture.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Define a lightweight `Error` struct with `ErrorCode` enum, `std::string message`, and optional `std::source_location` in namespace `goggles`.

Applied to files:

  • src/util/error.hpp
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Applies to openspec/src/capture/vk_layer/**/*.{cpp,hpp} : Capture layer (vk_layer/) MUST use raw Vulkan C API, not vulkan-hpp

Applied to files:

  • openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md
  • src/app/main.cpp
  • src/capture/vk_layer/vk_capture.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/tests/**/*.cpp : Only non-GPU logic is tested initially: in scope are utility functions, configuration parsing, error handling, pipeline graph logic. Out of scope: Vulkan initialization, GPU resource management, rendering, capture layer.

Applied to files:

  • tests/CMakeLists.txt
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Applies to openspec/tests/**/*.{cpp,hpp} : Focus unit tests on non-GPU logic: utility functions, configuration parsing, and pipeline graph logic

Applied to files:

  • tests/CMakeLists.txt
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Applies to openspec/tests/**/*.{cpp,hpp} : Use Catch2 v3 for unit testing with test files mirroring the `src/` directory structure

Applied to files:

  • tests/CMakeLists.txt
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/tests/**/*.{cpp,hpp} : Use Catch2 v3 as the project-wide testing framework.

Applied to files:

  • tests/CMakeLists.txt
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/**/*.{cpp,hpp} : All `vulkan-hpp` calls returning `vk::Result` must be explicitly checked. Prohibited: masking with `static_cast<void>()`. Use `VK_TRY` macro for early return with `vk::Result`, `GOGGLES_TRY` for Result<T> propagation (expression-style), `GOGGLES_MUST` for internal invariants (aborts on failure).

Applied to files:

  • tests/CMakeLists.txt
  • src/input/xwayland_server.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/**/*.{cpp,hpp} : Define project-specific logging macros (`GOGGLES_LOG_TRACE`, `GOGGLES_LOG_DEBUG`, `GOGGLES_LOG_INFO`, `GOGGLES_LOG_WARN`, `GOGGLES_LOG_ERROR`, `GOGGLES_LOG_CRITICAL`) wrapping spdlog for consistent usage.

Applied to files:

  • tests/CMakeLists.txt
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Applies to openspec/src/render/**/*.{cpp,hpp} : Application code MUST use vulkan-hpp with `VULKAN_HPP_NO_EXCEPTIONS` and dynamic dispatch

Applied to files:

  • tests/CMakeLists.txt
  • src/app/main.cpp
📚 Learning: 2025-12-29T15:44:30.499Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.499Z
Learning: Default to single-threaded render loop on main thread; use central job system (goggles::util::JobSystem) for any task parallelism

Applied to files:

  • src/app/main.cpp
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/proposal.md : List spec deltas in `proposal.md` under 'What Changes' section, marking breaking changes with **BREAKING**

Applied to files:

  • openspec/changes/add-input-forwarding/proposal.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Applied to files:

  • openspec/changes/add-input-forwarding/proposal.md
  • openspec/changes/add-input-forwarding/tasks.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Create a change proposal with `proposal.md`, `tasks.md`, and optional `design.md` under `openspec/changes/<change-id>/` before implementing changes to add features, make breaking changes, or update architecture

Applied to files:

  • openspec/changes/add-input-forwarding/proposal.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Write spec deltas using `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` format with at least one `#### Scenario:` per requirement in spec files

Applied to files:

  • openspec/changes/add-input-forwarding/proposal.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement

Applied to files:

  • openspec/changes/add-input-forwarding/proposal.md
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Applies to docs/**/*.{cpp,hpp} : Banned patterns: No `bool` returns for operations that can fail in multiple ways, no `-1`/`0`/`nullptr` as error indicators, no ignored return values. Mark all result-returning functions with `[[nodiscard]]`.

Applied to files:

  • src/input/xwayland_server.cpp
📚 Learning: 2026-01-02T07:02:32.708Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.708Z
Learning: Initialize logger once at application startup on the main thread. Capture layer should initialize logger lazily on first `vkCreateInstance` hook.

Applied to files:

  • src/capture/vk_layer/vk_capture.cpp
🧬 Code graph analysis (8)
src/capture/capture_receiver.hpp (3)
src/util/unique_fd.hpp (1)
  • fd (34-34)
src/capture/vk_layer/ipc_socket.hpp (1)
  • data (20-20)
src/util/serializer.hpp (2)
  • data (18-21)
  • data (18-18)
src/capture/vk_layer/ipc_socket.cpp (2)
src/capture/vk_layer/ipc_socket.hpp (1)
  • response (25-25)
src/capture/capture_receiver.hpp (1)
  • fd (54-54)
src/input/input_forwarder.cpp (3)
src/input/input_forwarder.hpp (8)
  • InputForwarder (11-11)
  • InputForwarder (13-13)
  • InputForwarder (15-15)
  • InputForwarder (26-26)
  • event (19-19)
  • event (20-20)
  • event (21-21)
  • event (22-22)
src/util/error.hpp (2)
  • make_result_ptr_error (63-64)
  • make_result_ptr (57-57)
src/capture/capture_receiver.hpp (2)
  • display_num (46-46)
  • display_num (46-46)
src/app/main.cpp (3)
src/input/input_forwarder.hpp (4)
  • event (19-19)
  • event (20-20)
  • event (21-21)
  • event (22-22)
src/input/input_forwarder.cpp (2)
  • create (146-168)
  • create (146-146)
src/capture/capture_receiver.hpp (2)
  • display_num (46-46)
  • display_num (46-46)
src/input/xwayland_server.hpp (2)
src/input/xwayland_server.cpp (2)
  • XWaylandServer (27-27)
  • XWaylandServer (29-31)
src/util/error.hpp (4)
  • nodiscard (50-54)
  • nodiscard (57-59)
  • nodiscard (62-66)
  • nodiscard (68-104)
src/input/xwayland_server.cpp (3)
src/input/xwayland_server.hpp (4)
  • XWaylandServer (23-23)
  • XWaylandServer (24-24)
  • XWaylandServer (26-26)
  • XWaylandServer (28-28)
src/util/error.hpp (1)
  • make_error (50-52)
src/capture/capture_receiver.hpp (2)
  • display_num (46-46)
  • display_num (46-46)
src/input/input_forwarder.hpp (2)
src/input/input_forwarder.cpp (2)
  • InputForwarder (142-142)
  • InputForwarder (144-144)
src/input/xwayland_server.hpp (1)
  • nodiscard (33-33)
src/capture/vk_layer/vk_capture.cpp (2)
src/capture/vk_layer/ipc_socket.hpp (2)
  • response (25-25)
  • get_layer_socket (35-35)
src/capture/vk_layer/ipc_socket.cpp (2)
  • get_layer_socket (17-20)
  • get_layer_socket (17-17)
🪛 ast-grep (0.40.3)
src/capture/vk_layer/vk_capture.cpp

[warning] 33-33: The getenv function returns NULL on error and this line dereferences the return value without checking for NULL.
Context: LAYER_DEBUG("layer_early_init: DISPLAY now '%s'", getenv("DISPLAY"))
Note: [CWE-476] NULL Pointer Dereference. [REFERENCES]
- https://wiki.sei.cmu.edu/confluence/display/c/EXP34-C.+Do+not+dereference+null+pointers

(null-library-function-cpp)

🪛 markdownlint-cli2 (0.18.1)
docs/input_forwarding.md

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


158-158: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


166-166: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


174-174: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build and Test (test preset)
  • GitHub Check: Static Analysis (clang-tidy)
🔇 Additional comments (36)
src/util/error.hpp (2)

26-27: LGTM: New error codes for input forwarding.

The additions input_init_failed and input_socket_send_failed follow the existing enum pattern and support the new input-forwarding subsystem.


94-97: LGTM: Error code string mappings are correct.

The string mappings for the new error codes match the enum names and follow the established convention.

openspec/changes/add-input-forwarding/specs/input-forwarding/spec.md (1)

1-200: LGTM: Comprehensive specification following openspec format guidelines.

The specification correctly uses:

  • ## ADDED Requirements format for the delta section
  • #### Scenario: format (4 hashtags) for all scenario headers
  • - **WHEN** ... - **THEN** ... format for scenario steps

The requirements comprehensively cover:

  • XWayland server lifecycle and resource cleanup order
  • Keyboard and mouse event forwarding with proper translation maps
  • X11 connection management
  • Error handling and graceful degradation
  • PIMPL pattern for implementation hiding
  • Integration points with main application
  • Namespace and module placement conventions

Each requirement includes at least one scenario with clear acceptance criteria.

src/input/xwayland_server.hpp (4)

1-17: LGTM: Proper forward declarations and header structure.

The header correctly:

  • Uses #pragma once
  • Places wlroots forward declarations in extern "C" block
  • Includes necessary dependencies (thread, error.hpp)
  • Uses NOLINT directives for external library naming conventions

21-29: LGTM: Proper resource management semantics.

The class correctly disables copy and move operations, which is appropriate for a class managing native wlroots resources and a compositor thread that cannot be safely transferred.


35-47: LGTM: Thread lifecycle and resource cleanup properly managed.

The implementation correctly handles thread termination before destruction:

  • wl_display_terminate() signals the event loop to exit
  • Explicit join() call (with joinable() check) ensures the thread fully completes before any resource destruction
  • Wlroots resources are destroyed in reverse initialization order, preventing use-after-free
  • Pointers are properly nullified after destruction

The pattern successfully prevents race conditions and resource leaks.


31-33: The thread safety concern is mitigated by the sequential initialization pattern, and the referenced specification does not exist in the repository.

The display_number() accessor does not require additional synchronization given the current usage:

  1. InputForwarder::create() synchronously calls start() and sets m_display_number before the compositor thread launches (line 111), preventing concurrent writes.
  2. Callers access display_number() only after initialization completes (e.g., main.cpp line 235), not during concurrent operations.
  3. The class's non-copyable, non-movable design enforces single ownership and sequential lifecycle.

However, the justification citing "specification (line 80-81 in spec.md)" is invalid—spec.md does not exist in the repository. If thread-safety expectations should be documented, consider adding a brief comment. Adding noexcept is optional but harmless for clarity.

src/capture/vk_layer/ipc_socket.cpp (1)

274-324: LGTM! Config handshake implementation is sound.

The request_display_config() method correctly implements the pre-main IPC handshake to negotiate DISPLAY. The 100ms blocking poll (line 306) is acceptable in this constructor-time context, and error handling consistently closes the socket on all failure paths. Not acquiring mutex_ is correct since this uses a separate temporary socket (fd) rather than the shared socket_fd_.

src/capture/capture_receiver.hpp (1)

46-46: LGTM! Clean additions for input display config.

The new members integrate cleanly: set_input_display() provides a simple public setter, handle_config_request() encapsulates the handshake logic, and m_input_display_number = -1 correctly defaults to "disabled." No validation is needed in the setter since negative values have semantic meaning.

Also applies to: 54-54, 61-61

src/input/CMakeLists.txt (1)

1-27: LGTM! Build configuration for goggles_input is correct.

The CMakeLists.txt properly configures the input forwarding library with all required dependencies (wlroots, wayland-server, xkbcommon, libX11, libXtst) and sets WLR_USE_UNSTABLE for wlroots API access. The past review comment about missing wlroots pertains to pixi.toml (external to this file), not the CMake configuration itself.

src/app/main.cpp (4)

19-21: LGTM: Signature change enables optional input forwarding.

Changing both capture_receiver and input_forwarder to pointers allows them to be optional (nullable), which is appropriate for this design where input forwarding may not be available.


36-68: LGTM: Event forwarding implementation is correct.

The event forwarding logic is well-implemented with:

  • Consistent null-pointer guards for optional input forwarding
  • Appropriate error handling (log errors without crashing the main loop)
  • Coverage of all relevant input event types (keyboard, mouse buttons, motion, wheel)

227-242: LGTM: InputForwarder initialization is well-structured.

The initialization sequence correctly:

  • Uses the factory pattern with proper error handling
  • Logs both success and failure cases appropriately
  • Propagates the display number to capture_receiver for the IPC handshake
  • Handles the case where capture_receiver may not be available

244-244: LGTM: Function invocation correctly passes optional components.

The call correctly dereferences vulkan_backend (required) and passes raw pointers for optional capture_receiver and input_forwarder components.

openspec/changes/add-input-forwarding/tasks.md (1)

1-17: LGTM: Task tracking document is clear and comprehensive.

The task breakdown is well-structured with:

  • Clear implementation milestones (all completed)
  • Reasonable follow-up items for future work
  • Validation steps to ensure quality

The documented follow-ups (display validation, coordinate mapping, handshake hardening, optional deps) are appropriate for future iterations.

src/capture/capture_protocol.hpp (3)

20-22: LGTM: Enum values follow existing pattern.

The new config_request and input_display_ready message types are correctly added with sequential values (6, 7) following the existing numbering scheme.


72-78: LGTM: CaptureConfigRequest follows protocol struct pattern.

The struct is correctly defined with:

  • Default-initialized type field
  • Version field for protocol evolution
  • Reserved fields for future extensions
  • Proper size assertion (16 bytes)

80-86: LGTM: CaptureInputDisplayReady is correctly defined.

The struct appropriately:

  • Uses int32_t for display_number (allows negative values for error signaling if needed)
  • Includes reserved fields for future protocol extensions
  • Maintains 16-byte alignment with proper size assertion
src/input/xwayland_server.cpp (4)

27-31: LGTM: RAII pattern correctly implemented.

The constructor/destructor pair follows RAII principles with the destructor ensuring cleanup via stop(), which is idempotent and safe to call multiple times.


33-115: LGTM: Comprehensive initialization with proper error handling.

The start() method is well-implemented with:

  • RAII guard ensuring cleanup on any failure path
  • Thorough error checking at each initialization step with descriptive messages
  • Automatic display number selection (1-9) to avoid conflicts
  • Separate compositor thread for event loop processing
  • Correct sequencing of wlroots object initialization

117-166: LGTM: Cleanup sequence is correct and safe.

The stop() method properly:

  • Terminates the display and joins the compositor thread before destroying objects (prevents use-after-free)
  • Follows the correct destruction order (XWayland before compositor, seat before display, etc.)
  • Correctly omits explicit destruction of m_xdg_shell and m_compositor as they're managed by wlroots
  • Is idempotent with early return check
  • Nulls out all pointers after cleanup

21-22: Forward declaration for wlr_xdg_shell is appropriate and sufficient.

The forward declaration at lines 21-22 correctly supports the usage at line 72 (wlr_xdg_shell_create), where only a pointer is stored and no struct members are accessed. This pattern is valid and intentional, reducing header dependencies. No changes needed.

src/input/input_forwarder.hpp (1)

9-32: LGTM: Well-designed PImpl interface with proper error handling.

The InputForwarder class interface is excellent:

  • PImpl pattern appropriately hides wlroots/X11 implementation details
  • Factory method enables complex initialization with error reporting
  • All result-returning methods are marked [[nodiscard]] (follows project policy)
  • Non-copyable/non-movable design is appropriate for RAII resource management
  • Consistent Result<void> return type for all forwarding operations enables proper error handling
openspec/changes/add-input-forwarding/specs/vk-layer-capture/spec.md (2)

42-60: LGTM!

The Protocol Message Types requirement is well-structured with clear scenarios covering message type definitions, struct layouts, and numbering scheme. The reserved value range (4-5) for future features is explicitly documented.


62-76: LGTM!

The CaptureReceiver Config Handling requirement clearly separates config request handling from frame capture flow. The two-connection model (config then frame) is well-specified.

src/input/input_forwarder.cpp (6)

15-127: LGTM! Keycode translation is appropriately scoped.

The SDL→Linux→X11 keycode translation covers common input (letters, numbers, arrows, modifiers) and gracefully handles unmapped keys by returning 0, which forward_key() checks and no-ops on (lines 176-178). The X11 +8 offset is correct per X11 protocol.


131-140: LGTM! Proper RAII resource cleanup.

The Impl destructor correctly closes the X11 display connection. The XWaylandServer member will be cleaned up automatically via its own destructor.


142-144: LGTM! Standard PIMPL pattern.

Constructor and destructor follow the PIMPL idiom correctly. The defaulted destructor ensures proper Impl cleanup.


146-168: LGTM! Robust error handling in factory.

The create() method properly handles initialization failures:

  • Stops the XWaylandServer if X11 connection fails (line 161).
  • Propagates errors via Result<T> pattern.
  • RAII ensures cleanup on all paths.

222-247: LGTM! Mouse wheel implementation is correct.

The button mapping for wheel events (4=up, 5=down, 6=left, 7=right) correctly follows the X11 protocol. The press-release pair (lines 242-243) properly simulates wheel scroll events.


249-251: LGTM! Simple delegation to XWaylandServer.

openspec/changes/add-input-forwarding/proposal.md (1)

1-90: LGTM! Proposal content is well-structured.

The problem statement, architecture, benefits, non-goals, alternatives, risks, and success criteria are clearly documented and align with the implementation. The ASCII diagram is properly formatted with a language identifier.

Also applies to: 111-154

openspec/changes/add-input-forwarding/design.md (4)

22-86: LGTM! Layer 2 and 3 definitions match implementation.

The InputForwarder and XWaylandServer class definitions correctly show the implemented API with SDL event parameters and Result return types.


88-136: LGTM! IPC protocol extension accurately documented.

The message types, structs, and two-phase handshake flow are correctly specified and align with the implementation in capture_protocol.hpp and the spec delta.


215-272: LGTM! Error handling strategy is well-designed.

The error handling approach with Result types, proper cleanup on failure, and graceful degradation aligns with the implementation. The auto-retry logic for DISPLAY number selection (lines 244-258) handles socket conflicts appropriately.


273-384: LGTM! Resource management, performance, and testing sections are comprehensive.

The RAII patterns, performance characteristics, testing strategy, and future enhancements are well-documented and align with the implementation approach.

Comment on lines +138 to +175
### Layer 5: Vulkan Layer Integration (Modified)

**Current**: Layer loads, intercepts `vkQueuePresentKHR`, sends frames

**New**: Layer performs config handshake in constructor before app initializes

```cpp
__attribute__((constructor(101))) static void
layer_early_init()
{
// Connect to `\0goggles/vkcapture` (best-effort; if unavailable, keep existing DISPLAY)
int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) return;
// connect(fd, CAPTURE_SOCKET_PATH, CAPTURE_SOCKET_PATH_LEN)

CaptureConfigRequest request{};
request.type = CaptureMessageType::config_request;
request.version = 1;

if (send(fd, &request, sizeof(request), MSG_NOSIGNAL) != sizeof(request)) return;

CaptureInputDisplayReady response{};
// poll(fd, 1, 100) then recv(fd, &response, sizeof(response), 0)

char display_str[32];
snprintf(display_str, sizeof(display_str), ":%d", response.display_number);
setenv("DISPLAY", display_str, 1);

close(fd);
}
```

**Constructor priority**: `101` (before default `100` for other constructors, ensures DISPLAY set first)

**Connection lifecycle**:
- Config connection: Created and destroyed in constructor
- Frame capture connection: Created later in `on_present()` (existing code unchanged)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix constructor priority documentation.

Line 170 incorrectly states "Constructor priority: 101 (before default 100 for other constructors)".

This is the same critical error as in the spec file:

  • Default constructor priority is 65535, not 100.
  • Priority 101 runs much earlier than default constructors, not "before priority 100."
🔎 Proposed fix
-**Constructor priority**: `101` (before default `100` for other constructors, ensures DISPLAY set first)
+**Constructor priority**: `101` (before default `65535` for other constructors, ensures DISPLAY set first)
🤖 Prompt for AI Agents
In openspec/changes/add-input-forwarding/design.md around lines 138 to 175,
update the constructor priority documentation: replace the incorrect statement
that default constructor priority is 100 and that 101 runs "before default 100"
with the correct facts — the default constructor priority is 65535 and a
constructor with priority 101 runs much earlier than default constructors;
clarify that a lower numeric priority value causes earlier execution and that
using 101 will run long before typical default constructors. Keep the note that
101 is intended to run early, but correct the numeric default and the wording
about ordering to avoid confusion.

#include "input/input_forwarder.hpp"

#include <SDL3/SDL.h>
#include <chrono>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'std::chrono|chrono::' src/app/main.cpp

Repository: K1ngst0m/Goggles

Length of output: 42


🏁 Script executed:

cat -n src/app/main.cpp

Repository: K1ngst0m/Goggles

Length of output: 13420


Remove unused <chrono> include.

The <chrono> header at line 6 is not used anywhere in the file and should be removed.

🤖 Prompt for AI Agents
In src/app/main.cpp around line 6, the #include <chrono> directive is unused;
remove that include line from the file and verify there are no remaining
references to std::chrono or chrono types—if you do need chrono elsewhere,
replace with the specific includes used; otherwise delete the include to clean
up unused headers.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/app/main.cpp (1)

6-6: Remove unused <chrono> include.

The <chrono> header is not used in this file. This was previously flagged but remains unaddressed.

🔎 Proposed fix
 #include <SDL3/SDL.h>
-#include <chrono>
 #include <cstdio>
🧹 Nitpick comments (1)
docs/input_forwarding.md (1)

19-19: Add language specifiers to fenced code blocks.

Markdown best practices recommend specifying a language identifier for fenced code blocks. For ASCII diagrams like these, use text or leave the identifier empty if the linter accepts it.

🔎 Proposed fix
-```
+```text
 ┌────────────────────────────────────────────────────────────┐
 │  Goggles Application (DISPLAY=:0, viewer window)           │

Apply the same change to the code blocks at lines 144, 152, and 160.

Also applies to: 144-144, 152-152, 160-160

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da4b842 and 40ec914.

📒 Files selected for processing (14)
  • config/goggles.toml
  • docs/input_forwarding.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/design.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/tasks.md
  • src/app/cli.hpp
  • src/app/main.cpp
  • src/capture/capture_protocol.hpp
  • src/capture/capture_receiver.cpp
  • src/capture/capture_receiver.hpp
  • src/capture/vk_layer/ipc_socket.cpp
  • src/util/config.cpp
  • src/util/config.hpp
💤 Files with no reviewable changes (2)
  • src/capture/capture_receiver.hpp
  • src/capture/capture_protocol.hpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/capture/vk_layer/ipc_socket.cpp
  • src/capture/capture_receiver.cpp
🧰 Additional context used
📓 Path-based instructions (3)
openspec/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/*.md: Use #### Scenario: format (4 hashtags) for scenario headers in requirements, not bullets or bold text, with at least one scenario per requirement
Use format - **WHEN** [condition] - **THEN** [expected result] for scenario steps in requirements

Files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/tasks.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/design.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
openspec/changes/**/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

List spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING

Files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
openspec/changes/**/specs/**/*.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/**/specs/**/*.md: Write spec deltas using ## ADDED|MODIFIED|REMOVED|RENAMED Requirements format with at least one #### Scenario: per requirement in spec files
When modifying existing requirements in a MODIFIED delta, paste the full requirement block including the header and all scenarios, as the archiver will replace the entire requirement
Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
🧠 Learnings (15)
📚 Learning: 2026-01-02T07:02:32.720Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.720Z
Learning: Applies to docs/config/goggles.toml : Development/testing configuration: `config/goggles.toml` in repository root. Structure should include sections for [capture], [pipeline], [render], and [logging].

Applied to files:

  • config/goggles.toml
  • src/app/main.cpp
📚 Learning: 2026-01-02T07:02:32.720Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.720Z
Learning: Applies to docs/**/{CMakeLists.txt,conanfile.txt,conanfile.py} : All dependencies must have explicit versions: CPM specifies `VERSION` parameter (Git tag or commit hash), Conan uses versioning in conanfile. Document rationale for version changes in commit messages.

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
📚 Learning: 2025-12-29T15:44:30.520Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.520Z
Learning: Applies to openspec/src/capture/vk_layer/**/*.{cpp,hpp} : Do not perform blocking operations or file I/O in capture layer performance-critical paths (e.g., vkQueuePresentKHR)

Applied to files:

  • docs/input_forwarding.md
  • src/app/main.cpp
📚 Learning: 2026-01-02T07:02:32.720Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.720Z
Learning: Applies to docs/src/capture/vk_layer/**/*.{cpp,hpp} : Vulkan layer code in `src/capture/vk_layer/` has strict logging constraints: use only `error` and `critical` levels, no file I/O or blocking operations in hot paths, prefix all logs with `[goggles_vklayer]`, never log in `vkQueuePresentKHR` hot path.

Applied to files:

  • docs/input_forwarding.md
  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
  • src/app/main.cpp
📚 Learning: 2026-01-02T07:02:32.720Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.720Z
Learning: Applies to docs/src/capture/vk_layer/**/*.{cpp,hpp} : Vulkan layer runs in host application threads (no control). Hooks execute in calling thread (usually render thread). No blocking in hooks, especially `vkQueuePresentKHR`. Use atomics or locks for thread-safe shared layer state.

Applied to files:

  • docs/input_forwarding.md
  • src/util/config.hpp
  • src/app/main.cpp
📚 Learning: 2025-12-29T15:44:30.520Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.520Z
Learning: Vulkan capture layer is the primary frame capture method, intercepting rendered frames before presentation for zero-copy access

Applied to files:

  • src/util/config.hpp
  • src/app/main.cpp
📚 Learning: 2025-12-29T15:44:30.520Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.520Z
Learning: Applies to openspec/src/capture/vk_layer/**/*.{cpp,hpp} : Capture layer (vk_layer/) MUST use raw Vulkan C API, not vulkan-hpp

Applied to files:

  • src/util/config.hpp
  • src/app/main.cpp
📚 Learning: 2026-01-02T07:02:32.720Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.720Z
Learning: Applies to docs/src/capture/vk_layer/**/*.{cpp,hpp} : Exception - Capture Layer Code (`src/capture/vk_layer/`) is exempt and MUST use the raw Vulkan C API because layer dispatch tables require C function pointers, layer must intercept C API calls from host applications, and minimal dependencies required.

Applied to files:

  • src/util/config.hpp
  • src/app/main.cpp
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/proposal.md : List spec deltas in `proposal.md` under 'What Changes' section, marking breaking changes with **BREAKING**

Applied to files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Use ADDED for new capabilities that can stand alone; use MODIFIED for changes to existing requirement behavior, scope, or acceptance criteria; use RENAMED for name-only changes

Applied to files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Create a change proposal with `proposal.md`, `tasks.md`, and optional `design.md` under `openspec/changes/<change-id>/` before implementing changes to add features, make breaking changes, or update architecture

Applied to files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
📚 Learning: 2025-12-19T08:54:18.412Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-12-19T08:54:18.412Z
Learning: Applies to openspec/changes/**/specs/**/*.md : Write spec deltas using `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` format with at least one `#### Scenario:` per requirement in spec files

Applied to files:

  • openspec/changes/archive/2026-01-04-add-input-forwarding-x11/proposal.md
📚 Learning: 2025-12-29T15:44:30.520Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.520Z
Learning: Applies to openspec/src/render/**/*.{cpp,hpp} : Application code MUST use vulkan-hpp with `VULKAN_HPP_NO_EXCEPTIONS` and dynamic dispatch

Applied to files:

  • src/app/main.cpp
📚 Learning: 2025-12-29T15:44:30.520Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: openspec/project.md:0-0
Timestamp: 2025-12-29T15:44:30.520Z
Learning: Default to single-threaded render loop on main thread; use central job system (goggles::util::JobSystem) for any task parallelism

Applied to files:

  • src/app/main.cpp
📚 Learning: 2026-01-02T07:02:32.720Z
Learnt from: CR
Repo: K1ngst0m/Goggles PR: 0
File: docs/project_policies.md:0-0
Timestamp: 2026-01-02T07:02:32.720Z
Learning: Applies to docs/**/*.{cpp,hpp} : Configuration loading: Read once at startup (no hot-reload initially). Validate all values after parsing. Provide defaults for all optional values. Fail fast on invalid config (don't silently ignore).

Applied to files:

  • src/app/main.cpp
🪛 markdownlint-cli2 (0.18.1)
docs/input_forwarding.md

19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


144-144: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


152-152: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


160-160: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Static Analysis (clang-tidy)
  • GitHub Check: Build and Test (test preset)
🔇 Additional comments (9)
src/util/config.cpp (1)

43-48: LGTM! Clean configuration parsing.

The input table parsing follows the established pattern used for other configuration sections and correctly populates config.input.forwarding. Type validation is handled implicitly by the toml library, consistent with other boolean fields like render.vsync.

src/app/cli.hpp (2)

14-14: LGTM! Appropriate default value.

The new enable_input_forwarding field defaults to false, ensuring opt-in behavior that maintains backward compatibility with existing usage.


31-32: LGTM! Clear CLI integration.

The --input-forwarding flag is well-documented with a concise description of the underlying mechanism (nested XWayland + XTest injection), helping users understand what they're enabling.

src/util/config.hpp (1)

38-40: LGTM! Consistent configuration structure.

The new Config::Input struct follows the established pattern used by other configuration sections, with an explicit default value that matches the TOML default.

config/goggles.toml (1)

12-19: LGTM! Well-documented configuration.

The new [input] section includes clear, comprehensive documentation that explains what the feature does (nested XWayland + XTest forwarding), when it applies, and why the default is false. This helps users make informed decisions about enabling the feature.

openspec/changes/archive/2026-01-04-add-input-forwarding-x11/tasks.md (1)

1-6: LGTM! Comprehensive task tracking.

All implementation tasks are marked complete, covering the key aspects of the input forwarding feature: module creation, InputForwarder implementation, SDL integration, testing, opt-in configuration, and documentation. The checklist provides a clear audit trail of the work completed in this PR.

src/app/main.cpp (2)

36-69: LGTM: Input event forwarding implementation.

The input forwarding integration is well-implemented with proper null checks, error handling, and logging. The design correctly forwards SDL events only when an InputForwarder instance is available, ensuring graceful behavior when input forwarding is disabled.


228-249: LGTM: InputForwarder initialization with graceful degradation.

The initialization logic properly handles errors from InputForwarder::create(), logs the display number on success, and continues without input forwarding on failure. This aligns with the design goal of graceful degradation.

openspec/changes/archive/2026-01-04-add-input-forwarding-x11/design.md (1)

1-309: LGTM: Comprehensive and well-structured design document.

This design document is excellent. It clearly explains the layered architecture, provides detailed data flow and threading models, demonstrates proper error handling and resource management patterns, and includes practical performance characteristics and testing strategies. The technical decisions are well-justified with comparisons to alternatives.

Comment on lines +89 to +122
## What Changes

### Configuration / CLI
- New config option: `input.forwarding` (default: false)
- New CLI flag: `--input-forwarding` (overrides config to enable)
- Input forwarding only works for targets launched with `DISPLAY=:N` (nested XWayland)
- Wayland input forwarding is not supported yet

### Public API
- New class `goggles::input::InputForwarder` (PIMPL pattern)
- `create() -> ResultPtr<InputForwarder>`
- `forward_key(const SDL_KeyboardEvent&) -> Result<void>`
- `forward_mouse_button(const SDL_MouseButtonEvent&) -> Result<void>`
- `forward_mouse_motion(const SDL_MouseMotionEvent&) -> Result<void>`
- `forward_mouse_wheel(const SDL_MouseWheelEvent&) -> Result<void>`
- `display_number() -> int`
- New class `goggles::input::XWaylandServer` (internal)
- `start() -> Result<int>`
- `stop() -> void`

### Error Codes
- `ErrorCode::input_init_failed` - XWayland/compositor startup failure
- `ErrorCode::input_socket_send_failed` - IPC message send failure

### Files Added
- `src/input/input_forwarder.cpp/hpp`
- `src/input/xwayland_server.cpp/hpp`

### Files Modified
- `src/app/main.cpp` - InputForwarder instantiation
- `src/app/cli.hpp` - `--input-forwarding` flag
- `src/util/config.*` - `input.forwarding` config option
- `docs/input_forwarding.md` - usage instructions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Document protocol/IPC changes in the "What Changes" section.

The PR summary explicitly mentions "IPC extension: small config handshake over existing Unix socket (config_request → input_display_ready) to communicate the selected DISPLAY," but the "What Changes" section does not document these protocol additions. Per coding guidelines for openspec/changes/**/proposal.md, all spec deltas must be listed, and breaking changes must be marked with BREAKING.

The following protocol changes are missing from this section:

  • New CaptureMessageType enum values: config_request, input_display_ready
  • New message structures: CaptureConfigRequest (layer → viewer), CaptureInputDisplayReady (viewer → layer)
  • BREAKING New IPC handshake phase: config negotiation before frame streaming (breaks backward compatibility with old layers/viewers)

As per coding guidelines, list spec deltas in proposal.md under 'What Changes' section, marking breaking changes with BREAKING.

🔎 Proposed fix

Add a "Protocol / IPC" subsection after line 95:

 - Wayland input forwarding is not supported yet
 
+### Protocol / IPC
+- New `CaptureMessageType` enum values: `config_request`, `input_display_ready`
+- New message structures: `CaptureConfigRequest` (layer → viewer), `CaptureInputDisplayReady` (viewer → layer, contains display number)
+- **BREAKING** New IPC handshake phase: config negotiation before frame streaming (old layers/viewers incompatible)
+
 ### Public API
 - New class `goggles::input::InputForwarder` (PIMPL pattern)

Comment on lines +53 to +57
#### Scenario: Press and release events
- **WHEN** `SDL_EVENT_KEY_DOWN` is received
- **THEN** `XTestFakeKeyEvent(..., True, ...)` SHALL be called (key press)
- **WHEN** `SDL_EVENT_KEY_UP` is received
- **THEN** `XTestFakeKeyEvent(..., False, ...)` SHALL be called (key release)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Split scenario into separate conditions or combine into single WHEN/THEN/AND structure.

The scenario contains two distinct WHEN/THEN pairs (one for KEY_DOWN, one for KEY_UP) which violates the single-WHEN-per-scenario format. Either split into two separate scenarios or restructure with a single WHEN followed by multiple THEN/AND steps.

🔎 Proposed restructure

Option 1: Split into two scenarios:

 ### Scenario: Press and release events

-#### Scenario: Press and release events
+#### Scenario: Key press event
- **WHEN** `SDL_EVENT_KEY_DOWN` is received
- **THEN** `XTestFakeKeyEvent(..., True, ...)` SHALL be called (key press)
- **WHEN** `SDL_EVENT_KEY_UP` is received
- **THEN** `XTestFakeKeyEvent(..., False, ...)` SHALL be called (key release)
+
+#### Scenario: Key release event
+- **WHEN** `SDL_EVENT_KEY_UP` is received
+- **THEN** `XTestFakeKeyEvent(..., False, ...)` SHALL be called (key release)

Option 2: Combine with single WHEN and multiple THEN/AND:

 #### Scenario: Press and release events
- **WHEN** `SDL_EVENT_KEY_DOWN` is received
- **THEN** `XTestFakeKeyEvent(..., True, ...)` SHALL be called (key press)
- **WHEN** `SDL_EVENT_KEY_UP` is received
- **THEN** `XTestFakeKeyEvent(..., False, ...)` SHALL be called (key release)
+- **WHEN** a keyboard event (`SDL_EVENT_KEY_DOWN` or `SDL_EVENT_KEY_UP`) is received
+- **THEN** `XTestFakeKeyEvent(...)` SHALL be called with `True` for press and `False` for release

As per coding guidelines, ...

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### Scenario: Press and release events
- **WHEN** `SDL_EVENT_KEY_DOWN` is received
- **THEN** `XTestFakeKeyEvent(..., True, ...)` SHALL be called (key press)
- **WHEN** `SDL_EVENT_KEY_UP` is received
- **THEN** `XTestFakeKeyEvent(..., False, ...)` SHALL be called (key release)
#### Scenario: Press and release events
- **WHEN** a keyboard event (`SDL_EVENT_KEY_DOWN` or `SDL_EVENT_KEY_UP`) is received
- **THEN** `XTestFakeKeyEvent(...)` SHALL be called with `True` for press and `False` for release
🤖 Prompt for AI Agents
In
openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
around lines 53-57, the "Press and release events" scenario contains two
separate WHEN/THEN pairs which violates the single-WHEN-per-scenario guideline;
fix by either splitting this into two distinct scenarios (one for
SDL_EVENT_KEY_DOWN -> XTestFakeKeyEvent(..., True, ...) and one for
SDL_EVENT_KEY_UP -> XTestFakeKeyEvent(..., False, ...)) or by combining into a
single scenario with one WHEN (e.g., "WHEN an SDL key event is received")
followed by multiple THEN/AND steps that assert the press and release calls
respectively; update wording and headings accordingly to reflect the chosen
structure.

Comment on lines +164 to +167
#### Scenario: Wayland-native target
- **GIVEN** a target application uses a Wayland backend
- **WHEN** the user attempts to use input forwarding
- **THEN** input forwarding is not supported for that target (X11-only)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use WHEN/THEN format instead of GIVEN/WHEN/THEN.

This scenario mixes GIVEN and WHEN keywords. Per coding guidelines, scenario steps should use the format - **WHEN** [condition] - **THEN** [expected result]. Rewrite to use WHEN/THEN structure.

🔎 Proposed fix
 #### Scenario: Wayland-native target
-- **GIVEN** a target application uses a Wayland backend
-- **WHEN** the user attempts to use input forwarding
-- **THEN** input forwarding is not supported for that target (X11-only)
+- **WHEN** a target application uses a Wayland backend
+- **THEN** input forwarding is not supported for that target (X11-only)

As per coding guidelines, ...

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### Scenario: Wayland-native target
- **GIVEN** a target application uses a Wayland backend
- **WHEN** the user attempts to use input forwarding
- **THEN** input forwarding is not supported for that target (X11-only)
#### Scenario: Wayland-native target
- **WHEN** a target application uses a Wayland backend
- **THEN** input forwarding is not supported for that target (X11-only)
🤖 Prompt for AI Agents
In
openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
around lines 164–167, the scenario mixes GIVEN with WHEN/THEN; rewrite the step
to use the WHEN/THEN format only by replacing the two-step GIVEN + WHEN with a
single WHEN that includes both conditions (e.g., "WHEN the target application
uses a Wayland backend and the user attempts to use input forwarding") and keep
the THEN as the expected result ("THEN input forwarding is not supported for
that target (X11-only)").

Comment on lines +173 to +189
#### Scenario: Common key mappings
- **GIVEN** SDL_SCANCODE_W (26)
- **THEN** SHALL translate to KEY_W (17)
- **GIVEN** SDL_SCANCODE_A (4)
- **THEN** SHALL translate to KEY_A (30)
- **GIVEN** SDL_SCANCODE_ESCAPE (41)
- **THEN** SHALL translate to KEY_ESC (1)

#### Scenario: Unmapped scancode
- **WHEN** an SDL scancode has no Linux keycode mapping
- **THEN** `forward_key()` SHALL return immediately (no-op)
- **AND** no error SHALL be logged

#### Scenario: X11 keycode offset
- **WHEN** translating Linux keycode to X11 keycode
- **THEN** 8 SHALL be added to the Linux keycode
- **AND** the resulting X11 keycode SHALL be passed to XTest
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Restructure "Common key mappings" scenario to follow single WHEN/THEN pattern.

This scenario contains three separate GIVEN/THEN pairs for different SDL scancodes. The guideline format specifies a single WHEN condition per scenario followed by THEN/AND steps. Restructure into either multiple separate scenarios (one per mapping) or combine into one scenario with unified structure.

🔎 Proposed restructure

Option 1: Create separate scenarios for each mapping:

 #### Scenario: Common key mappings
-- **GIVEN** SDL_SCANCODE_W (26)
-- **THEN** SHALL translate to KEY_W (17)
-- **GIVEN** SDL_SCANCODE_A (4)
-- **THEN** SHALL translate to KEY_A (30)
-- **GIVEN** SDL_SCANCODE_ESCAPE (41)
-- **THEN** SHALL translate to KEY_ESC (1)
+
+#### Scenario: Map SDL_SCANCODE_W to KEY_W
+- **WHEN** SDL_SCANCODE_W (26) is translated to Linux keycode
+- **THEN** it SHALL map to KEY_W (17)
+
+#### Scenario: Map SDL_SCANCODE_A to KEY_A
+- **WHEN** SDL_SCANCODE_A (4) is translated to Linux keycode
+- **THEN** it SHALL map to KEY_A (30)
+
+#### Scenario: Map SDL_SCANCODE_ESCAPE to KEY_ESC
+- **WHEN** SDL_SCANCODE_ESCAPE (41) is translated to Linux keycode
+- **THEN** it SHALL map to KEY_ESC (1)

Option 2: Combine into a single scenario with description:

 #### Scenario: Common key mappings
-- **GIVEN** SDL_SCANCODE_W (26)
-- **THEN** SHALL translate to KEY_W (17)
-- **GIVEN** SDL_SCANCODE_A (4)
-- **THEN** SHALL translate to KEY_A (30)
-- **GIVEN** SDL_SCANCODE_ESCAPE (41)
-- **THEN** SHALL translate to KEY_ESC (1)
+- **WHEN** common SDL scancodes are translated to Linux keycodes
+- **THEN** SDL_SCANCODE_W (26) SHALL map to KEY_W (17)
+- **AND** SDL_SCANCODE_A (4) SHALL map to KEY_A (30)
+- **AND** SDL_SCANCODE_ESCAPE (41) SHALL map to KEY_ESC (1)

As per coding guidelines, ...

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### Scenario: Common key mappings
- **GIVEN** SDL_SCANCODE_W (26)
- **THEN** SHALL translate to KEY_W (17)
- **GIVEN** SDL_SCANCODE_A (4)
- **THEN** SHALL translate to KEY_A (30)
- **GIVEN** SDL_SCANCODE_ESCAPE (41)
- **THEN** SHALL translate to KEY_ESC (1)
#### Scenario: Unmapped scancode
- **WHEN** an SDL scancode has no Linux keycode mapping
- **THEN** `forward_key()` SHALL return immediately (no-op)
- **AND** no error SHALL be logged
#### Scenario: X11 keycode offset
- **WHEN** translating Linux keycode to X11 keycode
- **THEN** 8 SHALL be added to the Linux keycode
- **AND** the resulting X11 keycode SHALL be passed to XTest
#### Scenario: Common key mappings
- **WHEN** common SDL scancodes are translated to Linux keycodes
- **THEN** SDL_SCANCODE_W (26) SHALL map to KEY_W (17)
- **AND** SDL_SCANCODE_A (4) SHALL map to KEY_A (30)
- **AND** SDL_SCANCODE_ESCAPE (41) SHALL map to KEY_ESC (1)
#### Scenario: Unmapped scancode
- **WHEN** an SDL scancode has no Linux keycode mapping
- **THEN** `forward_key()` SHALL return immediately (no-op)
- **AND** no error SHALL be logged
#### Scenario: X11 keycode offset
- **WHEN** translating Linux keycode to X11 keycode
- **THEN** 8 SHALL be added to the Linux keycode
- **AND** the resulting X11 keycode SHALL be passed to XTest
🤖 Prompt for AI Agents
In
openspec/changes/archive/2026-01-04-add-input-forwarding-x11/specs/input-forwarding/spec.md
around lines 173-189, the "Common key mappings" scenario uses multiple
GIVEN/THEN pairs which violates the single WHEN/THEN guideline; rewrite it so
each scenario has one WHEN and corresponding THEN/AND steps by either (A)
splitting into three distinct scenarios (one per mapping: SDL_SCANCODE_W ->
KEY_W, SDL_SCANCODE_A -> KEY_A, SDL_SCANCODE_ESCAPE -> KEY_ESC) or (B) combining
into a single scenario with one WHEN that covers "translating an SDL scancode"
and then three THEN/AND lines that assert each specific mapping, ensuring
consistent WHEN/THEN structure and preserving the mapping details.

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