Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions release/packages/ryoku-palette-bridge/PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Maintainer: Ryoku <[email protected]>
#
# Ryoku Palette Bridge: publishes the wallpaper palette via HTTP so Spotify,
# Vesktop, and Zen Browser can consume it live. A single Go binary listens on
# localhost and fans palette changes to connected clients; no systemd hard
# dependency here—the service unit ships so the optional user-service path works.
pkgname=ryoku-palette-bridge
pkgver=${RYOKU_PKGVER:-0.1.0}
pkgrel=1
pkgdesc="Ryoku wallpaper palette event bridge: exposes colours to SPA integrations"
arch=('x86_64')
url="https://ryoku.dev"
license=('MIT')
depends=()
makedepends=('go')
source=()

_repo="$startdir/../../.."

build() {
cd "$_repo/ryoku/palette-bridge"
# no vendor tree is shipped with the bridge; GOFLAGS from the publish
# container pin the proxy so the build stays offline and reproducible.
CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o "$srcdir/ryoku-palette-bridge" .
}

package() {
install -Dm755 "$srcdir/ryoku-palette-bridge" \
"$pkgdir/usr/bin/ryoku-palette-bridge"
install -Dm755 "$_repo/ryoku/palette-bridge/doctor.sh" \
"$pkgdir/usr/bin/ryoku-palette-bridge-doctor"
install -Dm755 "$_repo/ryoku/palette-bridge/remove-integrations.sh" \
"$pkgdir/usr/bin/ryoku-palette-bridge-remove-integrations"
install -Dm644 "$_repo/ryoku/palette-bridge/packaging/systemd/ryoku-palette-bridge.service" \
"$pkgdir/usr/lib/systemd/user/ryoku-palette-bridge.service"
install -dm755 "$pkgdir/usr/share/ryoku/palette-bridge"
cp -a "$_repo/ryoku/palette-bridge/." "$pkgdir/usr/share/ryoku/palette-bridge/"
install -Dm644 "$_repo/ryoku/palette-bridge/LICENSE" \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}
21 changes: 21 additions & 0 deletions ryoku/palette-bridge/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Rayyan

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.
77 changes: 77 additions & 0 deletions ryoku/palette-bridge/doctor.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: MIT
set -uo pipefail

palette=${RYOKU_PALETTE:-"${XDG_CACHE_HOME:-$HOME/.cache}/ryoku/colors.json"}
base_url=${RYOKU_PALETTE_BRIDGE_URL:-http://127.0.0.1:47616}
failed=0

ok() {
printf 'ok %s\n' "$1"
}

fail() {
printf 'FAIL %s\n' "$1" >&2
failed=1
}

for command_name in curl systemctl; do
if ! command -v "$command_name" >/dev/null; then
fail "required command is missing: $command_name"
fi
done
((failed == 0)) || exit "$failed"

have_jq=true
if ! command -v jq >/dev/null; then
have_jq=false
printf 'WARN jq is missing; the palette-diff check will be skipped\n'
fi

if $have_jq; then
if jq -e '
type == "object" and
(.primary | test("^#[0-9a-fA-F]{6}$")) and
(.surface | test("^#[0-9a-fA-F]{6}$")) and
(.onSurface | test("^#[0-9a-fA-F]{6}$")) and
all(.[]; type == "string" and test("^#[0-9a-fA-F]{6}$"))
' "$palette" >/dev/null 2>&1; then
ok "palette is valid: $palette"
else
fail "palette is missing or invalid: $palette"
fi
else
printf 'WARN jq is missing; palette validation skipped\n'
fi

if systemctl --user is-active --quiet ryoku-palette-bridge.service; then
ok "canonical service is active"
elif systemctl --user is-active --quiet ryoku-spicetify-palette.service; then
printf 'WARN legacy service is active; rerun install.sh to migrate it\n'
else
fail "palette bridge service is inactive"
fi

if [[ $(curl --connect-timeout 2 --max-time 5 -fsS "$base_url/healthz" 2>/dev/null) == ok ]]; then
ok "health endpoint responds"
else
fail "health endpoint is unavailable: $base_url/healthz"
fi

if $have_jq; then
published=$(curl --connect-timeout 2 --max-time 5 -fsS "$base_url/v1/palette" 2>/dev/null || true)
local_normalized=$(jq -cS . "$palette" 2>/dev/null || true)
published_normalized=$(jq -cS . <<< "$published" 2>/dev/null || true)
if [[ -n $local_normalized && $published_normalized == "$local_normalized" ]]; then
ok "published palette matches"
else
fail "published palette differs from the active palette"
fi
else
printf 'WARN jq is missing; the published-palette check was skipped\n'
fi

if ((failed == 0)); then
printf 'Ryoku Palette Bridge is healthy.\n'
fi
exit "$failed"
3 changes: 3 additions & 0 deletions ryoku/palette-bridge/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module ryoku-palette-bridge

go 1.24
180 changes: 180 additions & 0 deletions ryoku/palette-bridge/install-integrations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
#!/usr/bin/env bash
set -euo pipefail

project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
config_root="${XDG_CONFIG_HOME:-$HOME/.config}"
overlay_root="$config_root/ryoku/user_edits/matugen"
overlay_apps="$overlay_root/apps.toml"
live_apps="$config_root/matugen/apps.toml"
if [[ "$config_root" == "$HOME/.config" ]]; then
# Literal paths written into apps.toml; Matugen expands the tilde.
# shellcheck disable=SC2088
matugen_template_root='~/.config/matugen/templates'
# shellcheck disable=SC2088
vesktop_quick_css='~/.config/vesktop/settings/quickCss.css'
else
matugen_template_root="$config_root/matugen/templates"
vesktop_quick_css="$config_root/vesktop/settings/quickCss.css"
fi
want_spotify=false
want_vesktop=false
want_zen=false
state_root="${XDG_STATE_HOME:-$HOME/.local/state}/ryoku/palette-bridge"
ownership_file="$state_root/owned-files.tsv"

usage() {
printf 'Usage: %s --all | [--spotify] [--vesktop] [--zen]\n' "${0##*/}"
}

while (( $# )); do
case "$1" in
--all) want_spotify=true; want_vesktop=true; want_zen=true ;;
--spotify) want_spotify=true ;;
--vesktop) want_vesktop=true ;;
--zen) want_zen=true ;;
-h|--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
shift
done

if ! $want_spotify && ! $want_vesktop && ! $want_zen; then
usage >&2
exit 2
fi

prepare_matugen_overlay() {
install -d "$overlay_root/templates"
if [[ ! -f "$overlay_apps" ]]; then
[[ -f "$live_apps" ]] || {
printf 'Matugen apps file not found: %s\n' "$live_apps" >&2
exit 1
}
install -m 0644 "$live_apps" "$overlay_apps"
fi
}

record_owned() {
local integration="$1" path="$2" temporary
install -d "$state_root"
temporary=$(mktemp)
if [[ -f "$ownership_file" ]]; then
awk -F '\t' -v app="$integration" -v path="$path" '!($1 == app && $2 == path)' "$ownership_file" > "$temporary"
fi
printf '%s\t%s\n' "$integration" "$path" >> "$temporary"
install -m 0600 "$temporary" "$ownership_file"
rm -f "$temporary"
}

set_matugen_section() {
local section="$1" input_path="$2" output_path="$3" temporary
temporary=$(mktemp)
awk -v target="[$section]" '
$0 == target { skip=1; next }
/^\[/ && skip { skip=0 }
!skip { print }
' "$overlay_apps" > "$temporary"
{
printf '\n[%s]\n' "$section"
printf 'input_path = "%s"\n' "$input_path"
printf 'output_path = "%s"\n' "$output_path"
} >> "$temporary"
install -m 0644 "$temporary" "$overlay_apps"
rm -f "$temporary"
}

if $want_spotify; then
command -v spicetify >/dev/null || { printf 'spicetify is required for --spotify\n' >&2; exit 1; }
install -d "$config_root/spicetify/Extensions"
install -m 0644 "$project_root/spicetify/ryoku-wallpaper-colors.js" \
"$config_root/spicetify/Extensions/ryoku-wallpaper-colors.js"
record_owned spotify "$config_root/spicetify/Extensions/ryoku-wallpaper-colors.js"
if ! spicetify config extensions | grep -Fxq 'ryoku-wallpaper-colors.js'; then
spicetify config extensions ryoku-wallpaper-colors.js
fi
spicetify apply
printf 'Installed the Spotify live-palette extension.\n'
fi

if $want_vesktop; then
command -v jq >/dev/null || { printf 'jq is required for --vesktop\n' >&2; exit 1; }
settings="$config_root/vesktop/settings/settings.json"
[[ -f "$settings" ]] || { printf 'Vesktop settings not found: %s\n' "$settings" >&2; exit 1; }
prepare_matugen_overlay
install -m 0644 "$project_root/templates/vesktop-colors.css" "$overlay_root/templates/vesktop-colors.css"
record_owned vesktop "$overlay_root/templates/vesktop-colors.css"
set_matugen_section templates.vesktop \
"$matugen_template_root/vesktop-colors.css" \
"$vesktop_quick_css"
install -d "$config_root/vesktop/themes"
install -m 0644 "$project_root/vesktop/midnight-ryoku.theme.css" \
"$config_root/vesktop/themes/midnight-ryoku.theme.css"
record_owned vesktop "$config_root/vesktop/themes/midnight-ryoku.theme.css"
temporary=$(mktemp)
jq '.useQuickCss = true | .enabledThemes = ((.enabledThemes // []) | if index("midnight-ryoku.theme.css") then . else . + ["midnight-ryoku.theme.css"] end)' \
"$settings" > "$temporary"
install -m 0644 "$temporary" "$settings"
rm -f "$temporary"
printf 'Installed the Vesktop no-flash Midnight integration.\n'
fi

if $want_zen; then
zen_root="$config_root/zen"
if [[ -n "${ZEN_PROFILE_ROOT:-}" ]]; then
profile_root="$ZEN_PROFILE_ROOT"
else
profile_root=$(find "$zen_root" -mindepth 2 -maxdepth 2 -name .parentlock -printf '%h\n' -quit 2>/dev/null || true)
if [[ -z "$profile_root" && -f "$zen_root/profiles.ini" ]]; then
profile_path=$(awk -F= '
/^\[Profile/ { active=1; path=""; preferred=0; next }
/^\[/ { if (active && preferred && path != "") { print path; exit }; active=0 }
active && $1 == "Path" { path=$2 }
active && $1 == "Default" && $2 == "1" { preferred=1 }
END { if (active && preferred && path != "") print path }
' "$zen_root/profiles.ini" | head -n1)
[[ -n "$profile_path" ]] && profile_root="$zen_root/$profile_path"
fi
fi
[[ -n "${profile_root:-}" && -d "$profile_root" ]] || {
printf 'Could not identify a Zen profile; set ZEN_PROFILE_ROOT and retry.\n' >&2
exit 1
}
prepare_matugen_overlay
install -m 0644 "$project_root/templates/zen.css" "$overlay_root/templates/zen.css"
record_owned zen "$overlay_root/templates/zen.css"
if [[ "$profile_root" == "$HOME/"* ]]; then
profile_output="~${profile_root#"$HOME"}/chrome/ryoku-colors.css"
else
profile_output="$profile_root/chrome/ryoku-colors.css"
fi
set_matugen_section templates.zen "$matugen_template_root/zen.css" "$profile_output"
install -d "$profile_root/chrome"
user_chrome="$profile_root/chrome/userChrome.css"
touch "$user_chrome"
if ! grep -Fqx '@import "ryoku-colors.css";' "$user_chrome"; then
temporary=$(mktemp)
printf '@import "ryoku-colors.css";\n' > "$temporary"
sed -n '1,$p' "$user_chrome" >> "$temporary"
install -m 0644 "$temporary" "$user_chrome"
rm -f "$temporary"
fi
record_owned zen "$user_chrome"
user_js="$profile_root/user.js"
touch "$user_js"
temporary=$(mktemp)
grep -vE 'user_pref\("(toolkit\.legacyUserProfileCustomizations\.stylesheets|zen\.theme\.disable-lightweight)"' "$user_js" > "$temporary" || true
printf 'user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);\n' >> "$temporary"
printf 'user_pref("zen.theme.disable-lightweight", false);\n' >> "$temporary"
install -m 0644 "$temporary" "$user_js"
record_owned zen "$user_js"
rm -f "$temporary"
printf 'Installed Zen profile wiring. Sign/install zen-extension separately, then restart Zen once.\n'
fi

if $want_vesktop || $want_zen; then
command -v ryoku >/dev/null || { printf 'ryoku is required to materialize Matugen overlays\n' >&2; exit 1; }
ryoku materialize
if command -v ryogami >/dev/null; then
ryogami wallpaper repaint
fi
fi
22 changes: 22 additions & 0 deletions ryoku/palette-bridge/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail

project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
bin_dir="$HOME/.local/bin"
unit_dir="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"

install -d "$bin_dir" "$unit_dir"
(cd "$project_root" && go build -trimpath -ldflags='-s -w' -o "$bin_dir/ryoku-palette-bridge" .)
install -m 0755 "$project_root/doctor.sh" "$bin_dir/ryoku-palette-bridge-doctor"
install -m 0755 "$project_root/remove-integrations.sh" "$bin_dir/ryoku-palette-bridge-remove-integrations"
install -m 0644 "$project_root/packaging/systemd/ryoku-palette-bridge.service" \
"$unit_dir/ryoku-palette-bridge.service"
systemctl --user daemon-reload
for legacy_unit in ryoku-spicetify-palette.service spiceflow.service; do
if systemctl --user list-unit-files "$legacy_unit" --no-legend 2>/dev/null |
grep -q "^${legacy_unit}"; then
systemctl --user disable --now "$legacy_unit"
printf 'Disabled legacy %s (its unit file was preserved)\n' "$legacy_unit"
fi
done
printf 'Installed ryoku-palette-bridge.service (enable it when ready).\n'
Loading
Loading