Compare commits

...
35 Commits
Author SHA1 Message Date
SirBlob 5c7f8911ec Update wallpaper gallery for 9 new wallpapers 2026-08-01 22:30:19 -04:00
SirBlob 4eb3b23d4b Add 9 new wallpapers 2026-08-01 22:28:33 -04:00
SirBlob 45a60030d1 Fix Zen browser colors to follow active theme, not pywal cache
zen/userChrome.css read straight from ~/.cache/wal/colors.css, which
only updates on wallpaper pick and ignores static/shared theme
changes. Now generates zen.css from colors.toml via Omarchy's own
templating, same as waybar/alacritty/etc.
2026-08-01 20:09:39 -04:00
SirBlob 3266666ade Add 5 new wallpapers, update gallery 2026-08-01 15:02:15 -04:00
SirBlob acc96492ee 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
2026-07-31 22:14:51 -04:00
SirBlob cdff530e4b Fix gif wallpapers rendering static instead of animated
omarchy-theme-set launches swaybg asynchronously in the background,
which raced against the gif-detection logic and could leave a static
swaybg frame on top of the animated awww daemon. Skip theme-set's own
background step and own the daemon selection (swaybg vs awww) and
symlink update directly, launch awww-daemon through uwsm-app like
every other layer-shell client here, use awww's own graceful kill
instead of a raw pkill to avoid a stale IPC socket, and log daemon
output plus invocation env to ~/.cache for debugging.

Claude-Session: https://claude.ai/code/session_011kf23kB9NsaczSYazZsWs6
2026-07-31 16:01:34 -04:00
SirBlob bff3c13acc Add 20 wallpapers, rename to lowercase/no-spaces, rebuild gallery
Normalize wallpaper filenames (no capitals, no spaces) and regenerate
the alphabetical gallery split for the new 88-wallpaper total; m-r/r-z
replace n-r/s-z since the split boundaries shifted.

Claude-Session: https://claude.ai/code/session_011kf23kB9NsaczSYazZsWs6
2026-07-31 16:01:25 -04:00
SirBlob c2a05bbe8e Reapply "Add 25 new wallpapers, split gallery into wallpaper-gallery/"
This reverts commit 94518ffa30.
2026-07-31 13:48:24 -04:00
SirBlob 94518ffa30 Revert "Add 25 new wallpapers, split gallery into wallpaper-gallery/"
This reverts commit 07ef82ff73.
2026-07-31 13:46:46 -04:00
SirBlob 07ef82ff73 Add 25 new wallpapers, split gallery into wallpaper-gallery/
Move the wallpaper gallery docs into wallpaper-gallery/ (index +
alphabetical a-d/e-m/n-r/s-z pages) so the growing list stays readable,
and link the new structure from README.

Claude-Session: https://claude.ai/code/session_011kf23kB9NsaczSYazZsWs6
2026-07-31 13:44:31 -04:00
SirBlob 1ca522ddc0 Add mountain-snow-minima and vintage-ascent to wallpaper gallery 2026-07-27 12:32:53 -04:00
SirBlob f32b4ac102 Add mountain-snow-minima and vintage-ascent wallpapers 2026-07-27 12:31:07 -04:00
SirBlob 88f2740361 Extract commands.md from README, link wallpaper/keybinds/commands docs 2026-07-27 12:31:01 -04:00
SirBlob 5ecbcdf55b Stop side panels from stretching to center panel height 2026-07-27 12:26:51 -04:00
SirBlob c3f8738f00 Fix icon badge centering, remove uptime card, fix weather location, 3 tiles per row 2026-07-27 12:24:20 -04:00
SirBlob 9a2e352202 Center icon badges properly and split wallpaper tile row to 2 per row 2026-07-27 12:16:49 -04:00
SirBlob 0dd174d8c7 Move wallpaper button into tile grid, add breathing room to side card icons 2026-07-27 12:13:23 -04:00
SirBlob 0c0c914431 Fix missing icon glyphs on wallpaper, uptime, weather, and Claude Code cards 2026-07-27 12:09:55 -04:00
SirBlob cffe15e1f6 Widen side cards, add icon badges and graphics, add wallpaper button 2026-07-27 12:00:04 -04:00
SirBlob 9b839ffaf1 Read Claude Code usage from local session logs, redesign panel as separate cards 2026-07-27 11:54:11 -04:00
SirBlob c68e8c38cb Rename blob_claude_key to generic blob_key for storing multiple secrets 2026-07-27 11:41:14 -04:00
SirBlob fb57093a75 Add side panels with widget cards and Claude Code usage stats to quick settings 2026-07-27 11:39:54 -04:00
SirBlob 66a8482254 wallpaper update 2026-07-25 14:07:30 -04:00
SirBlob 692555f8e3 Add keybinds.md documenting custom Hyprland bindings 2026-07-16 10:37:28 -04:00
SirBlob 7cba2c0b3f Remove ChatGPT, Grok, X, Typora, and 1Password keybinds 2026-07-16 10:37:26 -04:00
SirBlob 83780c7ff1 Remove the Update.md 2026-07-03 16:24:40 -04:00
SirBlob ea7093e987 Update README for new widgets, blob_theme, and lid handling 2026-07-03 16:18:53 -04:00
SirBlob df72345a5c Ignore ags-dots reference clone 2026-07-03 16:16:33 -04:00
SirBlob f1c3dca364 Add dotfiles notes 2026-07-03 16:16:18 -04:00
SirBlob be14a1a730 Add blob_theme script and update wallpaper script 2026-07-03 16:16:18 -04:00
SirBlob 04f074b71c Update hypr bindings and hypridle config 2026-07-03 16:16:18 -04:00
SirBlob 0cf0bababc Update waybar config and style 2026-07-03 16:16:18 -04:00
SirBlob 1fe518ff2b Add AGS notification, quick settings, and sysmonitor widgets 2026-07-03 16:14:36 -04:00
SirBlob 91d8d5f84b Update install and revert scripts for lid switch config 2026-07-03 16:12:19 -04:00
SirBlob 850cef2372 Configure external monitors and fix lid-close display handling 2026-07-03 16:12:19 -04:00
102 changed files with 2774 additions and 81 deletions
+1
View File
@@ -4,3 +4,4 @@ node_modules
ags/@girs/
ags-dots/
+19 -11
View File
@@ -21,22 +21,21 @@ My current setup is built around these core components:
### Directory Structure
- **`hypr/`**: Hyprland configurations (keybindings, window rules, animations, layout settings, and autostart).
- **`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.
- **`ags/`**: Custom desktop widgets built with TypeScript and GTK.
- **`scripts/`**: Global utility scripts seamlessly exposed as commands by the installer.
- **`wallpapers/`**: A collection of local custom wallpapers for dynamic theming.
- **`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).
- **`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).
- **`branding/`**: Custom ASCII art and system branding assets.
## Custom Commands
## Docs
The installer automatically exposes scripts from the `scripts/` directory as global commands:
- **`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.
- **`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_wifi`**: A streamlined script to connect to the GMU Eduroam Wi-Fi network using `iwd` and `systemd-resolved` (replaces NetworkManager).
- [Wallpaper Gallery](wallpaper-gallery/index.md) — preview of every wallpaper in `wallpapers/`, split alphabetically across [0-9, A-E](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.
- [Commands](commands.md) — custom `blob_*` CLI commands exposed by the installer.
## Installation
@@ -53,3 +52,12 @@ An automated installer script (`install.sh`) is provided to safely apply these c
./install.sh --force
```
The installer also configures the laptop lid switch so the machine stays awake (and the external monitors stay active) when docked or on AC power.
To roll back to the backups the installer created, use `revert.sh`:
```bash
# Restore configs from their .bak versions (prompts first)
./revert.sh
```
+23 -1
View File
@@ -1,10 +1,32 @@
import app from "ags/gtk3/app"
import style from "./style.css"
import Media from "./widget/Media"
import NotificationCenter from "./widget/Notifications"
import QuickSettings from "./widget/QuickSettings"
import SysMonitor from "./widget/SysMonitor"
import ThemePicker from "./widget/ThemePicker"
import WallPicker from "./widget/WallPicker"
const SHOW_DESKTOP_MEDIA = false
function start(name: string, build: () => unknown) {
try {
build()
} catch (error) {
console.error(`Failed to start widget "${name}":`, error)
}
}
app.start({
css: style,
main() {
app.get_monitors().map(Media)
if (SHOW_DESKTOP_MEDIA) {
app.get_monitors().map(Media)
}
start("notification-center", NotificationCenter)
start("quick-settings", QuickSettings)
start("sys-monitor", SysMonitor)
start("theme-picker", ThemePicker)
start("wall-picker", WallPicker)
},
})
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
PROJECTS_DIR="$HOME/.claude/projects"
TODAY=$(date -u '+%Y-%m-%d')
EMPTY='{"available":false,"tokens":0,"messages":0,"cacheHitPercent":0}'
if [ ! -d "$PROJECTS_DIR" ]; then
echo "$EMPTY"
exit 0
fi
FILES=$(find "$PROJECTS_DIR" -name "*.jsonl" -newermt "$TODAY")
if [ -z "$FILES" ]; then
echo '{"available":true,"tokens":0,"messages":0,"cacheHitPercent":0}'
exit 0
fi
jq -s --arg today "$TODAY" '
[.[] | select(.timestamp != null and (.timestamp | startswith($today)) and .message.usage != null) | .message.usage] as $usages
| ([$usages[] | .input_tokens // 0] | add // 0) as $input
| ([$usages[] | .output_tokens // 0] | add // 0) as $output
| ([$usages[] | .cache_creation_input_tokens // 0] | add // 0) as $cacheCreate
| ([$usages[] | .cache_read_input_tokens // 0] | add // 0) as $cacheRead
| ($input + $cacheCreate + $cacheRead) as $totalInput
| {
available: true,
tokens: ($totalInput + $output),
messages: ($usages | length),
cacheHitPercent: (if $totalInput > 0 then (($cacheRead / $totalInput) * 100) else 0 end)
}
' $FILES 2>/dev/null || echo "$EMPTY"
+17
View File
@@ -0,0 +1,17 @@
import app from "ags/gtk3/app"
import { execAsync } from "ags/process"
export function sh(command: string) {
return execAsync(["bash", "-c", command]).catch((error) => {
console.error(error)
return ""
})
}
export function shell(command: string): string[] {
return ["bash", "-c", command]
}
export function toggleWindow(name: string) {
app.toggle_window(name)
}
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
LOCATION="Great+Falls,VA"
icon=$(omarchy-weather-icon 2>/dev/null)
weather=$(curl -fsS --max-time 4 "https://wttr.in/${LOCATION}?format=%l|%t|%w" 2>/dev/null | tr -d '\n')
if [[ -z $weather ]]; then
echo "Weather unavailable"
exit 1
fi
IFS='|' read -r place temperature wind <<< "$weather"
place=${place%%,*}
temperature=${temperature#+}
echo "$icon $place · Temp $temperature · Wind $wind"
+505
View File
@@ -79,3 +79,508 @@
border: none;
box-shadow: none;
}
.NotificationCenter,
.QuickSettings,
.SysMonitor,
.Calendar,
.WallPicker,
.ThemePicker {
background-color: transparent;
}
.panel {
background-color: alpha(@background, 0.92);
border-radius: 12px;
border: 2px solid alpha(@color4, 0.5);
padding: 14px;
font-family: "JetBrainsMono Nerd Font", sans-serif;
color: @foreground;
}
.panel-title {
color: @foreground;
font-size: 15px;
font-weight: bold;
}
.panel-header {
margin-bottom: 2px;
}
.panel-icon-btn {
color: @color5;
font-size: 13px;
padding: 3px 6px;
border-radius: 8px;
transition: all 0.2s;
}
.panel-icon-btn:hover {
background-color: alpha(@color4, 0.25);
color: @color4;
}
.panel-icon-btn.active {
background-color: @accent;
color: @background;
}
.notification-center {
min-width: 360px;
min-height: 420px;
}
.notification-scroll {
min-height: 360px;
}
.notification-item {
background-color: alpha(@color0, 0.6);
border-radius: 10px;
border-left: 3px solid @accent;
padding: 10px 12px;
}
.notification-item-app {
color: @accent;
font-size: 12px;
font-weight: bold;
}
.notification-item-close {
color: @color5;
font-size: 12px;
padding: 0 4px;
transition: all 0.2s;
}
.notification-item-close:hover {
color: @color1;
}
.notification-item-summary {
color: @foreground;
font-size: 13px;
font-weight: bold;
margin-top: 2px;
}
.notification-item-body {
color: @foreground;
font-size: 12px;
opacity: 0.8;
margin-top: 2px;
}
.notification-empty {
opacity: 0.6;
}
.notification-empty-icon {
font-size: 42px;
color: @color4;
margin-bottom: 8px;
}
.notification-empty-text {
font-size: 13px;
color: @foreground;
}
.quick-settings {
min-width: 320px;
}
.qs-tile {
background-color: alpha(@color0, 0.6);
border-radius: 10px;
padding: 8px 4px;
transition: all 0.2s;
}
.qs-tile:hover {
background-color: alpha(@color4, 0.25);
}
.qs-tile.active {
background-color: @accent;
color: @background;
}
.qs-tile-icon {
font-size: 15px;
}
.qs-tile-label {
font-size: 10px;
}
.qs-slider-row {
padding: 2px 4px;
}
.qs-slider-icon {
color: @color5;
font-size: 14px;
min-width: 20px;
}
.qs-slider trough {
min-height: 8px;
border-radius: 8px;
background-color: alpha(@foreground, 0.2);
}
.qs-slider highlight {
border-radius: 8px;
background-color: @accent;
}
.qs-slider slider {
background-color: @foreground;
border-radius: 50%;
min-width: 14px;
min-height: 14px;
margin: -4px;
}
.sys-monitor {
min-width: 320px;
}
.sys-metric-icon {
font-size: 16px;
color: @color5;
min-width: 22px;
}
.sys-metric-name {
font-size: 13px;
font-weight: bold;
}
.sys-metric-detail {
font-size: 12px;
opacity: 0.8;
}
.sys-metric-bar {
min-height: 8px;
}
.sys-metric-bar trough {
min-height: 8px;
border-radius: 8px;
background-color: alpha(@foreground, 0.2);
}
.sys-metric-bar block.filled {
border-radius: 8px;
background-color: @accent;
}
.calendar {
min-width: 280px;
}
.calendar-time {
font-size: 40px;
font-weight: bold;
color: @foreground;
}
.calendar-date {
font-size: 14px;
color: @accent;
}
.calendar-month {
font-size: 14px;
font-weight: bold;
color: @accent;
}
.calendar-days {
font-family: "JetBrainsMono Nerd Font", monospace;
font-size: 14px;
color: @foreground;
}
.wall-picker {
min-width: 680px;
min-height: 460px;
}
.wall-scroll {
min-height: 400px;
}
.wall-thumb {
padding: 0;
border-radius: 10px;
border: 2px solid transparent;
transition: all 0.2s;
}
.wall-thumb:hover {
border: 2px solid @accent;
}
.wall-thumb-image {
min-width: 200px;
min-height: 112px;
border-radius: 8px;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.wall-empty {
opacity: 0.6;
}
.wall-empty-icon {
font-size: 42px;
color: @color4;
margin-bottom: 8px;
}
.wall-empty-text {
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 {
min-width: 240px;
}
.widget-card {
padding: 12px;
}
.widget-card-header {
margin-bottom: 2px;
}
.widget-icon-badge {
background-color: alpha(@accent, 0.18);
border-radius: 8px;
min-width: 32px;
min-height: 32px;
color: @accent;
font-size: 16px;
}
.widget-icon-badge label {
padding: 0;
}
.widget-card-title {
font-size: 13px;
font-weight: bold;
color: @foreground;
}
.widget-card-label {
font-size: 12px;
opacity: 0.8;
}
.widget-card-value {
font-size: 13px;
font-weight: bold;
color: @foreground;
}
.widget-card-empty {
font-size: 12px;
opacity: 0.6;
}
.widget-stat-row {
padding: 2px 0;
}
.widget-stat-icon {
color: @color5;
font-size: 13px;
min-width: 22px;
padding-right: 2px;
}
.widget-level-bar {
min-height: 6px;
}
.widget-level-bar trough {
min-height: 6px;
border-radius: 6px;
background-color: alpha(@foreground, 0.15);
}
.widget-level-bar block.filled {
border-radius: 6px;
background-color: @accent;
}
.control-center {
min-width: 360px;
}
.cc-header {
margin-bottom: 2px;
}
.cc-clock {
font-size: 22px;
font-weight: bold;
color: @foreground;
}
.cc-date {
font-size: 12px;
color: @accent;
}
.cc-media {
background-color: alpha(@color0, 0.6);
border-radius: 10px;
padding: 10px;
}
.cc-media-cover {
min-width: 84px;
min-height: 84px;
border-radius: 8px;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.cc-media-info {
margin-left: 10px;
}
.cc-media-title {
font-size: 13px;
font-weight: bold;
color: @foreground;
}
.cc-media-artist {
font-size: 12px;
opacity: 0.8;
}
.cc-media-seek {
margin: 6px 0;
}
.cc-media-seek trough {
min-height: 4px;
border-radius: 4px;
background-color: alpha(@foreground, 0.2);
}
.cc-media-seek highlight {
border-radius: 4px;
background-color: @accent;
}
.cc-media-seek slider {
background-color: transparent;
min-width: 0;
min-height: 0;
border: none;
box-shadow: none;
}
.cc-media-controls button {
color: @color5;
font-size: 14px;
padding: 0 4px;
transition: all 0.2s;
}
.cc-media-controls button:hover {
color: @accent;
}
.cc-calendar {
margin-top: 2px;
}
.cc-calendar-month {
font-size: 13px;
font-weight: bold;
color: @accent;
}
.cc-calendar-time {
font-size: 13px;
color: @foreground;
}
.cc-calendar-grid {
font-family: "JetBrainsMono Nerd Font", monospace;
font-size: 13px;
color: @foreground;
}
+56
View File
@@ -0,0 +1,56 @@
import { Gtk } from "ags/gtk3"
import { createPoll } from "ags/time"
import { shell } from "../lib/utils"
import { CardHeader, StatRow } from "./WidgetCard"
type ClaudeUsage = {
available: boolean
tokens: number
messages: number
cacheHitPercent: number
}
const usage = createPoll<ClaudeUsage>(
{ available: false, tokens: 0, messages: 0, cacheHitPercent: 0 },
60000,
shell("bash $HOME/.config/ags/lib/claude-usage.sh"),
(stdout) => {
try {
return JSON.parse(stdout.trim())
} catch {
return { available: false, tokens: 0, messages: 0, cacheHitPercent: 0 }
}
},
)
function formatTokens(tokens: number) {
if (tokens >= 1000000) return `${(tokens / 1000000).toFixed(2)}M`
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}K`
return `${tokens}`
}
export function ClaudeUsageCard() {
return (
<box class="panel widget-card" vertical spacing={10}>
<CardHeader icon={""} title="Claude Code" />
<label
class="widget-card-empty"
label="No usage logs found"
xalign={0}
halign={Gtk.Align.START}
visible={usage.as((u) => !u.available)}
/>
<box vertical spacing={8} visible={usage.as((u) => u.available)}>
<StatRow icon={""} label="Tokens today" value={usage.as((u) => formatTokens(u.tokens))} />
<StatRow icon={""} label="Messages today" value={usage.as((u) => `${u.messages}`)} />
<box vertical spacing={4}>
<box spacing={8}>
<label class="widget-card-label" label="Cache hit rate" xalign={0} halign={Gtk.Align.START} hexpand />
<label class="widget-card-value" label={usage.as((u) => `${Math.round(u.cacheHitPercent)}%`)} />
</box>
<levelbar class="widget-level-bar" value={usage.as((u) => u.cacheHitPercent / 100)} />
</box>
</box>
</box>
)
}
+1 -1
View File
@@ -15,7 +15,7 @@ export default function Media(gdkmonitor: Gdk.Monitor) {
status: "Stopped",
length: 0,
position: 0
}, 1000, pollCmd, (stdout) => {
}, 1000, ["bash", "-c", pollCmd], (stdout) => {
const parts = stdout.split("|||");
const title = parts[0]?.trim() || "No Media";
const artist = parts[1]?.trim() || "";
+142
View File
@@ -0,0 +1,142 @@
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 NOTIFICATION_WINDOW = "notification-center"
type Notification = {
id: number
appName: string
summary: string
body: string
}
function parseNotifications(stdout: string): Notification[] {
try {
const parsed = JSON.parse(stdout)
const group = parsed?.data?.[0] ?? []
return group.map((item: any) => ({
id: item.id?.value ?? 0,
appName: item["app-name"]?.value ?? "",
summary: item.summary?.value ?? "",
body: item.body?.value ?? "",
}))
} catch {
return []
}
}
const notifications = createPoll<Notification[]>(
[],
1000,
shell("makoctl list -j 2>/dev/null || echo '{}'"),
(stdout) => parseNotifications(stdout),
)
const doNotDisturb = createPoll(
false,
1000,
shell("makoctl mode 2>/dev/null"),
(stdout) => stdout.split("\n").includes("do-not-disturb"),
)
const dismiss = (id: number) => sh(`makoctl dismiss -n ${id}`)
const clearAll = () => sh("makoctl dismiss -a")
const toggleDoNotDisturb = () => sh("makoctl mode -t do-not-disturb")
function NotificationItem({ item }: { item: Notification }) {
return (
<box class="notification-item" vertical>
<box class="notification-item-header">
<label
class="notification-item-app"
label={item.appName || "Notification"}
xalign={0}
halign={Gtk.Align.START}
hexpand
/>
<button
class="notification-item-close"
halign={Gtk.Align.END}
onClicked={() => dismiss(item.id)}
>
<label label={""} />
</button>
</box>
<label
class="notification-item-summary"
label={item.summary}
xalign={0}
halign={Gtk.Align.START}
wrap
maxWidthChars={34}
/>
<label
class="notification-item-body"
visible={item.body.length > 0}
label={item.body}
xalign={0}
halign={Gtk.Align.START}
wrap
maxWidthChars={34}
/>
</box>
)
}
export default function NotificationCenter() {
const { TOP, RIGHT } = Astal.WindowAnchor
const hasNotifications = notifications.as((list) => list.length > 0)
const isEmpty = notifications.as((list) => list.length === 0)
return (
<window
name={NOTIFICATION_WINDOW}
namespace={NOTIFICATION_WINDOW}
class="NotificationCenter"
anchor={TOP | RIGHT}
margin={10}
layer={Astal.Layer.OVERLAY}
exclusivity={Astal.Exclusivity.NORMAL}
visible={false}
application={app}
>
<box class="panel notification-center" vertical spacing={10}>
<box class="panel-header" spacing={8}>
<label class="panel-title" label="Notifications" xalign={0} hexpand halign={Gtk.Align.START} />
<button
class={doNotDisturb.as((on) => (on ? "panel-icon-btn active" : "panel-icon-btn"))}
tooltipText="Do not disturb"
onClicked={toggleDoNotDisturb}
>
<label label={doNotDisturb.as((on) => (on ? "" : ""))} />
</button>
<button class="panel-icon-btn" tooltipText="Clear all" onClicked={clearAll}>
<label label={""} />
</button>
</box>
<scrollable
class="notification-scroll"
visible={hasNotifications}
vexpand
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical spacing={8}>
<For each={notifications} id={(item: Notification) => item.id}>
{(item: Notification) => <NotificationItem item={item} />}
</For>
</box>
</scrollable>
<box class="notification-empty" visible={isEmpty} vertical valign={Gtk.Align.CENTER} vexpand>
<label class="notification-empty-icon" label={""} halign={Gtk.Align.CENTER} />
<label class="notification-empty-text" label="You're all caught up" halign={Gtk.Align.CENTER} />
</box>
</box>
</window>
)
}
+415
View File
@@ -0,0 +1,415 @@
import app from "ags/gtk3/app"
import { Astal, Gtk } from "ags/gtk3"
import { createState } from "ags"
import { createPoll, interval } from "ags/time"
import { sh, shell } from "../lib/utils"
import { ClaudeUsageCard } from "./ClaudeUsage"
import { CardHeader, StatRow } from "./WidgetCard"
export const QUICKSETTINGS_WINDOW = "quick-settings"
const closePanel = () => app.toggle_window(QUICKSETTINGS_WINDOW)
function pollBool(command: string, intervalMs: number) {
const [value, setValue] = createState(false)
const refresh = () => sh(command).then((out) => setValue(out.trim() === "on"))
refresh()
interval(intervalMs, refresh)
return [value, setValue] as const
}
const volume = createPoll(
0,
1000,
shell("pamixer --get-volume 2>/dev/null || echo 0"),
(stdout) => Number(stdout.trim()) || 0,
)
const [muted, setMuted] = pollBool(
"pamixer --get-mute 2>/dev/null | grep -qx true && echo on || echo off",
1000,
)
const brightness = createPoll(
0,
2000,
shell("brightnessctl -m 2>/dev/null | cut -d, -f4 | tr -d '%' || echo 0"),
(stdout) => Number(stdout.trim()) || 0,
)
const [bluetoothOn] = pollBool(
"bluetoothctl show 2>/dev/null | grep -q 'Powered: yes' && echo on || echo off",
2000,
)
const [doNotDisturb, setDoNotDisturb] = pollBool(
"makoctl mode 2>/dev/null | grep -qx do-not-disturb && echo on || echo off",
2000,
)
const [nightLight, setNightLight] = pollBool(
"pgrep -x hyprsunset >/dev/null && echo on || echo off",
2000,
)
const clock = createPoll("", 1000, shell("date '+%H:%M'"), (stdout) => stdout.trim())
const today = createPoll("", 10000, shell("date '+%A, %B %-d'"), (stdout) => stdout.trim())
type Media = {
title: string
artist: string
artUrl: string
status: string
length: number
position: number
}
const mediaCommand =
"playerctl metadata -f '{{title}}|||{{artist}}|||{{mpris:artUrl}}|||{{status}}|||{{mpris:length}}|||{{position}}' " +
"2>/dev/null || echo 'No Media||||||Stopped|||0|||0'"
const media = createPoll<Media>(
{ title: "No Media", artist: "", artUrl: "", status: "Stopped", length: 0, position: 0 },
1000,
shell(mediaCommand),
(stdout) => {
const parts = stdout.split("|||")
return {
title: parts[0]?.trim() || "No Media",
artist: parts[1]?.trim() || "",
artUrl: (parts[2]?.trim() || "").replace(/^file:\/\//, ""),
status: parts[3]?.trim() || "Stopped",
length: Number(parts[4]) || 0,
position: Number(parts[5]) || 0,
}
},
)
function Header() {
return (
<box class="cc-header" spacing={8}>
<box class="cc-datetime" vertical hexpand halign={Gtk.Align.START}>
<label class="cc-clock" label={clock} xalign={0} halign={Gtk.Align.START} />
<label class="cc-date" label={today} xalign={0} halign={Gtk.Align.START} />
</box>
<button class="panel-icon-btn" tooltipText="Lock" onClicked={() => { closePanel(); sh("omarchy-system-lock") }}>
<label label={""} />
</button>
<button class="panel-icon-btn" tooltipText="Log out" onClicked={() => { closePanel(); sh("omarchy-system-logout") }}>
<label label={""} />
</button>
<button class="panel-icon-btn" tooltipText="Close" onClicked={closePanel}>
<label label={"\uf00d"} />
</button>
</box>
)
}
function Tile({
icon,
label,
active,
onClicked,
}: {
icon: string
label: string
active?: any
onClicked: () => void
}) {
return (
<button
class={active ? active.as((on: boolean) => (on ? "qs-tile active" : "qs-tile")) : "qs-tile"}
onClicked={onClicked}
hexpand
>
<box vertical spacing={4} halign={Gtk.Align.CENTER}>
<label class="qs-tile-icon" label={icon} />
<label class="qs-tile-label" label={label} />
</box>
</button>
)
}
function Toggles() {
return (
<box vertical spacing={8}>
<box class="qs-tiles" spacing={8} homogeneous>
<Tile icon={""} label="Wi-Fi" onClicked={() => { closePanel(); sh("omarchy-launch-wifi") }} />
<Tile
icon={""}
label="Bluetooth"
active={bluetoothOn}
onClicked={() => {
closePanel()
sh("omarchy-launch-bluetooth")
}}
/>
<Tile
icon={""}
label="Wallpaper"
onClicked={() => {
closePanel()
sh("blob_wallpaper")
}}
/>
</box>
<box class="qs-tiles" spacing={8} homogeneous>
<Tile
icon={""}
label="Silence"
active={doNotDisturb}
onClicked={() => {
setDoNotDisturb(!doNotDisturb.get())
sh("makoctl mode -t do-not-disturb")
}}
/>
<Tile
icon={""}
label="Night Light"
active={nightLight}
onClicked={() => {
setNightLight(!nightLight.get())
sh("omarchy-toggle-nightlight")
}}
/>
<Tile icon={""} label="Record" onClicked={() => { closePanel(); sh("omarchy-capture-screenrecording") }} />
</box>
<box class="qs-tiles" spacing={8} homogeneous>
<Tile icon={""} label="Pick Color" onClicked={() => { closePanel(); sh("hyprpicker -a") }} />
<Tile
icon={""}
label="Theme"
onClicked={() => {
closePanel()
sh("blob_theme")
}}
/>
</box>
</box>
)
}
function VolumeSlider() {
return (
<box class="qs-slider-row" spacing={10}>
<button
class="qs-slider-icon"
onClicked={() => {
setMuted(!muted.get())
sh("pamixer -t")
}}
tooltipText="Toggle mute"
>
<label label={muted.as((m) => (m ? "" : ""))} />
</button>
<slider
class="qs-slider"
hexpand
min={0}
max={100}
step={1}
value={volume}
$={(self) => {
self.connect("value-changed", () => {
if (self.dragging) {
sh(`pamixer --set-volume ${Math.round(self.value)}`)
}
})
}}
/>
</box>
)
}
function BrightnessSlider() {
return (
<box class="qs-slider-row" spacing={10}>
<label class="qs-slider-icon" label={""} />
<slider
class="qs-slider"
hexpand
min={1}
max={100}
step={1}
value={brightness}
$={(self) => {
self.connect("value-changed", () => {
if (self.dragging) {
sh(`brightnessctl set ${Math.round(self.value)}%`)
}
})
}}
/>
</box>
)
}
function MediaPlayer() {
return (
<box class="cc-media" spacing={12} visible={media.as((m) => m.title !== "No Media")}>
<box
class="cc-media-cover"
css={media.as((m) =>
m.artUrl
? `background-image: url('${m.artUrl}');`
: "background-color: alpha(@color0, 0.5);",
)}
/>
<box class="cc-media-info" vertical valign={Gtk.Align.CENTER} hexpand>
<label
class="cc-media-title"
label={media.as((m) => m.title)}
xalign={0}
halign={Gtk.Align.START}
truncate
maxWidthChars={22}
/>
<label
class="cc-media-artist"
label={media.as((m) => m.artist)}
xalign={0}
halign={Gtk.Align.START}
truncate
maxWidthChars={26}
/>
<slider
class="cc-media-seek"
hexpand
min={0}
max={1}
step={0.01}
value={media.as((m) => {
const seconds = m.length / 1000000
return seconds > 0 ? Math.min(m.position / seconds, 1) : 0
})}
$={(self) => {
self.connect("value-changed", () => {
if (self.dragging) {
const seconds = media.get().length / 1000000
sh(`playerctl position ${Math.round(self.value * seconds)}`)
}
})
}}
/>
<box class="cc-media-controls" spacing={14} halign={Gtk.Align.CENTER}>
<button onClicked={() => sh("playerctl previous")}>
<label label={""} />
</button>
<button onClicked={() => sh("playerctl play-pause")}>
<label label={media.as((m) => (m.status === "Playing" ? "" : ""))} />
</button>
<button onClicked={() => sh("playerctl next")}>
<label label={""} />
</button>
</box>
</box>
</box>
)
}
const monthName = createPoll("", 60000, shell("date '+%B %Y'"), (stdout) => stdout.trim())
const monthGrid = createPoll(
"",
60000,
shell("cal | sed '1d'"),
(stdout) => stdout.replace(/\s+$/, ""),
)
function CalendarSection() {
return (
<box class="cc-calendar" vertical spacing={6}>
<box class="cc-calendar-head" spacing={8}>
<label class="cc-calendar-month" label={monthName} xalign={0} hexpand halign={Gtk.Align.START} />
</box>
<label class="cc-calendar-grid" label={monthGrid} halign={Gtk.Align.CENTER} />
</box>
)
}
type Weather = { ok: boolean; icon: string; place: string; temp: string; wind: string }
function parseWeather(raw: string): Weather {
const parts = raw.split(" · ")
if (parts.length < 3) return { ok: false, icon: "", place: raw, temp: "", wind: "" }
const match = parts[0].match(/^(\S+)\s*(.*)$/)
return {
ok: true,
icon: match ? match[1] : "",
place: match ? match[2].trim() : parts[0].trim(),
temp: parts[1].replace(/^Temp\s*/, ""),
wind: parts[2].replace(/^Wind\s*/, ""),
}
}
const weather = createPoll<Weather>(
{ ok: false, icon: "", place: "Loading...", temp: "", wind: "" },
600000,
shell("bash $HOME/.config/ags/lib/weather.sh"),
(stdout) => parseWeather(stdout.trim()),
)
function WeatherCard() {
return (
<box class="panel widget-card" vertical spacing={10}>
<CardHeader icon={weather.as((w) => w.icon)} title={weather.as((w) => w.place)} />
<label
class="widget-card-empty"
label="Weather unavailable"
xalign={0}
halign={Gtk.Align.START}
visible={weather.as((w) => !w.ok)}
/>
<box vertical spacing={8} visible={weather.as((w) => w.ok)}>
<StatRow icon={""} label="Temperature" value={weather.as((w) => w.temp)} />
<StatRow icon={""} label="Wind" value={weather.as((w) => w.wind)} />
</box>
</box>
)
}
function LeftPanel() {
return (
<box class="qs-side-panel" vertical spacing={12} valign={Gtk.Align.START}>
<WeatherCard />
</box>
)
}
function RightPanel() {
return (
<box class="qs-side-panel" vertical spacing={12} valign={Gtk.Align.START}>
<ClaudeUsageCard />
</box>
)
}
export default function QuickSettings() {
const { TOP } = Astal.WindowAnchor
return (
<window
name={QUICKSETTINGS_WINDOW}
namespace={QUICKSETTINGS_WINDOW}
class="QuickSettings"
anchor={TOP}
margin={10}
layer={Astal.Layer.OVERLAY}
exclusivity={Astal.Exclusivity.NORMAL}
visible={false}
application={app}
>
<box spacing={12}>
<LeftPanel />
<box class="panel quick-settings control-center" vertical spacing={12}>
<Header />
<CalendarSection />
<Toggles />
<VolumeSlider />
<BrightnessSlider />
<MediaPlayer />
</box>
<RightPanel />
</box>
</window>
)
}
+137
View File
@@ -0,0 +1,137 @@
import app from "ags/gtk3/app"
import { Astal, Gtk } from "ags/gtk3"
import { createPoll } from "ags/time"
import { shell } from "../lib/utils"
export const SYSMONITOR_WINDOW = "sys-monitor"
let previousIdle = 0
let previousTotal = 0
const cpuUsage = createPoll(
0,
2000,
shell("grep '^cpu ' /proc/stat"),
(stdout) => {
const values = stdout.trim().split(/\s+/).slice(1).map(Number)
const idle = (values[3] || 0) + (values[4] || 0)
const total = values.reduce((sum, value) => sum + value, 0)
const idleDelta = idle - previousIdle
const totalDelta = total - previousTotal
previousIdle = idle
previousTotal = total
if (totalDelta <= 0) return 0
return Math.round((1 - idleDelta / totalDelta) * 100)
},
)
type Usage = { percent: number; used: string; total: string }
const memory = createPoll<Usage>(
{ percent: 0, used: "0", total: "0" },
2000,
shell("free -m | awk '/^Mem:/ {print $3\" \"$2}'"),
(stdout) => {
const [used, total] = stdout.trim().split(/\s+/).map(Number)
if (!total) return { percent: 0, used: "0", total: "0" }
return {
percent: Math.round((used / total) * 100),
used: `${(used / 1024).toFixed(1)}G`,
total: `${(total / 1024).toFixed(1)}G`,
}
},
)
const disk = createPoll<Usage>(
{ percent: 0, used: "0", total: "0" },
30000,
shell("df -h --output=pcent,used,size / | tail -1"),
(stdout) => {
const [percent, used, total] = stdout.trim().split(/\s+/)
return {
percent: Number(percent.replace("%", "")) || 0,
used: used || "0",
total: total || "0",
}
},
)
const temperature = createPoll(
0,
2000,
shell("cat /sys/class/thermal/thermal_zone0/temp 2>/dev/null || echo 0"),
(stdout) => Math.round(Number(stdout.trim()) / 1000) || 0,
)
function Metric({
icon,
name,
percent,
detail,
}: {
icon: string
name: string
percent: any
detail: any
}) {
return (
<box class="sys-metric" vertical spacing={4}>
<box spacing={8}>
<label class="sys-metric-icon" label={icon} />
<label class="sys-metric-name" label={name} xalign={0} halign={Gtk.Align.START} hexpand />
<label class="sys-metric-detail" label={detail} halign={Gtk.Align.END} />
</box>
<levelbar
class="sys-metric-bar"
value={percent.as((value: number) => value / 100)}
/>
</box>
)
}
export default function SysMonitor() {
const { TOP, RIGHT } = Astal.WindowAnchor
return (
<window
name={SYSMONITOR_WINDOW}
namespace={SYSMONITOR_WINDOW}
class="SysMonitor"
anchor={TOP | RIGHT}
margin={10}
layer={Astal.Layer.OVERLAY}
exclusivity={Astal.Exclusivity.NORMAL}
visible={false}
application={app}
>
<box class="panel sys-monitor" vertical spacing={12}>
<label class="panel-title" label="System Monitor" xalign={0} halign={Gtk.Align.START} />
<Metric
icon={"\udb80\udf5b"}
name="CPU"
percent={cpuUsage}
detail={cpuUsage.as((value) => `${value}%`)}
/>
<Metric
icon={"\udb81\ude1a"}
name="Memory"
percent={memory.as((value) => value.percent)}
detail={memory.as((value) => `${value.used} / ${value.total}`)}
/>
<Metric
icon={"\udb80\udeca"}
name="Disk"
percent={disk.as((value) => value.percent)}
detail={disk.as((value) => `${value.used} / ${value.total}`)}
/>
<Metric
icon={"\uf2c9"}
name="Temperature"
percent={temperature}
detail={temperature.as((value) => `${value}°C`)}
/>
</box>
</window>
)
}
+164
View File
@@ -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>
)
}
+106
View File
@@ -0,0 +1,106 @@
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 WALLPICKER_WINDOW = "wall-picker"
const COLUMNS = 3
type Wallpaper = { name: string; path: string }
const findCommand =
"find -L \"$HOME/wallpapers\" -maxdepth 1 -type f " +
"\\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' -o -iname '*.gif' \\) " +
"2>/dev/null | sort"
const wallpapers = createPoll<Wallpaper[]>(
[],
5000,
shell(findCommand),
(stdout) =>
stdout
.split("\n")
.filter((line) => line.length > 0)
.map((path) => ({ path, name: path.split("/").pop() ?? path })),
)
const rows = wallpapers.as((list) => {
const chunks: Wallpaper[][] = []
for (let index = 0; index < list.length; index += COLUMNS) {
chunks.push(list.slice(index, index + COLUMNS))
}
return chunks
})
const setWallpaper = (path: string) => {
app.toggle_window(WALLPICKER_WINDOW)
sh(`"$HOME/scripts/blob_wallpaper.sh" "${path}"`)
}
function Thumbnail({ wall }: { wall: Wallpaper }) {
return (
<button class="wall-thumb" onClicked={() => setWallpaper(wall.path)} tooltipText={wall.name}>
<box
class="wall-thumb-image"
css={`background-image: url('${wall.path}');`}
/>
</button>
)
}
export default function WallPicker() {
const hasWalls = wallpapers.as((list) => list.length > 0)
const isEmpty = wallpapers.as((list) => list.length === 0)
return (
<window
name={WALLPICKER_WINDOW}
namespace={WALLPICKER_WINDOW}
class="WallPicker"
layer={Astal.Layer.OVERLAY}
keymode={Astal.Keymode.ON_DEMAND}
exclusivity={Astal.Exclusivity.NORMAL}
visible={false}
application={app}
>
<box class="panel wall-picker" vertical spacing={12}>
<box class="panel-header" spacing={8}>
<label class="panel-title" label="Wallpapers" xalign={0} hexpand halign={Gtk.Align.START} />
<button
class="panel-icon-btn"
tooltipText="Close"
onClicked={() => app.toggle_window(WALLPICKER_WINDOW)}
>
<label label={""} />
</button>
</box>
<scrollable
class="wall-scroll"
visible={hasWalls}
hscroll={Gtk.PolicyType.NEVER}
vscroll={Gtk.PolicyType.AUTOMATIC}
>
<box vertical spacing={8}>
<For each={rows} id={(row: Wallpaper[]) => row.map((wall) => wall.name).join("|")}>
{(row: Wallpaper[]) => (
<box spacing={8} halign={Gtk.Align.CENTER}>
{row.map((wall) => (
<Thumbnail wall={wall} />
))}
</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 wallpapers in ~/wallpapers" halign={Gtk.Align.CENTER} />
</box>
</box>
</window>
)
}
+22
View File
@@ -0,0 +1,22 @@
import { Gtk } from "ags/gtk3"
export function CardHeader({ icon, title }: { icon: any; title: any }) {
return (
<box class="widget-card-header" spacing={10}>
<box class="widget-icon-badge">
<label label={icon} hexpand vexpand halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} />
</box>
<label class="widget-card-title" label={title} xalign={0} halign={Gtk.Align.START} hexpand />
</box>
)
}
export function StatRow({ icon, label, value }: { icon: string; label: string; value: any }) {
return (
<box class="widget-stat-row" spacing={8}>
<label class="widget-stat-icon" label={icon} />
<label class="widget-card-label" label={label} xalign={0} halign={Gtk.Align.START} hexpand />
<label class="widget-card-value" label={value} />
</box>
)
}
+12
View File
@@ -0,0 +1,12 @@
# Custom Commands
The installer automatically exposes scripts from [`scripts/`](scripts/) as global commands.
| 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 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 [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_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_key <set\|show\|clear\|list> [NAME] [VALUE]` | Stores secrets/env values for widgets and services, e.g. `blob_key set SOME_TOKEN value`. |
+76
View File
@@ -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
+1 -6
View File
@@ -11,22 +11,17 @@ bindd = SUPER SHIFT, N, Editor, exec, omarchy-launch-editor
bindd = SUPER SHIFT, D, Docker, exec, omarchy-launch-tui lazydocker
bindd = SUPER SHIFT, G, Signal, exec, omarchy-launch-or-focus ^signal$ "uwsm-app -- signal-desktop"
bindd = SUPER SHIFT, O, Obsidian, exec, omarchy-launch-or-focus ^obsidian$ "uwsm-app -- obsidian -disable-gpu --enable-wayland-ime"
bindd = SUPER SHIFT, W, Typora, exec, uwsm-app -- typora --enable-wayland-ime
bindd = SUPER SHIFT, SLASH, Passwords, exec, uwsm-app -- 1password
# If your web app url contains #, type it as ## to prevent hyprland treating it as a comment
bindd = SUPER SHIFT, A, ChatGPT, exec, omarchy-launch-webapp "https://chatgpt.com"
bindd = SUPER SHIFT ALT, A, Grok, exec, omarchy-launch-webapp "https://grok.com"
bindd = SUPER SHIFT, C, Calendar, exec, omarchy-launch-webapp "https://app.hey.com/calendar/weeks/"
bindd = SUPER SHIFT, E, Email, exec, omarchy-launch-webapp "https://app.hey.com"
bindd = SUPER SHIFT, Y, YouTube, exec, omarchy-launch-webapp "https://youtube.com/"
bindd = SUPER SHIFT ALT, G, WhatsApp, exec, omarchy-launch-or-focus-webapp WhatsApp "https://web.whatsapp.com/"
bindd = SUPER SHIFT CTRL, G, Google Messages, exec, omarchy-launch-or-focus-webapp "Google Messages" "https://messages.google.com/web/conversations"
bindd = SUPER SHIFT, P, Google Photos, exec, omarchy-launch-or-focus-webapp "Google Photos" "https://photos.google.com/"
bindd = SUPER SHIFT, X, X, exec, omarchy-launch-webapp "https://x.com/"
bindd = SUPER SHIFT ALT, X, X Post, exec, omarchy-launch-webapp "https://x.com/compose/post"
# Add extra bindings
bindd = SUPER ALT, W, Wallpaper picker, exec, ags toggle wall-picker
# bind = SUPER SHIFT, R, exec, alacritty -e ssh your-server
# Overwrite existing bindings, like putting Omarchy Menu on Super + Space
+5 -5
View File
@@ -1,28 +1,28 @@
general {
lock_cmd = omarchy-lock-screen # lock screen and 1password
lock_cmd = omarchy-system-lock # lock screen and 1password
before_sleep_cmd = loginctl lock-session # lock before suspend.
after_sleep_cmd = sleep 1 && hyprctl dispatch dpms on # delay for PAM readiness, then turn on display.
inhibit_sleep = 3 # wait until screen is locked
}
listener {
timeout = 150 # 2.5min
timeout = 300 # 5min
on-timeout = pidof hyprlock || omarchy-launch-screensaver # start screensaver (if we haven't locked already)
}
listener {
timeout = 151 # 5min
timeout = 301 # 5min
on-timeout = loginctl lock-session # lock screen when timeout has passed
}
listener {
timeout = 330 # 5.5min
timeout = 480 # 8min
on-timeout = brightnessctl -sd '*::kbd_backlight' set 0 # save state and turn off keyboard backlight
on-resume = brightnessctl -rd '*::kbd_backlight' # restore keyboard backlight
}
listener {
timeout = 330 # 5.5min
timeout = 480 # 8min
on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed
on-resume = hyprctl dispatch dpms on && brightnessctl -r # screen on when activity is detected
}
+5
View File
@@ -19,5 +19,10 @@ source = ~/.config/hypr/bindings.conf
source = ~/.config/hypr/looknfeel.conf
source = ~/.config/hypr/autostart.conf
# Toggle config flags dynamically (e.g. the lid-close "disable internal display"
# flag written by omarchy-hyprland-monitor-internal). Without this the lid
# handler's disable never applies and the laptop workspace stays stranded.
source = ~/.local/state/omarchy/toggles/hypr/*.conf
# Add any other personal Hyprland configuration below
# windowrule = workspace 5, match:class qemu
+37
View File
@@ -0,0 +1,37 @@
# Custom Keybinds
Custom bindings on top of Omarchy's defaults, defined in `bindings.conf`.
## Apps
| Keybind | Action |
| --- | --- |
| `Super+Enter` | Terminal |
| `Super+Alt+Enter` | Terminal with tmux (attach or new "Work" session) |
| `Super+Shift+Enter` | Browser |
| `Super+Shift+F` | File manager |
| `Super+Alt+Shift+F` | File manager (opened at terminal's cwd) |
| `Super+Shift+B` | Browser |
| `Super+Shift+Alt+B` | Browser (private) |
| `Super+Shift+M` | Spotify |
| `Super+Shift+N` | Editor |
| `Super+Shift+D` | Docker (lazydocker) |
| `Super+Shift+G` | Signal |
| `Super+Shift+O` | Obsidian |
## Web Apps
| Keybind | Action |
| --- | --- |
| `Super+Shift+C` | Calendar (Hey) |
| `Super+Shift+E` | Email (Hey) |
| `Super+Shift+Y` | YouTube |
| `Super+Shift+Alt+G` | WhatsApp |
| `Super+Shift+Ctrl+G` | Google Messages |
| `Super+Shift+P` | Google Photos |
## Extras
| Keybind | Action |
| --- | --- |
| `Super+Alt+W` | Wallpaper picker |
+19 -15
View File
@@ -2,22 +2,26 @@
# List current monitors and resolutions possible: hyprctl monitors
# Format: monitor = [port], resolution, position, scale
# Optimized for retina-class 2x displays, like 13" 2.8K, 27" 5K, 32" 6K.
env = GDK_SCALE,2
monitor=,preferred,auto,auto,vrr,1
# --- Dual external monitor setup (laptop lid closed) ---
# Layout, left -> right: [ 1: C24 / DP-1 ] [ 2: F24 / HDMI-A-1 ]
# eDP-1 sits to the right of the externals and Hyprland automatically
# disables it when the lid is closed (since an external is connected).
# Good compromise for 27" or 32" 4K monitors (but fractional!)
# env = GDK_SCALE,1.75
# monitor=,preferred,auto,1.6
# Monitor 1 - C24 curved, DisplayPort via DP->HDMI adapter.
# NOTE: the DP2HDMI adapter caps this panel at 1920x1080@60 (no 75Hz mode).
monitor = DP-1, 1920x1080@60, 0x0, 1
# Straight 1x setup for low-resolution displays like 1080p or 1440p
# Or for ultrawide monitors like 34" 3440x1440 or 49" 5120x1440
# env = GDK_SCALE,1
# monitor=,preferred,auto,1
# Monitor 2 - Sceptre F24, HDMI, native 75Hz.
monitor = HDMI-A-1, 1920x1080@75, 1920x0, 1
# Portrait/rotated secondary monitor (transform: 1 = 90°, 3 = 270°)
# monitor = DP-2, preferred, auto, 1, transform, 1
# Laptop panel - only mode is 1920x1200@60. Parked right of the externals;
# auto-off on lid close so no windows get stranded on a closed screen.
monitor = eDP-1, 1920x1200@60, 3840x0, 1.5
# Example for Framework 13 w/ 6K XDR Apple display
# monitor = DP-5, 6016x3384@60, auto, 2
# monitor = eDP-1, 2880x1920@120, auto, 2
# Catch-all fallback for any other/unknown display.
monitor = , preferred, auto, 1
# GDK_SCALE forced integer scaling is intentionally disabled: the external
# monitors run at 1x, so a global 2x would blow up GTK apps on them. The
# per-monitor scale factors above are enough.
# env = GDK_SCALE,2
+121 -2
View File
@@ -69,7 +69,7 @@ backup_and_copy() {
if [ ! -e "$dest" ]; then
echo "[COPY] $name: New directory (creating)"
mkdir -p "$(dirname "$dest")"
mkdir -p "$dest"
cp -r "$src"/* "$dest"/
return
fi
@@ -95,6 +95,40 @@ backup_and_copy() {
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() {
echo "=== Checking Dependencies ==="
local deps_needed=()
@@ -130,14 +164,29 @@ check_status=0
check_file "$SCRIPT_DIR/waybar/config.jsonc" "$HOME_DIR/.config/waybar/config.jsonc" "waybar/config.jsonc" || check_status=1
check_file "$SCRIPT_DIR/hypr/hyprland.conf" "$HOME_DIR/.config/hypr/hyprland.conf" "hypr/hyprland.conf" || check_status=1
check_file "$SCRIPT_DIR/hypr/monitors.conf" "$HOME_DIR/.config/hypr/monitors.conf" "hypr/monitors.conf" || check_status=1
check_file "$SCRIPT_DIR/hypr/autostart.conf" "$HOME_DIR/.config/hypr/autostart.conf" "hypr/autostart.conf" || check_status=1
check_file "$SCRIPT_DIR/hypr/looknfeel.conf" "$HOME_DIR/.config/hypr/looknfeel.conf" "hypr/looknfeel.conf" || check_status=1
check_file "$SCRIPT_DIR/hypr/bindings.conf" "$HOME_DIR/.config/hypr/bindings.conf" "hypr/bindings.conf" || check_status=1
check_file "$SCRIPT_DIR/hypr/hypridle.conf" "$HOME_DIR/.config/hypr/hypridle.conf" "hypr/hypridle.conf" || check_status=1
check_file "$SCRIPT_DIR/omarchy/hooks/theme-set" "$HOME_DIR/.config/omarchy/hooks/theme-set" "omarchy/hooks/theme-set" || check_status=1
check_file "$SCRIPT_DIR/omarchy/themed/zen.css.tpl" "$HOME_DIR/.config/omarchy/themed/zen.css.tpl" "omarchy/themed/zen.css.tpl" || check_status=1
check_file "$SCRIPT_DIR/ags/app.ts" "$HOME_DIR/.config/ags/app.ts" "ags/app.ts" || check_status=1
check_file "$SCRIPT_DIR/ags/style.css" "$HOME_DIR/.config/ags/style.css" "ags/style.css" || check_status=1
check_file "$SCRIPT_DIR/ags/lib/utils.ts" "$HOME_DIR/.config/ags/lib/utils.ts" "ags/lib/utils.ts" || check_status=1
check_file "$SCRIPT_DIR/ags/lib/claude-usage.sh" "$HOME_DIR/.config/ags/lib/claude-usage.sh" "ags/lib/claude-usage.sh" || check_status=1
check_file "$SCRIPT_DIR/ags/lib/weather.sh" "$HOME_DIR/.config/ags/lib/weather.sh" "ags/lib/weather.sh" || check_status=1
check_file "$SCRIPT_DIR/ags/widget/ClaudeUsage.tsx" "$HOME_DIR/.config/ags/widget/ClaudeUsage.tsx" "ags/widget/ClaudeUsage.tsx" || check_status=1
check_file "$SCRIPT_DIR/ags/widget/Media.tsx" "$HOME_DIR/.config/ags/widget/Media.tsx" "ags/widget/Media.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/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/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/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/screensaver.txt" "$HOME_DIR/.config/omarchy/branding/screensaver.txt" "branding/screensaver.txt" || check_status=1
@@ -153,6 +202,14 @@ for script in "$SCRIPT_DIR/scripts"/*.sh; do
fi
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 ""
if [ "$CHECK" = true ]; then
@@ -179,7 +236,16 @@ 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/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/wallpapers" "$HOME_DIR/wallpapers" "Custom wallpapers"
backup_and_copy "$SCRIPT_DIR/omarchy/themed" "$HOME_DIR/.config/omarchy/themed" "Omarchy custom templates"
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)
if [ -n "$zen_profile" ]; then
@@ -235,6 +301,33 @@ add_system_path() {
add_system_path
# Keep the laptop awake with the lid closed while docked / on AC, so the two
# external monitors stay usable. Paired with the lid-switch binds in
# hypr/bindings.conf, which disable eDP-1 on close so no workspace is stranded.
configure_lid_switch() {
local dropin="/etc/systemd/logind.conf.d/10-lid.conf"
local content="[Login]
HandleLidSwitch=suspend
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore"
if [ -f "$dropin" ] && [ "$(cat "$dropin" 2>/dev/null)" = "$content" ]; then
echo "Lid switch behavior already configured"
return
fi
if sudo mkdir -p "$(dirname "$dropin")" 2>/dev/null \
&& printf '%s\n' "$content" | sudo tee "$dropin" > /dev/null 2>&1; then
sudo chmod 644 "$dropin"
sudo systemctl restart systemd-logind 2>/dev/null || true
echo "Configured lid switch (suspend on battery, ignore when docked/on AC)"
else
echo "Skipped lid switch config (no sudo available)"
fi
}
configure_lid_switch
add_path_to_shell() {
local shell_rc="$1"
local path_line='export PATH="$HOME/scripts:$HOME/.local/bin:$PATH"'
@@ -273,6 +366,32 @@ else
echo "Warning: ags not found. Please start ags manually."
fi
echo ""
echo "=== Reloading Hyprland ==="
if command -v hyprctl &> /dev/null; then
hyprctl reload || true
else
echo "Warning: hyprctl not found. Please reload Hyprland manually."
fi
echo ""
echo "=== Restarting hypridle ==="
if command -v hypridle &> /dev/null; then
pkill -x hypridle 2>/dev/null || true
nohup hypridle >/dev/null 2>&1 &
else
echo "Warning: hypridle not found. Please restart hypridle manually."
fi
echo ""
echo "=== Reapplying current theme (to pick up new templates) ==="
current_theme_name=$(cat "$HOME_DIR/.config/omarchy/current/theme.name" 2>/dev/null)
if [ -n "$current_theme_name" ] && command -v omarchy-theme-set &> /dev/null; then
OMARCHY_THEME_SKIP_BACKGROUND=1 omarchy-theme-set "$current_theme_name" || true
else
echo "Warning: no active theme found. Run blob_theme to generate zen.css."
fi
echo ""
echo "=== Installation Complete ==="
echo "Scripts are in: $HOME_DIR/scripts/"
+24
View File
@@ -0,0 +1,24 @@
/* Generated by omarchy-theme-set-templates from colors.toml on every
theme change (static, dynamic, or shared) - do not edit directly.
Consumed by zen/userChrome.css. */
:root {
--background: {{ background }};
--foreground: {{ foreground }};
--accent: {{ accent }};
--color0: {{ color0 }};
--color1: {{ color1 }};
--color2: {{ color2 }};
--color3: {{ color3 }};
--color4: {{ color4 }};
--color5: {{ color5 }};
--color6: {{ color6 }};
--color7: {{ color7 }};
--color8: {{ color8 }};
--color9: {{ color9 }};
--color10: {{ color10 }};
--color11: {{ color11 }};
--color12: {{ color12 }};
--color13: {{ color13 }};
--color14: {{ color14 }};
--color15: {{ color15 }};
}
+35
View File
@@ -69,6 +69,24 @@ if [ -n "$zen_profile" ]; then
revert_dir "$zen_profile/chrome" "Zen Browser config"
fi
# Undo the lid-switch override installed by install.sh (restores default
# logind behavior: suspend on lid close regardless of power/dock state).
remove_lid_switch() {
local dropin="/etc/systemd/logind.conf.d/10-lid.conf"
if [ ! -f "$dropin" ]; then
echo "[SKIP] No lid switch config to remove"
return
fi
if sudo rm -f "$dropin" 2>/dev/null; then
sudo systemctl restart systemd-logind 2>/dev/null || true
echo "✓ Removed lid switch config ($dropin)"
else
echo "[SKIP] Could not remove $dropin (no sudo available)"
fi
}
remove_lid_switch
echo ""
echo "=== Restarting Waybar ==="
if command -v omarchy-restart-waybar &> /dev/null; then
@@ -86,5 +104,22 @@ else
echo "Warning: ags not found. Please start ags manually."
fi
echo ""
echo "=== Reloading Hyprland ==="
if command -v hyprctl &> /dev/null; then
hyprctl reload || true
else
echo "Warning: hyprctl not found. Please reload Hyprland manually."
fi
echo ""
echo "=== Restarting hypridle ==="
if command -v hypridle &> /dev/null; then
pkill -x hypridle 2>/dev/null || true
nohup hypridle >/dev/null 2>&1 &
else
echo "Warning: hypridle not found. Please restart hypridle manually."
fi
echo ""
echo "=== Revert Complete ==="
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
SECRETS_DIR="$HOME/.config/ags/secrets"
usage() {
echo "Usage: blob_key <set|show|clear|list> [NAME] [VALUE]"
echo " set <NAME> <VALUE> Store a secret (e.g. ANTHROPIC_ADMIN_KEY)"
echo " show <NAME> Print a stored secret, masked"
echo " clear <NAME> Remove a stored secret"
echo " list List the names of stored secrets"
exit 1
}
case "$1" in
set)
name="$2"
value="$3"
if [ -z "$name" ] || [ -z "$value" ]; then
echo "Usage: blob_key set <NAME> <VALUE>"
exit 1
fi
mkdir -p "$SECRETS_DIR"
printf '%s' "$value" > "$SECRETS_DIR/$name"
chmod 600 "$SECRETS_DIR/$name"
echo "Saved $name to $SECRETS_DIR/$name"
;;
show)
name="$2"
if [ -z "$name" ]; then
echo "Usage: blob_key show <NAME>"
exit 1
fi
if [ -f "$SECRETS_DIR/$name" ]; then
value=$(cat "$SECRETS_DIR/$name")
echo "${value:0:6}...${value: -4}"
else
echo "No key stored for $name"
fi
;;
clear)
name="$2"
if [ -z "$name" ]; then
echo "Usage: blob_key clear <NAME>"
exit 1
fi
rm -f "$SECRETS_DIR/$name"
echo "Removed $name"
;;
list)
if [ -d "$SECRETS_DIR" ]; then
ls -1 "$SECRETS_DIR"
else
echo "No keys stored"
fi
;;
*)
usage
;;
esac
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# 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"
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.
# Override with: export BLOB_THEME_URL="https://your-deployment.vercel.app"
BASE_URL="${BLOB_THEME_URL:-https://wall-styles.vercel.app}"
slugify() {
echo "$1" | tr '[:upper:]' '[:lower:]' | tr ' ' '-'
}
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
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"
if [[ "$INPUT" == http*://* ]]; then
HOST=$(echo "$INPUT" | sed -E 's#^(https?://[^/]+).*#\1#')
ID=$(echo "$INPUT" | grep -oE 'id=[A-Za-z0-9]+' | head -1 | cut -d= -f2)
if [ -z "$ID" ]; then
echo "Error: '$INPUT' is not a local theme, and no share id was found in the link."
exit 1
fi
API="$HOST/api/theme?id=$ID&format=toml"
else
API="$BASE_URL/api/theme?id=$INPUT&format=toml"
fi
mkdir -p "$THEME_DIR/backgrounds"
echo "Fetching theme..."
TMP=$(mktemp)
if ! curl -fsSL "$API" -o "$TMP"; then
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"
exit 1
fi
if ! grep -q '^background = ' "$TMP"; then
echo "Error: the response did not look like a valid colors.toml."
rm -f "$TMP"
exit 1
fi
mv "$TMP" "$THEME_DIR/colors.toml"
cat <<EOF > "$THEME_DIR/neovim.lua"
return {
{
"LazyVim/LazyVim",
opts = {
colorscheme = "tokyonight",
},
},
}
EOF
# Apply the Blob-Dynamic theme (reloads waybar, ags, etc.)
omarchy-theme-set "blob-dynamic"
echo "Applied shared theme to blob-dynamic."
fi
;;
esac
+48 -17
View File
@@ -7,8 +7,20 @@ THEME_DIR="$HOME/.config/omarchy/themes/blob-dynamic"
mkdir -p "$WALLPAPER_DIR"
mkdir -p "$THEME_DIR/backgrounds"
{
echo "=== $(date '+%F %T') invoked with: $* ==="
echo "WAYLAND_DISPLAY=$WAYLAND_DISPLAY"
echo "XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR"
echo "DBUS_SESSION_BUS_ADDRESS=$DBUS_SESSION_BUS_ADDRESS"
echo "PATH=$PATH"
} >> "$HOME/.cache/blob-wallpaper.log"
if [ -z "$1" ]; then
# Use walker dmenu for GUI selection
# Open the AGS wallpaper selector widget
ags toggle wall-picker
exit 0
elif [ "$1" = "--menu" ]; then
# Fallback: walker dmenu selection
omarchy-launch-walker -m menus:blobBackgroundSelector --width 800 --minheight 400 -p "Select Wallpaper…"
exit 0
else
@@ -73,27 +85,46 @@ return {
}
EOF
# Apply the Blob-Dynamic theme
# omarchy-theme-set manages the background and reloads waybar and AGS
omarchy-theme-set "blob-dynamic"
# 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
# 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"
fi
CURRENT_BACKGROUND_LINK="$HOME/.config/omarchy/current/background"
ln -nsf "$THEME_DIR/backgrounds/$(basename "$IMAGE_PATH")" "$CURRENT_BACKGROUND_LINK"
# Stop whichever daemon was previously rendering the background before
# starting the one this wallpaper needs. `awww kill` shuts the daemon down
# via its own IPC so it cleans up its socket, unlike a raw `pkill`.
pkill -x swaybg 2>/dev/null
awww kill --all >/dev/null 2>&1
pkill -x awww-daemon 2>/dev/null
# If it's a GIF, override swaybg with awww (swww replacement)
if [[ "${IMAGE_PATH,,}" == *.gif ]]; then
echo "GIF detected, switching to awww..."
pkill -x swaybg
echo "GIF detected, using awww for animation..."
setsid uwsm-app -- awww-daemon >>"$HOME/.cache/awww-daemon.log" 2>&1 &
# Start awww daemon if not running
if ! pgrep -x awww-daemon >/dev/null; then
awww-daemon >/dev/null 2>&1 &
sleep 1
# Poll until the daemon's IPC socket is ready instead of guessing a fixed sleep
IMG_ERROR=""
for _ in {1..15}; do
IMG_ERROR=$(awww img "$CURRENT_BACKGROUND_LINK" 2>&1) && break
sleep 0.3
done
if [[ -n "$IMG_ERROR" ]]; then
echo "awww failed to set the wallpaper: $IMG_ERROR"
echo "See $HOME/.cache/awww-daemon.log for daemon output."
fi
awww img "$IMAGE_PATH"
else
# Ensure awww is stopped for static wallpapers so swaybg can render them
if pgrep -x awww-daemon >/dev/null; then
pkill -x awww-daemon
fi
setsid uwsm-app -- swaybg -i "$CURRENT_BACKGROUND_LINK" -m fill >/dev/null 2>&1 &
fi
echo "Wallpaper and dynamic theme applied successfully: $IMAGE_PATH"
+60
View File
@@ -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.
+23
View File
@@ -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"
+23
View File
@@ -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"
+100
View File
@@ -0,0 +1,100 @@
# Wallpapers: 0-9, A-E
26 of 102 wallpapers in [`wallpapers/`](../wallpapers/). Sorted alphabetically.
[All wallpapers](index.md) | [E - M >>](e-m.md)
<table>
<tr>
<td align="center"><img src="../wallpapers/1_a-slient-voice.png" width="280"/></td>
<td align="center"><img src="../wallpapers/7_firewatch.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/anime_cafe_tokyonight.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>1_a-slient-voice.png</code></sub></td>
<td align="center"><sub><code>7_firewatch.jpg</code></sub></td>
<td align="center"><sub><code>anime_cafe_tokyonight.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/anime_circuit.png" width="280"/></td>
<td align="center"><img src="../wallpapers/anime_skyline.png" width="280"/></td>
<td align="center"><img src="../wallpapers/astronaut.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>anime_circuit.png</code></sub></td>
<td align="center"><sub><code>anime_skyline.png</code></sub></td>
<td align="center"><sub><code>astronaut.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/astronaut2.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/autumn_forest.png" width="280"/></td>
<td align="center"><img src="../wallpapers/awesome.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>astronaut2.jpg</code></sub></td>
<td align="center"><sub><code>autumn_forest.png</code></sub></td>
<td align="center"><sub><code>awesome.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/baby-star.png" width="280"/></td>
<td align="center"><img src="../wallpapers/be_productive_4k.png" width="280"/></td>
<td align="center"><img src="../wallpapers/black-hole.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>baby-star.png</code></sub></td>
<td align="center"><sub><code>be_productive_4k.png</code></sub></td>
<td align="center"><sub><code>black-hole.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/black_car_girl.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/camille-unknown.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/cat.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>black_car_girl.jpg</code></sub></td>
<td align="center"><sub><code>camille-unknown.jpg</code></sub></td>
<td align="center"><sub><code>cat.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/chainsaw_denji_cat.png" width="280"/></td>
<td align="center"><img src="../wallpapers/cherry_blossoms.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/clan_connections.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>chainsaw_denji_cat.png</code></sub></td>
<td align="center"><sub><code>cherry_blossoms.jpg</code></sub></td>
<td align="center"><sub><code>clan_connections.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/cliff-edge.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/colorful-planets.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/deer-forest.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>cliff-edge.jpg</code></sub></td>
<td align="center"><sub><code>colorful-planets.jpg</code></sub></td>
<td align="center"><sub><code>deer-forest.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/delorean.png" width="280"/></td>
<td align="center"><img src="../wallpapers/delorean_pastel.png" width="280"/></td>
<td align="center"><img src="../wallpapers/doom-slayer.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>delorean.png</code></sub></td>
<td align="center"><sub><code>delorean_pastel.png</code></sub></td>
<td align="center"><sub><code>doom-slayer.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/ds_giyu.png" width="280"/></td>
<td align="center"><img src="../wallpapers/evangelion.png" width="280"/></td>
<td></td>
</tr>
<tr>
<td align="center"><sub><code>ds_giyu.png</code></sub></td>
<td align="center"><sub><code>evangelion.png</code></sub></td>
<td></td>
</tr>
</table>
[All wallpapers](index.md) | [E - M >>](e-m.md)
+100
View File
@@ -0,0 +1,100 @@
# Wallpapers: E - M
26 of 102 wallpapers in [`wallpapers/`](../wallpapers/). Sorted alphabetically.
[<< 0-9, A-E](a-d.md) | [All wallpapers](index.md) | [M - R >>](m-r.md)
<table>
<tr>
<td align="center"><img src="../wallpapers/everblush_creep.png" width="280"/></td>
<td align="center"><img src="../wallpapers/firewatch_purple.png" width="280"/></td>
<td align="center"><img src="../wallpapers/fishSchool.jpeg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>everblush_creep.png</code></sub></td>
<td align="center"><sub><code>firewatch_purple.png</code></sub></td>
<td align="center"><sub><code>fishSchool.jpeg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/goblin_slayer.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/goldfish.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/hello-worlds.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>goblin_slayer.jpg</code></sub></td>
<td align="center"><sub><code>goldfish.jpg</code></sub></td>
<td align="center"><sub><code>hello-worlds.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/hollow-knight-2.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/hollow-knight-3.png" width="280"/></td>
<td align="center"><img src="../wallpapers/hollow-knight.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>hollow-knight-2.jpg</code></sub></td>
<td align="center"><sub><code>hollow-knight-3.png</code></sub></td>
<td align="center"><sub><code>hollow-knight.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/home127-dark.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/jellyfish.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/jupiter.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>home127-dark.jpg</code></sub></td>
<td align="center"><sub><code>jellyfish.jpg</code></sub></td>
<td align="center"><sub><code>jupiter.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/kimi_no_nawa.png" width="280"/></td>
<td align="center"><img src="../wallpapers/kingler_drawing.png" width="280"/></td>
<td align="center"><img src="../wallpapers/krita_digiart.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>kimi_no_nawa.png</code></sub></td>
<td align="center"><sub><code>kingler_drawing.png</code></sub></td>
<td align="center"><sub><code>krita_digiart.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/large_asf_goodbg.png" width="280"/></td>
<td align="center"><img src="../wallpapers/literal-wallpaper.png" width="280"/></td>
<td align="center"><img src="../wallpapers/makima-oxo.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>large_asf_goodbg.png</code></sub></td>
<td align="center"><sub><code>literal-wallpaper.png</code></sub></td>
<td align="center"><sub><code>makima-oxo.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/material_pastel_1.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/material_pastel_2.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/material_pastel_3.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>material_pastel_1.jpg</code></sub></td>
<td align="center"><sub><code>material_pastel_2.jpg</code></sub></td>
<td align="center"><sub><code>material_pastel_3.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/material_pastel_4.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/material_pastel_5.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/material_pastel_6.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>material_pastel_4.jpg</code></sub></td>
<td align="center"><sub><code>material_pastel_5.jpg</code></sub></td>
<td align="center"><sub><code>material_pastel_6.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/material_pastel_7.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/material_pastel_8.jpg" width="280"/></td>
<td></td>
</tr>
<tr>
<td align="center"><sub><code>material_pastel_7.jpg</code></sub></td>
<td align="center"><sub><code>material_pastel_8.jpg</code></sub></td>
<td></td>
</tr>
</table>
[<< 0-9, A-E](a-d.md) | [All wallpapers](index.md) | [M - R >>](m-r.md)
+8
View File
@@ -0,0 +1,8 @@
# Wallpapers
A gallery of all 102 wallpapers in [`wallpapers/`](../wallpapers/), sorted alphabetically and split across pages:
- [0-9, A-E](a-d.md) &mdash; 26 wallpapers (`1_a-slient-voice.png` to `evangelion.png`)
- [E - M](e-m.md) &mdash; 26 wallpapers (`everblush_creep.png` to `material_pastel_8.jpg`)
- [M - R](m-r.md) &mdash; 25 wallpapers (`melting_astro.jpg` to `rm-rf.jpg`)
- [R - Y](r-z.md) &mdash; 25 wallpapers (`rocket_launch.png` to `yosemite-lowpoly.jpg`)
+100
View File
@@ -0,0 +1,100 @@
# Wallpapers: M - R
25 of 102 wallpapers in [`wallpapers/`](../wallpapers/). Sorted alphabetically.
[<< E - M](e-m.md) | [All wallpapers](index.md) | [R - Y >>](r-z.md)
<table>
<tr>
<td align="center"><img src="../wallpapers/melting_astro.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/montains.png" width="280"/></td>
<td align="center"><img src="../wallpapers/mountain-snow-minima.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>melting_astro.jpg</code></sub></td>
<td align="center"><sub><code>montains.png</code></sub></td>
<td align="center"><sub><code>mountain-snow-minima.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/nasa1.png" width="280"/></td>
<td align="center"><img src="../wallpapers/neon-singularity.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/nighttab_backdrop.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>nasa1.png</code></sub></td>
<td align="center"><sub><code>neon-singularity.jpg</code></sub></td>
<td align="center"><sub><code>nighttab_backdrop.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/nord-chainsaw.png" width="280"/></td>
<td align="center"><img src="../wallpapers/nord_car_live.gif" width="280"/></td>
<td align="center"><img src="../wallpapers/nord_minimal_cat.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>nord-chainsaw.png</code></sub></td>
<td align="center"><sub><code>nord_car_live.gif</code></sub></td>
<td align="center"><sub><code>nord_minimal_cat.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/nord_sky.jpeg" width="280"/></td>
<td align="center"><img src="../wallpapers/nordic-heroin.png" width="280"/></td>
<td align="center"><img src="../wallpapers/p3.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>nord_sky.jpeg</code></sub></td>
<td align="center"><sub><code>nordic-heroin.png</code></sub></td>
<td align="center"><sub><code>p3.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/pacman-black.png" width="280"/></td>
<td align="center"><img src="../wallpapers/pastel-car-girl.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/pastel-car.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>pacman-black.png</code></sub></td>
<td align="center"><sub><code>pastel-car-girl.jpg</code></sub></td>
<td align="center"><sub><code>pastel-car.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/pastel-city.png" width="280"/></td>
<td align="center"><img src="../wallpapers/peaks.png" width="280"/></td>
<td align="center"><img src="../wallpapers/perfect-blue.jpg" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>pastel-city.png</code></sub></td>
<td align="center"><sub><code>peaks.png</code></sub></td>
<td align="center"><sub><code>perfect-blue.jpg</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/pixel-city.png" width="280"/></td>
<td align="center"><img src="../wallpapers/pixel-earth.png" width="280"/></td>
<td align="center"><img src="../wallpapers/planet_minimal.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>pixel-city.png</code></sub></td>
<td align="center"><sub><code>pixel-earth.png</code></sub></td>
<td align="center"><sub><code>planet_minimal.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/pokemon-legends-arceus-wallpapers.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/rain_world1.png" width="280"/></td>
<td align="center"><img src="../wallpapers/rain_world2.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>pokemon-legends-arceus-wallpapers.jpg</code></sub></td>
<td align="center"><sub><code>rain_world1.png</code></sub></td>
<td align="center"><sub><code>rain_world2.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/rm-rf.jpg" width="280"/></td>
<td></td>
<td></td>
</tr>
<tr>
<td align="center"><sub><code>rm-rf.jpg</code></sub></td>
<td></td>
<td></td>
</tr>
</table>
[<< E - M](e-m.md) | [All wallpapers](index.md) | [R - Y >>](r-z.md)
+100
View File
@@ -0,0 +1,100 @@
# Wallpapers: R - Y
25 of 102 wallpapers in [`wallpapers/`](../wallpapers/). Sorted alphabetically.
[<< M - R](m-r.md) | [All wallpapers](index.md)
<table>
<tr>
<td align="center"><img src="../wallpapers/rocket_launch.png" width="280"/></td>
<td align="center"><img src="../wallpapers/sakura.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/shiny-colors.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>rocket_launch.png</code></sub></td>
<td align="center"><sub><code>sakura.jpg</code></sub></td>
<td align="center"><sub><code>shiny-colors.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/shooting_stars.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/siri.png" width="280"/></td>
<td align="center"><img src="../wallpapers/space-juice.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>shooting_stars.jpg</code></sub></td>
<td align="center"><sub><code>siri.png</code></sub></td>
<td align="center"><sub><code>space-juice.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/space_man.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/space_piano.png" width="280"/></td>
<td align="center"><img src="../wallpapers/sushi_dark.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>space_man.jpg</code></sub></td>
<td align="center"><sub><code>space_piano.png</code></sub></td>
<td align="center"><sub><code>sushi_dark.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/swords.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/tabs.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/think.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>swords.jpg</code></sub></td>
<td align="center"><sub><code>tabs.jpg</code></sub></td>
<td align="center"><sub><code>think.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/train_tokyo.gif" width="280"/></td>
<td align="center"><img src="../wallpapers/trigonometry.png" width="280"/></td>
<td align="center"><img src="../wallpapers/two-astronauts.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>train_tokyo.gif</code></sub></td>
<td align="center"><sub><code>trigonometry.png</code></sub></td>
<td align="center"><sub><code>two-astronauts.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/underwater.png" width="280"/></td>
<td align="center"><img src="../wallpapers/vintage-ascent.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/windows-black.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>underwater.png</code></sub></td>
<td align="center"><sub><code>vintage-ascent.jpg</code></sub></td>
<td align="center"><sub><code>windows-black.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/windows-error.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/windows-magenta-blue.png" width="280"/></td>
<td align="center"><img src="../wallpapers/windows-magenta-pink.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>windows-error.jpg</code></sub></td>
<td align="center"><sub><code>windows-magenta-blue.png</code></sub></td>
<td align="center"><sub><code>windows-magenta-pink.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/windows-xp.png" width="280"/></td>
<td align="center"><img src="../wallpapers/xavier-cuenca-w4-3.jpg" width="280"/></td>
<td align="center"><img src="../wallpapers/yosemite-color-block.png" width="280"/></td>
</tr>
<tr>
<td align="center"><sub><code>windows-xp.png</code></sub></td>
<td align="center"><sub><code>xavier-cuenca-w4-3.jpg</code></sub></td>
<td align="center"><sub><code>yosemite-color-block.png</code></sub></td>
</tr>
<tr>
<td align="center"><img src="../wallpapers/yosemite-lowpoly.jpg" width="280"/></td>
<td></td>
<td></td>
</tr>
<tr>
<td align="center"><sub><code>yosemite-lowpoly.jpg</code></sub></td>
<td></td>
<td></td>
</tr>
</table>
[<< M - R](m-r.md) | [All wallpapers](index.md)
Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1018 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 687 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 817 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

+14 -17
View File
@@ -22,20 +22,14 @@
"network",
"pulseaudio",
"cpu",
"memory",
"disk",
"battery"
"battery",
"custom/notification"
],
"memory": {
"interval": 10,
"format": "\udb81\ude1a",
"tooltip-format": "Memory: {used:0.1f}G/{total:0.1f}G"
},
"disk": {
"interval": 30,
"format": "\udb80\udeca",
"path": "/",
"tooltip-format": "Disk: {used}/{total}"
"custom/notification": {
"format": "",
"tooltip-format": "Notifications",
"on-click": "ags toggle notification-center",
"on-click-right": "omarchy-toggle-notification-silencing"
},
"hyprland/workspaces": {
"on-click": "activate",
@@ -84,12 +78,13 @@
"interval": 5,
"format": "\udb80\udf5b",
"on-click": "omarchy-launch-or-focus-tui btop",
"on-click-right": "alacritty"
"on-click-middle": "alacritty",
"on-click-right": "ags toggle sys-monitor"
},
"clock": {
"format": "{:L%A %d %B %H:%M}",
"format-alt": "{:L%d %B W%V %Y}",
"tooltip": false,
"on-click": "ags toggle quick-settings",
"on-click-right": "omarchy-launch-floating-terminal-with-presentation omarchy-tz-select"
},
"network": {
@@ -109,7 +104,8 @@
"tooltip-format-disconnected": "Disconnected",
"interval": 3,
"spacing": 1,
"on-click": "omarchy-launch-wifi"
"on-click": "omarchy-launch-wifi",
"on-click-right": "ags toggle quick-settings"
},
"battery": {
"format": "{capacity}% {icon}",
@@ -159,7 +155,8 @@
"format-connected": "\udb80\udcb1",
"format-no-controller": "",
"tooltip-format": "Devices connected: {num_connections}",
"on-click": "omarchy-launch-bluetooth"
"on-click": "omarchy-launch-bluetooth",
"on-click-right": "ags toggle quick-settings"
},
"pulseaudio": {
"format": "{icon}",

Some files were not shown because too many files have changed in this diff Show More