A Rust library for Discord Rich Presence that actually works without the headaches. No more fighting with the Discord SDK or dealing with complex C bindings.
Note: This is currently in development (v0.2.1). Things might break. This is a learning/hobby project. Features and APIs may change in future versions.
- Getting Started Guide - Installation and first steps
- API Reference - Complete API documentation
- Activity Builder Reference - Detailed guide to all fields
- Async Runtimes Guide - Using async/await with Tokio, async-std, or smol
- Error Handling Guide - Proper error handling patterns
- FAQ & Troubleshooting - Common questions and solutions
Want to build your own RPC client?
- Discord RPC from Scratch - this is What I found while building this library. I could be wrong about some things, so feel free to correct me!
- Linux and macOS (Unix domain sockets)
- Windows support (named pipes)
- Flatpak Discord support (automatic detection)
- Basic Rich Presence activities
- Activity builder pattern
- Images, buttons, and timestamps
- Async support with runtime-agnostic design
- Support for tokio, async-std, and smol
- Flexible pipe/socket selection
- IPC Event System (subscribe, unsubscribe, event polling)
Add PresenceForge to your Cargo.toml:
[dependencies]
presenceforge = "0.2.1"For async support, add one of the runtime features:
[dependencies]
presenceforge = { version = "0.2.1", features = ["tokio-runtime"] }
# OR
presenceforge = { version = "0.2.1", features = ["async-std-runtime"] }
# OR
presenceforge = { version = "0.2.1", features = ["smol-runtime"] }use presenceforge::ActivityBuilder;
use presenceforge::sync::DiscordIpcClient;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DiscordIpcClient::new("your_client_id")?;
client.connect()?;
// Optional sanity check if your setup is more complex
debug_assert!(client.is_connected(), "Discord handshake failed");
let activity = ActivityBuilder::new()
.state("Playing a game")
.details("In the menu")
.start_timestamp_now()?
.large_image("game_logo")
.large_text("My Awesome Game")
.build();
client.set_activity(&activity)?;
// Keep the activity active
std::thread::sleep(std::time::Duration::from_secs(10));
client.clear_activity()?;
Ok(())
}use presenceforge::{AsyncDiscordIpcClient, ActivityBuilder, Result};
#[tokio::main]
async fn main() -> Result {
let mut client = AsyncDiscordIpcClient::new("your_client_id").await?;
client.connect().await?;
debug_assert!(client.is_connected(), "Discord handshake failed");
let activity = ActivityBuilder::new()
.state("Playing a game")
.details("In the menu")
.start_timestamp_now()?
.large_image("game_logo")
.large_text("My Awesome Game")
.build();
client.set_activity(&activity).await?;
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
client.clear_activity().await?;
Ok(())
}use presenceforge::{AsyncDiscordIpcClient, ActivityBuilder, Result};
use async_std::task;
use std::time::Duration;
#[async_std::main]
async fn main() -> Result {
let mut client = AsyncDiscordIpcClient::new("your_client_id").await?;
client.connect().await?;
debug_assert!(client.is_connected(), "Discord handshake failed");
let activity = ActivityBuilder::new()
.state("Playing a game")
.details("In the menu")
.start_timestamp_now()?
.large_image("game_logo")
.large_text("My Awesome Game")
.build();
client.set_activity(&activity).await?;
task::sleep(Duration::from_secs(10)).await;
client.clear_activity().await?;
Ok(())
}use presenceforge::{AsyncDiscordIpcClient, ActivityBuilder, Result};
use std::time::Duration;
fn main() -> Result {
smol::block_on(async {
let mut client = AsyncDiscordIpcClient::new("your_client_id").await?;
client.connect().await?;
debug_assert!(client.is_connected(), "Discord handshake failed");
let activity = ActivityBuilder::new()
.state("Playing a game")
.details("In the menu")
.start_timestamp_now()?
.large_image("game_logo")
.large_text("My Awesome Game")
.build();
client.set_activity(&activity).await?;
smol::Timer::after(Duration::from_secs(10)).await;
client.clear_activity().await?;
Ok(())
})
}- Go to the Discord Developer Portal
- Create a new application or select an existing one
- Copy the Application ID from the General Information page
- (Optional) Upload images in the Rich Presence Art Assets section
All examples support three ways to provide your Discord Client ID:
cargo run --example basic -- --client-id YOUR_CLIENT_IDDISCORD_CLIENT_ID=YOUR_CLIENT_ID cargo run --example basic# Copy the example file
cp .env.example .env
# Edit .env and add your client ID
# DISCORD_CLIENT_ID=your_client_id_here
# Then run any example
cargo run --example basicPriority Order: Command-line argument → Environment variable → .env file
| Platform | IPC Method | Status |
|---|---|---|
| Linux | Unix Domain Sockets | [x] |
| macOS | Unix Domain Sockets | [x] |
| Windows | Named Pipes | [x] |
DiscordIpcClient::new(client_id)- Create a new clientclient.connect()- Connect to Discordclient.is_connected()- Check if the handshake succeededclient.set_activity(activity)- Set Rich Presence activityclient.clear_activity()- Clear current activityclient.subscribe(event, args)- Subscribe to IPC events (e.g., "READY", "ERROR")client.unsubscribe(event, args)- Unsubscribe from eventsclient.next_event()- Block until the next event is receivedclient.poll_event()- Check for events without blocking
The ActivityBuilder provides a fluent interface for creating activities:
ActivityBuilder::new()
.state("Custom state") // What the player is doing
.details("Custom details") // Additional context
.start_timestamp_now()? // Start time (current)
.start_timestamp(timestamp) // Start time (custom)
.end_timestamp(timestamp) // End time
.large_image("image_key") // Large image asset
.large_text("Hover text") // Large image hover text
.small_image("image_key") // Small image asset
.small_text("Hover text") // Small image hover text
.button("Label", "https://url") // Clickable button (max 2)
.party("id", 1, 4) // Party size with custom ID
.party_simple(1, 4) // Party size with auto-generated ID
.end_timestamp_from_now(dur)? // End time relative to now
.build()For detailed information about all available examples and configuration options, see the Examples README.
Clone the repository and run the included examples:
git clone https://github.com/Sreehari425/presenceforge.git
cd presenceforge
# Basic example (synchronous)
cargo run --example basic -- --client-id YOUR_CLIENT_ID
# Game demo with dynamic status
cargo run --example game_demo -- --client-id YOUR_CLIENT_ID
# Developer coding status
cargo run --example coding_status -- --client-id YOUR_CLIENT_ID
# Custom activity without builder
cargo run --example custom_activity -- --client-id YOUR_CLIENT_ID
# Async example with Tokio
cargo run --example async_tokio --features tokio-runtime -- --client-id YOUR_CLIENT_ID
# Async example with async-std
cargo run --example async_std --features async-std-runtime -- --client-id YOUR_CLIENT_ID
# Async example with smol
cargo run --example async_smol --features smol-runtime -- --client-id YOUR_CLIENT_ID
# Complete builder reference - Shows ALL ActivityBuilder options
cargo run --example builder_all -- --client-id YOUR_CLIENT_ID
# Flatpak-specific pipe selection
cargo run --example basic_flatpak -- --client-id YOUR_CLIENT_ID
# Connection retry and error handling
cargo run --example connection_retry -- --client-id YOUR_CLIENT_ID
# Async retry/reconnect with Tokio
cargo run --example async_tokio_reconnect --features tokio-runtime -- --client-id YOUR_CLIENT_ID
# Update activity without resetting the timer
cargo run --example update_activity_tokio --features tokio-runtime -- --client-id YOUR_CLIENT_ID
# Pipe selection and discovery
cargo run --example pipe_selection -- --client-id YOUR_CLIENT_ID
# IPC event subscription and polling
cargo run --example event_listener -- --client-id YOUR_CLIENT_ID
# Fetch typed READY payload data
cargo run --example ready_event -- --client-id YOUR_CLIENT_IDOr use the .env file method (recommended for development):
# Set up .env file once
cp .env.example .env
# Edit .env and add: DISCORD_CLIENT_ID=your_client_id_here
# Then run examples without specifying client ID
cargo run --example basic
cargo run --example game_demo
cargo run --example async_tokio --features tokio-runtimeNote: Replace YOUR_CLIENT_ID with your actual Discord application ID, or use the .env file method for convenience.
PresenceForge uses the Result type for error handling:
use presenceforge::DiscordIpcError;
use presenceforge::sync::DiscordIpcClient;
match client.connect() {
Ok(_) => println!("Connected successfully!"),
Err(DiscordIpcError::ConnectionFailed(_)) => {
eprintln!("Failed to connect - is Discord running?");
}
Err(DiscordIpcError::NoValidSocket) => {
eprintln!("No Discord IPC socket found - is Discord running?");
}
Err(e) => eprintln!("Error: {}", e),
}- Better error messages
- Party/lobby functionality (partial implementation)
- Async support (tokio, async-std, and smol)
- More comprehensive examples
- Publish to crates.io
- CI/CD pipeline
- Proper documentation
- Connection retry logic with exponential backoff
- Activity validation
This project is licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.