Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7f8911ec | ||
|
|
4eb3b23d4b | ||
|
|
45a60030d1 | ||
|
|
3266666ade | ||
|
|
acc96492ee | ||
|
|
cdff530e4b | ||
|
|
bff3c13acc | ||
|
|
c2a05bbe8e | ||
|
|
94518ffa30 | ||
|
|
07ef82ff73 | ||
|
|
1ca522ddc0 | ||
|
|
f32b4ac102 | ||
|
|
88f2740361 | ||
|
|
5ecbcdf55b | ||
|
|
c3f8738f00 | ||
|
|
9a2e352202 | ||
|
|
0dd174d8c7 | ||
|
|
0c0c914431 | ||
|
|
cffe15e1f6 | ||
|
|
9b839ffaf1 | ||
|
|
c68e8c38cb | ||
|
|
fb57093a75 | ||
|
|
66a8482254 | ||
|
|
692555f8e3 | ||
|
|
7cba2c0b3f | ||
|
|
83780c7ff1 | ||
|
|
ea7093e987 | ||
|
|
df72345a5c | ||
|
|
f1c3dca364 | ||
|
|
be14a1a730 | ||
|
|
04f074b71c | ||
|
|
0cf0bababc | ||
|
|
1fe518ff2b |
@@ -4,3 +4,4 @@ node_modules
|
||||
|
||||
|
||||
ags/@girs/
|
||||
ags-dots/
|
||||
@@ -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
|
||||
```
|
||||
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
})
|
||||
@@ -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"
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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() || "";
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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`. |
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 |
|
||||
@@ -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=()
|
||||
@@ -136,16 +170,23 @@ check_file "$SCRIPT_DIR/hypr/looknfeel.conf" "$HOME_DIR/.config/hypr/looknfeel.c
|
||||
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
|
||||
|
||||
@@ -161,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
|
||||
@@ -187,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
|
||||
@@ -325,6 +383,15 @@ 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/"
|
||||
|
||||
@@ -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 }};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -0,0 +1,60 @@
|
||||
# Themes
|
||||
|
||||
Drop-in static color themes. Each subdirectory here becomes a theme
|
||||
selectable from `blob_theme`, the theme picker widget, and the walker
|
||||
menu, once `install.sh` copies it into `~/.config/omarchy/themes/`.
|
||||
|
||||
## Adding a theme
|
||||
|
||||
Create a directory named after the theme (lowercase, hyphens instead
|
||||
of spaces — e.g. `tokyo-night/`) containing a `colors.toml`:
|
||||
|
||||
```toml
|
||||
accent = "#7aa2f7"
|
||||
cursor = "#c0caf5"
|
||||
foreground = "#c0caf5"
|
||||
background = "#1a1b26"
|
||||
selection_foreground = "#1a1b26"
|
||||
selection_background = "#7aa2f7"
|
||||
|
||||
color0 = "#1a1b26"
|
||||
color1 = "#f7768e"
|
||||
color2 = "#9ece6a"
|
||||
color3 = "#e0af68"
|
||||
color4 = "#7aa2f7"
|
||||
color5 = "#bb9af7"
|
||||
color6 = "#7dcfff"
|
||||
color7 = "#c0caf5"
|
||||
color8 = "#414868"
|
||||
color9 = "#f7768e"
|
||||
color10 = "#9ece6a"
|
||||
color11 = "#e0af68"
|
||||
color12 = "#7aa2f7"
|
||||
color13 = "#bb9af7"
|
||||
color14 = "#7dcfff"
|
||||
color15 = "#c0caf5"
|
||||
```
|
||||
|
||||
All 22 keys are required — this is the same format Omarchy themes and
|
||||
`blob_wallpaper`'s pywal-generated `blob-dynamic` theme already use, so
|
||||
`omarchy-theme-set` and its app-config templates pick it up with no
|
||||
extra work.
|
||||
|
||||
### Saving a dynamic palette you like
|
||||
|
||||
If a wallpaper's pywal-generated colors are worth keeping permanently,
|
||||
dump the currently active theme's `colors.toml` straight into a new
|
||||
theme folder instead of typing it out by hand:
|
||||
|
||||
```bash
|
||||
mkdir -p themes/my-new-theme
|
||||
blob_theme --print > themes/my-new-theme/colors.toml
|
||||
```
|
||||
|
||||
Run `./install.sh` (add `--force` if the theme already exists on disk
|
||||
and you're updating it) to deploy new/changed theme folders.
|
||||
|
||||
Any theme already installed under `~/.config/omarchy/themes/` or
|
||||
bundled with Omarchy itself (`$OMARCHY_PATH/themes/`) shows up
|
||||
automatically too — this directory is just for ones you want tracked
|
||||
in dotfiles.
|
||||
@@ -0,0 +1,23 @@
|
||||
accent = "#7babe3"
|
||||
cursor = "#cbd2d9"
|
||||
foreground = "#cbd2d9"
|
||||
background = "#0f1012"
|
||||
selection_foreground = "#0f1012"
|
||||
selection_background = "#7babe3"
|
||||
|
||||
color0 = "#000000"
|
||||
color1 = "#d53838"
|
||||
color2 = "#38d4d5"
|
||||
color3 = "#d58638"
|
||||
color4 = "#d53886"
|
||||
color5 = "#38d586"
|
||||
color6 = "#3886d5"
|
||||
color7 = "#d4d4d4"
|
||||
color8 = "#949494"
|
||||
color9 = "#d53838"
|
||||
color10 = "#38d4d5"
|
||||
color11 = "#d58638"
|
||||
color12 = "#d53886"
|
||||
color13 = "#38d586"
|
||||
color14 = "#3886d5"
|
||||
color15 = "#d4d4d4"
|
||||
@@ -0,0 +1,23 @@
|
||||
accent = "#d53838"
|
||||
cursor = "#d4d4d4"
|
||||
foreground = "#d4d4d4"
|
||||
background = "#000000"
|
||||
selection_foreground = "#000000"
|
||||
selection_background = "#d53838"
|
||||
|
||||
color0 = "#000000"
|
||||
color1 = "#d53838"
|
||||
color2 = "#38d4d5"
|
||||
color3 = "#d58638"
|
||||
color4 = "#d53886"
|
||||
color5 = "#38d586"
|
||||
color6 = "#3886d5"
|
||||
color7 = "#d4d4d4"
|
||||
color8 = "#949494"
|
||||
color9 = "#d53838"
|
||||
color10 = "#38d4d5"
|
||||
color11 = "#d58638"
|
||||
color12 = "#d53886"
|
||||
color13 = "#38d586"
|
||||
color14 = "#3886d5"
|
||||
color15 = "#d4d4d4"
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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) — 26 wallpapers (`1_a-slient-voice.png` to `evangelion.png`)
|
||||
- [E - M](e-m.md) — 26 wallpapers (`everblush_creep.png` to `material_pastel_8.jpg`)
|
||||
- [M - R](m-r.md) — 25 wallpapers (`melting_astro.jpg` to `rm-rf.jpg`)
|
||||
- [R - Y](r-z.md) — 25 wallpapers (`rocket_launch.png` to `yosemite-lowpoly.jpg`)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
After Width: | Height: | Size: 921 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 391 KiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 9.2 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 901 KiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1021 KiB |
|
After Width: | Height: | Size: 1018 KiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 495 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 291 KiB |
|
After Width: | Height: | Size: 974 KiB |
|
After Width: | Height: | Size: 486 KiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 820 KiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 12 MiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 261 KiB |
|
After Width: | Height: | Size: 220 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 687 KiB |
|
After Width: | Height: | Size: 726 KiB |
|
After Width: | Height: | Size: 458 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 378 KiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 231 KiB |
|
After Width: | Height: | Size: 4.8 MiB |
|
After Width: | Height: | Size: 599 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 13 MiB |
|
After Width: | Height: | Size: 347 KiB |
|
After Width: | Height: | Size: 9.8 MiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 817 KiB |
|
After Width: | Height: | Size: 212 KiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 755 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 306 KiB |
@@ -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}",
|
||||
|
||||
@@ -112,3 +112,9 @@ tooltip {
|
||||
#custom-notification-silencing-indicator.active {
|
||||
color: #a55555;
|
||||
}
|
||||
|
||||
#custom-notification {
|
||||
min-width: 16px;
|
||||
margin-left: 7.5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/* Import Omarchy Pywal-generated system colors */
|
||||
@import url("file:///home/blob/.cache/wal/colors.css");
|
||||
/* Import the active Omarchy theme's colors (regenerated from colors.toml
|
||||
on every theme change - static, dynamic, or shared - by
|
||||
omarchy-theme-set-templates via omarchy/themed/zen.css.tpl) */
|
||||
@import url("file:///home/blob/.config/omarchy/current/theme/zen.css");
|
||||
|
||||
:root {
|
||||
/* Map Omarchy colors to Zen variables */
|
||||
|
||||