commit c39d8296cff701e101c374de22fdaec98ca8596d
Author: Matthew Gantenbein <ganten1998@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:
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)."