vesktop-glass

Transparent Vesktop for macOS — frosted glass, default Discord look, one opacity knob.
Log | Files | Refs | README | LICENSE

commit d3d4f424aaa744510da5577e33f1eb39a3fe149e
Author: ganten7 <ganten7@gmail.com>
Date:   Mon, 25 May 2026 06:56:51 -0500

Glass: transparent Vesktop for macOS — theme + optional persistent-frost installer

Confirmed only on MacBook Pro (Mac17,9) / Apple M5 Pro / macOS 26.5 / Vesktop 1.6.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Diffstat:
A.gitignore | 2++
AGlass.theme.css | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ALICENSE | 21+++++++++++++++++++++
AREADME.md | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascripts/disable-frost.sh | 5+++++
Ascripts/enable-frost.sh | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 404 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +node_modules/ diff --git a/Glass.theme.css b/Glass.theme.css @@ -0,0 +1,88 @@ +/** + * @name Glass + * @author ganten7 + * @version 1.0.0 + * @description Default Discord, made transparent — macOS frosted glass via Vesktop's + * vibrancy. Opacity only; no recoloring. Tune with one number. + * @source https://github.com/ganten7/vesktop-glass + * + * REQUIRES (Vesktop can't set these from a theme): + * Settings → Transparent Window = ON + * Settings → macOS Vibrancy Style = under-window + * …then fully quit & reopen Vesktop. + */ + +:root { + /* ── THE KNOBS ───────────────────────────────────────────────────────────── + * Edit these two numbers, save, done. + * --glass surfaces. 0 = fully see-through, 1 = solid (normal Discord). + * --glass-menus menus / popouts / modals. Keep high so they stay readable. + * ────────────────────────────────────────────────────────────────────────── */ + --glass: 0.55; + --glass-menus: 0.90; +} + +/* Apply the opacity to Discord's surfaces, keeping its default dark tones. + * Targets the 2025 "visual refresh" tokens AND the legacy ones, with !important + * so they win regardless of which token a given build paints with. */ +:root, +.theme-dark, +.theme-midnight, +.visual-refresh, +.visual-refresh.theme-dark, +.visual-refresh.theme-midnight { + + /* visual-refresh base surfaces (rail → chat → elevated), Discord's own dark ramp */ + --background-base-lowest: rgba(18, 19, 22, var(--glass)) !important; + --background-base-lower: rgba(24, 25, 28, var(--glass)) !important; + --background-base-low: rgba(30, 31, 34, var(--glass)) !important; + --background-base-high: rgba(43, 45, 49, var(--glass)) !important; + --background-base-higher: rgba(49, 51, 56, var(--glass)) !important; + --background-base-highest: rgba(56, 58, 64, var(--glass)) !important; + + --bg-overlay-app-frame: transparent !important; /* frame → pure vibrancy */ + --bg-overlay-chat: rgba(30, 31, 34, var(--glass)) !important; + --bg-overlay-1: rgba(43, 45, 49, var(--glass-menus)) !important; + --bg-overlay-2: rgba(37, 39, 43, var(--glass-menus)) !important; + --bg-overlay-3: rgba(30, 31, 34, var(--glass-menus)) !important; + --bg-overlay-floating: rgba(43, 45, 49, var(--glass-menus)) !important; + --bg-surface-overlay: rgba(43, 45, 49, var(--glass-menus)) !important; + --bg-surface-raised: rgba(43, 45, 49, var(--glass)) !important; + --bg-surface-high: rgba(43, 45, 49, var(--glass-menus)) !important; + --bg-surface-highest: rgba(49, 51, 56, var(--glass-menus)) !important; + + /* legacy surfaces (older views / popouts) */ + --background-primary: rgba(30, 31, 34, var(--glass)) !important; + --background-secondary: rgba(24, 25, 28, var(--glass)) !important; + --background-secondary-alt: rgba(24, 25, 28, var(--glass)) !important; + --background-tertiary: rgba(18, 19, 22, var(--glass)) !important; + --background-floating: rgba(43, 45, 49, var(--glass-menus)) !important; + --modal-background: rgba(43, 45, 49, var(--glass-menus)) !important; + --modal-footer-background: rgba(37, 39, 43, var(--glass-menus)) !important; + --channeltextarea-background: rgba(43, 45, 49, var(--glass)) !important; + + /* If you've set a custom Discord background, it paints an OPAQUE gradient image + * over these surfaces — flatten it to the same tint so transparency still shows. */ + --background-gradient-app-frame: linear-gradient(rgba(18,19,22,var(--glass)), rgba(18,19,22,var(--glass))) !important; + --background-gradient-lowest: linear-gradient(rgba(18,19,22,var(--glass)), rgba(18,19,22,var(--glass))) !important; + --background-gradient-lower: linear-gradient(rgba(24,25,28,var(--glass)), rgba(24,25,28,var(--glass))) !important; + --background-gradient-low: linear-gradient(rgba(30,31,34,var(--glass)), rgba(30,31,34,var(--glass))) !important; + --background-gradient-chat: linear-gradient(rgba(30,31,34,var(--glass)), rgba(30,31,34,var(--glass))) !important; + --background-gradient-high: linear-gradient(rgba(43,45,49,var(--glass)), rgba(43,45,49,var(--glass))) !important; + --background-gradient-higher: linear-gradient(rgba(49,51,56,var(--glass)), rgba(49,51,56,var(--glass))) !important; + --background-gradient-highest: linear-gradient(rgba(56,58,64,var(--glass)), rgba(56,58,64,var(--glass))) !important; + --chat-background: rgba(30, 31, 34, var(--glass)) !important; + --app-frame-background: rgba(18, 19, 22, var(--glass)) !important; +} + +/* Strip the opaque backing off the window frame so the macOS vibrancy shows through. */ +html, +body, +#app-mount, +[class*="appMount_"], +[class*="appAsideContainer_"], +[class*="base_"], +[class*="content_"] { + background-color: transparent !important; + background-image: none !important; +} diff --git a/LICENSE b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ganten + +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/README.md b/README.md @@ -0,0 +1,102 @@ +# Glass — transparent Vesktop for macOS + +Makes Discord (via [Vesktop](https://github.com/Vencord/Vesktop)) **frosted-transparent on macOS**, while keeping Discord's default look. Opacity only — no recoloring. + +![it's see-through; that's the whole pitch]() + +--- + +## Enable it (2 minutes) + +### Step 1 — turn on the window transparency +A theme **cannot** do this part; it's a Vesktop window setting. + +1. Open Vesktop → **Settings** (Vesktop/Vencord settings, the cog). +2. **Transparent Window** → **ON**. +3. **macOS Vibrancy Style** → **`under-window`**. +4. **Fully quit Vesktop and reopen it** — ⌘Q, not just close. (This setting only applies on a fresh launch.) + +### Step 2 — add the theme (pick ONE) + +**A. Online link (easiest, auto-updates):** +Settings → **Themes** → paste this into the online-themes box, press enter, tick it on: +``` +https://raw.githubusercontent.com/ganten7/vesktop-glass/main/Glass.theme.css +``` + +**B. Local file:** +Settings → **Themes** → **Open Themes Folder** → drop in [`Glass.theme.css`](Glass.theme.css) → tick its checkbox. + +That's it. You should see your desktop frosted through Discord. + +--- + +## Requirements +- **macOS** + **Vesktop** (does *not* work on the official Discord app — only Vesktop can make the window transparent). +- **System Settings → Accessibility → Display → Reduce Transparency = OFF.** If this is on, macOS disables *all* transparency. + +> ### ⚠ Honest scope: confirmed on exactly **one** machine +> Everything here was built and verified on a single setup: +> - **MacBook Pro (Mac17,9), Apple M5 Pro** +> - **macOS 26.5 (Tahoe, build 25F71)**, arm64 +> - **Vesktop 1.6.5** +> +> That's the *only* configuration I've actually tested. The theme is plain CSS and the +> vibrancy/`under-window` API is long-standing, so I *expect* it to work on other Macs and +> on Sonoma/Sequoia — but I have **not** verified that, and the optional patch script is +> resilient-by-design rather than proven across versions. If you run it elsewhere, a quick +> note (works / breaks + your specs) via an issue or PR helps everyone. Treat anything +> beyond the config above as "should work," not "known to work." + +--- + +## Tinkering — how transparent? + +Open `Glass.theme.css`. The only two knobs are at the very top: + +```css +--glass: 0.55; /* surfaces: 0 = fully see-through, 1 = solid Discord */ +--glass-menus: 0.90; /* menus/popouts: keep high so they stay readable */ +``` + +Change the numbers and save. The **local file** hot-reloads instantly; the **online link** updates when you toggle it off/on. Lower `--glass` = more see-through. That's the whole knob — everything else scales from it. + +--- + +## ⚠ Known limitation: frost dims when unfocused + +By default the frost **flattens to opaque whenever the Vesktop window loses focus**. That's not this theme — it's Vesktop never setting Electron's `visualEffectState`, so macOS deactivates the vibrancy when the window isn't frontmost. Clicking back restores it. Purely cosmetic. + +If that bugs you, there's an **optional** fix below. + +--- + +## Optional — make the frost *persistent* (advanced) + +`scripts/enable-frost.sh` patches your local Vesktop so the vibrancy stays active even when unfocused (it injects `visualEffectState: "active"` into the window options). It is **environment-aware**, backs everything up, and re-signs the app preserving entitlements (so mic/camera keep working). + +```bash +# from the repo root +chmod +x scripts/*.sh +./scripts/enable-frost.sh # interactive; prints what it sees, asks before patching +./scripts/enable-frost.sh --yes # no prompt +./scripts/enable-frost.sh --revert # undo (restores the backup) +``` + +**What it does:** finds Vesktop → backs up `app.asar` + `Info.plist` → injects the option (matched by pattern, so it survives Vesktop's minified rebuilds) → repacks the asar → recomputes the `ElectronAsarIntegrity` hash → ad-hoc re-signs the bundle. + +**Caveats — read these:** +- Needs **Node.js** (`brew install node`) and Xcode CLT (`xcode-select --install`) for the asar tooling + `codesign`. +- It modifies and re-signs `Vesktop.app`. Backups are made; `--revert` restores them. +- A **Vesktop update replaces `app.asar`**, so re-run the script after updating. +- macOS may re-prompt for mic/screen permission once after re-signing — just allow it. + +If the script can't find the vibrancy assignment (a future Vesktop refactor), it restores the original untouched and tells you — please open an issue with your Vesktop version. + +--- + +## How it works (for the curious) +Discord's 2025 "visual refresh" paints surfaces from `--background-base-*`, `--bg-overlay-*`, and (with a custom background set) opaque `--background-gradient-*` *images*. Glass overrides all of those — plus the legacy `--background-*` — with translucent versions of Discord's own dark tones, and strips the opaque backing off the app frame so the macOS vibrancy shows through. No text or accent colors are touched. + +## License +MIT. Frost responsibly. diff --git a/scripts/disable-frost.sh b/scripts/disable-frost.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# Revert the persistent-frost patch (restore Vesktop's original app.asar + Info.plist). +# Thin wrapper around enable-frost.sh --revert. +set -e +exec "$(cd "$(dirname "$0")" && pwd)/enable-frost.sh" --revert diff --git a/scripts/enable-frost.sh b/scripts/enable-frost.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# +# enable-frost.sh — make Vesktop's frosted transparency STAY ON when the window +# loses focus (macOS). Optional companion to the Glass theme. +# +# Why this exists: Vesktop never sets Electron's `visualEffectState`, so macOS +# deactivates the window vibrancy whenever Vesktop isn't focused and the frost +# collapses to opaque. There's no setting for it — it's a window-construction +# option — so we patch it in. This script: +# 1) finds your Vesktop, prints what it sees (hardware/OS/version) +# 2) backs up app.asar + Info.plist (originals, once) +# 3) injects visualEffectState:"active" next to the vibrancy assignment, +# matching it by PATTERN so it survives minifier renames across builds +# 4) repacks the asar, recomputes the ElectronAsarIntegrity hash (if present) +# 5) ad-hoc re-signs the app (preserving entitlements, so mic/camera keep working) +# +# Revert anytime: ./scripts/enable-frost.sh --revert (or run disable-frost.sh) +# Note: a Vesktop UPDATE replaces app.asar, so re-run this after updating. +# +# Usage: +# ./scripts/enable-frost.sh # interactive +# ./scripts/enable-frost.sh --yes # no prompt +# ./scripts/enable-frost.sh --revert # restore the backup +# VESKTOP_APP=/path/Vesktop.app ./scripts/enable-frost.sh # override location + +set -euo pipefail + +YES=0; REVERT=0 +for a in "$@"; do + case "$a" in + --yes|-y) YES=1 ;; + --revert|--uninstall) REVERT=1 ;; + -h|--help) sed -n '2,30p' "$0"; exit 0 ;; + *) echo "unknown arg: $a" >&2; exit 64 ;; + esac +done + +die(){ echo "✗ $*" >&2; exit 1; } +say(){ echo " $*"; } +step(){ echo; echo "▸ $*"; } + +# ── locate Vesktop ────────────────────────────────────────────────────────── +APP="${VESKTOP_APP:-}" +if [ -z "$APP" ]; then + for p in "/Applications/Vesktop.app" "$HOME/Applications/Vesktop.app"; do + [ -d "$p" ] && APP="$p" && break + done +fi +[ -z "$APP" ] && APP="$(mdfind "kMDItemCFBundleIdentifier == 'dev.vencord.vesktop'" 2>/dev/null | head -1 || true)" +[ -n "$APP" ] && [ -d "$APP" ] || die "Couldn't find Vesktop.app. Pass VESKTOP_APP=/path/to/Vesktop.app" + +RES="$APP/Contents/Resources" +ASAR="$RES/app.asar" +PLIST="$APP/Contents/Info.plist" +[ -f "$ASAR" ] || die "No app.asar at $ASAR" +[ -f "$PLIST" ] || die "No Info.plist at $PLIST" + +# ── environment report (hardware-aware) ───────────────────────────────────── +step "Environment" +say "macOS: $(sw_vers -productVersion) ($(sw_vers -buildVersion))" +say "arch: $(uname -m)" +say "Vesktop: $(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$PLIST" 2>/dev/null || echo '?') at $APP" +RT="$(defaults read com.apple.universalaccess reduceTransparency 2>/dev/null || echo 0)" +[ "$RT" = "1" ] && say "⚠ 'Reduce Transparency' is ON — turn it OFF (System Settings → Accessibility → Display) or you'll see no transparency." + +# ── revert path ───────────────────────────────────────────────────────────── +if [ "$REVERT" = "1" ]; then + step "Reverting" + [ -f "$ASAR.bak" ] || die "No backup ($ASAR.bak) — nothing to revert." + osascript -e 'quit app "Vesktop"' 2>/dev/null || true; sleep 1; pkill -x vesktop 2>/dev/null || true + cp -p "$ASAR.bak" "$ASAR" + [ -f "$PLIST.bak" ] && cp -p "$PLIST.bak" "$PLIST" + codesign --force --deep --sign - --preserve-metadata=entitlements "$APP" 2>/dev/null \ + || codesign --force --deep --sign - "$APP" + say "Restored original app.asar + Info.plist and re-signed. Backups kept." + echo; echo "✓ Reverted."; exit 0 +fi + +# ── preconditions ─────────────────────────────────────────────────────────── +step "Checks" +command -v node >/dev/null || die "Node.js required (the asar tooling needs it). Install: brew install node" +command -v npm >/dev/null || die "npm required (ships with Node)." +command -v codesign >/dev/null || die "codesign required (install Xcode Command Line Tools: xcode-select --install)." +if [ ! -w "$ASAR" ] || [ ! -w "$PLIST" ]; then + die "No write permission to $APP. Re-run with sudo: sudo VESKTOP_APP='$APP' $0 ${*:-}" +fi +if grep -aq 'visualEffectState' "$ASAR"; then + say "Already patched (visualEffectState present). Nothing to do." + echo; echo "✓ Already enabled."; exit 0 +fi +say "OK — Node $(node -v), writable app bundle, not yet patched." + +# ── confirm ───────────────────────────────────────────────────────────────── +if [ "$YES" != "1" ]; then + echo + echo "This will modify and re-sign:" + echo " $APP" + echo "Originals are backed up to app.asar.bak / Info.plist.bak (revert with --revert)." + printf "Continue? [y/N] " + read -r ans; case "$ans" in y|Y|yes|YES) ;; *) echo "Aborted."; exit 0 ;; esac +fi + +# ── quit Vesktop ──────────────────────────────────────────────────────────── +step "Quitting Vesktop" +osascript -e 'quit app "Vesktop"' 2>/dev/null || true +for _ in 1 2 3 4 5 6 7 8 9 10; do pgrep -x vesktop >/dev/null || break; sleep 0.5; done +pgrep -x vesktop >/dev/null && pkill -x vesktop 2>/dev/null || true +sleep 1; pgrep -x vesktop >/dev/null && pkill -9 -x vesktop 2>/dev/null || true +say "stopped." + +# ── backups (originals only) ──────────────────────────────────────────────── +step "Backup" +[ -f "$ASAR.bak" ] || cp -p "$ASAR" "$ASAR.bak" +[ -f "$PLIST.bak" ] || cp -p "$PLIST" "$PLIST.bak" +say "app.asar.bak + Info.plist.bak in place." + +# ── patch + repack + hash (Node, with @electron/asar) ─────────────────────── +step "Patching app.asar" +WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT +say "installing @electron/asar (temporary)…" +( cd "$WORK" && npm init -y >/dev/null 2>&1 && npm i @electron/asar@4 >/dev/null 2>&1 ) || die "npm install of @electron/asar failed (offline?)." + +cat > "$WORK/patch.mjs" <<'NODE' +import { extractAll, createPackage, getRawHeader } from '@electron/asar'; +import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; +const ASAR = process.env.ASAR, EX = path.join(process.env.WORK, 'x'); +fs.rmSync(EX, { recursive: true, force: true }); +extractAll(ASAR, EX); +// Match `OBJ.vibrancy = VALUE` (any minified names) and inject visualEffectState +// on the same object, right after. Survives Vesktop rebuilds / renames. +const RE = /([A-Za-z_$][\w$]*)\.vibrancy\s*=\s*([^,;)]+)/g; +let patched = 0; +const walk = d => { for (const e of fs.readdirSync(d, { withFileTypes: true })) { + const p = path.join(d, e.name); + if (e.isDirectory()) walk(p); + else if (e.name.endsWith('.js')) { + let s = fs.readFileSync(p, 'utf8'); + if (!RE.test(s)) { RE.lastIndex = 0; continue; } RE.lastIndex = 0; + if (s.includes('visualEffectState')) continue; + const out = s.replace(RE, (_m, obj, val) => `${obj}.vibrancy=${val},${obj}.visualEffectState="active"`); + if (out !== s) { fs.writeFileSync(p, out); patched++; } + } +} }; +walk(EX); +if (patched === 0) { console.error('NOPATTERN'); process.exit(2); } +await createPackage(EX, ASAR); +if (!fs.readFileSync(ASAR).includes('visualEffectState')) { console.error('VERIFYFAIL'); process.exit(3); } +const { headerString } = getRawHeader(ASAR); +process.stdout.write('HASH=' + crypto.createHash('sha256').update(headerString).digest('hex') + ' FILES=' + patched); +NODE + +RESULT="$(ASAR="$ASAR" WORK="$WORK" node "$WORK/patch.mjs" 2>"$WORK/err" || true)" +if [ -z "$RESULT" ]; then + ERR="$(cat "$WORK/err" 2>/dev/null || true)" + cp -p "$ASAR.bak" "$ASAR" # restore on failure + case "$ERR" in + *NOPATTERN*) die "Couldn't find the vibrancy assignment in this Vesktop build. Please open an issue with your Vesktop version; original restored." ;; + *) die "Patch failed ($ERR). Original restored." ;; + esac +fi +HASH="${RESULT#HASH=}"; HASH="${HASH%% *}" +say "injected visualEffectState — ${RESULT#*FILES=} file(s) patched." + +# ── update asar integrity hash (only if the fuse uses it) ─────────────────── +step "Integrity" +if /usr/libexec/PlistBuddy -c "Print :ElectronAsarIntegrity:Resources/app.asar:hash" "$PLIST" >/dev/null 2>&1; then + /usr/libexec/PlistBuddy -c "Set :ElectronAsarIntegrity:Resources/app.asar:hash $HASH" "$PLIST" + say "ElectronAsarIntegrity hash updated." +else + say "No asar-integrity fuse on this build — skipping (nothing to update)." +fi + +# ── re-sign (ad-hoc, keep entitlements so mic/camera survive) ─────────────── +step "Re-signing (ad-hoc)" +codesign --force --deep --sign - --preserve-metadata=entitlements "$APP" 2>/dev/null \ + || codesign --force --deep --sign - "$APP" \ + || die "codesign failed. Restore with: cp '$ASAR.bak' '$ASAR' && cp '$PLIST.bak' '$PLIST'" +codesign --verify --deep "$APP" 2>/dev/null && say "signature valid." || say "⚠ verify reported issues (it may still launch)." + +# ── done ──────────────────────────────────────────────────────────────────── +step "Launching" +open -a Vesktop || true +echo +echo "✓ Frost is now persistent — it will stay when Vesktop loses focus." +echo " Revert: $0 --revert" +echo " Re-run this after any Vesktop update (updates replace app.asar)."