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
611 changes: 585 additions & 26 deletions Cargo.lock

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,42 @@ version = "0.1.0"
edition = "2024"
rust-version = "1.85"

[features]
default = ["animated"]
animated = ["dep:gstreamer", "dep:gstreamer-allocators", "dep:gstreamer-app", "dep:gstreamer-video", "dep:libavif-sys"]

[dependencies]
# Calloop with signal handling for graceful shutdown
calloop = { version = "0.14", features = ["signals"] }
color-eyre = "0.6.5"
colorgrad = { workspace = true }
cosmic-bg-config = { path = "./config" }
# Directory utilities for cache paths
dirs = "6.0"
drm-fourcc = "2.2"
eyre = "0.6.12"
# libavif-sys for animated AVIF (AVIS) CPU decoding
libavif-sys = { version = "0.17", optional = true, default-features = false, features = ["codec-dav1d"] }
# For DMA-BUF file descriptor duplication
nix = { version = "0.29", features = ["fs"] }
fast_image_resize = { version = "5.1.4", features = ["image"] }
image = { workspace = true, features = ["hdr", "jpeg", "png", "rayon", "webp"] }
# GStreamer for hardware-accelerated video decoding (VAAPI, NVDEC, etc.)
gstreamer = { version = "0.24", optional = true }
gstreamer-allocators = { version = "0.24", optional = true }
gstreamer-app = { version = "0.24", optional = true, features = ["v1_24"] }
gstreamer-video = { version = "0.24", optional = true }
# avif-native required for decoding AVIF (avif is for encoding only)
image = { workspace = true, features = ["avif-native", "hdr", "jpeg", "png", "rayon", "webp"] }
jxl-oxide = { version = "0.12.4", features = ["image"] }
notify = "8.2.0"
rand = "0.9.2"
rayon = "1.11"
sctk = { package = "smithay-client-toolkit", version = "0.20.0" }
tracing = { workspace = true }
tracing-subscriber = "0.3.20"
walkdir = "2.5"
# Wayland protocols for DMA-BUF support
wayland-protocols = { version = "0.32", features = ["client"] }

[workspace]
members = ["config"]
Expand All @@ -33,3 +55,4 @@ features = ["calloop"]

[profile.release]
opt-level = 3
lto = "thin"
160 changes: 159 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,181 @@

COSMIC session service which applies backgrounds to displays. Supports the following features:

- Supports common image formats supported by [image-rs](https://github.com/image-rs/image#supported-image-formats)
- Supports common image formats: JPEG, PNG, WebP, AVIF, JPEG XL, and more via [image-rs](https://github.com/image-rs/image#supported-image-formats)
- **Live/Animated wallpapers** - Animated AVIF, and video formats (MP4, WebM, MKV, etc.) with hardware acceleration
- 8 and 10-bit background surface layers
- Use of colors and gradients for backgrounds
- Per-display background application
- Wallpaper slideshows that alternate between backgrounds periodically

## Live Wallpaper Support

The `animated` feature (enabled by default) adds support for animated wallpapers using GStreamer for hardware-accelerated video playback.

### Supported Formats

| Format | Extension | Decode Method |
|--------|-----------|---------------|
| Animated AVIF | `.avif` | CPU via libavif (frames cached in memory) |
| MPEG-4 | `.mp4`, `.m4v` | NVIDIA (all codecs), AMD/Intel (H.264/H.265 with freeworld drivers, VP9, AV1) |
| WebM | `.webm` | Full (VP8, VP9, AV1) - **Recommended for AMD without freeworld drivers** |
| Matroska | `.mkv` | Depends on contained codec |
| AVI | `.avi` | Depends on contained codec |
| QuickTime | `.mov` | Depends on contained codec |

### Hardware Requirements

#### NVIDIA GPUs
- **Driver**: NVIDIA proprietary driver 470+
- **GStreamer plugins**: `gstreamer1-plugins-bad` (provides `nvh264dec`, `nvh265dec`, etc.)
- **Supported codecs**: H.264, H.265/HEVC, VP9, AV1
- **Optional**: [gst-cuda-dmabuf](https://github.com/Ericky14/gst-cuda-dmabuf) plugin for zero-copy DMA-BUF rendering

#### AMD/Intel GPUs (VAAPI)
- **Driver**: Mesa 21.0+ with VAAPI support
- **GStreamer plugins**: `gstreamer1-plugins-bad` (GStreamer 1.20+ provides `vah264dec`, `vapostproc`, etc.)
- **Zero-copy**: Native DMA-BUF support via `vapostproc` for efficient compositor rendering
- **Supported codecs** (varies by GPU generation):
- AMD RDNA/RDNA2+: VP9, AV1 (H.264/H.265 requires `mesa-va-drivers-freeworld` on Fedora)
- Intel Gen9+: H.264, H.265, VP9, AV1
- **Recommendation**: Use **VP9 or AV1** encoded videos for best AMD compatibility, or install freeworld drivers for H.264/H.265

#### Software Fallback
If no hardware decoder is available, the system falls back to software decoding via GStreamer's `decodebin`. For H.264 content on systems without hardware decode (e.g., AMD on Fedora), install `openh264` for software decode support.

### Codec Detection

At startup, `cosmic-bg` automatically detects available hardware decoders and selects the best pipeline:

1. **Probes GStreamer registry** for NVIDIA (NVDEC) and AMD/Intel (VAAPI) decoders
2. **Tests decoder functionality** - demotes non-functional decoders (e.g., NVDEC when CUDA unavailable)
3. **Selects optimal pipeline** based on container format and available decoders
4. **Falls back gracefully** to software decode if no hardware path available

This ensures videos play correctly regardless of GPU vendor or codec availability.

### Performance

| Scenario | CPU Usage | Notes |
|----------|-----------|-------|
| VP9 1080p on AMD (VAAPI) | ~0.2-0.5% | Hardware decode |
| H.264 1080p on AMD (VAAPI + freeworld) | ~0.2-0.5% | Hardware decode with mesa-va-drivers-freeworld |
| H.264 1080p on NVIDIA (NVDEC) | ~0.3-0.5% | Hardware decode |
| H.264 4K on AMD (software) | ~60-80% | Software fallback (no freeworld drivers) |
| Animated AVIF | ~1-5% | Depends on frame count/size |

### Configuration

Set an animated wallpaper via cosmic-config:

```ron
(
output: "all",
source: Path("/path/to/video.webm"),
filter_by_theme: false,
rotation_frequency: 3600,
filter_method: Lanczos,
scaling_mode: Zoom,
sampling_method: Alphanumeric,
animation_settings: (
loop_playback: true,
playback_speed: 1.0,
frame_cache_size: 30,
),
)
```

### Building Without Animation Support

To build without video/animation support (smaller binary, no GStreamer dependency):

```bash
cargo build --release --no-default-features
```

## Dependencies

Developers should install Rust from from https://rustup.rs/.

### Build Dependencies

- just
- cargo / rustc
- libwayland-dev
- libxkbcommon-dev
- mold
- pkg-config
- **libdav1d-devel** - Required for static AVIF image decoding
- **nasm** - Required for building the dav1d AV1 decoder (used for animated AVIF)

```bash
# Fedora
sudo dnf install libdav1d-devel nasm

# Ubuntu/Debian
sudo apt install libdav1d-dev nasm

# Arch
sudo pacman -S dav1d nasm
```

### For Live Wallpaper Support (animated feature)

GStreamer 1.20+ with the following plugins:

**Core (required)**:
- `gstreamer1` - Core GStreamer
- `gstreamer1-plugins-base` - Base plugins including `videoconvert`
- `gstreamer1-plugins-good` - Container demuxers (MP4, WebM, MKV)

**Hardware Acceleration (recommended)**:
- `gstreamer1-plugins-bad` - NVIDIA NVDEC (`nvh264dec`, etc.) and AMD/Intel VA-API (`vah264dec`, `vapostproc`, etc.)

> **Note**: GStreamer 1.20+ includes the modern `va` plugin in `gstreamer1-plugins-bad`. The legacy `gstreamer1-vaapi` package is no longer required on modern systems.

**Example installation**:

```bash
# Fedora
sudo dnf install gstreamer1 gstreamer1-plugins-base gstreamer1-plugins-good \
gstreamer1-plugins-bad-free gstreamer1-vaapi

# Ubuntu/Debian
sudo apt install gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-vaapi

# Arch
sudo pacman -S gstreamer gst-plugins-base gst-plugins-good \
gst-plugins-bad gstreamer-vaapi
```

**H.264/H.265 Hardware Decode for AMD (Fedora)**:

AMD GPUs on Fedora lack VAAPI H.264/H.265 hardware decode by default due to patent restrictions in the standard Mesa VA drivers. To enable **hardware-accelerated** H.264/H.265 decoding, install the freeworld Mesa VA drivers from RPM Fusion:

```bash
# Requires RPM Fusion free repository to be enabled
# https://rpmfusion.org/Configuration

# Replace standard Mesa VA drivers with freeworld version (includes H.264/H.265)
sudo dnf swap mesa-va-drivers mesa-va-drivers-freeworld
```

Verify hardware decode is working:
```bash
vainfo | grep -i h264
# Should show: VAProfileH264Main : VAEntrypointVLD
```

**Software fallback (if hardware decode unavailable)**:

If you cannot install the freeworld drivers, you can use the OpenH264 software decoder (high CPU usage for HD/4K content):

```bash
sudo dnf --enable-repo=fedora-cisco-openh264 install gstreamer1-plugin-openh264
```

**Recommendation**: Use VP9/WebM or AV1 encoded videos which have full VAAPI hardware support on AMD without needing freeworld drivers.

### Install

Expand Down
91 changes: 90 additions & 1 deletion config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ pub struct Entry {
pub scaling_mode: ScalingMode,
#[serde(default)]
pub sampling_method: SamplingMethod,
/// Animation playback settings for animated wallpapers (AVIF, video)
#[serde(default)]
pub animation_settings: AnimationSettings,
}

/// A background image which is colored.
Expand All @@ -111,15 +114,99 @@ pub struct Gradient {
pub radius: f32,
}

/// The source of a background image.
/// The source of a background image or animation.
///
/// # Variants
///
/// - `Path`: A static image or animated file (AVIF, video). The file extension
/// determines how it's rendered. Supported: jpg, png, webp, avif, jxl, mp4, webm, mkv.
/// - `Color`: A solid color or gradient background.
///
/// # Example (RON format)
///
/// ```ron
/// // Static image
/// source: Path("/usr/share/backgrounds/cosmic/default.jpg")
///
/// // Animated wallpaper (video)
/// source: Path("/home/user/wallpapers/nature.webm")
///
/// // Solid color (RGB values 0.0-1.0)
/// source: Color(Single([0.1, 0.1, 0.2]))
/// ```
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
pub enum Source {
/// Background image(s) from a path.
///
/// If the path points to a directory, images are rotated based on
/// `rotation_frequency`. If it points to an animated file (AVIF, video),
/// it will be rendered as an animated wallpaper.
Path(PathBuf),
/// A background color or gradient.
Color(Color),
}

/// Settings for animated wallpaper playback (AVIF and video files).
///
/// These settings control how animated wallpapers are rendered. The defaults
/// are optimized for smooth playback with minimal resource usage.
///
/// # Example Configuration (RON format)
///
/// ```ron
/// animation_settings: (
/// loop_playback: true,
/// playback_speed: 1.0,
/// frame_cache_size: 30,
/// )
/// ```
///
/// # Hardware Acceleration Notes
///
/// For best performance with video wallpapers:
/// - **NVIDIA**: Any codec works well (H.264, H.265, VP9, AV1)
/// - **AMD (Mesa)**: Use VP9 or AV1 encoded videos for hardware decode
/// - **Intel**: Most codecs supported via VAAPI
///
/// TODO: These settings are not yet implemented. Videos always loop unconditionally.
/// Future work: wire these settings into the video player.
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
pub struct AnimationSettings {
/// Whether to loop the animation when it reaches the end.
///
/// Default: `true` (continuous looping)
#[serde(default = "default_true")]
pub loop_playback: bool,

/// Playback speed multiplier.
///
/// - `1.0` = normal speed
/// - `0.5` = half speed (slower)
/// - `2.0` = double speed (faster)
///
/// Default: `1.0`
///
/// Note: This setting is currently reserved for future use.
#[serde(default = "default_playback_speed")]
pub playback_speed: f32,
}

fn default_true() -> bool {
true
}

fn default_playback_speed() -> f32 {
1.0
}

impl AnimationSettings {
/// Create default animation settings.
#[must_use]
pub fn new() -> Self {
Self::default()
}
}

impl Entry {
/// Define a preferred background for a given output device.
pub fn new(output: String, source: Source) -> Self {
Expand All @@ -131,6 +218,7 @@ impl Entry {
filter_method: FilterMethod::default(),
scaling_mode: ScalingMode::default(),
sampling_method: SamplingMethod::default(),
animation_settings: AnimationSettings::default(),
}
}

Expand All @@ -146,6 +234,7 @@ impl Entry {
filter_method: FilterMethod::default(),
scaling_mode: ScalingMode::default(),
sampling_method: SamplingMethod::default(),
animation_settings: AnimationSettings::default(),
}
}
}
Expand Down
18 changes: 17 additions & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,28 @@ Build-Depends:
mold,
nasm,
pkg-config,
cmake,
meson,
ninja-build,
clang,
libgstreamer1.0-dev,
libgstreamer-plugins-base1.0-dev,
libdav1d-dev,
Standards-Version: 4.3.0
Homepage: https://github.com/pop-os/cosmic-bg

Package: cosmic-bg
Architecture: amd64 arm64
Depends:
${misc:Depends},
${shlibs:Depends}
${shlibs:Depends},
gstreamer1.0-plugins-base,
gstreamer1.0-plugins-good,
gstreamer1.0-plugins-bad,
Recommends:
gstreamer1.0-plugins-ugly,
gstreamer1.0-libav,
Copy link
Member

Choose a reason for hiding this comment

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

Both the libav and ugly packages contain codecs which are illegal for us to distribute.

Copy link
Author

Choose a reason for hiding this comment

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

I can switch to openh264 and do some testing to see if it works well still.
But waiting on @jackpot51 before I spend any more of my time on this, he didn't sound like he wants this addition in cosmic-bg.

Copy link
Member

Choose a reason for hiding this comment

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

Ideally the animated image support should be packaged as its own separate crate that is shareable across apps that need the same functionality. And image/video detection would be better done based on media content instead of the extension. If something can be upstreamed to the gstreamer maintainers, it should. Likewise if possible it would be a good idea to contribute iced-specific integrations to iced-player.

gstreamer1.0-vaapi,
Suggests:
gst-cuda-dmabuf,
Description: Cosmic Background
Loading