Skip to content

Repository files navigation

CyberPet

A lightweight, open-source desktop pet for Linux/Hyprland that renders an animated Pokémon companion reacting to what you're actually doing: typing, listening to music, or in a voice call. Frameless, transparent, always on top, pinned across workspaces, and hidden automatically when something goes truly fullscreen.

Sprite artwork — important

This repo ships no Pokémon artwork. src/main.ts and src/settings.ts build sprite URLs at runtime against the community-maintained PokeAPI sprite mirror and pokeapi.co, the same way PokeAPI's own docs describe for fan/hobby projects. Only this repository's own code is distributed; images are fetched live and cached by the webview's normal HTTP cache. If you'd rather use your own art (or a licensed sprite pack), swap src/sprites.ts for your own URL scheme or bundle local files.

Architecture

┌─────────────────────────┐        pet://state / pet://config
│  Rust backend (Tauri)   │ ───────────────────────────────────┐
│                         │                                    │
│  input_evdev.rs  ── reads /dev/input/event* directly         │
│  media.rs        ── polls playerctl (MPRIS)                  │
│  voicechat.rs    ── polls pactl source-outputs (PipeWire)    │
│  hyprland.rs     ── polls hyprctl for true-fullscreen hide    │
│  state.rs        ── priority state machine: vc > typing >    │
│                      music > wander/sleep                    │
│  tray.rs         ── tray icon + right-click context menu     │
└─────────────────────────┘                                    │
                                                                 ▼
┌───────────────────────────────────────────────────────────────┐
│  Frontend (TypeScript, no framework)                          │
│  pet.html/main.ts       — the pet window: renders sprite state, │
│                            drags the OS window around to "walk" │
│  settings.html/settings.ts — species picker + behavior toggles  │
└───────────────────────────────────────────────────────────────┘

Everything runs as a single Tauri binary — the "system polling daemon" is just async Rust tasks inside the same process, which keeps installation to one binary. The module boundaries (input_evdev.rs, media.rs, voicechat.rs) are intentionally decoupled from Tauri specifics if you ever want to split them into a standalone daemon later.

Behavior priority

Highest wins, each tick: voice chat (pactl capture stream from a known VC app) → typing (evdev keyboard press within the last ~1.8s) → music (playerctl reports "Playing") → sleep (no input for the configured idle window, or wandering disabled) → wander (default).

Requirements

  • Linux, Wayland (developed against Hyprland; degrades gracefully elsewhere)
  • playerctl, PipeWire's pactl compat layer
  • Node.js + npm, Rust + Cargo
  • Your user in the input group, to read /dev/input/event* for typing detection (see below — this is the one privileged step)

Setup

./scripts/setup.sh   # checks deps, runs npm install, prints next steps

The script will tell you if you're not in the input group yet:

sudo usermod -aG input $USER
# log out and back in for this to take effect

Without this, typing detection silently does nothing (permission denied opening the event devices) — everything else still works.

Run

npm run tauri dev      # dev, hot-reloading frontend
npm run tauri build    # release binary -> src-tauri/target/release/cyberpet

Hyprland integration

echo 'source = ~/Projects/cyberpet/hypr/cyberpet.conf' >> ~/.config/hypr/hyprland.conf

See hypr/cyberpet.conf for the window rules (float, pin, no border/shadow/ blur/anim, no focus stealing) and an exec-once line to autostart the built binary. Double-check the window class with hyprctl clients | grep -i cyberpet and adjust the regex in that file if your build reports a different one.

Fullscreen-hide is enforced twice: once by Hyprland's own pinned-window behavior, and again explicitly by src-tauri/src/hyprland.rs, which polls hyprctl activewindow -j and hides the pet window whenever the focused window is truly fullscreen. This means the feature works even if a given Hyprland version's default pinned-window/fullscreen interaction changes.

Using it

  • Left-click drag the pet to reposition it manually.
  • Right-click (or the tray icon) opens the same menu: switch behaviors on/off, or open Settings.
  • Settings window: pick any of the first 151 species (extend the limit in src/settings.ts / fetchPokemonList for later generations), tune walk speed, scale, and idle-to-sleep timing.
  • Voice-chat mode: when a known VC app (Discord, WebCord, Vesktop, Mumble, Teams, TeamSpeak — edit vcApps in Settings' saved config or config.rs's Config::default()) has an active PipeWire capture stream, the pet puts on a pixel headset and shows a clickable speech bubble (click it to cycle flavor text). This is a heuristic — it detects an open mic stream, not real voice-activity — see voicechat.rs.

Config persists to ~/.config/cyberpet/config.toml.

Theming

Colors/fonts match the "CyberArch" rice this was built alongside: accent red #FF2D3D, near-black translucent panels, Tektur for headers, Quantico for labels, FiraCode Nerd Font for monospace bits. Edit src/style.css and src/settings.css directly — there's no theming abstraction layer, just CSS.

Troubleshooting

App crashes instantly with Gdk-Message: Error 71 (Protocol error) dispatching to Wayland display. — this is a known WebKitGTK/NVIDIA-proprietary-driver incompatibility (its DMA-BUF compositing path isn't supported there), not a bug in this app. main.rs already sets WEBKIT_DISABLE_DMABUF_RENDERER=1 before the webview is created to work around it; if you still hit it on some other GPU/driver combo, try also exporting WEBKIT_DISABLE_COMPOSITING_MODE=1 before launching.

Known limitations

  • Typing/idle detection only sees devices present at startup (no hotplug handling yet).
  • Voice-chat detection is a mic-stream heuristic, not true VAD.
  • Only one "facing" sprite frame is available (animated PokeAPI sprites are front-only); walking left/right is a CSS horizontal flip, not a real walk-cycle sprite sheet.

About

a cool lightweight desk pet pokimon

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages