A customizable ring light overlay for Hyprland/Wayland
Features • Demo • Installation • Usage • Configuration • Architecture • IPC API • License
hypr-ringlight displays a glowing ring light effect around the edges of your screen(s). Inspired by the Edge Light feature introduced in macOS Tahoe.
Note: This project is experimental and under active development.
Click to watch the demo on YouTube
- Smooth glow effect with customizable intensity
- Rounded inner corners for a polished look
- Multi-monitor support with per-monitor toggle
- System tray integration with full control
- Interactive TUI configurator with live preview
- Multiple animations: None, Pulse, Rainbow, Breathe, Wave
- Click-through: doesn't block mouse input or scrolling
- IPC support for scripting and automation
- Omarchy theme integration: automatically uses accent color from Omarchy themes
- Camera notifications: alerts when webcam is in use to remind you to enable the ring light
- Configurable thickness, color, opacity, and glow radius
- Persistent configuration (
~/.config/hypr-ringlight/config.toml) - Works with any Wayland compositor supporting
wlr-layer-shell
- Hyprland (or any Wayland compositor with
wlr-layer-shellsupport) - System tray (e.g., Waybar with tray module)
- D-Bus (for tray integration)
fusercommand (for camera notifications, usually inpsmiscpackage)- Notification daemon (e.g.,
mako,dunst) for camera alerts
# Clone the repository
git clone https://github.com/stefanomainardi/hypr-ringlight.git
cd hypr-ringlight
# Build
cargo build --release
# Install system-wide (optional)
sudo cp target/release/hypr-ringlight /usr/local/bin/sudo pacman -S rust dbussudo apt install rustc cargo libdbus-1-dev pkg-configsudo dnf install rust cargo dbus-devel# Start the ring light
hypr-ringlight
# Open the TUI configurator
hypr-ringlight config# Red ring with rainbow animation
hypr-ringlight --color ff0000 --animation rainbow
# Thicker ring with more glow
hypr-ringlight --thickness 120 --glow 100
# Adjust for a bottom bar
hypr-ringlight --bar-position bottom --bar-height 40hypr-ringlight configThe TUI provides an interactive way to configure all ring light parameters. It automatically uses your Omarchy theme colors if available, falling back to Catppuccin Mocha otherwise.
┌─────────────────────────────────────────┐
│ hypr-ringlight config │
│ [LIVE] │
├─────────────────────────────────────────┤
│ > Ring Light: ON │
│ Color │
│ Thickness: 80 │
│ Opacity: 100% │
│ Glow: 80 │
│ Corner Radius: 2.5 │
│ Animation: None │
│ Animation Speed: 120 │
│ Monitors │
│ ────────────── │
│ Save & Exit │
│ Exit without saving │
└─────────────────────────────────────────┘
Controls:
| Key | Action |
|---|---|
↑/↓ or j/k |
Navigate options |
←/→ or h/l |
Adjust values |
Enter |
Select / Enter submenu |
Esc |
Go back / Exit |
q |
Quit |
Live Preview: If the ring light is already running, changes are applied in real-time. The TUI shows [LIVE] when connected or [OFFLINE] when the ring light isn't running.
| Option | Default | Description |
|---|---|---|
-c, --color |
ffffff |
Ring color in hex format (e.g., ff0000) |
-t, --thickness |
80 |
Ring thickness in pixels |
-o, --opacity |
1.0 |
Ring opacity (0.0 - 1.0) |
-g, --glow |
80 |
Glow/blur radius in pixels |
--corner-radius |
2.5 |
Inner corner radius multiplier |
-a, --animation |
none |
Animation: none, pulse, rainbow, breathe, wave |
--animation-speed |
120 |
Animation speed (lower = faster) |
--bar-height |
35 |
Height of your status bar in pixels |
--bar-position |
top |
Bar position: top, bottom, left, right |
Settings are stored in ~/.config/hypr-ringlight/config.toml:
color = "89b4fa"
thickness = 80
opacity = 1.0
glow = 80
corner_radius = 2.5
animation = "none"
animation_speed = 120
bar_height = 35
bar_position = "top"
disabled_monitors = []| Field | Type | Description |
|---|---|---|
color |
String | Hex color without # prefix |
thickness |
Integer | Ring thickness in pixels (10-200) |
opacity |
Float | Opacity value (0.0-1.0) |
glow |
Integer | Glow radius in pixels (0-200) |
corner_radius |
Float | Corner radius multiplier (0.0-10.0) |
animation |
String | Animation mode |
animation_speed |
Integer | Animation speed (1-500) |
bar_height |
Integer | Status bar height compensation |
bar_position |
String | Status bar position |
disabled_monitors |
Array | List of disabled monitor names |
Right-click the tray icon to access:
| Menu Item | Description |
|---|---|
| Show/Hide Ring | Toggle visibility |
| Width | Preset sizes and fine adjustment (+5/-5) |
| Animation | Select animation mode |
| Monitors | Enable/disable per monitor |
Changes made from the tray are automatically saved to the config file.
flowchart TB
subgraph Main["Main Process (hypr-ringlight)"]
direction TB
subgraph Input["Input Sources"]
Wayland["Wayland Surfaces<br><i>smithay-client-toolkit</i>"]
Tray["System Tray<br><i>ksni / D-Bus</i>"]
IPC["IPC Server<br><i>Unix Socket</i>"]
end
State["SharedState<br><code>Arc<Mutex<IpcState>></code>"]
subgraph StateFields[" "]
Fields["color | thickness | opacity | glow<br>animation | visible | disabled_monitors"]
end
Render["Render Loop<br><i>calloop event loop</i>"]
end
subgraph TUI["TUI Process (hypr-ringlight config)"]
direction TB
Ratatui["Ratatui Interface<br><i>Color, Thickness, Animation, Monitors</i>"]
Client["IPC Client"]
end
Config[("Config File<br>~/.config/hypr-ringlight/config.toml")]
Socket{{"Unix Socket<br>/run/user/{uid}/hypr-ringlight.sock"}}
Monitor1["Monitor 1"]
Monitor2["Monitor 2"]
MonitorN["Monitor N..."]
Wayland --> State
Tray --> State
IPC --> State
State --> StateFields
StateFields --> Render
Render --> Monitor1
Render --> Monitor2
Render --> MonitorN
Ratatui --> Client
Client <--> Socket
Socket <--> IPC
Config -.->|"Load on startup"| Main
State -.->|"Auto-save"| Config
style Main fill:#1e1e2e,stroke:#89b4fa,stroke-width:2px,color:#cdd6f4
style TUI fill:#1e1e2e,stroke:#a6e3a1,stroke-width:2px,color:#cdd6f4
style Config fill:#313244,stroke:#f9e2af,stroke-width:2px,color:#cdd6f4
style Socket fill:#313244,stroke:#fab387,stroke-width:2px,color:#cdd6f4
style State fill:#45475a,stroke:#cba6f7,stroke-width:2px,color:#cdd6f4
style Render fill:#45475a,stroke:#f38ba8,stroke-width:2px,color:#cdd6f4
| Component | Description |
|---|---|
| Wayland Surfaces | Layer shell surfaces rendered on each monitor using smithay-client-toolkit |
| System Tray | D-Bus based tray icon using ksni crate |
| IPC Server | Unix socket server for inter-process communication |
| SharedState | Thread-safe state container (Arc<Mutex<IpcState>>) |
| Render Loop | Main event loop using calloop for frame scheduling |
| TUI Process | Separate process for configuration, communicates via IPC |
- User Input (Tray/TUI/CLI) modifies
SharedState - Render Loop reads
SharedStateon each frame - Wayland Surfaces are redrawn with new parameters
- Config File is updated when changes are saved
hypr-ringlight exposes a Unix socket at /run/user/{uid}/hypr-ringlight.sock for external control.
Send JSON commands terminated by newline. Responses are JSON objects.
{"SetColor": "ff0000"}{"SetThickness": 100}{"SetOpacity": 0.8}{"SetGlow": 120}{"SetCornerRadius": 3.0}{"SetAnimation": "rainbow"}Values: "none", "pulse", "rainbow", "breathe", "wave"
{"SetAnimationSpeed": 80}{"SetVisible": true}"GetMonitors"Response:
{
"monitors": [
{"name": "DP-1", "enabled": true},
{"name": "HDMI-A-1", "enabled": false}
]
}{"SetMonitorEnabled": {"monitor": "HDMI-A-1", "enabled": true}}#!/bin/bash
SOCKET="/run/user/$(id -u)/hypr-ringlight.sock"
# Set color to red
echo '{"SetColor": "ff0000"}' | nc -U "$SOCKET"
# Enable rainbow animation
echo '{"SetAnimation": "rainbow"}' | nc -U "$SOCKET"
# Hide the ring
echo '{"SetVisible": false}' | nc -U "$SOCKET"
# Get monitor list
echo '"GetMonitors"' | nc -U "$SOCKET"import socket
import json
import os
def send_command(command):
sock_path = f"/run/user/{os.getuid()}/hypr-ringlight.sock"
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(sock_path)
s.send((json.dumps(command) + "\n").encode())
return s.recv(1024).decode()
# Set thickness
send_command({"SetThickness": 120})
# Get monitors
response = send_command("GetMonitors")
print(json.loads(response))Add to ~/.config/hypr/hyprland.conf:
exec-once = hypr-ringlightexec-once = hypr-ringlight --color 00ffff --animation breathe --thickness 60hypr-ringlight integrates with Omarchy to automatically use your theme colors.
When hypr-ringlight starts:
- If no
--coloris specified and the config has the default white (ffffff) - It reads the
accentcolor from~/.config/omarchy/current/theme/colors.toml - The ring light uses your theme's accent color automatically
The TUI configurator also reads Omarchy theme colors:
- Uses
accentfor highlights and selected items - Uses
backgroundfor the interface background - Uses
foregroundfor text - Falls back to Catppuccin Mocha if Omarchy isn't available
When you change your Omarchy theme, you can reload the ring light color without restarting:
# Send SIGUSR2 to reload theme color
killall -SIGUSR2 hypr-ringlightFor seamless Omarchy integration, install the helper script:
sudo cp scripts/omarchy-restart-hypr-ringlight /usr/local/bin/This script can be called by omarchy-theme-set to automatically update the ring light when themes change.
hypr-ringlight monitors your webcam and shows a desktop notification when it detects the camera is in use.
- Monitors
/dev/video*devices every 5 seconds usingfuser - When a camera becomes active and the ring light is hidden, shows a notification
- The notification reminds you to enable the ring light for video calls
fusercommand (usually part ofpsmiscpackage)- A notification daemon (e.g.,
mako,dunst)
Camera monitoring runs automatically. To disable it, you can start hypr-ringlight with the ring already visible:
hypr-ringlight --visibleOr toggle visibility via the system tray immediately after starting.
| Issue | Solution |
|---|---|
| Ring doesn't appear | Ensure compositor supports wlr-layer-shell |
| Tray icon missing | Check if system tray is running (Waybar tray module) |
| Tray icon not working | Verify D-Bus: systemctl --user status dbus |
| High CPU usage | Expected during animations; use --animation none |
| TUI shows [OFFLINE] | Start the main ring light process first |
| Changes not persisting | Use "Save & Exit" in TUI or modify from tray |
| Camera notifications not showing | Install psmisc for fuser and ensure notification daemon is running |
| Theme color not updating | Send killall -SIGUSR2 hypr-ringlight after changing Omarchy theme |
| Crate | Purpose |
|---|---|
smithay-client-toolkit |
Wayland protocol bindings |
wayland-client |
Low-level Wayland communication |
calloop |
Event loop for frame scheduling |
ksni |
System tray (StatusNotifierItem) |
ratatui |
Terminal UI framework |
crossterm |
Terminal manipulation |
serde / serde_json |
Serialization |
toml |
Config file parsing |
clap |
Command line argument parsing |
signal-hook |
Unix signal handling (SIGUSR2) |
notify-rust |
Desktop notifications |
Contributions are welcome! Please feel free to submit issues and pull requests.
This project is licensed under the GNU General Public License v3.0 or later - see the LICENSE file for details.
Made with Rust for the Hyprland community