server2 9b4334835a Performance: surfaces, the candle ring and the equalizer curve as shaders, the pill's bars as quads; tools/perf.sh
A playing pill cost the iGPU about 2.9 ms and the CPU 3.7 ms a frame, 60 frames a
second: two vello passes over the whole island window for the surface's shadow and
gradients (vello keeps no gradient between frames) and one for the bars. Now 0.4 ms
GPU and 1.2 ms CPU. The open card went from 5.2 to 2.6 ms GPU and 6.2 to 3.4 ms CPU
a frame. surface.wgsl draws every design's shadow, fill and inner gradient in one
pass, with vello's own blurred-rectangle formula
2026-10-19 13:00:35 +02:00
2026-10-06 03:28:55 +02:00

Magic Island: an Amane media widget

A Dynamic-Island-style media widget for Amane (Rust Wayland shell library), running on Hyprland. It sits as a small pill at the top centre of the screen and opens into a now-playing card.

  • Pill: cover thumbnail, title, 7 live bars from the shown player's own sound (lows left, highs right, growing up and down from the middle), or a pause sign. With no player open, an idle pill: a music note and "Nothing playing". It opens too, into the last song played (kept across restarts) with Play, which starts its player and asks it to play, and chips that start the players of island.idle.players (desktop file names, gtk-launch).
  • Card: a still CD of the cover ringed by 40 cava "candles", title and artist, a seekable time bar, previous / play-pause / next.
  • Player switcher ("VIA …" chip): every MPRIS player; picking one pins it, otherwise the island follows whichever plays.
  • Output switcher: moves only the shown player's audio to another device.
  • Per-player volume (scroll the volume chip, click to mute), set on that player's own PulseAudio/PipeWire streams, so it also works for browsers that ignore MPRIS volume.
  • Real 10-band equalizer per player (31 Hz to 16 kHz, ±12 dB): a PipeWire filter-chain per player, with 8 presets (Flat, Bass, Treble, Vocal, Pop, Rock, Jazz, Classic). Curves are saved per player.
  • Background: a diagonal gradient in the wallpaper's colours (see Colours); optionally the open card takes the song cover's colours.
  • Opening animation: the pill fades out, the empty shape grows, then the card fades in. Closing runs backwards. Moving the pointer off the open card closes it.
  • Every monitor: each monitor shows the pill. The card opens only on the monitor you click (a keybind opens it on the focused one), and only the focused monitor's pill shows live bars; the others show still bars, which keeps the cost at about one island's.
  • Workspace pill: top left on every monitor, that monitor's Hyprland workspaces as round numbered slots in the island's look. The focused workspace sits on an accent disc, which slides over (stretching a little on the way) when the workspace changes; the one shown on another monitor has a softer accent, empty ones are dim. Click one to switch to it.
  • System tray: right of the workspace pill, a pill with every tray app's icon (StatusNotifierItem). Left click activates the app (or opens its menu if that's all it offers), right click opens its menu, drawn in the island's look (checkmarks, separators, submenus with a way back, disabled entries dim), middle click is the app's secondary action, scrolling its scroll. Nothing on this desktop ran a tray watcher, so the shell owns org.kde.StatusNotifierWatcher on the session bus and serves it (if another bar already does, the shell shows no tray). Icons come from the app's icon name (its own theme folder, gtk's icon theme, hicolor, AdwaitaLegacy, Adwaita, /usr/share/pixmaps) or from the pixels it sends, written as PNGs to $XDG_RUNTIME_DIR/amane-tray/. An app that quits takes its icon with it (src/tray.rs).
  • Control centre: top right on every monitor, a pill with Ethernet, Wi-Fi (or the hotspot), VPN, Bluetooth and the battery. Click it and it opens, in the island's way, into a panel like macOS's Control Center. See Control centre.
  • Out of the way of fullscreen windows: on a monitor whose workspace shows a fullscreen window (Hyprland fullscreen 2 or 3; only maximised keeps the bar), the island, the workspace pill (with the tray) and the control centre's pill slide out of sight. Resting the pointer on that monitor's top edge for 0.3 s brings them back until it has been off them and the edge for 0.7 s; an open card or panel stays. Everywhere else the bar is always there. Hyprland's event socket says when windows go fullscreen or workspaces change, and its command socket answers monitors and clients; nothing polls. The edge strip is a one-pixel Overlay layer, there only on fullscreen monitors (it takes their top pixel row). While hidden the bar draws nothing at all, a playing player included (src/edge.rs).
  • Room for the bar: windows that aren't fullscreen stay below the bar. An empty, click-through strip as tall as the pills reach (44 px) reserves the top of every monitor (a layer-shell exclusive zone, namespace magic-reserve); Hyprland keeps tiled and maximised windows out of it, plus gaps_out, while fullscreen windows cover it, just where the bar hides. The reservation goes away with the shell. The bar's own windows ignore it, so they stay at the top edge.
  • Peeks: some events bring one part in for a moment over a fullscreen window: the workspace pill alone (without the tray) for 1.6 s when the workspace changes (with the disc sliding over), the island for 3.5 s when the song changes or a player appears, on the focused monitor. The island also pops (it widens and glows, then lets go) on those events when it's visible anyway. The control centre never peeks.

Control centre

The pill shows only what runs: the Ethernet socket while a cable is connected, Wi-Fi signal while iwd runs (dim while its radio is off; the hotspot sign instead while it's on), the VPN lock only while a VPN connects or is connected (accent once connected), Bluetooth while bluetoothd runs (dim while powered off, accent with a device connected), and the battery with its percentage (a bolt while charging, red below 15 %). The pill is as wide as what it shows.

The panel has:

  • Tiles: Wi-Fi, Bluetooth, VPN, Hotspot, and Ethernet across the panel when the machine has a wired card. Accent = on; grey with a grey strike through the icon = its service isn't running (iwd, bluetoothd, the openvpn service); grey without the strike = nothing to work with (no device, hotspot not set up). Wi-Fi, Bluetooth and VPN open their menu when clicked anywhere; Hotspot switches at once.
    • Wi-Fi (iwd): iwd stopped → grey; the switch in its menu runs rc-service iwd start. iwd running → the switch turns the device (station) on or off. The menu is a map: this desktop as a bubble in the middle (its hostname under it), every network in range floating around it, nearer the stronger its signal, rings rippling out while iwd scans. Each is tied to the bubble by a line: grey and wobbling while not connected; after a click the line runs out from the bubble to it until it's connected; then solid, with a pulse travelling along it. Click a saved or open network to join it (iwctl station <dev> connect), the connected one to disconnect; a new network asks for its password in a popup first (802.1x networks still need iwctl). If joining fails, the popup says why, in iwctl's words. Right-click a saved network for its saved password (read with doas -n cat /var/lib/iwd/<profile>.psk), which Copy (or a click on it) puts on the clipboard through wl-copy. Click the bubble in the middle to search again: rings run out from it while iwd scans. The tile and the connected network show the receive link rate (iwd's diagnostics, asked at most once a minute). "Stop the Wi-Fi service" at the bottom runs rc-service iwd stop. The shell never wakes iwd by asking it anything: it checks NameHasOwner first, because any D-Bus call to net.connman.iwd starts it.
    • Bluetooth (BlueZ, through Amane's Bluetooth): the switch powers the adapter; with bluez installed but stopped it runs rc-service bluetooth start. The menu is the same map with the devices bluez knows or finds (it scans while open): connected ones closest, then paired, then the rest. Click to connect, disconnect, or pair (then trust and connect). Pairing and connecting go through a bluetoothctl session with its own agent: a PIN or passkey it needs, a code to confirm or to type on the device are asked in a popup, and a failure shows why. Click the bubble to search for devices (bluetoothctl --timeout 15 scan on).
    • VPN: every openrc service /etc/init.d/openvpn.* (here openvpn.swiss), each with a switch. Connecting while openrc has it inactive, connected once its up.sh marks it started. A switch runs rc-service openvpn.<name> start|stop.
    • Hotspot (iwd access point): see Hotspot setup.
    • Ethernet: every real wired card in /sys/class/net (a device behind it, not wireless): connected or no cable, and the link speed it negotiated (speed, as 1 Gb/s). A click turns the panel into its details (it morphs like into any menu; the back button returns): the interface and driver, status (speed, duplex), every IPv4 and IPv6 address, gateway, DNS servers, MAC, MTU and traffic since boot (ip -o addr, ip -o route, /etc/resolv.conf, sysfs). Addresses, gateway, DNS and MAC have a Copy chip; a click anywhere on such a row copies it (addresses without their prefix).
  • Popups: questions (a password, PIN or passkey), a code to confirm, a saved password or why something failed, in a small card over the menu. Only a popup with a field takes the keyboard (Enter answers, Escape cancels); the others leave keys to other windows until clicked.
  • Menus: one shape the whole way: the panel fades its content out and takes the menu's size (the Wi-Fi and Bluetooth maps reach from the monitor's right edge to the media island's pill, leaving a 16 px gap, room for the pill's pop: 822 × 480 px on a 1920 px monitor; at most 8 networks or devices, the strongest first, so their labels never meet), then the menu's content fades in over it from a window of its own (so the maps' animation redraws only that window). The back button returns to the panel. Moving the pointer off both closes the menu where it is: its content fades out and it shrinks straight into the pill, without the panel growing back first.
  • Sound: the default output's volume (drag or scroll the slider); the value next to the title is the mute button. With control.services.extra_output set (see Settings) a second slider follows for the output whose name contains it (here NR515: the [TX-NR515] receiver on HDMI 1). Clicking the block elsewhere opens the Sound menu: every output with its own slider and mute; click a name to make it the default output.
  • Display: the laptop screen's brightness and the keyboard light (tpacpi::kbd_backlight: Off, Low, High), written straight to their sysfs files (a udev rule makes them writable by everyone on this laptop), else through logind, which refuses a shell without a seat (one started over ssh). For external monitors see External monitor brightness.
  • Performance: the firmware profiles in /sys/firmware/acpi/platform_profile_choices (Low power, Balanced, Performance), and the battery's state and time left. Picking one sets the platform profile and the CPU governor and energy preference with it: Low power = powersave + power, Balanced = powersave + balance_performance, Performance = the performance governor.
  • External GPU: the GPU the machine didn't boot with (boot_vga = 0, here the Radeon RX 6600 in the Thunderbolt enclosure): load, temperature, power draw, video memory, and every program that has it open with its video memory (and its share of the GPU while the driver reports engine time). Found through /proc/<pid>/fdinfo (drm-pdev), only while the panel is open. Root's processes can't be read and don't show; the shell itself shows up, because Vulkan opens every GPU.

Changes that need root (starting services, the performance mode) run as doas -n <fixed command>, so they need a doas rule without a password for these commands (here permit nopass fabian). Nothing from other programs is passed to a shell.

amane ipc call control toggle    # also: open, close
amane ipc call control menu wifi # also: bluetooth, vpn, sound, close
amane ipc call control status    # expanded=<bool> menu=<name|none> wifi=<state> vpn=<state> profile=<p> battery=<n%> prompt=<what the popup shows|none>
amane ipc call control wifi join <SSID>          # as a click on it in the map; also: wifi password <SSID>, wifi search
amane ipc call control bluetooth pair <ADDRESS>  # also: bluetooth connect <ADDRESS>, bluetooth search
amane ipc call control prompt answer <text>      # answers the popup; also: prompt cancel
amane ipc call control ethernet                  # as a click on the Ethernet tile
amane ipc call tray list                         # every tray item: key, title, icon file
amane ipc call tray open <n>                     # the n-th icon's menu, as a right click; also: activate <n>
amane ipc call tray menu                         # the open menu's entries; then: choose <id>, enter <id>, back, close

Hotspot setup

iwd turns the Wi-Fi device into an access point, so the laptop isn't a Wi-Fi client while the hotspot is on (the internet then comes from eth0). Once, as root:

  1. Let iwd hand out addresses, in /etc/iwd/main.conf. This also makes iwd configure IPv4 for Wi-Fi as a client:

    [General]
    EnableNetworkConfiguration=true
    
  2. A profile /var/lib/iwd/ap/<Name>.ap (man iwd.ap):

    [Security]
    Passphrase=at-least-eight-characters
    
    [IPv4]
    Address=192.168.80.1
    DNSList=<your router, or a public resolver>
    
  3. hotspot = "<Name>" under [control.services] in config.toml, or Control centre › Services in the config window (applies live).

  4. To share eth0's internet with the hotspot's devices, forwarding and NAT (iwd doesn't do this), e.g. with nftables:

    sysctl -w net.ipv4.ip_forward=1
    nft add table ip hotspot
    nft 'add chain ip hotspot post { type nat hook postrouting priority 100 ; }'
    nft add rule ip hotspot post ip saddr 192.168.80.0/24 oifname eth0 masquerade
    

Clicking the tile switches the device to access-point mode and calls StartProfile(<Name>); if that fails (no such profile) it switches back to a client. The tile shows the profile's name, then how many devices are connected.

External monitor brightness

External monitors take brightness over DDC/CI (VCP code 10), which ddcutil speaks. Not wired in yet; to try it:

  1. emerge --ask app-misc/ddcutil
  2. Load i2c-dev: modprobe i2c-dev, and to keep it, modules="i2c-dev" in /etc/conf.d/modules.
  3. Access without root: ddcutil's udev rule gives the /dev/i2c-* buses to group i2c; gpasswd -a fabian i2c, then log in again.
  4. Turn on DDC/CI in each monitor's on-screen menu, if it has the option.
  5. ddcutil detect lists the monitors; ddcutil --display 1 getvcp 10 reads one's brightness, ddcutil --display 1 setvcp 10 60 sets it.

Each call takes 50–300 ms, so a slider has to send only where the drag ends. Monitors behind docks or MST hubs often don't pass DDC; the eGPU's own DP and HDMI ports usually do. Once ddcutil detect works, the Display block can get one slider per external monitor.

Colours

The shell takes its colours from the wallpaper through matugen. Skwd-wall v2 runs matugen on every new wallpaper and renders matugen/amane.colors into ~/.config/amane/theme; the shell picks the file up the moment it changes (inotify through amane::watch_file) and cross-fades to the new colours. Without the file it keeps its original pink on purple; deleting it takes effect at the next start.

Register the template in Skwd-wall as a matugen integration:

  • template: ~/.config/amane/matugen/amane.colors
  • output: ~/.config/amane/theme

The Material You scheme type is Skwd-wall's setting (matugen.schemeType).

Whether the open card takes the song cover's colours instead (appearance.colors.cover_colors), the fade time, and the colours used until there is a theme file are settings.

Side pills

Two small one-icon pills sit left of the island and two right of it, on every monitor; they move outward while the island opens. A click morphs a pill into its card. Only one card is open across the shell: opening the island closes a card, and opening a card closes the island. Esc, the cross or a click elsewhere closes it.

  • AI (sparkle): a chat with a local Ollama model, gemma4:e4b at http://localhost:11434 by default; the model chip in its header lists the installed models and switches at once. Answers stream in through curl. When Ollama doesn't answer, the shell starts the OpenRC service with doas rc-service ollama start (pills.ai.start_service). Stop ends an answer and New chat starts over. Nothing leaves the machine.
  • System (pulse): CPU, the integrated GPU's clock, the external GPU's load (with its video memory and the programs on it, formerly in the control centre), memory, swap, network and disk read/write as graphs over the last minute, plus the hottest sensors with icons, in cool/warm/hot colours (pills.monitor.warm|hot). Everything is read from /proc and /sys, so there is no process list. Processes opens ghostty --class=amane.monitor -e btm on the special workspace monitor; a second press shows or hides it.
  • Home (house): ~/.face as the avatar (a person glyph until the file exists), a greeting, the date, a big clock, and Munich's weather now and for four days from open-meteo (no key), over a photo of the current weather (~/.config/amane/weather/<kind>-<day|night>.jpg, downloaded once from Wikimedia Commons, credits in weather/CREDITS.md; swap any file, pills.home.photo_opacity tones it down). A big month grid marks events (dots in their calendars' colours), birthdays from your iCloud contacts (a cake) and the public holidays of pills.calendar.holidays (BY by default); click a day for its list. Events can be created, edited and deleted there (a series: title, place and calendar only). For iCloud, set pills.calendar.account to your Apple ID and store an app-specific password in the keyring: secret-tool store --label='Magic Island calendar' service amane-calendar account <Apple ID> (gnome-keyring or another Secret Service has to run). The password never goes into config.toml. Other CalDAV/CardDAV servers work through pills.calendar.server and contacts_server.
  • Notifications (bell, with the unread count): Amane is the notification daemon (org.freedesktop.Notifications), so dunst must not run; it is commented out in ~/.config/hypr/autocmds.lua (the old file is beside it as autocmds.lua.before-amane-notifications). Popups appear at the top right of the focused monitor with the app's icon or image, summary, body (simple markup) and action buttons. They time out by urgency (critical ones stay) and wait while the pointer is over them. A click runs the default action. The card lists the history, newest first; each entry's cross deletes it, and Clear all deletes everything. Mute app keeps an app's notifications in the history without popups; Block app drops them (notifications.apps.muted|blocked, undone from chips at the top of the card). Do not disturb turns all popups off.

amane ipc call pods open|toggle ai|monitor|home|notifications [monitor], close, ask <question>, processes, notices list|dismiss <id>|clear, status. Sizes, colours, poll intervals, the city and its coordinates, the model and the popup timeouts are under Side pills and Notifications in the settings.

Left out on purpose: phone stats (iOS shares nothing Linux can read without extra daemons).

Settings

Every setting lives in ~/.config/amane/config.toml: sizes, timings, text sizes, colours, poll intervals, equalizer presets and bands, the hotspot profile, the extra Sound output. The shell writes the file with every setting and its explanation the first time it starts (moving cover_colors, transition_ms, hotspot and extra_output over from an old shell.conf, which it no longer reads). Saving the file applies it at once; a line left out takes its default, and a line the shell can't use is listed at the top of the config window. The font and the cava bar count take effect on restart.

The config window edits the same file: Settings at the foot of the control centre, or amane ipc call config toggle. It is a normal window with a page per part of the shell, a page per Hyprland category, and a search over everything. Numbers with a range get a slider and a field, switches toggle, colours show a swatch; a typed value applies on Enter. Reset brings back the default.

Glass

appearance.surface.design picks what the island, the pills, the tray menu, the control centre and its popups are made of: solid (the wallpaper's gradient with an accent glow), frosted (a thin tint, a hairline white rim, a sheen along the top, a soft shadow) or liquid (clear glass, denser toward the bottom right, a light-catching edge, the accent's glow). Each design's opacity, rim and light are settings of their own (appearance.solid|frosted|liquid.*); the look itself is src/surface.rs, drawn by one shader (src/surface.wgsl: the shadow, the fill and the sheen or lit edge in one pass; the rim stays a border). On see-through glass a face fades with .opacity() instead of a veil, a GPU layer only while it fades.

Performance

Amane draws rectangles, borders, letters and pictures as cheap quads, and gradients, shadows and Canvas paths with vello, which renders over the whole window and then lays the result on top. vello keeps a result between frames only when its shapes didn't change and hold no gradient, so anything drawn with it on every frame costs a pass over the whole window. What redraws every frame is therefore kept off vello: the surfaces are a shader, the pill's bars are rounded rectangles, the candle ring and the equalizer's curve are shaders (src/ring.wgsl, src/curve.wgsl). A shader's file is written to $XDG_RUNTIME_DIR/magic-island/ when it is first drawn. A shader must not contain the word time, or amane redraws its window on every frame.

tools/perf.sh PID [SECONDS] (on the desktop) prints a process's CPU (and its children's), GPU per engine (DRM fdinfo) and memory.

Amane can only blur what the shell draws, so the frosting comes from Hyprland: a layer rule for the shell's windows (the namespace match is a full match). ignore_alpha leaves pixels at or below that opacity unblurred, which keeps the clear margin around the pills sharp; keep it below the design's thinnest part, or that part shows the desktop unblurred (liquid at its default 0.4 is 0.22 in its top left corner).

hl.layer_rule({
    name = "amane-glass",
    match = { namespace = "^magic-.*$" },
    blur = true,
    ignore_alpha = 0.2,
})

The blur's strength is Hyprland's own (decoration.blur.size, passes; also on the config window's Decoration page).

Hyprland options

The Hyprland pages list every option Hyprland itself describes (hyprctl descriptions), with its current value and default, and whether your Hyprland config sets it. A change applies at once (hl.config) and is kept in config.toml under [hyprland.*]; the shell writes those to ~/.config/hypr/amane.lua. Load that file last in hyprland.lua, so the window's values survive a Hyprland reload and win over the rest of your config:

require("amane")

Reset on a Hyprland option removes it from both files and reloads Hyprland's config, so your own value comes back.

Gradients are typed rgba(80d9ffff) rgba(b18cffff) 45deg (or #80d9ff), points 0 0, gaps 6 or 6 10 (css-style).

Layout

src/main.rs        app setup: Fira Code font, the `island` IPC handler, palette from the cover
src/island.rs      the view: pill, card, ring, equalizer, dropdowns, animation, IPC commands
src/sound.rs       per-player volume, mute, output and equalizer (pactl, pw-cli, pipewire)
src/spectrum.rs    the shown player's sound as 24 levels (pw-record | cava)
src/art.rs         cover download (curl) and the palette file
src/workspaces.rs  the workspace pill, top left on every monitor
src/control.rs     the control centre: status pill top right, its panel and menus, `control` IPC
src/map.rs         the Wi-Fi and Bluetooth menus' map: this desktop in the middle, what it can connect to around it
src/prompt.rs      the control centre's popup: passwords, PINs, codes, saved passwords, why a connection failed; joins through iwctl, pairs through bluetoothctl
src/edge.rs        hides the bar over fullscreen windows (Hyprland's sockets), the edge strip, peeks, the workspace slide, the space reserved for the bar
src/tray.rs        the system tray: StatusNotifierWatcher and host, icon lookup, dbusmenu menus
src/png.rs         a stdlib png writer, for tray icons sent as pixels
src/system.rs      what the control centre shows: iwd, wired cards, openvpn, power, the external GPU
src/glyph.rs       every drawn icon, as a Canvas or as shapes for a shared one
src/theme.rs       the shell's colours: the matugen theme file, the cross-fade; watches config.toml
src/surface.rs     what the island, pills, menus and popups are made of: the solid, frosted and liquid designs as values for surface.wgsl
src/*.wgsl         shaders: surface (every surface), ring (the candles), curve (the equalizer's line)
src/config.rs      every setting, declared once (typed accessors + the window's list), config.toml read and written
src/hypr.rs        Hyprland's options for the config window: hyprctl descriptions/getoption, hl.config, amane.lua
src/settings.rs    the config window, `config` IPC
src/parse.rs       pure text parsing with unit tests, no amane dependency
matugen/           the matugen template (amane.colors), rendered by skwd-wall into ./theme
island-eq          saved equalizer curves (a snapshot of ~/.config/amane/island-eq)
tools/             remote test driver, fake MPRIS players, a fake tray app (`fake_tray.py name|path`), perf.sh (CPU, GPU, memory of a process), see AGENTS.md

Requirements

On the desktop that runs the shell:

  • the amane CLI and Rust toolchain (amane compile builds src/)
  • PipeWire with pipewire-pulse: pactl, pw-record, pw-cli, pipewire (filter-chain module)
  • cava, curl
  • the Fira Code font
  • for the control centre: iwd, openrc openvpn.<name> services, pciutils (lspci), wl-clipboard (wl-copy, for copying a saved password or an address), iproute2 (ip, for the Ethernet details), optionally bluez (with bluetoothctl), and a doas rule without a password for rc-service, sh and cat (see Control centre)

Install and run

The shell lives in ~/.config/amane/. Clone this repo there (or copy its src/ and matugen/), then build and start:

amane compile
amane run

amane compile saves the shell as ~/.cache/amane/amane-shell, which amane run starts.

Control it

amane ipc call island toggle           # also: open, close
amane ipc call island next             # show the next player
amane ipc call island play-pause       # play/pause the shown player; the icon flips at once
amane ipc call island menu output      # open a dropdown: output | player | close
amane ipc call island preset Rock      # apply an equalizer preset to the shown player
amane ipc call island output <sink>    # move the shown player to a sink (pactl sink name)
amane ipc call island status           # player=<identity> expanded=<bool> menu=<bool>
amane ipc call config toggle           # the config window; also: open, close
amane ipc call config page island      # open it on a page: appearance, island, ..., hyprland:decoration
amane ipc call config search blur      # open it searching
amane ipc call config get island.pill.width
amane ipc call config set island.pill.width 260         # any setting by its config.toml path
amane ipc call config set hyprland.decoration.blur.size 3
amane ipc call config reset island.pill.width
amane ipc call config status           # open=<bool> page=<id> errors=<n> search=<words>

Example Hyprland keybind:

bind = SUPER, M, exec, amane ipc call island toggle

Runtime files

  • ~/.config/amane/config.toml: every setting (see Settings); rewritten by the config window.
  • ~/.config/hypr/amane.lua: the Hyprland options set in the config window, as hl.config; rewritten by the shell.
  • $XDG_STATE_HOME/amane/ (~/.local/state/amane/): last-played (the song the island last heard, key = value) and a copy of its cover, for the card shown while nothing plays.
  • ~/.config/amane/island-eq: one line per player identity, <player>\t<preset or Custom>\t<10 gains in dB>.
  • $XDG_RUNTIME_DIR/magic-island/: downloaded covers, palette-cover, cava.conf, eq-<key>.conf, the shaders (*.wgsl). On tmpfs, gone at logout.
  • Equalizer nodes are named magic_island_eq.<key>. The key is the player's MPRIS bus name without org.mpris.MediaPlayer2., non-alphanumerics as _ (e.g. chromium_instance123).

Known limits

  • The time bar moves in 1 s steps (the MPRIS poll rate).
  • Every slider step starts one pactl or pw-cli process.
  • An equalizer keeps running, and keeps that player routed through its last curve, if the shell stops while the player is open.
  • The candle ring around the CD is still mirrored left against right; the pill's bars are not.
  • Dropdowns have no keyboard navigation.
S
Description
Config for amane
Readme 5.2 MiB
Languages
Rust 94.5%
WGSL 2.3%
Python 2.1%
Shell 1.1%