NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

commit 0d80e3262c9f6b0ed4e5d7d1659358576de413df
parent d9c39bdde4535d8be00dcc575ba0238ec9f23864
Author: DAEMON <zer0sec.xp@icloud.com>
Date:   Thu,  8 Oct 2026 04:26:01 +0100

added new tools

Diffstat:
MREADME.md | 5++++-
Mhome/default.nix | 2++
Mhome/modules/dotfiles.nix | 5+----
Mhome/modules/fan.nix | 22+++++++++++++++-------
Ahome/modules/media.nix | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mhome/modules/sops.nix | 14+++++++++++++-
Mhome/modules/tools.nix | 5-----
Ahome/modules/yazi.nix | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahome/mpv/shaders/CfL_Prediction.LICENSE | 21+++++++++++++++++++++
Ahome/mpv/shaders/CfL_Prediction.glsl | 363+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahome/rmpc/config.ron | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahome/rmpc/scripts/fetch-lyrics | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahome/rmpc/themes/rose-pine.ron | 320+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahome/yazi/flavors/rose-pine.yazi/flavor.toml | 253+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahome/yazi/flavors/rose-pine.yazi/tmtheme.xml | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 1967 insertions(+), 18 deletions(-)

diff --git a/README.md b/README.md @@ -73,12 +73,15 @@ NixDaemon/ │ ├── modules/ssh.nix the ssh key (private half from sops) and ~/.ssh/config │ ├── modules/gpg.nix the GPG main key: public in home/gpg/, secret from sops, gpg.conf, trust │ ├── modules/git.nix git identity, signing with the main key, aliases, delta +│ ├── modules/media.nix mpd user service, rmpc (full config, Rosé Pine, lyrics), mpv (gpu-next, uosc, thumbfast) +│ ├── modules/yazi.nix yazi: inline image/video/pdf previews, openers, Rosé Pine, git + full-border plugins │ ├── modules/gtk.nix Yaru-purple icons, cursor, prefer-dark │ ├── hypr/*.lua omarchy (shim) · core · looknfeel · defaults · bindings · lid · caelestia │ ├── kitty/scrollback.lua kitty copy mode as a Neovim buffer │ ├── caelestia/ scheme.json (dark) · scheme-dawn.json · rose-pine-{dark,dawn}.txt · shell-tokens.json │ ├── cheats/*.md the cards: nix.md, gpg.md -│ └── gpg/daemon-main.pub.asc the main key's public half +│ ├── gpg/daemon-main.pub.asc the main key's public half +│ ├── rmpc/, mpv/shaders/, yazi/flavors/ the carried rmpc config and theme, the CfL shader, the Rosé Pine flavor └── modules/workstation.nix Claude Code, Claude desktop, Obsidian, gh, glab ``` diff --git a/home/default.nix b/home/default.nix @@ -16,6 +16,8 @@ ./modules/ssh.nix # the ssh key (sops) and ~/.ssh/config ./modules/gpg.nix # the GPG main key (public in-repo, secret via sops) and gpg.conf ./modules/git.nix # git identity, signing with the main key, delta + ./modules/media.nix # mpd + rmpc, mpv + ./modules/yazi.nix # yazi with previews, Rosé Pine, plugins ./modules/gtk.nix # Yaru-purple icons, cursor, prefer-dark ]; diff --git a/home/modules/dotfiles.nix b/home/modules/dotfiles.nix @@ -13,6 +13,7 @@ # .config/nvim the AstroNvim tree; Neovim is nvf now (home/modules/neovim.nix) # .config/starship.toml, .config/fastfetch the Omarchy-era prompt and fetch; both are # Nix-managed now (home/modules/prompt.nix) +# .config/mpd, rmpc, mpv, yazi Nix-managed (home/modules/media.nix, yazi.nix), carried from the checkout # .config/omarchy*, Omacom, hyprmoncfg Omarchy's own trees; caelestia.nix reads the # wallpaper directory straight from the checkout # .config/systemd/user Omarchy-era units; caelestia-shell.service there would @@ -80,7 +81,6 @@ in "mise" # tools "btop" - "yazi" "lazygit" "jj" "emacs" @@ -93,9 +93,6 @@ in "bg-pasticcio" "libvirt" # media - "mpd" - "rmpc" - "mpv" "cava" "imv" "zathura" diff --git a/home/modules/fan.nix b/home/modules/fan.nix @@ -16,7 +16,10 @@ # Reads need no root at all: k10temp and the uniwill hwmon are world-readable. { pkgs, lib, ... }: let - bin = lib.makeBinPath [ pkgs.coreutils pkgs.gawk pkgs.gnugrep pkgs.gnused pkgs.systemd pkgs.libnotify pkgs.sudo ]; + bin = lib.makeBinPath [ pkgs.coreutils pkgs.gawk pkgs.gnugrep pkgs.gnused pkgs.systemd pkgs.libnotify ]; + # NixOS's setuid sudo lives in /run/wrappers; the store copy is not setuid + # and refuses to run ("must be owned by uid 0 and have the setuid bit set"). + sudo = "/run/wrappers/bin/sudo"; # shared read-only sampler, sourced by both scripts lib-sh = pkgs.writeText "fan-lib.sh" '' @@ -45,7 +48,7 @@ let clamped() { # 1 when busy yet no core above TRIP_MHZ local b=$1 mx=$2; [ "$b" -ge "$BUSY_MIN" ] && [ "$mx" -gt 0 ] && [ "$mx" -lt "$TRIP_MHZ" ] && echo 1 || echo 0 } - ec_mode() { sudo -n /run/current-system/sw/bin/fan-ec mode 2>/dev/null || echo "?"; } + ec_mode() { ${sudo} -n /run/current-system/sw/bin/fan-ec mode 2>/dev/null || echo "?"; } status_line() { local b mx avg; b=$(busy); read -r avg mx <<< "$(clocks)" local cl; cl=$(clamped "$b" "$mx") @@ -66,7 +69,7 @@ let while :; do b=$(busy); read -r _ mx <<< "$(clocks)" if [ "$(clamped "$b" "$mx")" = 1 ]; then - out=$(sudo -n /run/current-system/sw/bin/fan-ec auto 2>&1) + out=$(${sudo} -n /run/current-system/sw/bin/fan-ec auto 2>&1) log "CLAMP: load $b %, max core $mx MHz -> fan-ec auto: $out" notify-send -a fan -u critical "Fans back to auto" "EC clamp hit (load $b %, cores at $mx MHz). Manual fan mode released." exit 0 @@ -90,15 +93,20 @@ let } disarm() { systemctl --user stop "$UNIT" 2>/dev/null || true; } + if [ "$(id -u)" = 0 ]; then + echo "fan: run this as yourself, not under sudo (it needs your user session for the watchdog; root access is handled inside)" >&2 + exit 1 + fi + case "''${1:-}" in ""|status) status_line ;; watch) iv=''${2:-2}; busy >/dev/null; sleep "$iv" while :; do status_line; sleep "$iv"; done ;; - max) sudo -n "$EC" max && arm max ;; - auto) disarm; sudo -n "$EC" auto ;; - [0-9]*) p=''${1%\%}; sudo -n "$EC" "$p" && arm "$p %" ;; - ec) sudo -n "$EC" status ;; + max) ${sudo} -n "$EC" max && arm max ;; + auto) disarm; ${sudo} -n "$EC" auto ;; + [0-9]*) p=''${1%\%}; ${sudo} -n "$EC" "$p" && arm "$p %" ;; + ec) ${sudo} -n "$EC" status ;; log) [ -r "$LOG" ] && tail -n ''${2:-20} "$LOG" || echo "no watchdog log yet" ;; -h|--help|help) echo "usage: fan [status] | watch [sec] | max | <30-100> | auto | ec | log [n]" diff --git a/home/modules/media.nix b/home/modules/media.nix @@ -0,0 +1,115 @@ +# home/modules/media.nix — music and video: mpd + rmpc, and mpv. +# +# mpd runs as a user service (starts at login), library ~/Music (the layout +# contract is ~/Music/AGENTS.md), state in ~/.local/share/mpd, PipeWire +# output plus the FIFO rmpc's visualiser reads. It listens on the socket +# $XDG_RUNTIME_DIR/mpd/socket and on 127.0.0.1:6600. +# +# rmpc: the dotfiles' full config (home/rmpc/config.ron: tabs with album art, +# lyrics and cava panes, vim keys, 1-0 tab switching), the Rosé Pine theme +# (home/rmpc/themes/rose-pine.ron) and the LRCLIB lyrics fetcher that runs on +# song change. The Discord presence script was not carried (1.4k lines of +# Python with its own deps; say so if wanted). +# +# mpv: the dotfiles' gpu-next/Vulkan profile with the CfL chroma shader, plus +# uosc (the on-screen UI), thumbfast (seek thumbnails) and mpris (media keys, +# Caelestia's player widget). +{ config, pkgs, ... }: +let + rt = "/run/user/1000"; +in +{ + services.mpd = { + enable = true; + musicDirectory = "${config.home.homeDirectory}/Music"; + playlistDirectory = "${config.xdg.dataHome}/mpd/playlists"; + network = { + listenAddress = "${rt}/mpd/socket"; + port = 6600; + startWhenNeeded = false; + }; + extraConfig = '' + bind_to_address "127.0.0.1" + auto_update "yes" + restore_paused "yes" + audio_output { + type "pipewire" + name "PipeWire" + } + audio_output { + type "fifo" + name "Visualizer FIFO" + path "${rt}/mpd/fifo" + format "44100:16:2" + } + ''; + }; + # the socket's directory, created by systemd before mpd starts + systemd.user.services.mpd.Service.RuntimeDirectory = "mpd"; + + programs.rmpc = { + enable = true; + config = builtins.readFile ../rmpc/config.ron; + }; + xdg.configFile = { + "rmpc/themes/rose-pine.ron".source = ../rmpc/themes/rose-pine.ron; + "rmpc/scripts/fetch-lyrics" = { + source = ../rmpc/scripts/fetch-lyrics; + executable = true; + }; + "mpv/shaders".source = ../mpv/shaders; + }; + + programs.mpv = { + enable = true; + scripts = with pkgs.mpvScripts; [ uosc thumbfast mpris ]; + config = { + vo = "gpu-next"; + gpu-api = "vulkan"; + gpu-context = "waylandvk"; + profile = "high-quality"; + hwdec = "auto"; + scale = "ewa_lanczos4sharpest"; + glsl-shader = "~~/shaders/CfL_Prediction.glsl"; + cscale = "ewa_lanczossharp"; + cscale-antiring = 0.65; + dscale = "mitchell"; + correct-downscaling = true; + linear-downscaling = true; + sigmoid-upscaling = true; + deband = true; + deband-iterations = 2; + deband-threshold = 32; + deband-range = 12; + deband-grain = 16; + dither = "error-diffusion"; + error-diffusion = "burkes"; + dither-depth = 8; + temporal-dither = false; + video-sync = "display-resample"; + interpolation = true; + tscale = "oversample"; + target-colorspace-hint = "auto"; + target-colorspace-hint-mode = "target"; + target-prim = "bt.709"; + target-trc = "srgb"; + gamut-mapping-mode = "perceptual"; + tone-mapping = "auto"; + hdr-compute-peak = true; + contrast = 4; + saturation = 5; + screenshot-format = "png"; + screenshot-high-bit-depth = true; + screenshot-tag-colorspace = true; + screenshot-directory = "~/Pictures/mpv"; + osc = false; # uosc replaces it + border = false; + save-position-on-quit = true; + keep-open = true; + sub-auto = "fuzzy"; + slang = "en,eng"; + alang = "ja,jpn,en,eng"; + ytdl-format = "bestvideo[height<=?1440]+bestaudio/best"; + }; + }; +} diff --git a/home/modules/sops.nix b/home/modules/sops.nix @@ -12,7 +12,7 @@ # secretspec (home/modules/shell.nix) is the complement: per-project runtime # secrets pulled from the keyring at `secretspec run`, declared next to the # project in secretspec.toml, not in this repo. -{ config, inputs, pkgs, ... }: +{ config, inputs, pkgs, lib, ... }: { imports = [ inputs.sops-nix.homeManagerModules.sops ]; @@ -28,4 +28,16 @@ age ssh-to-age # turn an ssh-ed25519 key into an age recipient when adding another machine ]; + + # sops-nix's activation step restarts the sops-nix user unit. That unit is a + # home-manager file (~/.config/systemd/user/sops-nix.service), linked by the + # linkGeneration step, and the user systemd manager only sees new unit + # files after a daemon-reload, which home-manager runs at the very end. With + # the extra steps this config adds, the DAG happened to order sops-nix + # before linkGeneration, so the first switch died with "Unit + # sops-nix.service not found". This entry pins the order: linkGeneration → + # daemon-reload → sops-nix. + home.activation.reloadUserUnitsForSops = lib.hm.dag.entryBetween [ "sops-nix" ] [ "linkGeneration" "installPackages" ] '' + ${pkgs.systemd}/bin/systemctl --user daemon-reload 2>/dev/null || true + ''; } diff --git a/home/modules/tools.nix b/home/modules/tools.nix @@ -60,10 +60,6 @@ in yt-dlp atomicparsley gallery-dl - mpd - mpc - rmpc - mpv imagemagick img2pdf resvg @@ -91,7 +87,6 @@ in uv # PEP-723 scripts in ~/.local/bin (dcx, dorkforge); managed Pythons work via nix-ld nodejs btop - yazi lazygit lazydocker tealdeer # `tldr` diff --git a/home/modules/yazi.nix b/home/modules/yazi.nix @@ -0,0 +1,130 @@ +# home/modules/yazi.nix — yazi, the terminal file manager, with previews. +# +# Pictures preview inline in kitty (its graphics protocol; yazi draws them +# itself, nothing else needed), video frames via ffmpeg, PDFs via poppler, +# SVG via resvg, archives via 7z, JSON via jq, code with syntax colours, and +# the rest as text. Enter / l on a file opens it: images in imv, video and +# audio in mpv, PDFs and books in zathura, text in $EDITOR, anything else via +# xdg-open; o shows every opener. The toolbox's zimg/zbook/comx/gamex/dux +# are offered where they apply (they live in ~/.local/bin). +# +# y start yazi and cd to where you left it (zsh wrapper) +# T maximise the preview pane (toggle-pane) ! shell here +# <C-e>/<C-y> scroll the preview . toggle hidden +# l / Enter smart-enter: open a file, enter a directory +# Theme: Rosé Pine (home/yazi/flavors). Git status marks via the git plugin. +{ pkgs, ... }: +{ + programs.yazi = { + enable = true; + enableZshIntegration = true; + shellWrapperName = "y"; + extraPackages = with pkgs; [ + ffmpeg + poppler-utils + imagemagick + resvg + p7zip + jq + fd + ripgrep + fzf + zoxide + file + mediainfo + imv + ]; + + settings = { + mgr = { + show_hidden = false; + show_symlink = true; + sort_by = "natural"; + sort_sensitive = false; + sort_dir_first = true; + linemode = "size"; + scrolloff = 5; + }; + preview = { + max_width = 1600; + max_height = 1600; + image_delay = 20; + image_filter = "lanczos3"; + image_quality = 90; + wrap = "yes"; + }; + opener = { + edit = [ { run = ''${EDITOR:-nvim} "$@"''; desc = "Edit"; block = true; for = "unix"; } ]; + open = [ { run = ''xdg-open "$1"''; desc = "Open (system default)"; orphan = true; for = "unix"; } ]; + view-image = [ + { run = ''imv "$@"''; desc = "View (imv)"; orphan = true; for = "unix"; } + { run = ''zimg "$1"''; desc = "View in zathura (zimg)"; orphan = true; for = "unix"; } + { run = ''xdg-open "$1"''; desc = "Open (system default)"; orphan = true; for = "unix"; } + ]; + play = [ + { run = ''mpv "$@"''; desc = "Play (mpv)"; orphan = true; for = "unix"; } + { run = ''mpv --no-video "$@"''; desc = "Play audio only (mpv)"; block = true; for = "unix"; } + ]; + open-pdf = [ + { run = ''zathura "$@"''; desc = "Open (zathura)"; orphan = true; for = "unix"; } + { run = ''xdg-open "$1"''; desc = "Open (system default)"; orphan = true; for = "unix"; } + ]; + open-book = [ + { run = ''zbook "$1"''; desc = "Read in zathura (zbook)"; orphan = true; for = "unix"; } + { run = ''zathura "$@"''; desc = "Open (zathura)"; orphan = true; for = "unix"; } + ]; + open-comic = [ + { run = ''comx "$1"''; desc = "Guided view (comx)"; orphan = true; for = "unix"; } + { run = ''zbook "$1"''; desc = "Panels in zathura (zbook)"; orphan = true; for = "unix"; } + { run = ''7z x -y "$1"''; desc = "Extract here (7z)"; block = true; for = "unix"; } + ]; + extract = [ { run = ''7z x -y "$1"''; desc = "Extract here (7z)"; for = "unix"; } ]; + run-exe = [ + { run = ''gamex run "$1"''; desc = "Run (gamex, dGPU)"; block = true; for = "unix"; } + { run = ''gamex proton "$1"''; desc = "Run with Proton-GE"; block = true; for = "unix"; } + ]; + }; + open = { + rules = [ + { mime = "image/gif"; use = [ "view-image" "open" ]; } + { mime = "image/*"; use = [ "view-image" "open" ]; } + { mime = "video/*"; use = [ "play" "open" ]; } + { mime = "audio/*"; use = [ "play" "open" ]; } + { mime = "application/pdf"; use = [ "open-pdf" "open" ]; } + { mime = "application/epub+zip"; use = [ "open-book" "open" ]; } + { name = "*.cbz"; use = [ "open-comic" "extract" ]; } + { name = "*.cbr"; use = [ "open-comic" "extract" ]; } + { name = "*.exe"; use = [ "run-exe" "open" ]; } + { mime = "application/{zip,gzip,x-tar,x-bzip*,x-7z-compressed,x-rar,x-xz,zstd}"; use = [ "extract" "open" ]; } + { mime = "inode/directory"; use = [ "edit" "open" ]; } + { mime = "text/*"; use = [ "edit" "open" ]; } + { mime = "application/{json,toml,x-ndjson,javascript,x-sh,x-shellscript,xml}"; use = [ "edit" "open" ]; } + { mime = "*"; use = [ "open" "edit" ]; } + ]; + }; + }; + + keymap.mgr.prepend_keymap = [ + { on = [ "!" ]; run = ''shell "$SHELL" --block''; desc = "Open a shell here"; } + { on = [ "<C-e>" ]; run = "seek 5"; desc = "Scroll preview down"; } + { on = [ "<C-y>" ]; run = "seek -5"; desc = "Scroll preview up"; } + { on = [ "l" ]; run = "plugin smart-enter"; desc = "Enter directory or open file"; } + { on = [ "<Enter>" ]; run = "plugin smart-enter"; desc = "Enter directory or open file"; } + { on = [ "T" ]; run = "plugin toggle-pane max-preview"; desc = "Maximise the preview"; } + { on = [ "u" "d" ]; run = "shell -- dux %h %s"; desc = "Copy file(s) to the clipboard as a paste-able file"; } + ]; + + plugins = with pkgs.yaziPlugins; { + inherit full-border git smart-enter toggle-pane; + }; + flavors.rose-pine = ../yazi/flavors/rose-pine.yazi; + theme.flavor = { + dark = "rose-pine"; + light = "rose-pine"; + }; + initLua = '' + require("full-border"):setup() + require("git"):setup() + ''; + }; +} diff --git a/home/mpv/shaders/CfL_Prediction.LICENSE b/home/mpv/shaders/CfL_Prediction.LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 João Chrisóstomo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/home/mpv/shaders/CfL_Prediction.glsl b/home/mpv/shaders/CfL_Prediction.glsl @@ -0,0 +1,363 @@ +// MIT License + +// Copyright (c) 2023 João Chrisóstomo + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//!PARAM chroma_offset_x +//!TYPE float +0.0 + +//!PARAM chroma_offset_y +//!TYPE float +0.0 + +//!HOOK CHROMA +//!BIND LUMA +//!BIND CHROMA +//!SAVE LUMA_LR +//!WIDTH CHROMA.w +//!HEIGHT LUMA.h +//!WHEN CHROMA.w LUMA.w < +//!DESC Chroma From Luma Prediction (Hermite 1st step, Downscaling Luma) + +float comp_wd(vec2 v) { + float x = min(length(v), 1.0); + return smoothstep(0.0, 1.0, 1.0 - x); +} + +vec4 hook() { + vec2 luma_pos = LUMA_pos; + luma_pos.x += chroma_offset_x / LUMA_size.x; + float start = ceil((luma_pos.x - (1.0 / CHROMA_size.x)) * LUMA_size.x - 0.5); + float end = floor((luma_pos.x + (1.0 / CHROMA_size.x)) * LUMA_size.x - 0.5); + + float wt = 0.0; + float luma_sum = 0.0; + vec2 pos = luma_pos; + + for (float dx = start.x; dx <= end.x; dx++) { + pos.x = LUMA_pt.x * (dx + 0.5); + vec2 dist = (pos - luma_pos) * CHROMA_size; + float wd = comp_wd(dist); + float luma_pix = LUMA_tex(pos).x; + luma_sum += wd * luma_pix; + wt += wd; + } + + vec4 output_pix = vec4(luma_sum /= wt, 0.0, 0.0, 1.0); + return clamp(output_pix, 0.0, 1.0); +} + +//!HOOK CHROMA +//!BIND LUMA_LR +//!BIND CHROMA +//!BIND LUMA +//!SAVE LUMA_LR +//!WIDTH CHROMA.w +//!HEIGHT CHROMA.h +//!WHEN CHROMA.w LUMA.w < +//!DESC Chroma From Luma Prediction (Hermite 2nd step, Downscaling Luma) + +float comp_wd(vec2 v) { + float x = min(length(v), 1.0); + return smoothstep(0.0, 1.0, 1.0 - x); +} + +vec4 hook() { + vec2 luma_pos = LUMA_LR_pos; + luma_pos.y += chroma_offset_y / LUMA_LR_size.y; + float start = ceil((luma_pos.y - (1.0 / CHROMA_size.y)) * LUMA_LR_size.y - 0.5); + float end = floor((luma_pos.y + (1.0 / CHROMA_size.y)) * LUMA_LR_size.y - 0.5); + + float wt = 0.0; + float luma_sum = 0.0; + vec2 pos = luma_pos; + + for (float dy = start; dy <= end; dy++) { + pos.y = LUMA_LR_pt.y * (dy + 0.5); + vec2 dist = (pos - luma_pos) * CHROMA_size; + float wd = comp_wd(dist); + float luma_pix = LUMA_LR_tex(pos).x; + luma_sum += wd * luma_pix; + wt += wd; + } + + vec4 output_pix = vec4(luma_sum /= wt, 0.0, 0.0, 1.0); + return clamp(output_pix, 0.0, 1.0); +} + +//!HOOK CHROMA +//!BIND HOOKED +//!BIND LUMA +//!BIND LUMA_LR +//!WHEN CHROMA.w LUMA.w < +//!WIDTH LUMA.w +//!HEIGHT LUMA.h +//!OFFSET ALIGN +//!DESC Chroma From Luma Prediction (Upscaling Chroma) + +#define USE_12_TAP_REGRESSION 1 +#define USE_8_TAP_REGRESSIONS 1 +#define DEBUG 0 + +float comp_wd(vec2 v) { + float d = min(length(v), 2.0); + float d2 = d * d; + float d3 = d2 * d; + + if (d < 1.0) { + return 1.25 * d3 - 2.25 * d2 + 1.0; + } else { + return -0.75 * d3 + 3.75 * d2 - 6.0 * d + 3.0; + } +} + +vec4 hook() { + float ar_strength = 0.8; + vec2 mix_coeff = vec2(0.8); + vec2 corr_exponent = vec2(4.0); + + vec4 output_pix = vec4(0.0, 0.0, 0.0, 1.0); + float luma_zero = LUMA_texOff(0.0).x; + + vec2 pp = HOOKED_pos * HOOKED_size - vec2(0.5); + vec2 fp = floor(pp); + pp -= fp; + +#ifdef HOOKED_gather + vec2 quad_idx[4] = {{0.0, 0.0}, {2.0, 0.0}, {0.0, 2.0}, {2.0, 2.0}}; + + vec4 luma_quads[4]; + vec4 chroma_quads[4][2]; + + for (int i = 0; i < 4; i++) { + luma_quads[i] = LUMA_LR_gather(vec2((fp + quad_idx[i]) * HOOKED_pt), 0); + chroma_quads[i][0] = HOOKED_gather(vec2((fp + quad_idx[i]) * HOOKED_pt), 0); + chroma_quads[i][1] = HOOKED_gather(vec2((fp + quad_idx[i]) * HOOKED_pt), 1); + } + + vec2 chroma_pixels[16]; + chroma_pixels[0] = vec2(chroma_quads[0][0].w, chroma_quads[0][1].w); + chroma_pixels[1] = vec2(chroma_quads[0][0].z, chroma_quads[0][1].z); + chroma_pixels[2] = vec2(chroma_quads[1][0].w, chroma_quads[1][1].w); + chroma_pixels[3] = vec2(chroma_quads[1][0].z, chroma_quads[1][1].z); + chroma_pixels[4] = vec2(chroma_quads[0][0].x, chroma_quads[0][1].x); + chroma_pixels[5] = vec2(chroma_quads[0][0].y, chroma_quads[0][1].y); + chroma_pixels[6] = vec2(chroma_quads[1][0].x, chroma_quads[1][1].x); + chroma_pixels[7] = vec2(chroma_quads[1][0].y, chroma_quads[1][1].y); + chroma_pixels[8] = vec2(chroma_quads[2][0].w, chroma_quads[2][1].w); + chroma_pixels[9] = vec2(chroma_quads[2][0].z, chroma_quads[2][1].z); + chroma_pixels[10] = vec2(chroma_quads[3][0].w, chroma_quads[3][1].w); + chroma_pixels[11] = vec2(chroma_quads[3][0].z, chroma_quads[3][1].z); + chroma_pixels[12] = vec2(chroma_quads[2][0].x, chroma_quads[2][1].x); + chroma_pixels[13] = vec2(chroma_quads[2][0].y, chroma_quads[2][1].y); + chroma_pixels[14] = vec2(chroma_quads[3][0].x, chroma_quads[3][1].x); + chroma_pixels[15] = vec2(chroma_quads[3][0].y, chroma_quads[3][1].y); + + float luma_pixels[16]; + luma_pixels[0] = luma_quads[0].w; + luma_pixels[1] = luma_quads[0].z; + luma_pixels[2] = luma_quads[1].w; + luma_pixels[3] = luma_quads[1].z; + luma_pixels[4] = luma_quads[0].x; + luma_pixels[5] = luma_quads[0].y; + luma_pixels[6] = luma_quads[1].x; + luma_pixels[7] = luma_quads[1].y; + luma_pixels[8] = luma_quads[2].w; + luma_pixels[9] = luma_quads[2].z; + luma_pixels[10] = luma_quads[3].w; + luma_pixels[11] = luma_quads[3].z; + luma_pixels[12] = luma_quads[2].x; + luma_pixels[13] = luma_quads[2].y; + luma_pixels[14] = luma_quads[3].x; + luma_pixels[15] = luma_quads[3].y; +#else + vec2 pix_idx[16] = {{-0.5,-0.5}, {0.5,-0.5}, {1.5,-0.5}, {2.5,-0.5}, + {-0.5, 0.5}, {0.5, 0.5}, {1.5, 0.5}, {2.5, 0.5}, + {-0.5, 1.5}, {0.5, 1.5}, {1.5, 1.5}, {2.5, 1.5}, + {-0.5, 2.5}, {0.5, 2.5}, {1.5, 2.5}, {2.5, 2.5}}; + + float luma_pixels[16]; + vec2 chroma_pixels[16]; + + for (int i = 0; i < 16; i++) { + luma_pixels[i] = LUMA_LR_tex(vec2((fp + pix_idx[i]) * HOOKED_pt)).x; + chroma_pixels[i] = HOOKED_tex(vec2((fp + pix_idx[i]) * HOOKED_pt)).xy; + } +#endif + +#if (DEBUG == 1) + vec2 chroma_spatial = vec2(0.5); + mix_coeff = vec2(1.0); +#else + float wd[16]; + float wt = 0.0; + vec2 ct = vec2(0.0); + + vec2 chroma_min = min(min(min(chroma_pixels[5], chroma_pixels[6]), chroma_pixels[9]), chroma_pixels[10]); + vec2 chroma_max = max(max(max(chroma_pixels[5], chroma_pixels[6]), chroma_pixels[9]), chroma_pixels[10]); + + const int dx[16] = {-1, 0, 1, 2, -1, 0, 1, 2, -1, 0, 1, 2, -1, 0, 1, 2}; + const int dy[16] = {-1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2}; + + for (int i = 0; i < 16; i++) { + wd[i] = comp_wd(vec2(dx[i], dy[i]) - pp); + wt += wd[i]; + ct += wd[i] * chroma_pixels[i]; + } + + vec2 chroma_spatial = ct / wt; + chroma_spatial = clamp(mix(chroma_spatial, clamp(chroma_spatial, chroma_min, chroma_max), ar_strength), 0.0, 1.0); +#endif + +#if (USE_12_TAP_REGRESSION == 1 || USE_8_TAP_REGRESSIONS == 1) + const int i12[12] = {1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14}; + const int i4y[4] = {1, 2, 13, 14}; + const int i4x[4] = {4, 7, 8, 11}; + const int i4[4] = {5, 6, 9, 10}; + + float luma_sum_4 = 0.0; + float luma_sum_4y = 0.0; + float luma_sum_4x = 0.0; + vec2 chroma_sum_4 = vec2(0.0); + vec2 chroma_sum_4y = vec2(0.0); + vec2 chroma_sum_4x = vec2(0.0); + + for (int i = 0; i < 4; i++) { + luma_sum_4 += luma_pixels[i4[i]]; + luma_sum_4y += luma_pixels[i4y[i]]; + luma_sum_4x += luma_pixels[i4x[i]]; + chroma_sum_4 += chroma_pixels[i4[i]]; + chroma_sum_4y += chroma_pixels[i4y[i]]; + chroma_sum_4x += chroma_pixels[i4x[i]]; + } + + float luma_avg_12 = (luma_sum_4 + luma_sum_4y + luma_sum_4x) / 12.0; + float luma_min_12 = luma_pixels[i12[0]]; + float luma_max_12 = luma_pixels[i12[0]]; + float luma_var_12 = 0.0; + vec2 chroma_avg_12 = (chroma_sum_4 + chroma_sum_4y + chroma_sum_4x) / 12.0; + vec2 chroma_var_12 = vec2(0.0); + vec2 luma_chroma_cov_12 = vec2(0.0); + + for (int i = 0; i < 12; i++) { + luma_min_12 = min(luma_min_12, luma_pixels[i12[i]]); + luma_max_12 = max(luma_max_12, luma_pixels[i12[i]]); + luma_var_12 += pow(luma_pixels[i12[i]] - luma_avg_12, 2.0); + chroma_var_12 += pow(chroma_pixels[i12[i]] - chroma_avg_12, vec2(2.0)); + luma_chroma_cov_12 += (luma_pixels[i12[i]] - luma_avg_12) * (chroma_pixels[i12[i]] - chroma_avg_12); + } + + vec2 corr = clamp(abs(luma_chroma_cov_12 / max(sqrt(luma_var_12 * chroma_var_12), 1e-6)), 0.0, 1.0); + mix_coeff = pow(corr, corr_exponent) * mix_coeff; + + float luma_range_12 = max(luma_max_12 - luma_min_12, 1e-6); + float extrapolation = max(max(luma_zero - luma_max_12, luma_min_12 - luma_zero), 0.0) / luma_range_12; + mix_coeff *= clamp((8.0 - extrapolation) / 4.0, 0.0, 1.0); +#endif + +#if (USE_12_TAP_REGRESSION == 1) + vec2 alpha_12 = luma_chroma_cov_12 / max(luma_var_12, 1e-6); + vec2 beta_12 = chroma_avg_12 - alpha_12 * luma_avg_12; + vec2 chroma_pred_12 = clamp(alpha_12 * luma_zero + beta_12, 0.0, 1.0); +#endif + +#if (USE_8_TAP_REGRESSIONS == 1) + const int i8y[8] = {1, 2, 5, 6, 9, 10, 13, 14}; + const int i8x[8] = {4, 5, 6, 7, 8, 9, 10, 11}; + + float luma_avg_8y = (luma_sum_4 + luma_sum_4y) / 8.0; + float luma_avg_8x = (luma_sum_4 + luma_sum_4x) / 8.0; + float luma_var_8y = 0.0; + float luma_var_8x = 0.0; + vec2 chroma_avg_8y = (chroma_sum_4 + chroma_sum_4y) / 8.0; + vec2 chroma_avg_8x = (chroma_sum_4 + chroma_sum_4x) / 8.0; + vec2 luma_chroma_cov_8y = vec2(0.0); + vec2 luma_chroma_cov_8x = vec2(0.0); + + for (int i = 0; i < 8; i++) { + luma_var_8y += pow(luma_pixels[i8y[i]] - luma_avg_8y, 2.0); + luma_var_8x += pow(luma_pixels[i8x[i]] - luma_avg_8x, 2.0); + luma_chroma_cov_8y += (luma_pixels[i8y[i]] - luma_avg_8y) * (chroma_pixels[i8y[i]] - chroma_avg_8y); + luma_chroma_cov_8x += (luma_pixels[i8x[i]] - luma_avg_8x) * (chroma_pixels[i8x[i]] - chroma_avg_8x); + } + + vec2 alpha_8y = luma_chroma_cov_8y / max(luma_var_8y, 1e-6); + vec2 alpha_8x = luma_chroma_cov_8x / max(luma_var_8x, 1e-6); + vec2 beta_8y = chroma_avg_8y - alpha_8y * luma_avg_8y; + vec2 beta_8x = chroma_avg_8x - alpha_8x * luma_avg_8x; + vec2 chroma_pred_8y = clamp(alpha_8y * luma_zero + beta_8y, 0.0, 1.0); + vec2 chroma_pred_8x = clamp(alpha_8x * luma_zero + beta_8x, 0.0, 1.0); + vec2 chroma_pred_8 = mix(chroma_pred_8y, chroma_pred_8x, 0.5); +#endif + +#if (USE_12_TAP_REGRESSION == 1 && USE_8_TAP_REGRESSIONS == 1) + output_pix.xy = mix(chroma_spatial, mix(chroma_pred_12, chroma_pred_8, 0.5), mix_coeff); +#elif (USE_12_TAP_REGRESSION == 1 && USE_8_TAP_REGRESSIONS == 0) + output_pix.xy = mix(chroma_spatial, chroma_pred_12, mix_coeff); +#elif (USE_12_TAP_REGRESSION == 0 && USE_8_TAP_REGRESSIONS == 1) + output_pix.xy = mix(chroma_spatial, chroma_pred_8, mix_coeff); +#else + output_pix.xy = chroma_spatial; +#endif + + output_pix.xy = clamp(output_pix.xy, 0.0, 1.0); + return output_pix; +} + +//!PARAM distance_coeff +//!TYPE float +//!MINIMUM 0.0 +2.0 + +//!PARAM intensity_coeff +//!TYPE float +//!MINIMUM 0.0 +128.0 + +//!HOOK CHROMA +//!BIND CHROMA +//!BIND LUMA +//!BIND LUMA_LR +//!DESC Chroma From Luma Prediction (Smoothing Chroma) + +float comp_w(vec2 spatial_distance, float intensity_distance) { + return max(100.0 * exp(-distance_coeff * pow(length(spatial_distance), 2.0) - intensity_coeff * pow(intensity_distance, 2.0)), 1e-32); +} + +vec4 hook() { + vec4 output_pix = vec4(0.0, 0.0, 0.0, 1.0); + float luma_zero = LUMA_texOff(0).x; + float wt = 0.0; + vec2 ct = vec2(0.0); + + for (int i = -1; i < 2; i++) { + for (int j = -1; j < 2; j++) { + vec2 chroma_pixels = CHROMA_texOff(vec2(i, j)).xy; + float luma_pixels = LUMA_texOff(vec2(i, j)).x; + float w = comp_w(vec2(i, j), luma_zero - luma_pixels); + wt += w; + ct += w * chroma_pixels; + } + } + + output_pix.xy = clamp(ct / wt, 0.0, 1.0); + return output_pix; +} diff --git a/home/rmpc/config.ron b/home/rmpc/config.ron @@ -0,0 +1,316 @@ +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +( + address: "/run/user/1000/mpd/socket", + password: None, + theme: "rose-pine", + cache_dir: None, + on_song_change: ["~/.config/rmpc/scripts/fetch-lyrics"], + lyrics_dir: "~/.local/share/rmpc/lyrics", + volume_step: 5, + max_fps: 60, + scrolloff: 3, + wrap_navigation: false, + enable_mouse: true, + scroll_amount: 1, + enable_config_hot_reload: true, + enable_lyrics_hot_reload: true, + status_update_interval_ms: 500, + rewind_to_start_sec: None, + keep_state_on_song_change: true, + reflect_changes_to_playlist: false, + select_current_song_on_change: false, + ignore_leading_the: false, + browser_song_sort: [Disc, Track, Artist, Title], + directories_sort: SortFormat(group_by_type: true, reverse: false), + auto_open_downloads: true, + album_art: ( + method: Auto, + max_size_px: (width: 1200, height: 1200), + disabled_protocols: ["http://", "https://"], + vertical_align: Center, + horizontal_align: Center, + ), + cava: ( + framerate: 60, + autosens: true, + input: ( + method: Fifo, + source: "/run/user/1000/mpd/fifo", + sample_rate: 44100, + channels: 2, + sample_bits: 16, + ), + smoothing: ( + noise_reduction: 77, + monstercat: true, + waves: false, + ), + ), + keybinds: ( + global: { + "q": Quit, + "?": ShowHelp, + ":": CommandMode, + "oI": ShowCurrentSongInfo, + "oo": ShowOutputs, + "op": ShowDecoders, + "od": ShowDownloads, + "oP": Partition(), + "z": ToggleRepeat, + "x": ToggleRandom, + "c": ToggleConsume, + "v": ToggleSingle, + "p": TogglePause, + "s": Stop, + ">": NextTrack, + "<": PreviousTrack, + "f": SeekForward, + "b": SeekBack, + ".": VolumeUp, + ",": VolumeDown, + "<Tab>": NextTab, + "gt": NextTab, + "<S-Tab>": PreviousTab, + "gT": PreviousTab, + "1": SwitchToTab("Queue"), + "2": SwitchToTab("Now Playing"), + "3": SwitchToTab("Directories"), + "4": SwitchToTab("Artists"), + "5": SwitchToTab("Album Artists"), + "6": SwitchToTab("Albums"), + "7": SwitchToTab("Genre"), + "8": SwitchToTab("Playlists"), + "9": SwitchToTab("Search"), + "0": SwitchToTab("Lyrics"), + "L": SwitchToTab("Lyrics"), + "<C-u>": Update, + "<C-U>": Rescan, + "R": AddRandom, + }, + navigation: { + "<C-c>": Close, + "<Esc>": Close, + "<CR>": Confirm, + "k": Up, + "<Up>": Up, + "j": Down, + "<Down>": Down, + "h": Left, + "<Left>": Left, + "l": Right, + "<Right>": Right, + "<C-w>k": PaneUp, + "<C-Up>": PaneUp, + "<C-w>j": PaneDown, + "<C-Down>": PaneDown, + "<C-w>h": PaneLeft, + "<C-Left>": PaneLeft, + "<C-w>l": PaneRight, + "<C-Right>": PaneRight, + "K": MoveUp, + "J": MoveDown, + "<C-u>": UpHalf, + "<C-d>": DownHalf, + "<C-b>": PageUp, + "<PageUp>": PageUp, + "<C-f>": PageDown, + "<PageDown>": PageDown, + "gg": Top, + "G": Bottom, + "<Space>": Select, + "<C-Space>": InvertSelection, + "/": EnterSearch, + "n": NextResult, + "N": PreviousResult, + "a": Add, + "A": AddAll, + "D": Delete, + "<C-r>": Rename, + "i": FocusInput, + "oi": ShowInfo, + "<C-z>": ContextMenu(), + "<C-s>s": Save(kind: Modal(all: false, duplicates_strategy: Ask)), + "<C-s>a": Save(kind: Modal(all: true, duplicates_strategy: Ask)), + "r": Rate(), + }, + queue: { + "d": Delete, + "D": DeleteAll, + "<CR>": Play, + "C": JumpToCurrent, + "X": Shuffle, + }, + ), + search: ( + case_sensitive: false, + ignore_diacritics: false, + search_button: false, + mode: Contains, + tags: [ + (value: "any", label: "Any Tag"), + (value: "artist", label: "Artist"), + (value: "album", label: "Album"), + (value: "albumartist", label: "Album Artist"), + (value: "title", label: "Title"), + (value: "filename", label: "Filename"), + (value: "genre", label: "Genre"), + ], + ), + artists: ( + album_display_mode: SplitByDate, + album_sort_by: Date, + album_date_tags: [Date], + ), + tabs: [ + ( + name: "Queue", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "40%", + borders: "ALL", + border_symbols: Rounded, + pane: Pane(AlbumArt), + ), + ( + size: "60%", + pane: Split( + direction: Vertical, + panes: [ + ( + size: "3", + borders: "ALL", + border_symbols: Inherited(parent: Rounded, bottom_left: "├", bottom_right: "┤"), + pane: Split( + direction: Horizontal, + panes: [ + (size: "1", pane: Pane(Empty())), + (size: "100%", pane: Pane(QueueHeader())), + ], + ), + ), + ( + size: "100%", + borders: "LEFT | RIGHT | BOTTOM", + border_symbols: Rounded, + pane: Split( + direction: Horizontal, + panes: [ + (size: "1", pane: Pane(Empty())), + (size: "100%", pane: Pane(Queue)), + ], + ), + ), + ( + size: "35%", + borders: "ALL", + border_symbols: Rounded, + border_title: [(kind: Text(" Visualiser "))], + border_title_alignment: Right, + pane: Pane(Cava), + ), + ], + ), + ), + ], + ), + ), + ( + name: "Now Playing", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "42%", + borders: "ALL", + border_symbols: Rounded, + pane: Pane(AlbumArt), + ), + ( + size: "58%", + pane: Split( + direction: Vertical, + panes: [ + ( + size: "65%", + borders: "ALL", + border_symbols: Rounded, + border_title: [(kind: Text(" Synced Lyrics "))], + border_title_alignment: Center, + pane: Pane(Lyrics), + ), + ( + size: "35%", + borders: "ALL", + border_symbols: Rounded, + border_title: [(kind: Text(" Visualiser "))], + border_title_alignment: Right, + pane: Pane(Cava), + ), + ], + ), + ), + ], + ), + ), + ( + name: "Directories", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Directories), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Artists", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Artists), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Album Artists", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(AlbumArtists), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Albums", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Albums), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Genre", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Browser(root_tag: "genre")), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Playlists", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Playlists), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Search", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Search), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ( + name: "Lyrics", + pane: Split( + direction: Vertical, + panes: [(pane: Pane(Lyrics), size: "100%", borders: "ALL", border_symbols: Rounded)], + ), + ), + ], +) diff --git a/home/rmpc/scripts/fetch-lyrics b/home/rmpc/scripts/fetch-lyrics @@ -0,0 +1,98 @@ +#!/usr/bin/env bash +# rmpc on_song_change hook: fetch synced lyrics from LRCLIB when missing. +# +# rmpc provides (among others): $ARTIST $TITLE $ALBUM $DURATION $FILE +# $HAS_LRC $LRC_FILE $PID $VERSION +# Writes to $LRC_FILE (under lyrics_dir), then hot-indexes it via +# `rmpc remote indexlrc`. Never blocks rmpc: all work happens in a +# backgrounded subshell; all failures are silent (logged to cache dir). + +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/rmpc-lyrics" +MISS_DIR="$CACHE_DIR/misses" +LOG_FILE="$CACHE_DIR/fetch.log" +LRCLIB="https://lrclib.net" +MISS_TTL_DAYS=14 + +main() { + mkdir -p "$MISS_DIR" + + # Nothing to do without metadata or if lyrics already resolved. + [ "${HAS_LRC:-true}" = "false" ] || exit 0 + [ -n "${ARTIST:-}" ] && [ -n "${TITLE:-}" ] || exit 0 + [ -n "${LRC_FILE:-}" ] || exit 0 + [ -s "$LRC_FILE" ] && exit 0 + + # Negative cache: don't hammer LRCLIB for known misses. + local key miss + key=$(printf '%s\t%s\t%s' "$ARTIST" "$TITLE" "${ALBUM:-}" | sha1sum | cut -d' ' -f1) + miss="$MISS_DIR/$key" + if [ -e "$miss" ] && [ -z "$(find "$miss" -mtime +"$MISS_TTL_DAYS" 2>/dev/null)" ]; then + exit 0 + fi + + # Serialize concurrent hook invocations (fast track skipping). + exec 9>"$CACHE_DIR/lock" + flock -n 9 || exit 0 + + # DURATION may be seconds ("312.43") or "MM:SS"; normalise to integer seconds. + local dur_s="" + case "${DURATION:-}" in + *:*) dur_s=$(( $(echo "$DURATION" | awk -F: '{print $(NF-1)*60 + int($NF)}') )) ;; + *[0-9]*) dur_s=$(printf '%.0f' "$DURATION" 2>/dev/null || true) ;; + esac + + # Try /api/get with maximum specificity first, then progressively relax. + local resp="" synced="" instrumental="" + for attempt in full noalbum bare; do + local args=(-sG --max-time 15 -H "Lrclib-Client: rmpc-${VERSION:-unknown}" + --data-urlencode "artist_name=$ARTIST" + --data-urlencode "track_name=$TITLE") + case $attempt in + full) + [ -n "${ALBUM:-}" ] && args+=(--data-urlencode "album_name=$ALBUM") + [ -n "$dur_s" ] && args+=(--data-urlencode "duration=$dur_s") + ;; + noalbum) + [ -n "$dur_s" ] && args+=(--data-urlencode "duration=$dur_s") + ;; + esac + resp=$(curl "${args[@]}" "$LRCLIB/api/get" 2>>"$LOG_FILE") || resp="" + synced=$(printf '%s' "$resp" | jq -r '.syncedLyrics // empty' 2>/dev/null) + instrumental=$(printf '%s' "$resp" | jq -r '.instrumental // false' 2>/dev/null) + [ -n "$synced" ] && break + [ "$instrumental" = "true" ] && break + sleep 1 # be polite between retries + done + + if [ -z "$synced" ]; then + # Instrumental or genuinely not found: remember the miss, stay silent. + touch "$miss" + printf '%s MISS %s - %s\n' "$(date -Iseconds)" "$ARTIST" "$TITLE" >>"$LOG_FILE" + exit 0 + fi + + # Write the .lrc with metadata tags so rmpc's index matches it to the + # song regardless of file path. UTF-8 passes through untouched. + mkdir -p "$(dirname "$LRC_FILE")" + local tmp + tmp=$(mktemp "$(dirname "$LRC_FILE")/.fetch.XXXXXX") || exit 0 + { + printf '[ar:%s]\n' "$ARTIST" + [ -n "${ALBUM:-}" ] && printf '[al:%s]\n' "$ALBUM" + printf '[ti:%s]\n' "$TITLE" + printf '%s\n' "$synced" | sed -E '/^\[(ar|al|ti|length):/d' + } >"$tmp" + mv -f "$tmp" "$LRC_FILE" + rm -f "$miss" + printf '%s HIT %s - %s -> %s\n' "$(date -Iseconds)" "$ARTIST" "$TITLE" "$LRC_FILE" >>"$LOG_FILE" + + # Tell the running rmpc instance about the new file (hot reload). + if [ -n "${PID:-}" ]; then + rmpc remote --pid "$PID" indexlrc --path "$LRC_FILE" 2>>"$LOG_FILE" || true + rmpc remote --pid "$PID" status "Lyrics downloaded: $ARTIST - $TITLE" --level info 2>>"$LOG_FILE" || true + fi +} + +# Never block rmpc's hook runner. +main >/dev/null 2>&1 & +exit 0 diff --git a/home/rmpc/themes/rose-pine.ron b/home/rmpc/themes/rose-pine.ron @@ -0,0 +1,320 @@ +#![enable(implicit_some)] +#![enable(unwrap_newtypes)] +#![enable(unwrap_variant_newtypes)] +// "rose-pine" — Rosé Pine (main) theme for rmpc +// palette: base #191724 / text #e0def4 / subtle #908caa / hl-med #403d52 +// iris #c4a7e7 / rose #ebbcba / love #eb6f92 / foam #9ccfd8 / gold #f6c177 +( + default_album_art_path: None, + format_tag_separator: " | ", + browser_column_widths: [20, 38, 42], + background_color: "#191724", + text_color: "#e0def4", + header_background_color: "#191724", + modal_background_color: "#1f1d2e", + modal_backdrop: true, + preview_label_style: (fg: "#c4a7e7"), + preview_metadata_group_style: (fg: "#c4a7e7", modifiers: "Bold"), + highlighted_item_style: (fg: "#ebbcba", modifiers: "Bold"), + current_item_style: (fg: "#191724", bg: "#c4a7e7", modifiers: "Bold"), + borders_style: (fg: "#403d52"), + highlight_border_style: (fg: "#c4a7e7"), + symbols: ( + song: "♪", + dir: "▸", + playlist: "≡", + marker: "▌", + ellipsis: "…", + song_style: (fg: "#908caa"), + dir_style: (fg: "#c4a7e7"), + playlist_style: (fg: "#ebbcba"), + ), + level_styles: ( + info: (fg: "#c4a7e7", bg: "#191724"), + warn: (fg: "#f6c177", bg: "#191724"), + error: (fg: "#eb6f92", bg: "#191724"), + debug: (fg: "#9ccfd8", bg: "#191724"), + trace: (fg: "#ebbcba", bg: "#191724"), + ), + progress_bar: ( + symbols: ["━", "━", "●", "─", "─"], + track_style: (fg: "#403d52"), + elapsed_style: (fg: "#c4a7e7"), + thumb_style: (fg: "#ebbcba"), + use_track_when_empty: true, + ), + scrollbar: ( + symbols: ["│", "█", "▲", "▼"], + track_style: (fg: "#403d52"), + ends_style: (fg: "#403d52"), + thumb_style: (fg: "#c4a7e7"), + ), + tab_bar: ( + active_style: (fg: "#191724", bg: "#c4a7e7", modifiers: "Bold"), + inactive_style: (fg: "#908caa"), + ), + lyrics: ( + timestamp: false, + ), + cava: ( + bar_width: 2, + bar_spacing: 1, + bar_color: Gradient({ + 0: "#eb6f92", + 45: "#c4a7e7", + 100: "#9ccfd8", + }), + ), + browser_song_format: [ + ( + kind: Group([ + (kind: Property(Track)), + (kind: Text(" ")), + ]) + ), + ( + kind: Group([ + (kind: Property(Artist)), + (kind: Text(" - ")), + (kind: Property(Title)), + ]), + default: (kind: Property(Filename)) + ), + ], + song_table_format: [ + ( + prop: (kind: Property(Artist), style: (fg: "#ebbcba"), + default: (kind: Text("Unknown"), style: (fg: "#908caa")) + ), + label_prop: (kind: Text("Artist"), style: (fg: "#c4a7e7", modifiers: "Bold")), + width: "22%", + ), + ( + prop: (kind: Property(Title), style: (fg: "#e0def4", modifiers: "Bold"), + default: (kind: Text("Unknown"), style: (fg: "#908caa")) + ), + label_prop: (kind: Text("Title"), style: (fg: "#c4a7e7", modifiers: "Bold")), + width: "38%", + ), + ( + prop: (kind: Property(Album), style: (fg: "#908caa"), + default: (kind: Text("Unknown Album"), style: (fg: "#908caa")) + ), + label_prop: (kind: Text("Album"), style: (fg: "#c4a7e7", modifiers: "Bold")), + width: "30%", + ), + ( + prop: (kind: Property(Duration), + default: (kind: Text("-")) + ), + label_prop: (kind: Text("Length"), style: (fg: "#c4a7e7", modifiers: "Bold")), + width: "10%", + alignment: Right, + ), + ], + layout: Split( + direction: Vertical, + panes: [ + ( + size: "4", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "35", + borders: "LEFT | TOP | BOTTOM", + border_symbols: Inherited(parent: Rounded, bottom_left: "├"), + pane: Component("header_left") + ), + ( + size: "100%", + borders: "ALL", + border_symbols: Inherited(parent: Rounded, top_left: "┬", top_right: "┬", bottom_left: "┴", bottom_right: "┴"), + pane: Component("header_center") + ), + ( + size: "35", + borders: "RIGHT | TOP | BOTTOM", + border_symbols: Inherited(parent: Rounded, bottom_right: "┤"), + pane: Component("header_right") + ), + ] + ) + ), + ( + pane: Pane(Tabs), + borders: "RIGHT | LEFT | BOTTOM", + border_symbols: Rounded, + size: "2", + ), + ( + pane: Pane(TabContent), + size: "100%", + ), + ( + size: "3", + pane: Split( + direction: Horizontal, + panes: [ + ( + size: "12", + borders: "ALL", + border_symbols: Inherited(parent: Rounded, top_right: "┬", bottom_right: "┴"), + pane: Component("input_mode") + ), + ( + size: "100%", + borders: "TOP | BOTTOM | RIGHT", + border_symbols: Rounded, + border_title: [(kind: Text(" ")), (kind: Property(Status(QueueLength()))), (kind: Text(" songs / ")), (kind: Property(Status(QueueTimeTotal()))), (kind: Text(" total time "))], + border_title_alignment: Right, + pane: Component("progress_bar"), + ), + ] + ), + ), + ], + ), + components: { + "state": Pane(Property( + content: [ + (kind: Text("["), style: (fg: "#9ccfd8", modifiers: "Bold")), + (kind: Property(Status(StateV2( ))), style: (fg: "#9ccfd8", modifiers: "Bold")), + (kind: Text("]"), style: (fg: "#9ccfd8", modifiers: "Bold")), + ], align: Left, + )), + "title": Pane(Property( + content: [ + (kind: Property(Song(Title)), style: (fg: "#e0def4", modifiers: "Bold"), + default: (kind: Text("No Song"), style: (fg: "#908caa", modifiers: "Bold"))), + ], align: Center, scroll_speed: 1 + )), + "volume": Split( + direction: Horizontal, + panes: [ + (size: "1", pane: Pane(Property(content: [(kind: Text(""))]))), + (size: "100%", pane: Pane(Volume(kind: Slider(symbols: (filled: "─", thumb: "●", track: "─"))))), + (size: "3", pane: Pane(Property(content: [(kind: Property(Status(Volume)), style: (fg: "#c4a7e7"))], align: Right))), + (size: "2", pane: Pane(Property(content: [(kind: Text("%"), style: (fg: "#c4a7e7"))]))), + ] + ), + "elapsed_and_bitrate": Pane(Property( + content: [ + (kind: Property(Status(Elapsed)), style: (fg: "#e0def4")), + (kind: Text(" / "), style: (fg: "#908caa")), + (kind: Property(Status(Duration)), style: (fg: "#e0def4")), + (kind: Group([ + (kind: Text(" ("), style: (fg: "#908caa")), + (kind: Property(Status(Bitrate)), style: (fg: "#908caa")), + (kind: Text(" kbps)"), style: (fg: "#908caa")), + ])), + ], + align: Left, + )), + "artist_and_album": Pane(Property( + content: [ + (kind: Property(Song(Artist)), style: (fg: "#eb6f92", modifiers: "Bold"), + default: (kind: Text("Unknown"), style: (fg: "#908caa", modifiers: "Bold"))), + (kind: Text(" — "), style: (fg: "#908caa")), + (kind: Property(Song(Album)), style: (fg: "#ebbcba"), + default: (kind: Text("Unknown Album"), style: (fg: "#908caa"))), + ], align: Center, scroll_speed: 1 + )), + "states": Split( + direction: Horizontal, + panes: [ + ( + size: "1", + pane: Pane(Empty()) + ), + ( + size: "100%", + pane: Pane(Property(content: [(kind: Property(Status(InputBuffer())), style: (fg: "#c4a7e7"), align: Left)])) + ), + ( + size: "6", + pane: Pane(Property(content: [ + (kind: Text("["), style: (fg: "#c4a7e7", modifiers: "Bold")), + (kind: Property(Status(RepeatV2( + on_label: "z", + off_label: "z", + on_style: (fg: "#9ccfd8", modifiers: "Bold"), + off_style: (fg: "#403d52"), + )))), + (kind: Property(Status(RandomV2( + on_label: "x", + off_label: "x", + on_style: (fg: "#9ccfd8", modifiers: "Bold"), + off_style: (fg: "#403d52"), + )))), + (kind: Property(Status(ConsumeV2( + on_label: "c", + off_label: "c", + oneshot_label: "c", + on_style: (fg: "#9ccfd8", modifiers: "Bold"), + off_style: (fg: "#403d52"), + oneshot_style: (fg: "#eb6f92", modifiers: "Bold"), + )))), + (kind: Property(Status(SingleV2( + on_label: "v", + off_label: "v", + oneshot_label: "v", + on_style: (fg: "#9ccfd8", modifiers: "Bold"), + off_style: (fg: "#403d52"), + oneshot_style: (fg: "#eb6f92", modifiers: "Bold"), + )))), + (kind: Text("]"), style: (fg: "#c4a7e7", modifiers: "Bold")), + ], + align: Right + )) + ), + ] + ), + "input_mode": Pane(Property( + content: [ + (kind: Transform(Replace(content: (kind: Property(Status(InputMode()))), replacements: [ + (match: "Normal", replace: (kind: Text(" NORMAL "), style: (fg: "#191724", bg: "#c4a7e7"))), + (match: "Insert", replace: (kind: Text(" INSERT "), style: (fg: "#191724", bg: "#9ccfd8"))), + ]))) + ], align: Center + )), + "header_left": Split( + direction: Vertical, + panes: [ + (size: "1", pane: Component("state")), + (size: "1", pane: Component("elapsed_and_bitrate")), + ] + ), + "header_center": Split( + direction: Vertical, + panes: [ + (size: "1", pane: Component("title")), + (size: "1", pane: Component("artist_and_album")), + ] + ), + "header_right": Split( + direction: Vertical, + panes: [ + (size: "1", pane: Component("volume")), + (size: "1", pane: Component("states")), + ] + ), + "progress_bar": Split( + direction: Horizontal, + panes: [ + ( + size: "1", + pane: Pane(Empty()) + ), + ( + size: "100%", + pane: Pane(ProgressBar) + ), + ( + size: "1", + pane: Pane(Empty()) + ), + ] + ) + }, +) diff --git a/home/yazi/flavors/rose-pine.yazi/flavor.toml b/home/yazi/flavors/rose-pine.yazi/flavor.toml @@ -0,0 +1,253 @@ +# vim:fileencoding=utf-8:foldmethod=marker + +# : App {{{ + +[app] +overall = { bg = "#191724" } + +# : }}} + + +# : Manager {{{ + +[mgr] +cwd = { fg = "#9ccfd8" } + +# Find +find_keyword = { fg = "#f6c177", bold = true, italic = true, underline = true } +find_position = { fg = "#c4a7e7", bg = "reset", bold = true, italic = true } + +# Symlink +symlink_target = { italic = true } + +# Marker +marker_copied = { fg = "#31748f", bg = "#31748f" } +marker_cut = { fg = "#eb6f92", bg = "#eb6f92" } +marker_marked = { fg = "#ebbcba", bg = "#ebbcba" } +marker_selected = { fg = "#f6c177", bg = "#f6c177" } + +# Count +count_copied = { fg = "#e0def4", bg = "#31748f" } +count_cut = { fg = "#e0def4", bg = "#eb6f92" } +count_selected = { fg = "#191724", bg = "#f6c177" } + +# Border +border_symbol = "│" +border_style = { fg = "#524f67" } + +# Highlighting +syntect_theme = "" + +# : }}} + + +# : Tabs {{{ + +[tabs] +active = { fg = "#e0def4", bg = "#31748f", bold = true } +inactive = { fg = "#31748f", bg = "#e0def4" } + +# Separator +sep_inner = { open = "", close = "" } +sep_outer = { open = "", close = "" } + +# : }}} + + +# : Mode {{{ + +[mode] +normal_main = { fg = "#191724", bg = "#9ccfd8", bold = true } +normal_alt = { fg = "#191724", bg = "#e0def4", bold = true } + +# Select mode +select_main = { fg = "#191724", bg = "#eb6f92", bold = true } +select_alt = { fg = "#191724", bg = "#e0def4", bold = true } + +# Unset mode +unset_main = { fg = "#191724", bg = "#eb6f92", bold = true } +unset_alt = { fg = "#191724", bg = "#e0def4", bold = true } + +# : }}} + + +# : Indicator of hovered file {{{ + +[indicator] +parent = { reversed = true } +current = { reversed = true } +preview = { underline = true } +padding = { open = "", close = "" } + +# : }}} + + +# : Status bar {{{ + +[status] +overall = { bg = "#191724" } +sep_left = { open = "", close = "" } +sep_right = { open = "", close = "" } + +# Permissions +perm_sep = { fg = "#6e6a86" } +perm_type = { fg = "#c4a7e7" } +perm_read = { fg = "#f6c177" } +perm_write = { fg = "#eb6f92" } +perm_exec = { fg = "#9ccfd8" } + +# Progress +progress_label = { fg = "#e0def4", bold = true } +progress_normal = { fg = "#31748f", bg = "#21202e" } +progress_error = { fg = "#eb6f92", bg = "#21202e" } + +# : }}} + + +# : Which {{{ + +[which] +cols = 3 +mask = { bg = "#1f1d2e" } +cand = { fg = "#9ccfd8" } +rest = { fg = "#6e6a86" } +desc = { fg = "#f6c177" } +separator = "  " +separator_style = { fg = "#6e6a86" } + +# : }}} + + +# : Confirmation {{{ + +[confirm] +border = { fg = "#524f67" } +title = { fg = "#eb6f92", bold = true } +body = { fg = "#e0def4" } +list = { fg = "#e0def4" } +btn_yes = { fg = "#191724", bg = "#31748f", bold = true } +btn_no = { fg = "#191724", bg = "#e0def4", bold = true } +btn_labels = [ " [y]es ", " (n)o " ] + +# : }}} + + +# : Spotter {{{ + +[spot] +border = { fg = "#524f67" } +title = { fg = "#9ccfd8", bold = true } + +# Table +tbl_col = { fg = "#9ccfd8" } +tbl_cell = { fg = "#191724", bg = "#f6c177", bold = true } + +# : }}} + + +# : Notification {{{ + +[notify] +title_info = { fg = "#31748f" } +title_warn = { fg = "#f6c177" } +title_error = { fg = "#eb6f92" } + +# Icons +icon_info = "" +icon_warn = "" +icon_error = "" + +# : }}} + + +# : Picker {{{ + +[pick] +border = { fg = "#31748f" } +active = { fg = "#c4a7e7", bold = true } +inactive = { fg = "#908caa" } + +# : }}} + + +# : Input {{{ + +[input] +border = { fg = "#31748f" } +title = { bold = true } +value = { fg = "#e0def4" } +selected = { reversed = true } + +# : }}} + + +# : Completion {{{ + +[cmp] +border = { fg = "#31748f" } +active = { bg = "#e0def4", fg = "#191724", bold = true } +inactive = { fg = "#908caa" } + +# Icons +icon_file = "" +icon_folder = "" +icon_command = "" + +# : }}} + + +# : Task manager {{{ + +[tasks] +border = { fg = "#31748f" } +title = { bold = true } +hovered = { fg = "#c4a7e7", bold = true, reversed = true } + +# : }}} + + +# : Help menu {{{ + +[help] +on = { fg = "#9ccfd8" } +run = { fg = "#c4a7e7" } +desc = { fg = "#f6c177" } +hovered = { reversed = true, bold = true } +footer = { fg = "#191724", bg = "#e0def4" } + +# : }}} + + +# : File-specific styles {{{ + +[filetype] +rules = [ + # Images + { mime = "image/*", fg = "#c4a7e7" }, + + # Media + { mime = "{audio,video}/*", fg = "#f6c177" }, + + # Archives + { mime = "application/{zip,rar,7z*,tar,gzip,xz,zstd,bzip*,lzma,compress,archive,cpio,arj,xar,ms-cab*}", fg = "#eb6f92" }, + + # Documents + { mime = "application/{pdf,doc,rtf}", fg = "#ebbcba" }, + + # Empty files + # { mime = "inode/empty", fg = "#eb6f92" }, + + # Special files + { url = "*", is = "orphan", fg = "#191724", bg = "#eb6f92" }, + { url = "*", is = "exec", fg = "#9ccfd8" }, + + # Dummy files + { url = "*", is = "dummy", fg = "#191724", bg = "#eb6f92" }, + { url = "*/", is = "dummy", fg = "#191724", bg = "#eb6f92" }, + + # Fallback + { url = "*", fg = "#e0def4" }, + { url = "*/", fg = "#31748f" } +] + +# : }}} diff --git a/home/yazi/flavors/rose-pine.yazi/tmtheme.xml b/home/yazi/flavors/rose-pine.yazi/tmtheme.xml @@ -0,0 +1,316 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>name</key> + <string>Rosé Pine</string> + <key>settings</key> + <array> + <dict> + <key>settings</key> + <dict> + <key>background</key> + <string>#191724</string> + <key>caret</key> + <string>#524f67</string> + <key>foreground</key> + <string>#e0def4</string> + <key>invisibles</key> + <string>#1f1d2e</string> + <key>lineHighlight</key> + <string>#21202e</string> + <key>selection</key> + <string>#403d52</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Comment</string> + <key>scope</key> + <string>comment</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>italic</string> + <key>foreground</key> + <string>#908caa</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>String</string> + <key>scope</key> + <string>string, punctuation.definition.string</string> + <key>settings</key> + <dict> + <key>foreground</key> + <string>#f6c177</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Number</string> + <key>scope</key> + <string>constant.numeric</string> + <key>settings</key> + <dict> + <key>foreground</key> + <string>#f6c177</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Built-in constant</string> + <key>scope</key> + <string>constant.language</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#f6c177</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>User-defined constant</string> + <key>scope</key> + <string>constant.character, constant.other</string> + <key>settings</key> + <dict> + <key>foreground</key> + <string>#f6c177</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Variable</string> + <key>scope</key> + <string>variable</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>italic</string> + <key>foreground</key> + <string>#e0def4</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Keyword</string> + <key>scope</key> + <string>keyword</string> + <key>settings</key> + <dict> + <key>foreground</key> + <string>#31748f</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Storage</string> + <key>scope</key> + <string>storage</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string></string> + <key>foreground</key> + <string>#9ccfd8</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Storage type</string> + <key>scope</key> + <string>storage.type</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string></string> + <key>foreground</key> + <string>#9ccfd8</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Class name</string> + <key>scope</key> + <string>entity.name.class</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#31748f</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Inherited class</string> + <key>scope</key> + <string>entity.other.inherited-class</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>italic </string> + <key>foreground</key> + <string>#31748f</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Function name</string> + <key>scope</key> + <string>entity.name.function</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>italic</string> + <key>foreground</key> + <string>#ebbcba</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Function argument</string> + <key>scope</key> + <string>variable.parameter</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string></string> + <key>foreground</key> + <string>#c4a7e7</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Tag name</string> + <key>scope</key> + <string>entity.name.tag</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#31748f</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Tag attribute</string> + <key>scope</key> + <string>entity.other.attribute-name</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string></string> + <key>foreground</key> + <string>#c4a7e7</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Library function</string> + <key>scope</key> + <string>support.function</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#ebbcba</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Library constant</string> + <key>scope</key> + <string>support.constant</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#f6c177</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Library class&#x2f;type</string> + <key>scope</key> + <string>support.type, support.class</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#9ccfd8</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Library variable</string> + <key>scope</key> + <string>support.other.variable</string> + <key>settings</key> + <dict> + <key>fontStyle</key> + <string>bold</string> + <key>foreground</key> + <string>#eb6f92</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Invalid</string> + <key>scope</key> + <string>invalid</string> + <key>settings</key> + <dict> + <key>background</key> + <string>#eb6f92</string> + <key>fontStyle</key> + <string></string> + <key>foreground</key> + <string>#e0def4</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Invalid deprecated</string> + <key>scope</key> + <string>invalid.deprecated</string> + <key>settings</key> + <dict> + <key>background</key> + <string>#c4a7e7</string> + <key>foreground</key> + <string>#e0def4</string> + </dict> + </dict> + <dict> + <key>name</key> + <string>Punctuation, Operators</string> + <key>scope</key> + <string>punctuation, keyword.operator</string> + <key>settings</key> + <dict> + <key>foreground</key> + <string>#908caa</string> + </dict> + </dict> + </array> + <key>colorSpaceName</key> + <string>sRGB</string> + <key>semanticClass</key> + <string>theme.dark.rosé-pine</string> + <key>author</key> + <string>arrrgi</string> + <key>comment</key> + <string>All natural pine, faux fur and a bit of soho vibes for the classy minimalist</string> + <key>uuid</key> + <string>14991673-80EB-41A2-BEFF-03216A233730</string> +</dict> +</plist>