Wi-Fi and hotspot (iwd, never D-Bus-activated by a read), VPN (openrc openvpn.*), Bluetooth (bluez), battery, volume, screen and keyboard light, performance mode (platform profile + governor + EPP through doas -n), and the external GPU with the programs using it (drm fdinfo). Icons move to glyph.rs.
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".
- 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 takes the accent colour, the one shown on another monitor a softer accent, empty ones dim. Click one to switch to it.
- Control centre: top right on every monitor, a pill with 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.
Control centre
The pill shows Wi-Fi signal (the hotspot sign while it's on), the VPN lock (accent when connected), Bluetooth (accent with a device connected) and the battery with its percentage (a bolt while charging, red below 15 %). Dim means off or not running.
The panel has:
- Four tiles: Wi-Fi, Bluetooth, VPN, Hotspot. Accent = on, grey = the service behind it isn't running. A grey tile is clickable when it can start that service.
- Wi-Fi (iwd): iwd stopped → grey, a click runs
rc-service iwd start. iwd running → a click turns the device (station) on or off. The shell never wakes iwd by asking it anything: it checksNameHasOwnerfirst, because any D-Bus call tonet.connman.iwdstarts it. - Bluetooth (BlueZ, through Amane's
Bluetooth): on/off. With bluez installed but stopped, a click runsrc-service bluetooth start; without bluez the tile says so. - VPN: the openrc services
/etc/init.d/openvpn.*(hereopenvpn.swiss). Connecting while openrc has it inactive, connected once itsup.shmarks it started. A click runsrc-service openvpn.<name> start|stop. - Hotspot (iwd access point): see Hotspot setup.
- Wi-Fi (iwd): iwd stopped → grey, a click runs
- Sound: the default output's volume (drag or scroll the slider); the value next to the title is the mute button.
- Display: the laptop screen's brightness (through logind, like Amane's
Brightness) and the keyboard light (tpacpi::kbd_backlight: Off, Low, High). 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 = theperformancegovernor. - 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 status # expanded=<bool> wifi=<state> vpn=<state> profile=<p> battery=<n%>
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:
-
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 -
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> -
hotspot = <Name>inshell.conf(applies live). -
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:
emerge --ask app-misc/ddcutil- Load
i2c-dev:modprobe i2c-dev, and to keep it,modules="i2c-dev"in/etc/conf.d/modules. - Access without root: ddcutil's udev rule gives the
/dev/i2c-*buses to groupi2c;gpasswd -a fabian i2c, then log in again. - Turn on DDC/CI in each monitor's on-screen menu, if it has the option.
ddcutil detectlists the monitors;ddcutil --display 1 getvcp 10reads one's brightness,ddcutil --display 1 setvcp 10 60sets 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).
shell.conf (read live, no restart):
cover_colors = false # true: the open media card and equalizer take the song cover's colours as background
transition_ms = 400 # how long a colour change cross-fades; 0 switches instantly
hotspot = MyHotspot # the iwd access point profile the control centre's hotspot starts
Keep comments on lines of their own in the real file: a # after a value becomes part of it.
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, `control` IPC
src/system.rs what the control centre shows: iwd, openvpn, power, the external GPU
src/glyph.rs every drawn icon
src/theme.rs the shell's colours: the matugen theme file, shell.conf, the cross-fade
src/parse.rs pure text parsing with unit tests, no amane dependency
matugen/ the matugen template (amane.colors), rendered by skwd-wall into ./theme
shell.conf settings: cover_colors, transition_ms, hotspot
island-eq saved equalizer curves (a snapshot of ~/.config/amane/island-eq)
tools/ remote test driver and fake MPRIS players, see AGENTS.md
Requirements
On the desktop that runs the shell:
- the
amaneCLI and Rust toolchain (amane compilebuildssrc/) - PipeWire with
pipewire-pulse:pactl,pw-record,pw-cli,pipewire(filter-chain module) cava,curl- the Fira Code font
- for the control centre:
iwd, openrcopenvpn.<name>services,pciutils(lspci), optionallybluez, and a doas rule without a password forrc-serviceandsh(see Control centre)
Install and run
The shell lives in ~/.config/amane/. Clone this repo there (or copy its src/, matugen/ and shell.conf), 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>
Example Hyprland keybind:
bind = SUPER, M, exec, amane ipc call island toggle
Runtime files
~/.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. On tmpfs, gone at logout.- Equalizer nodes are named
magic_island_eq.<key>. The key is the player's MPRIS bus name withoutorg.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
pactlorpw-cliprocess. - 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.