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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/linux-build-and-upload-release-assets.yml
Copy link
Owner

Choose a reason for hiding this comment

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

Please modify this file according to other github action workflow files in this repo, modify the trigger to a workflow_dispatch with a version parameter, run tests before build, include the version in the archive file name, delete existing aset with the same name if exists.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Linux Build and Upload Release Assets

on:
release:
types: [created]

env:
CARGO_TERM_COLOR: always

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: Build Linux Client
run: |
cd linux
cargo build --release

- name: Create release archive
run: |
cd linux
mkdir -p stickdeck-linux
cp target/release/stickdeck-linux stickdeck-linux/
cp -r scripts stickdeck-linux/
tar -czf stickdeck-linux-x86_64.tar.gz stickdeck-linux

- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: linux/stickdeck-linux-x86_64.tar.gz
asset_name: stickdeck-linux-x86_64.tar.gz
asset_content_type: application/gzip
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# CHANGELOG

## v0.3.2 (Unreleased)

- **New**: Linux client support
- Feat: add native Linux client using uinput for virtual controller emulation
- Feat: support for all Xbox 360 controller features (buttons, analog sticks, triggers, D-pad)
- Feat: mouse input support on Linux
- Feat: setup, launch, and debug scripts for Linux
- Feat: GitHub Actions CI/CD for Linux builds
- Note: requires `/dev/uinput` access (run with sudo or add user to `input` group)

## v0.3.1

- Client (PC)
Expand Down
137 changes: 137 additions & 0 deletions CLAUDE.md
Copy link
Owner

Choose a reason for hiding this comment

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

Please remove intermediate files in the final PR.

Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Stickdeck-RS transforms a Steam Deck into a virtual game controller for PC using a client-server architecture over TCP.

## Key Architecture

### Components
- **common/**: Shared networking protocol and data structures (Packet, Mouse, MouseButton, XGamepad, XButtons)
- **deck/**: Server running on Steam Deck - captures inputs via Steam Input API
- **win/**: Windows client - creates virtual Xbox 360 controller via ViGEm
- **linux/**: Linux client - creates virtual Xbox 360 controller via uinput

### Network Protocol
- TCP on port 7777
- 16-byte binary packets
- Packet types: Timestamp, Gamepad, Mouse
- Only sends on input changes

### Threading Model
- Server: Separate threads for GUI (Iced), input polling, and network
- Client: Main thread for controller, network thread for receiving

## Essential Commands

### Building
```bash
# Server (Steam Deck)
cd deck && cargo build --release

# Windows Client
cd win && cargo build --release

# Linux Client
cd linux && cargo build --release

# Debug builds
cd deck && cargo build
cd win && cargo build
cd linux && cargo build
```

### Testing
```bash
# Run tests for a specific component
cd deck && cargo test
cd win && cargo test
cd linux && cargo test
cd common && cargo test

# Run specific test
cd deck && cargo test test_name
```

### Running
```bash
# Server (use provided scripts)
cd deck && ./launch.sh # Release mode
cd deck && ./debug.sh # Debug mode with logging

# Windows Client
cd win && cargo run --release -- <server_ip>

# Linux Client
cd linux && cargo run --release -- <server_ip>
# Or use provided scripts
cd linux && ./scripts/launch.sh <server_ip>
cd linux && ./scripts/debug.sh <server_ip>
```

### Development
```bash
# Format code
cargo fmt

# Check for issues
cargo clippy

# Check types
cargo check
```

## Code Patterns

### Error Handling
- Use `Result<T, Box<dyn Error>>` for main functions
- Use `anyhow::Result` or specific error types in libraries
- Always handle disconnections gracefully

### Performance Considerations
- Use bounded channels (capacity ~10) to prevent buffer growth
- Avoid allocations in hot paths (input polling)
- Use `perf!` macro for performance monitoring in debug builds

### Logging
- Default log level: info
- Debug level shows update rates and performance metrics
- Use `RUST_LOG=debug` environment variable

## Key Implementation Details

### Steam Deck Server
- Requires Steam client running
- Uses Steamworks SDK 154 (AppID 480 - Spacewar)
- Input polling at configurable rate (default 3ms)
- GUI built with Iced framework

### Windows Client
- Requires ViGEm Bus Driver installed
- Emulates Xbox 360 controller
- Mouse support via SendInput API
- Auto-reconnect on disconnection

### Linux Client
- Requires /dev/uinput access (sudo or input group membership)
- Uses input-linux crate for uinput integration
- Emulates Xbox 360 controller compatible with evdev
- Mouse support via uinput mouse device
- Auto-reconnect on disconnection

### Packet Structure
- Fixed 16-byte frames
- Binary serialization (not JSON)
- See common/src/lib.rs for Packet enum

## Testing Virtual Controller
- Windows: Use `joy.cpl` (Game Controllers panel)
- Linux: Use `evtest` or `jstest-gtk` to test the virtual controller
- Steam Deck: Configure inputs in Steam's controller settings

## Release Process
- Version synchronized across deck/Cargo.toml, win/Cargo.toml, and linux/Cargo.toml
- GitHub Actions builds release binaries for all platforms
- Update CHANGELOG.md with changes
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,35 @@ Now you can proceed to the [client side setup](#client-side-pc). -->

### Client Side (PC)

#### Windows

1. Install [ViGEm Bus Driver](https://github.com/nefarius/ViGEmBus) and **_restart_** your PC.
2. Download `stickdeck-win-vX.X.X.zip` from the [latest release](https://github.com/DiscreteTom/stickdeck-rs/releases/latest) and extract it.

#### Linux

1. Download `stickdeck-linux-x86_64.tar.gz` from the [latest release](https://github.com/DiscreteTom/stickdeck-rs/releases/latest) and extract it.
2. Run `./scripts/setup.sh` in the extracted folder to install dependencies and configure permissions.
3. Logout and login again if you were added to the `input` group during setup.

## Usage

### General

1. Start the server on Steam Deck. Make sure the server is running and the input is captured.
2. Make sure your PC and your Steam Deck are in the same network.
3. Make sure the client on your PC is under the same minor version as the server on Steam Deck.
4. Run `launch.bat` on your PC. Once you see `Virtual controller is ready` in the console, StickDeck is ready.
5. (Optional) If you want to test the controller, run `joy.cpl` (which is a built-in Windows joystick test tool).
4. Run the client on your PC:
- **Windows**: Run `launch.bat`. Once you see `Virtual controller is ready` in the console, StickDeck is ready.
- **Linux**: Run `./scripts/launch.sh <steam_deck_ip>`. Once you see `Ready! Waiting for inputs from Steam Deck...`, StickDeck is ready.
Copy link
Owner

@DiscreteTom DiscreteTom Jul 13, 2025

Choose a reason for hiding this comment

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

You can use steamdeck as the default host name for the steam deck so users can omit the steam_deck_ip for most of the case.

5. (Optional) Test the controller:
- **Windows**: Run `joy.cpl` (built-in Windows joystick test tool).
- **Linux**: Run `evtest` or `jstest-gtk` to test the virtual controller.

> [!NOTE]
> By default the client will try to connect `steamdeck:7777`. If you want to connect to a different server, you can edit `launch.bat`, replace the `steamdeck` with your server IP.
> You can find the server IP on the first line of the StickDeck UI window while the server is started.
> - **Windows**: By default the client will try to connect `steamdeck:7777`. If you want to connect to a different server, you can edit `launch.bat`, replace the `steamdeck` with your server IP.
> - **Linux**: You need to provide the Steam Deck IP address as an argument to the launch script.
Copy link
Owner

Choose a reason for hiding this comment

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

Same here

> - You can find the server IP on the first line of the StickDeck UI window while the server is started.

### Mouse Actions

Expand All @@ -65,7 +78,14 @@ and map any action to mouse buttons.
- Poll/update rate?
- Depends on the configurable input update interval. In my case, set the input update interval to 3ms to reach the max update rate of 250+Hz.
- Besides, the server side will only send the input when there is a change, so the actual update rate will be lower than the configured rate.
- You can checkout the actual update rate on the PC side by running `debug.bat`.
- You can checkout the actual update rate on the PC side by running:
- **Windows**: `debug.bat`
- **Linux**: `./scripts/debug.sh <steam_deck_ip>`
Copy link
Owner

Choose a reason for hiding this comment

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

Same here


- Linux client requirements?
- Requires access to `/dev/uinput` for creating virtual input devices.
- Either run with `sudo` or add your user to the `input` group: `sudo usermod -aG input $USER`
- The `setup.sh` script will help configure this automatically.

## Credit

Expand Down
57 changes: 57 additions & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
mod perf;

pub const PORT: u16 = 7777;

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub struct MouseButton(pub u8);

impl MouseButton {
pub const MOUSE_LEFT_BUTTON: u8 = 1;
pub const MOUSE_RIGHT_BUTTON: u8 = 2;
pub const MOUSE_MIDDLE_BUTTON: u8 = 4;

pub fn left_button_down(&mut self) {
self.0 |= Self::MOUSE_LEFT_BUTTON;
Expand All @@ -19,6 +22,18 @@ impl MouseButton {
pub fn is_right_button_down(&self) -> bool {
self.0 & Self::MOUSE_RIGHT_BUTTON != 0
}

pub const LEFT: Self = Self(Self::MOUSE_LEFT_BUTTON);
pub const RIGHT: Self = Self(Self::MOUSE_RIGHT_BUTTON);
pub const MIDDLE: Self = Self(Self::MOUSE_MIDDLE_BUTTON);

pub const fn empty() -> Self {
Self(0)
}

pub fn contains(&self, other: Self) -> bool {
self.0 & other.0 != 0
}
}

/// The mouse movement data in pixels in one update.
Expand Down Expand Up @@ -60,3 +75,45 @@ pub enum Packet<Gamepad> {
}

pub const PACKET_FRAME_SIZE: usize = 16;

#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct XButtons {
pub raw: u16,
}

impl XButtons {
pub const DPAD_UP: u16 = 0x0001;
pub const DPAD_DOWN: u16 = 0x0002;
pub const DPAD_LEFT: u16 = 0x0004;
pub const DPAD_RIGHT: u16 = 0x0008;
pub const START: u16 = 0x0010;
pub const BACK: u16 = 0x0020;
pub const LEFT_THUMB: u16 = 0x0040;
pub const RIGHT_THUMB: u16 = 0x0080;
pub const LEFT_SHOULDER: u16 = 0x0100;
pub const RIGHT_SHOULDER: u16 = 0x0200;
pub const GUIDE: u16 = 0x0400;
pub const A: u16 = 0x1000;
pub const B: u16 = 0x2000;
pub const X: u16 = 0x4000;
pub const Y: u16 = 0x8000;

pub const fn empty() -> Self {
Self { raw: 0 }
}

pub fn contains(&self, button: u16) -> bool {
self.raw & button != 0
}
}

#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct XGamepad {
pub buttons: XButtons,
pub left_trigger: u8,
pub right_trigger: u8,
pub thumb_lx: i16,
pub thumb_ly: i16,
pub thumb_rx: i16,
pub thumb_ry: i16,
}
Empty file modified deck/scripts/debug.sh
100644 → 100755
Empty file.
Empty file modified deck/scripts/launch.sh
100644 → 100755
Empty file.
Empty file modified deck/scripts/setup.sh
100644 → 100755
Empty file.
Loading