Add theme system: local themes, picker, static/dynamic mode
- blob_theme: apply local or shared themes, --dynamic recolors from the current wallpaper without changing it, --print saves the active theme's colors.toml (e.g. for a dynamic palette worth keeping) - themes/: drop-in colors.toml files that install.sh syncs into Omarchy's theme dir, alongside any already-installed themes - ThemePicker widget, walker menu, and Quick Settings tile mirroring the existing wallpaper picker, with color-swatch previews - blob_wallpaper no longer forces dynamic mode on every wallpaper change; it only recolors if you're already in dynamic mode - install.sh: fix a new-directory mkdir bug in backup_and_copy, and stop backing up wallpapers/themes (.bak dirs were showing up as fake entries in the picker) since both are pure git mirrors Claude-Session: https://claude.ai/code/session_011kf23kB9NsaczSYazZsWs6
This commit is contained in:
@@ -23,15 +23,17 @@ My current setup is built around these core components:
|
|||||||
|
|
||||||
- **`hypr/`**: Hyprland configurations (keybindings, window rules, animations, monitor layout, lid-close display handling, and autostart). See [`hypr/keybinds.md`](hypr/keybinds.md) for custom keybindings.
|
- **`hypr/`**: Hyprland configurations (keybindings, window rules, animations, monitor layout, lid-close display handling, and autostart). See [`hypr/keybinds.md`](hypr/keybinds.md) for custom keybindings.
|
||||||
- **`waybar/`**: Status bar layout, CSS styling, and custom interactive modules.
|
- **`waybar/`**: Status bar layout, CSS styling, and custom interactive modules.
|
||||||
- **`ags/`**: Custom desktop widgets built with TypeScript and GTK — media player, notification hub, quick settings, system monitor, and wallpaper picker.
|
- **`ags/`**: Custom desktop widgets built with TypeScript and GTK — media player, notification hub, quick settings, system monitor, wallpaper picker, and theme picker.
|
||||||
- **`scripts/`**: Global utility scripts seamlessly exposed as commands by the installer. See [`commands.md`](commands.md).
|
- **`scripts/`**: Global utility scripts seamlessly exposed as commands by the installer. See [`commands.md`](commands.md).
|
||||||
- **`wallpapers/`**: A collection of local custom wallpapers for dynamic theming. See [`wallpaper-gallery/`](wallpaper-gallery/index.md) for the full gallery (split alphabetically across multiple pages).
|
- **`wallpapers/`**: A collection of local custom wallpapers for dynamic theming. See [`wallpaper-gallery/`](wallpaper-gallery/index.md) for the full gallery (split alphabetically across multiple pages).
|
||||||
|
- **`themes/`**: Drop-in local color themes (one `colors.toml` per theme) that `install.sh` deploys into Omarchy's theme directory. See [`themes/README.md`](themes/README.md).
|
||||||
- **`omarchy/hooks/`**: Event hooks for the Omarchy system (e.g. automatically applying dynamic themes when changing wallpapers).
|
- **`omarchy/hooks/`**: Event hooks for the Omarchy system (e.g. automatically applying dynamic themes when changing wallpapers).
|
||||||
- **`branding/`**: Custom ASCII art and system branding assets.
|
- **`branding/`**: Custom ASCII art and system branding assets.
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
- [Wallpaper Gallery](wallpaper-gallery/index.md) — preview of every wallpaper in `wallpapers/`, split alphabetically across [0-9, A-D](wallpaper-gallery/a-d.md), [E-M](wallpaper-gallery/e-m.md), [M-R](wallpaper-gallery/m-r.md), [R-Y](wallpaper-gallery/r-z.md).
|
- [Wallpaper Gallery](wallpaper-gallery/index.md) — preview of every wallpaper in `wallpapers/`, split alphabetically across [0-9, A-D](wallpaper-gallery/a-d.md), [E-M](wallpaper-gallery/e-m.md), [M-R](wallpaper-gallery/m-r.md), [R-Y](wallpaper-gallery/r-z.md).
|
||||||
|
- [Themes](themes/README.md) — how to add a local color theme.
|
||||||
- [Keybinds](hypr/keybinds.md) — custom Hyprland keybindings on top of Omarchy's defaults.
|
- [Keybinds](hypr/keybinds.md) — custom Hyprland keybindings on top of Omarchy's defaults.
|
||||||
- [Commands](commands.md) — custom `blob_*` CLI commands exposed by the installer.
|
- [Commands](commands.md) — custom `blob_*` CLI commands exposed by the installer.
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Media from "./widget/Media"
|
|||||||
import NotificationCenter from "./widget/Notifications"
|
import NotificationCenter from "./widget/Notifications"
|
||||||
import QuickSettings from "./widget/QuickSettings"
|
import QuickSettings from "./widget/QuickSettings"
|
||||||
import SysMonitor from "./widget/SysMonitor"
|
import SysMonitor from "./widget/SysMonitor"
|
||||||
|
import ThemePicker from "./widget/ThemePicker"
|
||||||
import WallPicker from "./widget/WallPicker"
|
import WallPicker from "./widget/WallPicker"
|
||||||
|
|
||||||
const SHOW_DESKTOP_MEDIA = false
|
const SHOW_DESKTOP_MEDIA = false
|
||||||
@@ -25,6 +26,7 @@ app.start({
|
|||||||
start("notification-center", NotificationCenter)
|
start("notification-center", NotificationCenter)
|
||||||
start("quick-settings", QuickSettings)
|
start("quick-settings", QuickSettings)
|
||||||
start("sys-monitor", SysMonitor)
|
start("sys-monitor", SysMonitor)
|
||||||
|
start("theme-picker", ThemePicker)
|
||||||
start("wall-picker", WallPicker)
|
start("wall-picker", WallPicker)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
+60
-1
@@ -84,7 +84,8 @@
|
|||||||
.QuickSettings,
|
.QuickSettings,
|
||||||
.SysMonitor,
|
.SysMonitor,
|
||||||
.Calendar,
|
.Calendar,
|
||||||
.WallPicker {
|
.WallPicker,
|
||||||
|
.ThemePicker {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +350,64 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.theme-picker {
|
||||||
|
min-width: 680px;
|
||||||
|
min-height: 460px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-scroll {
|
||||||
|
min-height: 360px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip {
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
background-color: alpha(@color0, 0.6);
|
||||||
|
transition: all 0.2s;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip:hover {
|
||||||
|
border: 2px solid alpha(@accent, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip.active {
|
||||||
|
border: 2px solid @accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip-dynamic {
|
||||||
|
background-color: alpha(@accent, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip-dynamic.active {
|
||||||
|
background-color: @accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip-dynamic.active .theme-chip-label,
|
||||||
|
.theme-chip-dynamic.active .theme-chip-icon {
|
||||||
|
color: @background;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
color: @accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-swatches {
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-swatch {
|
||||||
|
min-width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-chip-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: @foreground;
|
||||||
|
}
|
||||||
|
|
||||||
.qs-side-panel {
|
.qs-side-panel {
|
||||||
min-width: 240px;
|
min-width: 240px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,6 +176,14 @@ function Toggles() {
|
|||||||
</box>
|
</box>
|
||||||
<box class="qs-tiles" spacing={8} homogeneous>
|
<box class="qs-tiles" spacing={8} homogeneous>
|
||||||
<Tile icon={""} label="Pick Color" onClicked={() => { closePanel(); sh("hyprpicker -a") }} />
|
<Tile icon={""} label="Pick Color" onClicked={() => { closePanel(); sh("hyprpicker -a") }} />
|
||||||
|
<Tile
|
||||||
|
icon={""}
|
||||||
|
label="Theme"
|
||||||
|
onClicked={() => {
|
||||||
|
closePanel()
|
||||||
|
sh("blob_theme")
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
import app from "ags/gtk3/app"
|
||||||
|
import { Astal, Gtk } from "ags/gtk3"
|
||||||
|
import { createPoll } from "ags/time"
|
||||||
|
import { For } from "ags"
|
||||||
|
import { sh, shell } from "../lib/utils"
|
||||||
|
|
||||||
|
export const THEMEPICKER_WINDOW = "theme-picker"
|
||||||
|
|
||||||
|
const COLUMNS = 3
|
||||||
|
|
||||||
|
type Theme = { slug: string; name: string; swatches: string[] }
|
||||||
|
|
||||||
|
const listCommand =
|
||||||
|
"{ find \"$HOME/.config/omarchy/themes\" -mindepth 1 -maxdepth 1 -type d 2>/dev/null; " +
|
||||||
|
"find \"$OMARCHY_PATH/themes\" -mindepth 1 -maxdepth 1 -type d 2>/dev/null; } | " +
|
||||||
|
"while read -r d; do " +
|
||||||
|
"[ -f \"$d/colors.toml\" ] || continue; " +
|
||||||
|
"n=$(basename \"$d\"); " +
|
||||||
|
"[ \"$n\" = blob-dynamic ] && continue; " +
|
||||||
|
"c=$(grep -E '^color[1-6] *=' \"$d/colors.toml\" | sed -E 's/^color[0-9]+ *= *\"?([^\"]*)\"?.*/\\1/' | paste -sd,); " +
|
||||||
|
"p=$(echo \"$n\" | sed -E 's/(^|-)([a-z])/\\1\\u\\2/g; s/-/ /g'); " +
|
||||||
|
"echo \"$n|$p|$c\"; " +
|
||||||
|
"done | awk -F'|' '!seen[$1]++' | sort -t'|' -k2,2"
|
||||||
|
|
||||||
|
const themes = createPoll<Theme[]>(
|
||||||
|
[],
|
||||||
|
5000,
|
||||||
|
shell(listCommand),
|
||||||
|
(stdout) =>
|
||||||
|
stdout
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => line.length > 0)
|
||||||
|
.map((line) => {
|
||||||
|
const [slug, name, swatchStr] = line.split("|")
|
||||||
|
return { slug, name: name ?? slug, swatches: (swatchStr ?? "").split(",").filter(Boolean) }
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentThemeName = createPoll(
|
||||||
|
"",
|
||||||
|
2000,
|
||||||
|
shell("cat \"$HOME/.config/omarchy/current/theme.name\" 2>/dev/null"),
|
||||||
|
(stdout) => stdout.trim(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const isDynamic = currentThemeName.as((name) => name === "blob-dynamic")
|
||||||
|
|
||||||
|
const rows = themes.as((list) => {
|
||||||
|
const chunks: Theme[][] = []
|
||||||
|
for (let index = 0; index < list.length; index += COLUMNS) {
|
||||||
|
chunks.push(list.slice(index, index + COLUMNS))
|
||||||
|
}
|
||||||
|
return chunks
|
||||||
|
})
|
||||||
|
|
||||||
|
const applyTheme = (slug: string) => {
|
||||||
|
app.toggle_window(THEMEPICKER_WINDOW)
|
||||||
|
sh(`"$HOME/scripts/blob_theme.sh" "${slug}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyDynamic = () => {
|
||||||
|
app.toggle_window(THEMEPICKER_WINDOW)
|
||||||
|
sh(`"$HOME/scripts/blob_theme.sh" --dynamic`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Swatches({ colors }: { colors: string[] }) {
|
||||||
|
return (
|
||||||
|
<box class="theme-swatches" spacing={0}>
|
||||||
|
{colors.map((color) => (
|
||||||
|
<box class="theme-swatch" css={`background-color: ${color};`} hexpand />
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ThemeChip({ theme }: { theme: Theme }) {
|
||||||
|
const active = currentThemeName.as((name) => name === theme.slug)
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
class={active.as((on: boolean) => (on ? "theme-chip active" : "theme-chip"))}
|
||||||
|
onClicked={() => applyTheme(theme.slug)}
|
||||||
|
tooltipText={theme.name}
|
||||||
|
>
|
||||||
|
<box vertical spacing={6}>
|
||||||
|
<Swatches colors={theme.swatches} />
|
||||||
|
<label class="theme-chip-label" label={theme.name} maxWidthChars={16} />
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DynamicChip() {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
class={isDynamic.as((on: boolean) =>
|
||||||
|
on ? "theme-chip theme-chip-dynamic active" : "theme-chip theme-chip-dynamic",
|
||||||
|
)}
|
||||||
|
onClicked={applyDynamic}
|
||||||
|
tooltipText="Recolor from the current wallpaper, without changing it"
|
||||||
|
>
|
||||||
|
<box spacing={8} halign={Gtk.Align.CENTER}>
|
||||||
|
<label class="theme-chip-icon" label={""} />
|
||||||
|
<label class="theme-chip-label" label="Dynamic (from wallpaper)" />
|
||||||
|
</box>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ThemePicker() {
|
||||||
|
const hasThemes = themes.as((list) => list.length > 0)
|
||||||
|
const isEmpty = themes.as((list) => list.length === 0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name={THEMEPICKER_WINDOW}
|
||||||
|
namespace={THEMEPICKER_WINDOW}
|
||||||
|
class="ThemePicker"
|
||||||
|
layer={Astal.Layer.OVERLAY}
|
||||||
|
keymode={Astal.Keymode.ON_DEMAND}
|
||||||
|
exclusivity={Astal.Exclusivity.NORMAL}
|
||||||
|
visible={false}
|
||||||
|
application={app}
|
||||||
|
>
|
||||||
|
<box class="panel theme-picker" vertical spacing={12}>
|
||||||
|
<box class="panel-header" spacing={8}>
|
||||||
|
<label class="panel-title" label="Themes" xalign={0} hexpand halign={Gtk.Align.START} />
|
||||||
|
<button
|
||||||
|
class="panel-icon-btn"
|
||||||
|
tooltipText="Close"
|
||||||
|
onClicked={() => app.toggle_window(THEMEPICKER_WINDOW)}
|
||||||
|
>
|
||||||
|
<label label={""} />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
|
||||||
|
<DynamicChip />
|
||||||
|
|
||||||
|
<scrollable
|
||||||
|
class="theme-scroll"
|
||||||
|
visible={hasThemes}
|
||||||
|
hscroll={Gtk.PolicyType.NEVER}
|
||||||
|
vscroll={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
<box vertical spacing={8}>
|
||||||
|
<For each={rows} id={(row: Theme[]) => row.map((t) => t.slug).join("|")}>
|
||||||
|
{(row: Theme[]) => (
|
||||||
|
<box spacing={8} halign={Gtk.Align.CENTER}>
|
||||||
|
{row.map((theme) => (
|
||||||
|
<ThemeChip theme={theme} />
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</box>
|
||||||
|
</scrollable>
|
||||||
|
|
||||||
|
<box class="wall-empty" visible={isEmpty} vertical valign={Gtk.Align.CENTER}>
|
||||||
|
<label class="wall-empty-icon" label={""} halign={Gtk.Align.CENTER} />
|
||||||
|
<label class="wall-empty-text" label="No local themes found" halign={Gtk.Align.CENTER} />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
)
|
||||||
|
}
|
||||||
+2
-2
@@ -4,8 +4,8 @@ The installer automatically exposes scripts from [`scripts/`](scripts/) as globa
|
|||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `blob_wallpaper [path]` | Sets your background using Omarchy's background system. If used with an image from `~/wallpapers/` or a valid path, it leverages Pywal to generate a full system color palette and dynamically updates the `blob-dynamic` theme, AGS widgets, and Waybar. With no argument, it opens the AGS wallpaper picker. |
|
| `blob_wallpaper [path]` | Sets your background using Omarchy's background system. If used with an image from `~/wallpapers/` or a valid path, it leverages Pywal to generate a full system color palette into the `blob-dynamic` theme and always updates the desktop background — but only switches your active color theme to it if you're already in dynamic mode (see `blob_theme --mode`), so it won't pull you out of a static theme. With no argument, it opens the AGS wallpaper picker. |
|
||||||
| `blob_theme <share-link-or-id>` | Pulls a color palette shared from the wall-styles site and applies it as the `blob-dynamic` theme (e.g. `blob_theme "https://wall-styles.vercel.app/?id=ab12cd34ef"`). |
|
| `blob_theme [name\|--dynamic\|--mode\|--print\|share-link-or-id]` | Manages color themes. With no argument, opens the AGS theme picker. `--dynamic` recolors from the current wallpaper without changing it; `--mode` prints `static` or `dynamic`; `--print` dumps the currently active theme's `colors.toml` to stdout, e.g. `blob_theme --print > themes/my-theme/colors.toml` to save a dynamically-generated palette you like. A `name` applies a local theme (see [`themes/`](themes/README.md)) or, if no local theme matches, falls back to pulling a shared palette from the wall-styles site by link or id (e.g. `blob_theme "https://wall-styles.vercel.app/?id=ab12cd34ef"`). |
|
||||||
| `blob_glass [on\|off\|toggle]` | A quick toggle to enable or disable window transparency on the fly. |
|
| `blob_glass [on\|off\|toggle]` | A quick toggle to enable or disable window transparency on the fly. |
|
||||||
| `blob_boot [path]` | Safely updates your Plymouth boot splash image (defaults to `branding/boot_flash.png`) and rebuilds the `initramfs` (GRUB compatible via `mkinitcpio`). |
|
| `blob_boot [path]` | Safely updates your Plymouth boot splash image (defaults to `branding/boot_flash.png`) and rebuilds the `initramfs` (GRUB compatible via `mkinitcpio`). |
|
||||||
| `blob_wifi` | A streamlined script to connect to the GMU Eduroam Wi-Fi network using `iwd` and `systemd-resolved` (replaces NetworkManager). |
|
| `blob_wifi` | A streamlined script to connect to the GMU Eduroam Wi-Fi network using `iwd` and `systemd-resolved` (replaces NetworkManager). |
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
Name = "blobThemeSelector"
|
||||||
|
NamePretty = "Blob's Theme Selector"
|
||||||
|
Cache = false
|
||||||
|
HideFromProviderlist = true
|
||||||
|
SearchName = true
|
||||||
|
|
||||||
|
local function ShellEscape(s)
|
||||||
|
return "'" .. s:gsub("'", "'\\''") .. "'"
|
||||||
|
end
|
||||||
|
|
||||||
|
function FormatName(slug)
|
||||||
|
local name = slug:gsub("-", " ")
|
||||||
|
name = name:gsub("%S+", function(word)
|
||||||
|
return word:sub(1, 1):upper() .. word:sub(2):lower()
|
||||||
|
end)
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
function GetEntries()
|
||||||
|
local entries = {}
|
||||||
|
local home = os.getenv("HOME")
|
||||||
|
local omarchy_path = os.getenv("OMARCHY_PATH") or ""
|
||||||
|
|
||||||
|
entries[1] = {
|
||||||
|
Text = "Dynamic (from wallpaper)",
|
||||||
|
Value = "dynamic",
|
||||||
|
Actions = {
|
||||||
|
activate = "blob_theme --dynamic",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local dirs = {
|
||||||
|
home .. "/.config/omarchy/themes",
|
||||||
|
omarchy_path .. "/themes",
|
||||||
|
}
|
||||||
|
|
||||||
|
local seen = { ["blob-dynamic"] = true }
|
||||||
|
|
||||||
|
for _, themes_dir in ipairs(dirs) do
|
||||||
|
local handle = io.popen(
|
||||||
|
"find " .. ShellEscape(themes_dir) .. " -mindepth 1 -maxdepth 1 -type d 2>/dev/null | sort"
|
||||||
|
)
|
||||||
|
if handle then
|
||||||
|
for theme_dir in handle:lines() do
|
||||||
|
local slug = theme_dir:match("([^/]+)$")
|
||||||
|
if slug and not seen[slug] then
|
||||||
|
local colors_file = io.open(theme_dir .. "/colors.toml", "r")
|
||||||
|
if colors_file then
|
||||||
|
colors_file:close()
|
||||||
|
seen[slug] = true
|
||||||
|
|
||||||
|
local entry = {
|
||||||
|
Text = FormatName(slug),
|
||||||
|
Value = slug,
|
||||||
|
Actions = {
|
||||||
|
activate = "blob_theme " .. ShellEscape(slug),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
local preview_file = io.open(theme_dir .. "/preview.png", "r")
|
||||||
|
if preview_file then
|
||||||
|
preview_file:close()
|
||||||
|
entry.Preview = theme_dir .. "/preview.png"
|
||||||
|
entry.PreviewType = "file"
|
||||||
|
end
|
||||||
|
|
||||||
|
table.insert(entries, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
handle:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
+54
-2
@@ -69,7 +69,7 @@ backup_and_copy() {
|
|||||||
|
|
||||||
if [ ! -e "$dest" ]; then
|
if [ ! -e "$dest" ]; then
|
||||||
echo "[COPY] $name: New directory (creating)"
|
echo "[COPY] $name: New directory (creating)"
|
||||||
mkdir -p "$(dirname "$dest")"
|
mkdir -p "$dest"
|
||||||
cp -r "$src"/* "$dest"/
|
cp -r "$src"/* "$dest"/
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -95,6 +95,40 @@ backup_and_copy() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Like backup_and_copy, but for destinations that are pure git-tracked
|
||||||
|
# mirrors with no local state worth protecting (wallpapers, themes).
|
||||||
|
# No .bak sibling is created — a stale one would otherwise show up
|
||||||
|
# alongside the real thing (e.g. in the theme picker's directory scan).
|
||||||
|
replace_and_copy() {
|
||||||
|
local src="$1"
|
||||||
|
local dest="$2"
|
||||||
|
local name="$3"
|
||||||
|
|
||||||
|
if [ ! -e "$dest" ]; then
|
||||||
|
echo "[COPY] $name: New directory (creating)"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
cp -r "$src"/* "$dest"/
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local src_hash=$(compute_hash "$src")
|
||||||
|
local dest_hash=$(compute_hash "$dest")
|
||||||
|
|
||||||
|
if [ "$src_hash" = "$dest_hash" ]; then
|
||||||
|
echo "[SKIP] $name: Up to date"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$FORCE" = true ]; then
|
||||||
|
echo "[COPY] $name: Replacing (--force)"
|
||||||
|
rm -rf "$dest"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
cp -r "$src"/* "$dest"/
|
||||||
|
else
|
||||||
|
echo "[SKIP] $name: Has local changes (use --force to overwrite)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
install_dependencies() {
|
install_dependencies() {
|
||||||
echo "=== Checking Dependencies ==="
|
echo "=== Checking Dependencies ==="
|
||||||
local deps_needed=()
|
local deps_needed=()
|
||||||
@@ -146,10 +180,12 @@ check_file "$SCRIPT_DIR/ags/widget/Media.tsx" "$HOME_DIR/.config/ags/widget/Medi
|
|||||||
check_file "$SCRIPT_DIR/ags/widget/Notifications.tsx" "$HOME_DIR/.config/ags/widget/Notifications.tsx" "ags/widget/Notifications.tsx" || check_status=1
|
check_file "$SCRIPT_DIR/ags/widget/Notifications.tsx" "$HOME_DIR/.config/ags/widget/Notifications.tsx" "ags/widget/Notifications.tsx" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/ags/widget/QuickSettings.tsx" "$HOME_DIR/.config/ags/widget/QuickSettings.tsx" "ags/widget/QuickSettings.tsx" || check_status=1
|
check_file "$SCRIPT_DIR/ags/widget/QuickSettings.tsx" "$HOME_DIR/.config/ags/widget/QuickSettings.tsx" "ags/widget/QuickSettings.tsx" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/ags/widget/SysMonitor.tsx" "$HOME_DIR/.config/ags/widget/SysMonitor.tsx" "ags/widget/SysMonitor.tsx" || check_status=1
|
check_file "$SCRIPT_DIR/ags/widget/SysMonitor.tsx" "$HOME_DIR/.config/ags/widget/SysMonitor.tsx" "ags/widget/SysMonitor.tsx" || check_status=1
|
||||||
|
check_file "$SCRIPT_DIR/ags/widget/ThemePicker.tsx" "$HOME_DIR/.config/ags/widget/ThemePicker.tsx" "ags/widget/ThemePicker.tsx" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/ags/widget/WallPicker.tsx" "$HOME_DIR/.config/ags/widget/WallPicker.tsx" "ags/widget/WallPicker.tsx" || check_status=1
|
check_file "$SCRIPT_DIR/ags/widget/WallPicker.tsx" "$HOME_DIR/.config/ags/widget/WallPicker.tsx" "ags/widget/WallPicker.tsx" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/ags/widget/WidgetCard.tsx" "$HOME_DIR/.config/ags/widget/WidgetCard.tsx" "ags/widget/WidgetCard.tsx" || check_status=1
|
check_file "$SCRIPT_DIR/ags/widget/WidgetCard.tsx" "$HOME_DIR/.config/ags/widget/WidgetCard.tsx" "ags/widget/WidgetCard.tsx" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/waybar/style.css" "$HOME_DIR/.config/waybar/style.css" "waybar/style.css" || check_status=1
|
check_file "$SCRIPT_DIR/waybar/style.css" "$HOME_DIR/.config/waybar/style.css" "waybar/style.css" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/elephant/menus/blob_background_selector.lua" "$HOME_DIR/.config/elephant/menus/blob_background_selector.lua" "elephant/menus/blob_background_selector.lua" || check_status=1
|
check_file "$SCRIPT_DIR/elephant/menus/blob_background_selector.lua" "$HOME_DIR/.config/elephant/menus/blob_background_selector.lua" "elephant/menus/blob_background_selector.lua" || check_status=1
|
||||||
|
check_file "$SCRIPT_DIR/elephant/menus/blob_theme_selector.lua" "$HOME_DIR/.config/elephant/menus/blob_theme_selector.lua" "elephant/menus/blob_theme_selector.lua" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/branding/about.txt" "$HOME_DIR/.config/omarchy/branding/about.txt" "branding/about.txt" || check_status=1
|
check_file "$SCRIPT_DIR/branding/about.txt" "$HOME_DIR/.config/omarchy/branding/about.txt" "branding/about.txt" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/branding/screensaver.txt" "$HOME_DIR/.config/omarchy/branding/screensaver.txt" "branding/screensaver.txt" || check_status=1
|
check_file "$SCRIPT_DIR/branding/screensaver.txt" "$HOME_DIR/.config/omarchy/branding/screensaver.txt" "branding/screensaver.txt" || check_status=1
|
||||||
|
|
||||||
@@ -165,6 +201,14 @@ for script in "$SCRIPT_DIR/scripts"/*.sh; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ -d "$SCRIPT_DIR/themes" ]; then
|
||||||
|
for theme_dir in "$SCRIPT_DIR/themes"/*/; do
|
||||||
|
[ -d "$theme_dir" ] || continue
|
||||||
|
theme_name=$(basename "$theme_dir")
|
||||||
|
check_file "$theme_dir/colors.toml" "$HOME_DIR/.config/omarchy/themes/$theme_name/colors.toml" "themes/$theme_name/colors.toml" || check_status=1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [ "$CHECK" = true ]; then
|
if [ "$CHECK" = true ]; then
|
||||||
@@ -191,7 +235,15 @@ backup_and_copy "$SCRIPT_DIR/hypr" "$HOME_DIR/.config/hypr" "Hyprland config"
|
|||||||
backup_and_copy "$SCRIPT_DIR/branding" "$HOME_DIR/.config/omarchy/branding" "Branding files"
|
backup_and_copy "$SCRIPT_DIR/branding" "$HOME_DIR/.config/omarchy/branding" "Branding files"
|
||||||
backup_and_copy "$SCRIPT_DIR/elephant" "$HOME_DIR/.config/elephant" "Elephant configs"
|
backup_and_copy "$SCRIPT_DIR/elephant" "$HOME_DIR/.config/elephant" "Elephant configs"
|
||||||
backup_and_copy "$SCRIPT_DIR/omarchy/hooks" "$HOME_DIR/.config/omarchy/hooks" "Omarchy hooks"
|
backup_and_copy "$SCRIPT_DIR/omarchy/hooks" "$HOME_DIR/.config/omarchy/hooks" "Omarchy hooks"
|
||||||
backup_and_copy "$SCRIPT_DIR/wallpapers" "$HOME_DIR/wallpapers" "Custom wallpapers"
|
replace_and_copy "$SCRIPT_DIR/wallpapers" "$HOME_DIR/wallpapers" "Custom wallpapers"
|
||||||
|
|
||||||
|
if [ -d "$SCRIPT_DIR/themes" ]; then
|
||||||
|
for theme_dir in "$SCRIPT_DIR/themes"/*/; do
|
||||||
|
[ -d "$theme_dir" ] || continue
|
||||||
|
theme_name=$(basename "$theme_dir")
|
||||||
|
replace_and_copy "$theme_dir" "$HOME_DIR/.config/omarchy/themes/$theme_name" "Theme: $theme_name"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
zen_profile=$(find "$HOME_DIR/.config/zen" -maxdepth 1 -type d -name "*.Default (release)*" 2>/dev/null | head -n 1)
|
zen_profile=$(find "$HOME_DIR/.config/zen" -maxdepth 1 -type d -name "*.Default (release)*" 2>/dev/null | head -n 1)
|
||||||
if [ -n "$zen_profile" ]; then
|
if [ -n "$zen_profile" ]; then
|
||||||
|
|||||||
+76
-6
@@ -1,26 +1,93 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Pull a color theme shared from the wall-styles website and apply it.
|
# Manage color themes: local static themes installed under
|
||||||
|
# ~/.config/omarchy/themes/, the pywal-generated dynamic theme
|
||||||
|
# (blob-dynamic), and themes shared from the wall-styles website.
|
||||||
|
|
||||||
THEME_DIR="$HOME/.config/omarchy/themes/blob-dynamic"
|
THEME_DIR="$HOME/.config/omarchy/themes/blob-dynamic"
|
||||||
|
USER_THEMES_DIR="$HOME/.config/omarchy/themes"
|
||||||
|
CURRENT_DIR="$HOME/.config/omarchy/current"
|
||||||
|
|
||||||
# Used only when a bare id is passed instead of a full share link.
|
# Used only when a bare id is passed instead of a full share link.
|
||||||
# Override with: export BLOB_THEME_URL="https://your-deployment.vercel.app"
|
# Override with: export BLOB_THEME_URL="https://your-deployment.vercel.app"
|
||||||
BASE_URL="${BLOB_THEME_URL:-https://wall-styles.vercel.app}"
|
BASE_URL="${BLOB_THEME_URL:-https://wall-styles.vercel.app}"
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
slugify() {
|
||||||
echo "Usage: blob_theme <share-link-or-id>"
|
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'
|
||||||
echo "Example: blob_theme \"https://wall-styles.vercel.app/?id=ab12cd34ef\""
|
}
|
||||||
|
|
||||||
|
current_mode() {
|
||||||
|
local name
|
||||||
|
name=$(cat "$CURRENT_DIR/theme.name" 2>/dev/null)
|
||||||
|
if [ "$name" = "blob-dynamic" ]; then
|
||||||
|
echo "dynamic"
|
||||||
|
else
|
||||||
|
echo "static"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
theme_exists_locally() {
|
||||||
|
local slug
|
||||||
|
slug=$(slugify "$1")
|
||||||
|
[ -d "$USER_THEMES_DIR/$slug" ] || [ -d "$OMARCHY_PATH/themes/$slug" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_static() {
|
||||||
|
local name="$1"
|
||||||
|
# Clean up a lingering gif-animation daemon from a previous dynamic
|
||||||
|
# wallpaper before switching to a static theme's own background.
|
||||||
|
awww kill --all >/dev/null 2>&1
|
||||||
|
omarchy-theme-set "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_dynamic() {
|
||||||
|
if [ ! -f "$THEME_DIR/colors.toml" ]; then
|
||||||
|
echo "No dynamic theme yet. Pick a wallpaper first: blob_wallpaper"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# Skip the background step: this is purely a recolor of whatever
|
||||||
|
# wallpaper is already showing, not a wallpaper change.
|
||||||
|
OMARCHY_THEME_SKIP_BACKGROUND=1 omarchy-theme-set "blob-dynamic"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
"")
|
||||||
|
ags toggle theme-picker
|
||||||
|
;;
|
||||||
|
--menu)
|
||||||
|
omarchy-launch-walker -m menus:blobThemeSelector --width 800 --minheight 400 -p "Select Theme…"
|
||||||
|
;;
|
||||||
|
--mode)
|
||||||
|
current_mode
|
||||||
|
;;
|
||||||
|
--print)
|
||||||
|
CURRENT_COLORS="$CURRENT_DIR/theme/colors.toml"
|
||||||
|
if [ ! -f "$CURRENT_COLORS" ]; then
|
||||||
|
echo "No active theme colors.toml found at $CURRENT_COLORS" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# awk always terminates the last line with a newline, even if the
|
||||||
|
# source file doesn't. Omarchy's template engine reads colors.toml
|
||||||
|
# with a bash `while read` loop, which silently drops a final line
|
||||||
|
# missing its newline (e.g. color15) — some built-in themes are
|
||||||
|
# missing it too, so this guards every theme saved via --print.
|
||||||
|
awk '1' "$CURRENT_COLORS"
|
||||||
|
;;
|
||||||
|
--dynamic)
|
||||||
|
apply_dynamic
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if theme_exists_locally "$1"; then
|
||||||
|
apply_static "$1"
|
||||||
|
else
|
||||||
|
# Not a local theme: treat it as a wall-styles share link or id.
|
||||||
INPUT="$1"
|
INPUT="$1"
|
||||||
|
|
||||||
if [[ "$INPUT" == http*://* ]]; then
|
if [[ "$INPUT" == http*://* ]]; then
|
||||||
HOST=$(echo "$INPUT" | sed -E 's#^(https?://[^/]+).*#\1#')
|
HOST=$(echo "$INPUT" | sed -E 's#^(https?://[^/]+).*#\1#')
|
||||||
ID=$(echo "$INPUT" | grep -oE 'id=[A-Za-z0-9]+' | head -1 | cut -d= -f2)
|
ID=$(echo "$INPUT" | grep -oE 'id=[A-Za-z0-9]+' | head -1 | cut -d= -f2)
|
||||||
if [ -z "$ID" ]; then
|
if [ -z "$ID" ]; then
|
||||||
echo "Error: no theme id found in link '$INPUT'."
|
echo "Error: '$INPUT' is not a local theme, and no share id was found in the link."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
API="$HOST/api/theme?id=$ID&format=toml"
|
API="$HOST/api/theme?id=$ID&format=toml"
|
||||||
@@ -33,7 +100,7 @@ mkdir -p "$THEME_DIR/backgrounds"
|
|||||||
echo "Fetching theme..."
|
echo "Fetching theme..."
|
||||||
TMP=$(mktemp)
|
TMP=$(mktemp)
|
||||||
if ! curl -fsSL "$API" -o "$TMP"; then
|
if ! curl -fsSL "$API" -o "$TMP"; then
|
||||||
echo "Error: could not fetch theme. The link may be expired (themes last one hour)."
|
echo "Error: could not fetch theme, and '$1' is not a local theme name. The link may be expired (themes last one hour)."
|
||||||
rm -f "$TMP"
|
rm -f "$TMP"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -61,3 +128,6 @@ EOF
|
|||||||
omarchy-theme-set "blob-dynamic"
|
omarchy-theme-set "blob-dynamic"
|
||||||
|
|
||||||
echo "Applied shared theme to blob-dynamic."
|
echo "Applied shared theme to blob-dynamic."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
@@ -85,10 +85,19 @@ return {
|
|||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Apply the Blob-Dynamic theme, but skip its own background step: it
|
# Only switch the active color theme to blob-dynamic if you're already
|
||||||
|
# in dynamic mode. The palette above and the background below are kept
|
||||||
|
# up to date regardless, so `blob_theme --dynamic` always reflects the
|
||||||
|
# latest wallpaper — but picking a wallpaper while on a static theme
|
||||||
|
# should change the wallpaper, not silently pull you out of it.
|
||||||
|
#
|
||||||
|
# When it does apply, skip omarchy-theme-set's own background step: it
|
||||||
# launches swaybg asynchronously, which races with the gif handling
|
# launches swaybg asynchronously, which races with the gif handling
|
||||||
# below and can leave a static swaybg frame on top of an animated gif.
|
# below and can leave a static swaybg frame on top of an animated gif.
|
||||||
|
CURRENT_THEME_NAME=$(cat "$HOME/.config/omarchy/current/theme.name" 2>/dev/null)
|
||||||
|
if [ "$CURRENT_THEME_NAME" = "blob-dynamic" ]; then
|
||||||
OMARCHY_THEME_SKIP_BACKGROUND=1 omarchy-theme-set "blob-dynamic"
|
OMARCHY_THEME_SKIP_BACKGROUND=1 omarchy-theme-set "blob-dynamic"
|
||||||
|
fi
|
||||||
|
|
||||||
CURRENT_BACKGROUND_LINK="$HOME/.config/omarchy/current/background"
|
CURRENT_BACKGROUND_LINK="$HOME/.config/omarchy/current/background"
|
||||||
ln -nsf "$THEME_DIR/backgrounds/$(basename "$IMAGE_PATH")" "$CURRENT_BACKGROUND_LINK"
|
ln -nsf "$THEME_DIR/backgrounds/$(basename "$IMAGE_PATH")" "$CURRENT_BACKGROUND_LINK"
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Themes
|
||||||
|
|
||||||
|
Drop-in static color themes. Each subdirectory here becomes a theme
|
||||||
|
selectable from `blob_theme`, the theme picker widget, and the walker
|
||||||
|
menu, once `install.sh` copies it into `~/.config/omarchy/themes/`.
|
||||||
|
|
||||||
|
## Adding a theme
|
||||||
|
|
||||||
|
Create a directory named after the theme (lowercase, hyphens instead
|
||||||
|
of spaces — e.g. `tokyo-night/`) containing a `colors.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
accent = "#7aa2f7"
|
||||||
|
cursor = "#c0caf5"
|
||||||
|
foreground = "#c0caf5"
|
||||||
|
background = "#1a1b26"
|
||||||
|
selection_foreground = "#1a1b26"
|
||||||
|
selection_background = "#7aa2f7"
|
||||||
|
|
||||||
|
color0 = "#1a1b26"
|
||||||
|
color1 = "#f7768e"
|
||||||
|
color2 = "#9ece6a"
|
||||||
|
color3 = "#e0af68"
|
||||||
|
color4 = "#7aa2f7"
|
||||||
|
color5 = "#bb9af7"
|
||||||
|
color6 = "#7dcfff"
|
||||||
|
color7 = "#c0caf5"
|
||||||
|
color8 = "#414868"
|
||||||
|
color9 = "#f7768e"
|
||||||
|
color10 = "#9ece6a"
|
||||||
|
color11 = "#e0af68"
|
||||||
|
color12 = "#7aa2f7"
|
||||||
|
color13 = "#bb9af7"
|
||||||
|
color14 = "#7dcfff"
|
||||||
|
color15 = "#c0caf5"
|
||||||
|
```
|
||||||
|
|
||||||
|
All 22 keys are required — this is the same format Omarchy themes and
|
||||||
|
`blob_wallpaper`'s pywal-generated `blob-dynamic` theme already use, so
|
||||||
|
`omarchy-theme-set` and its app-config templates pick it up with no
|
||||||
|
extra work.
|
||||||
|
|
||||||
|
### Saving a dynamic palette you like
|
||||||
|
|
||||||
|
If a wallpaper's pywal-generated colors are worth keeping permanently,
|
||||||
|
dump the currently active theme's `colors.toml` straight into a new
|
||||||
|
theme folder instead of typing it out by hand:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p themes/my-new-theme
|
||||||
|
blob_theme --print > themes/my-new-theme/colors.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Run `./install.sh` (add `--force` if the theme already exists on disk
|
||||||
|
and you're updating it) to deploy new/changed theme folders.
|
||||||
|
|
||||||
|
Any theme already installed under `~/.config/omarchy/themes/` or
|
||||||
|
bundled with Omarchy itself (`$OMARCHY_PATH/themes/`) shows up
|
||||||
|
automatically too — this directory is just for ones you want tracked
|
||||||
|
in dotfiles.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
accent = "#7babe3"
|
||||||
|
cursor = "#cbd2d9"
|
||||||
|
foreground = "#cbd2d9"
|
||||||
|
background = "#0f1012"
|
||||||
|
selection_foreground = "#0f1012"
|
||||||
|
selection_background = "#7babe3"
|
||||||
|
|
||||||
|
color0 = "#000000"
|
||||||
|
color1 = "#d53838"
|
||||||
|
color2 = "#38d4d5"
|
||||||
|
color3 = "#d58638"
|
||||||
|
color4 = "#d53886"
|
||||||
|
color5 = "#38d586"
|
||||||
|
color6 = "#3886d5"
|
||||||
|
color7 = "#d4d4d4"
|
||||||
|
color8 = "#949494"
|
||||||
|
color9 = "#d53838"
|
||||||
|
color10 = "#38d4d5"
|
||||||
|
color11 = "#d58638"
|
||||||
|
color12 = "#d53886"
|
||||||
|
color13 = "#38d586"
|
||||||
|
color14 = "#3886d5"
|
||||||
|
color15 = "#d4d4d4"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
accent = "#d53838"
|
||||||
|
cursor = "#d4d4d4"
|
||||||
|
foreground = "#d4d4d4"
|
||||||
|
background = "#000000"
|
||||||
|
selection_foreground = "#000000"
|
||||||
|
selection_background = "#d53838"
|
||||||
|
|
||||||
|
color0 = "#000000"
|
||||||
|
color1 = "#d53838"
|
||||||
|
color2 = "#38d4d5"
|
||||||
|
color3 = "#d58638"
|
||||||
|
color4 = "#d53886"
|
||||||
|
color5 = "#38d586"
|
||||||
|
color6 = "#3886d5"
|
||||||
|
color7 = "#d4d4d4"
|
||||||
|
color8 = "#949494"
|
||||||
|
color9 = "#d53838"
|
||||||
|
color10 = "#38d4d5"
|
||||||
|
color11 = "#d58638"
|
||||||
|
color12 = "#d53886"
|
||||||
|
color13 = "#38d586"
|
||||||
|
color14 = "#3886d5"
|
||||||
|
color15 = "#d4d4d4"
|
||||||
Reference in New Issue
Block a user