Compare commits
22 Commits
8a081737d3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| fc2c5e8d18 | |||
| 59e2d9de97 | |||
| 8d1be4712d | |||
| 320dc003c4 | |||
| 3195f61782 | |||
| 85a06ae0f8 | |||
| 1ee02f2a2f | |||
| bfa9ed67f7 | |||
| b2717d88e8 | |||
| b98ddd3db9 | |||
| 80bb43ae13 | |||
| 4143384758 | |||
| 33081f8fa3 | |||
| 8501165f84 | |||
| e5eb201d16 | |||
| 35647d799b | |||
| 15680ecfec | |||
| 99eb1be04c | |||
| 2c7ade084d | |||
| c302033063 | |||
| 02c896a6c7 | |||
| 68a165862d |
@@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<h1>Blob's Dotfiles</h1>
|
<h1>Blob's Dotfiles</h1>
|
||||||
<p>My personal system configurations for a custom Wayland desktop environment.</p>
|
<p>My personal system configurations for a custom Wayland desktop environment built on <a href="https://omarchy.org">Omarchy</a>.</p>
|
||||||
|
|
||||||
<img src="https://img.shields.io/badge/Arch_Linux-1793D1?style=for-the-badge&logo=arch-linux&logoColor=white" alt="Arch Linux" />
|
<img src="https://img.shields.io/badge/Arch_Linux-1793D1?style=for-the-badge&logo=arch-linux&logoColor=white" alt="Arch Linux" />
|
||||||
<img src="https://img.shields.io/badge/Hyprland-00A86B?style=for-the-badge&logo=hyprland&logoColor=white" alt="Hyprland" />
|
<img src="https://img.shields.io/badge/Hyprland-00A86B?style=for-the-badge&logo=hyprland&logoColor=white" alt="Hyprland" />
|
||||||
@@ -8,6 +8,8 @@
|
|||||||
<img src="https://img.shields.io/badge/AGS-231F20?style=for-the-badge&logo=gnome&logoColor=white" alt="AGS" />
|
<img src="https://img.shields.io/badge/AGS-231F20?style=for-the-badge&logo=gnome&logoColor=white" alt="AGS" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## What's Inside?
|
## What's Inside?
|
||||||
|
|
||||||
My current setup is built around these core components:
|
My current setup is built around these core components:
|
||||||
@@ -15,15 +17,27 @@ My current setup is built around these core components:
|
|||||||
- **[Hyprland](https://hyprland.org/):** A highly customizable dynamic tiling Wayland compositor.
|
- **[Hyprland](https://hyprland.org/):** A highly customizable dynamic tiling Wayland compositor.
|
||||||
- **[Waybar](https://github.com/Alexays/Waybar):** A customizable, modular status bar.
|
- **[Waybar](https://github.com/Alexays/Waybar):** A customizable, modular status bar.
|
||||||
- **[AGS](https://github.com/Aylur/ags):** Aylur's Gtk Shell, used for creating custom, scriptable desktop widgets.
|
- **[AGS](https://github.com/Aylur/ags):** Aylur's Gtk Shell, used for creating custom, scriptable desktop widgets.
|
||||||
|
- **Dynamic Theming:** Seamlessly integrated with Pywal to extract color palettes from wallpapers and apply them instantly across the entire system (widgets, terminal, status bar).
|
||||||
|
|
||||||
### Directory Structure
|
### Directory Structure
|
||||||
|
|
||||||
- **`hypr/`**: Hyprland configurations (keybindings, window rules, animations, layout settings, and autostart).
|
- **`hypr/`**: Hyprland configurations (keybindings, window rules, animations, layout settings, and autostart).
|
||||||
- **`waybar/`**: Status bar layout, CSS styling, and custom interactive modules.
|
- **`waybar/`**: Status bar layout, CSS styling, and custom interactive modules.
|
||||||
- **`ags/`**: Custom desktop widgets built with TypeScript and GTK.
|
- **`ags/`**: Custom desktop widgets built with TypeScript and GTK.
|
||||||
- **`scripts/`**: Global utility scripts (such as a streamlined GMU Eduroam Wi-Fi connector).
|
- **`scripts/`**: Global utility scripts seamlessly exposed as commands by the installer.
|
||||||
|
- **`wallpapers/`**: A collection of local custom wallpapers for dynamic theming.
|
||||||
|
- **`omarchy/hooks/`**: Event hooks for the Omarchy system (e.g. automatically applying dynamic themes when changing wallpapers).
|
||||||
- **`branding/`**: Custom ASCII art and system branding assets.
|
- **`branding/`**: Custom ASCII art and system branding assets.
|
||||||
|
|
||||||
|
## Custom Commands
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
An automated installer script (`install.sh`) is provided to safely apply these configurations to your system.
|
An automated installer script (`install.sh`) is provided to safely apply these configurations to your system.
|
||||||
@@ -39,10 +53,3 @@ An automated installer script (`install.sh`) is provided to safely apply these c
|
|||||||
./install.sh --force
|
./install.sh --force
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installer Features
|
|
||||||
|
|
||||||
1. **Safety First:** Computes file hashes to detect local changes. Backs up existing configurations before applying updates.
|
|
||||||
2. **Auto-Deployment:** Copies the tracked configurations seamlessly into your `~/.config/` directory.
|
|
||||||
3. **Command Wrapping:** Automatically sets up scripts from the `scripts/` directory as global commands in `~/.local/bin/`.
|
|
||||||
4. **Shell Integration:** Injects the necessary paths into your `~/.bashrc`, `~/.zshrc`, and system-wide profiles.
|
|
||||||
5. **Instant Refresh:** Automatically restarts background services like `waybar` and `ags` so changes take effect immediately.
|
|
||||||
@@ -13,9 +13,38 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 2px solid alpha(@color4, 0.5);
|
border: 2px solid alpha(@color4, 0.5);
|
||||||
padding: 8px 15px;
|
padding: 8px 15px;
|
||||||
|
min-width: 300px;
|
||||||
font-family: "JetBrainsMono Nerd Font", sans-serif;
|
font-family: "JetBrainsMono Nerd Font", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-cover {
|
||||||
|
min-width: 64px;
|
||||||
|
min-height: 64px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-info-box {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-title {
|
||||||
|
color: @foreground;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-artist {
|
||||||
|
color: @foreground;
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-controls {
|
||||||
|
/* Box spacing is handled by the component */
|
||||||
|
}
|
||||||
|
|
||||||
.media-btn {
|
.media-btn {
|
||||||
color: @color5;
|
color: @color5;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
@@ -27,8 +56,26 @@
|
|||||||
color: @color4;
|
color: @color4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-text {
|
.media-progress {
|
||||||
color: @foreground;
|
min-height: 4px;
|
||||||
font-size: 14px;
|
padding: 0;
|
||||||
margin-left: 10px;
|
}
|
||||||
}
|
|
||||||
|
.media-progress trough {
|
||||||
|
min-height: 4px;
|
||||||
|
background-color: alpha(@foreground, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-progress highlight {
|
||||||
|
background-color: @accent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-progress slider {
|
||||||
|
background-color: transparent;
|
||||||
|
min-width: 0px;
|
||||||
|
min-height: 0px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,9 +6,26 @@ import { createPoll } from "ags/time"
|
|||||||
export default function Media(gdkmonitor: Gdk.Monitor) {
|
export default function Media(gdkmonitor: Gdk.Monitor) {
|
||||||
const { TOP, LEFT } = Astal.WindowAnchor
|
const { TOP, LEFT } = Astal.WindowAnchor
|
||||||
|
|
||||||
// Poll playerctl for metadata and status
|
const pollCmd = "playerctl metadata -f '{{title}}|||{{artist}}|||{{mpris:artUrl}}|||{{status}}|||{{mpris:length}}|||{{position}}' 2>/dev/null || echo 'No Media||||||Stopped|||0|||0'";
|
||||||
const mediaInfo = createPoll("No Media Playing", 1000, 'sh -c "playerctl metadata -f \'{{title}} - {{artist}}\' 2>/dev/null || echo \'No Media Playing\'"')
|
|
||||||
const statusIcon = createPoll("▶", 1000, 'sh -c "s=\\$(playerctl status 2>/dev/null); if [ \\"\\$s\\" = \\"Playing\\" ]; then echo \\"⏸\\"; else echo \\"▶\\"; fi"')
|
const mediaState = createPoll({
|
||||||
|
title: "No Media",
|
||||||
|
artist: "",
|
||||||
|
artUrl: "",
|
||||||
|
status: "Stopped",
|
||||||
|
length: 0,
|
||||||
|
position: 0
|
||||||
|
}, 1000, pollCmd, (stdout) => {
|
||||||
|
const parts = stdout.split("|||");
|
||||||
|
const title = parts[0]?.trim() || "No Media";
|
||||||
|
const artist = parts[1]?.trim() || "";
|
||||||
|
const rawUrl = parts[2]?.trim() || "";
|
||||||
|
const artUrl = rawUrl.replace(/^file:\/\//, '');
|
||||||
|
const status = parts[3]?.trim() || "Stopped";
|
||||||
|
const length = Number(parts[4]) || 0;
|
||||||
|
const position = Number(parts[5]) || 0;
|
||||||
|
return { title, artist, artUrl, status, length, position };
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<window
|
<window
|
||||||
@@ -20,30 +37,68 @@ export default function Media(gdkmonitor: Gdk.Monitor) {
|
|||||||
margin={20}
|
margin={20}
|
||||||
application={app}
|
application={app}
|
||||||
>
|
>
|
||||||
<box class="media-container" spacing={10}>
|
<box class="media-container" css="min-width: 350px;" spacing={15}>
|
||||||
<button
|
<box
|
||||||
class="media-btn"
|
class="media-cover"
|
||||||
onClicked={() => execAsync("playerctl previous").catch(print)}
|
css={mediaState.as(s => s.artUrl ? `background-image: url('${s.artUrl}'); min-width: 80px; min-height: 80px;` : 'min-width: 80px; min-height: 80px; background-color: alpha(@color0, 0.5);')}
|
||||||
halign={Gtk.Align.CENTER}
|
/>
|
||||||
>
|
|
||||||
<label label="⏮" />
|
<box vertical class="media-info-box" valign={Gtk.Align.CENTER} hexpand>
|
||||||
</button>
|
<label
|
||||||
<button
|
class="media-title"
|
||||||
class="media-btn"
|
label={mediaState.as(s => s.title)}
|
||||||
onClicked={() => execAsync("playerctl play-pause").catch(print)}
|
halign={Gtk.Align.FILL}
|
||||||
halign={Gtk.Align.CENTER}
|
xalign={0}
|
||||||
>
|
truncate
|
||||||
<label label={statusIcon} />
|
maxWidthChars={35}
|
||||||
</button>
|
css="font-size: 15px;"
|
||||||
<button
|
/>
|
||||||
class="media-btn"
|
<label
|
||||||
onClicked={() => execAsync("playerctl next").catch(print)}
|
class="media-artist"
|
||||||
halign={Gtk.Align.CENTER}
|
label={mediaState.as(s => s.artist)}
|
||||||
>
|
halign={Gtk.Align.FILL}
|
||||||
<label label="⏭" />
|
xalign={0}
|
||||||
</button>
|
truncate
|
||||||
<label class="media-text" label={mediaInfo} />
|
maxWidthChars={40}
|
||||||
|
css="font-size: 13px;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<slider
|
||||||
|
class="media-progress"
|
||||||
|
drawValue={false}
|
||||||
|
hexpand
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
value={mediaState.as(s => {
|
||||||
|
const len = s.length / 1000000;
|
||||||
|
return len > 0 ? Math.min(s.position / len, 1) : 0;
|
||||||
|
})}
|
||||||
|
marginTop={10}
|
||||||
|
marginBottom={10}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<box class="media-controls" spacing={15} halign={Gtk.Align.CENTER}>
|
||||||
|
<button
|
||||||
|
class="media-btn"
|
||||||
|
onClicked={() => execAsync("playerctl previous").catch(print)}
|
||||||
|
>
|
||||||
|
<label label="" css="font-size: 18px;" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="media-btn"
|
||||||
|
onClicked={() => execAsync("playerctl play-pause").catch(print)}
|
||||||
|
>
|
||||||
|
<label label={mediaState.as(s => s.status === "Playing" ? "" : "")} css="font-size: 22px;" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="media-btn"
|
||||||
|
onClicked={() => execAsync("playerctl next").catch(print)}
|
||||||
|
>
|
||||||
|
<label label="" css="font-size: 18px;" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
</box>
|
</box>
|
||||||
</window>
|
</window>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,57 @@
|
|||||||
|
Name = "blobBackgroundSelector"
|
||||||
|
NamePretty = "Blob's Background Selector"
|
||||||
|
Cache = false
|
||||||
|
HideFromProviderlist = true
|
||||||
|
SearchName = true
|
||||||
|
|
||||||
|
local function ShellEscape(s)
|
||||||
|
return "'" .. s:gsub("'", "'\\''") .. "'"
|
||||||
|
end
|
||||||
|
|
||||||
|
function FormatName(filename)
|
||||||
|
local name = filename:gsub("^%d+", ""):gsub("^%-", "")
|
||||||
|
name = name:gsub("%.[^%.]+$", "")
|
||||||
|
name = name: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 dirs = {
|
||||||
|
home .. "/wallpapers",
|
||||||
|
}
|
||||||
|
|
||||||
|
local seen = {}
|
||||||
|
|
||||||
|
for _, wallpaper_dir in ipairs(dirs) do
|
||||||
|
local handle = io.popen(
|
||||||
|
"find " .. ShellEscape(wallpaper_dir)
|
||||||
|
.. " -maxdepth 1 -type f \\( -name '*.jpg' -o -name '*.jpeg' -o -name '*.png' -o -name '*.gif' -o -name '*.bmp' -o -name '*.webp' \\) 2>/dev/null | sort"
|
||||||
|
)
|
||||||
|
if handle then
|
||||||
|
for background in handle:lines() do
|
||||||
|
local filename = background:match("([^/]+)$")
|
||||||
|
if filename and not seen[filename] then
|
||||||
|
seen[filename] = true
|
||||||
|
table.insert(entries, {
|
||||||
|
Text = FormatName(filename),
|
||||||
|
Value = filename,
|
||||||
|
Actions = {
|
||||||
|
activate = "blob_wallpaper " .. ShellEscape(background),
|
||||||
|
},
|
||||||
|
Preview = background,
|
||||||
|
PreviewType = "file",
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
handle:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return entries
|
||||||
|
end
|
||||||
@@ -32,3 +32,6 @@ layout {
|
|||||||
# Avoid overly wide single-window layouts on wide screens
|
# Avoid overly wide single-window layouts on wide screens
|
||||||
# single_window_aspect_ratio = 1 1
|
# single_window_aspect_ratio = 1 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Remove default window transparency
|
||||||
|
windowrule = opacity 1.0 override 1.0 override, match:tag default-opacity
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ echo ""
|
|||||||
|
|
||||||
compute_hash() {
|
compute_hash() {
|
||||||
if [ -d "$1" ]; then
|
if [ -d "$1" ]; then
|
||||||
find "$1" -type f -name '*.jsonc' -o -name '*.css' -o -name '*.txt' -o -name '*.sh' 2>/dev/null | sort | xargs -I{} sha256sum {} 2>/dev/null | sha256sum | cut -d' ' -f1
|
find "$1" -type f 2>/dev/null | sort | xargs -I{} sha256sum {} 2>/dev/null | sha256sum | cut -d' ' -f1
|
||||||
else
|
else
|
||||||
sha256sum "$1" 2>/dev/null | cut -d' ' -f1
|
sha256sum "$1" 2>/dev/null | cut -d' ' -f1
|
||||||
fi
|
fi
|
||||||
@@ -95,6 +95,34 @@ backup_and_copy() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_dependencies() {
|
||||||
|
echo "=== Checking Dependencies ==="
|
||||||
|
local deps_needed=()
|
||||||
|
if ! command -v wal &> /dev/null; then
|
||||||
|
deps_needed+=("python-pywal")
|
||||||
|
fi
|
||||||
|
if ! command -v magick &> /dev/null && ! command -v convert &> /dev/null; then
|
||||||
|
deps_needed+=("imagemagick")
|
||||||
|
fi
|
||||||
|
if ! command -v awww &> /dev/null; then
|
||||||
|
deps_needed+=("awww")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#deps_needed[@]} -gt 0 ]; then
|
||||||
|
echo "Installing missing dependencies: ${deps_needed[*]}"
|
||||||
|
if command -v sudo &> /dev/null; then
|
||||||
|
sudo pacman -S --needed --noconfirm "${deps_needed[@]}"
|
||||||
|
else
|
||||||
|
echo "Warning: sudo not found. Please install manually: ${deps_needed[*]}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "All dependencies are installed."
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
install_dependencies
|
||||||
|
|
||||||
echo "=== Checking for local changes ==="
|
echo "=== Checking for local changes ==="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -109,6 +137,7 @@ check_file "$SCRIPT_DIR/ags/app.ts" "$HOME_DIR/.config/ags/app.ts" "ags/app.ts"
|
|||||||
check_file "$SCRIPT_DIR/ags/style.css" "$HOME_DIR/.config/ags/style.css" "ags/style.css" || 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/widget/Media.tsx" "$HOME_DIR/.config/ags/widget/Media.tsx" "ags/widget/Media.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/waybar/style.css" "$HOME_DIR/.config/waybar/style.css" "waybar/style.css" || check_status=1
|
check_file "$SCRIPT_DIR/waybar/style.css" "$HOME_DIR/.config/waybar/style.css" "waybar/style.css" || check_status=1
|
||||||
|
check_file "$SCRIPT_DIR/elephant/menus/blob_background_selector.lua" "$HOME_DIR/.config/elephant/menus/blob_background_selector.lua" "elephant/menus/blob_background_selector.lua" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/branding/about.txt" "$HOME_DIR/.config/omarchy/branding/about.txt" "branding/about.txt" || check_status=1
|
check_file "$SCRIPT_DIR/branding/about.txt" "$HOME_DIR/.config/omarchy/branding/about.txt" "branding/about.txt" || check_status=1
|
||||||
check_file "$SCRIPT_DIR/branding/screensaver.txt" "$HOME_DIR/.config/omarchy/branding/screensaver.txt" "branding/screensaver.txt" || check_status=1
|
check_file "$SCRIPT_DIR/branding/screensaver.txt" "$HOME_DIR/.config/omarchy/branding/screensaver.txt" "branding/screensaver.txt" || check_status=1
|
||||||
|
|
||||||
@@ -143,7 +172,9 @@ backup_and_copy "$SCRIPT_DIR/waybar" "$HOME_DIR/.config/waybar" "Waybar config"
|
|||||||
backup_and_copy "$SCRIPT_DIR/ags" "$HOME_DIR/.config/ags" "AGS config"
|
backup_and_copy "$SCRIPT_DIR/ags" "$HOME_DIR/.config/ags" "AGS config"
|
||||||
backup_and_copy "$SCRIPT_DIR/hypr" "$HOME_DIR/.config/hypr" "Hyprland config"
|
backup_and_copy "$SCRIPT_DIR/hypr" "$HOME_DIR/.config/hypr" "Hyprland config"
|
||||||
backup_and_copy "$SCRIPT_DIR/branding" "$HOME_DIR/.config/omarchy/branding" "Branding files"
|
backup_and_copy "$SCRIPT_DIR/branding" "$HOME_DIR/.config/omarchy/branding" "Branding files"
|
||||||
|
backup_and_copy "$SCRIPT_DIR/elephant" "$HOME_DIR/.config/elephant" "Elephant configs"
|
||||||
backup_and_copy "$SCRIPT_DIR/omarchy/hooks" "$HOME_DIR/.config/omarchy/hooks" "Omarchy hooks"
|
backup_and_copy "$SCRIPT_DIR/omarchy/hooks" "$HOME_DIR/.config/omarchy/hooks" "Omarchy hooks"
|
||||||
|
backup_and_copy "$SCRIPT_DIR/wallpapers" "$HOME_DIR/wallpapers" "Custom wallpapers"
|
||||||
|
|
||||||
mkdir -p "$HOME_DIR/scripts"
|
mkdir -p "$HOME_DIR/scripts"
|
||||||
backup_and_copy "$SCRIPT_DIR/scripts" "$HOME_DIR/scripts" "Custom scripts"
|
backup_and_copy "$SCRIPT_DIR/scripts" "$HOME_DIR/scripts" "Custom scripts"
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Default to the branding image if no argument is provided
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
IMAGE_PATH="$HOME/Documents/dotfiles/branding/boot_flash.png"
|
||||||
|
else
|
||||||
|
IMAGE_PATH=$(realpath "$1")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$IMAGE_PATH" ]; then
|
||||||
|
echo "Error: File '$IMAGE_PATH' does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Applying boot splash image: $IMAGE_PATH"
|
||||||
|
echo "This requires sudo privileges."
|
||||||
|
|
||||||
|
# Copy the image to the Plymouth theme directory
|
||||||
|
sudo cp "$IMAGE_PATH" /usr/share/plymouth/themes/omarchy/logo.png
|
||||||
|
|
||||||
|
# Ensure the correct permissions
|
||||||
|
sudo chmod 644 /usr/share/plymouth/themes/omarchy/logo.png
|
||||||
|
|
||||||
|
echo "Rebuilding initramfs..."
|
||||||
|
# Exclusively use mkinitcpio for GRUB compatibility
|
||||||
|
sudo mkinitcpio -P
|
||||||
|
|
||||||
|
echo "Boot splash successfully updated!"
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
blob_color_fixer.py — Detects monotone/bland wal color palettes and replaces
|
||||||
|
accent colors with vibrant, harmonically-spread alternatives.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
import colorsys
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
MIN_SATURATION = 0.65
|
||||||
|
MIN_LIGHTNESS = 0.40
|
||||||
|
MAX_LIGHTNESS = 0.70
|
||||||
|
HUE_THRESHOLD = 0.15
|
||||||
|
SAT_THRESHOLD = 0.35
|
||||||
|
HUE_VARIANCE_THRESHOLD = 0.15
|
||||||
|
ACCENT_SLICE = slice(1, 7)
|
||||||
|
BRIGHT_OFFSET = 8
|
||||||
|
|
||||||
|
HUE_SHIFTS: list[float] = [
|
||||||
|
0.0,
|
||||||
|
0.5,
|
||||||
|
0.083,
|
||||||
|
-0.083,
|
||||||
|
0.416,
|
||||||
|
-0.416,
|
||||||
|
]
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class HLS:
|
||||||
|
h: float
|
||||||
|
l: float
|
||||||
|
s: float
|
||||||
|
|
||||||
|
|
||||||
|
def hex_to_rgb(hex_str: str) -> tuple[float, float, float]:
|
||||||
|
hex_str = hex_str.lstrip("#")
|
||||||
|
return tuple(int(hex_str[i : i + 2], 16) / 255.0 for i in (0, 2, 4)) # type: ignore[return-value]
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_to_hex(r: float, g: float, b: float) -> str:
|
||||||
|
return "#{:02x}{:02x}{:02x}".format(int(r * 255), int(g * 255), int(b * 255))
|
||||||
|
|
||||||
|
|
||||||
|
def hex_to_hls(hex_str: str) -> HLS:
|
||||||
|
h, l, s = colorsys.rgb_to_hls(*hex_to_rgb(hex_str))
|
||||||
|
return HLS(h, l, s)
|
||||||
|
|
||||||
|
|
||||||
|
def vibrant_shift(hex_str: str, hue_shift: float) -> str:
|
||||||
|
"""Return *hex_str* with its hue rotated by *hue_shift* and vibrancy enforced."""
|
||||||
|
hls = hex_to_hls(hex_str)
|
||||||
|
h = (hls.h + hue_shift) % 1.0
|
||||||
|
s = max(hls.s, MIN_SATURATION)
|
||||||
|
l = min(max(hls.l, MIN_LIGHTNESS), MAX_LIGHTNESS)
|
||||||
|
return rgb_to_hex(*colorsys.hls_to_rgb(h, l, s))
|
||||||
|
|
||||||
|
|
||||||
|
def palette_is_bland(accents: list[str]) -> bool:
|
||||||
|
"""Return True when accent colors are too similar, clustered, or too desaturated."""
|
||||||
|
stats = [hex_to_hls(c) for c in accents]
|
||||||
|
hues = [hls.h for hls in stats]
|
||||||
|
sats = [hls.s for hls in stats]
|
||||||
|
|
||||||
|
raw_spread = max(hues) - min(hues)
|
||||||
|
hue_spread = min(raw_spread, 1.0 - raw_spread)
|
||||||
|
avg_sat = sum(sats) / len(sats)
|
||||||
|
|
||||||
|
if hue_spread < HUE_THRESHOLD or avg_sat < SAT_THRESHOLD:
|
||||||
|
return True
|
||||||
|
|
||||||
|
mean_sin = sum(math.sin(2 * math.pi * h) for h in hues) / len(hues)
|
||||||
|
mean_cos = sum(math.cos(2 * math.pi * h) for h in hues) / len(hues)
|
||||||
|
hue_variance = 1.0 - math.sqrt(mean_sin ** 2 + mean_cos ** 2)
|
||||||
|
if hue_variance < HUE_VARIANCE_THRESHOLD:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def most_saturated(accents: list[str]) -> str:
|
||||||
|
return max(accents, key=lambda c: hex_to_hls(c).s)
|
||||||
|
|
||||||
|
|
||||||
|
def load_colors(filepath: str) -> list[str]:
|
||||||
|
try:
|
||||||
|
with open(filepath) as f:
|
||||||
|
colors = [line.strip() for line in f if line.strip()]
|
||||||
|
except OSError as exc:
|
||||||
|
sys.exit(f"Error reading '{filepath}': {exc}")
|
||||||
|
|
||||||
|
if len(colors) < 16:
|
||||||
|
sys.exit(f"Expected ≥ 16 colors in '{filepath}', found {len(colors)}.")
|
||||||
|
|
||||||
|
return colors
|
||||||
|
|
||||||
|
|
||||||
|
def save_colors(filepath: str, colors: list[str]) -> None:
|
||||||
|
try:
|
||||||
|
with open(filepath, "w") as f:
|
||||||
|
f.write("\n".join(colors) + "\n")
|
||||||
|
except OSError as exc:
|
||||||
|
sys.exit(f"Error writing '{filepath}': {exc}")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
sys.exit("Usage: blob_color_fixer.py <path_to_wal_colors>")
|
||||||
|
|
||||||
|
filepath = sys.argv[1]
|
||||||
|
colors = load_colors(filepath)
|
||||||
|
accents = colors[ACCENT_SLICE]
|
||||||
|
|
||||||
|
if not palette_is_bland(accents):
|
||||||
|
print("Palette is already vibrant and diverse — nothing to do.")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("Monotone / bland palette detected. Generating vibrant harmony…")
|
||||||
|
|
||||||
|
base = most_saturated(accents)
|
||||||
|
new_accents = [vibrant_shift(base, shift) for shift in HUE_SHIFTS]
|
||||||
|
|
||||||
|
for i, color in enumerate(new_accents):
|
||||||
|
colors[i + 1] = color
|
||||||
|
colors[i + 1 + BRIGHT_OFFSET] = color
|
||||||
|
|
||||||
|
save_colors(filepath, colors)
|
||||||
|
print("Done — colors enhanced and written back to file.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Configuration files to modify
|
||||||
|
ACTIVE_CONF="$HOME/.config/hypr/looknfeel.conf"
|
||||||
|
REPO_CONF="$HOME/Documents/dotfiles/hypr/looknfeel.conf"
|
||||||
|
|
||||||
|
# The rule to enforce solid opacity
|
||||||
|
OVERRIDE_RULE="windowrule = opacity 1.0 override 1.0 override, match:tag default-opacity"
|
||||||
|
# A marker comment
|
||||||
|
MARKER="# Remove default window transparency"
|
||||||
|
|
||||||
|
ACTION=$1
|
||||||
|
|
||||||
|
if [ -z "$ACTION" ]; then
|
||||||
|
ACTION="toggle"
|
||||||
|
fi
|
||||||
|
|
||||||
|
enable_glass() {
|
||||||
|
# Remove the rules
|
||||||
|
sed -i "/$MARKER/d" "$ACTIVE_CONF" "$REPO_CONF" 2>/dev/null
|
||||||
|
sed -i "/opacity 1.0 override/d" "$ACTIVE_CONF" "$REPO_CONF" 2>/dev/null
|
||||||
|
echo "Transparency enabled (glass on)."
|
||||||
|
hyprctl reload >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_glass() {
|
||||||
|
# Add the rules if they don't exist
|
||||||
|
if ! grep -q "opacity 1.0 override" "$ACTIVE_CONF"; then
|
||||||
|
echo -e "\n$MARKER\n$OVERRIDE_RULE" >> "$ACTIVE_CONF"
|
||||||
|
echo -e "\n$MARKER\n$OVERRIDE_RULE" >> "$REPO_CONF"
|
||||||
|
fi
|
||||||
|
echo "Transparency disabled (glass off)."
|
||||||
|
hyprctl reload >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "$ACTION" == "on" ]; then
|
||||||
|
enable_glass
|
||||||
|
elif [ "$ACTION" == "off" ]; then
|
||||||
|
disable_glass
|
||||||
|
elif [ "$ACTION" == "toggle" ]; then
|
||||||
|
if grep -q "opacity 1.0 override" "$ACTIVE_CONF"; then
|
||||||
|
enable_glass
|
||||||
|
else
|
||||||
|
disable_glass
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Usage: blob_glass [on|off|toggle]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,21 +1,99 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
WALLPAPER_DIR="$HOME/wallpapers"
|
||||||
|
THEME_DIR="$HOME/.config/omarchy/themes/blob-dynamic"
|
||||||
|
|
||||||
|
# Create the directory if it doesn't exist
|
||||||
|
mkdir -p "$WALLPAPER_DIR"
|
||||||
|
mkdir -p "$THEME_DIR/backgrounds"
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
if [ -z "$1" ]; then
|
||||||
echo "Usage: blob_wallpaper <path-to-image>"
|
# Use walker dmenu for GUI selection
|
||||||
exit 1
|
omarchy-launch-walker -m menus:blobBackgroundSelector --width 800 --minheight 400 -p "Select Wallpaper…"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
# Check if the argument is a file in the wallpapers directory
|
||||||
|
if [ -f "$WALLPAPER_DIR/$1" ]; then
|
||||||
|
IMAGE_PATH=$(realpath "$WALLPAPER_DIR/$1")
|
||||||
|
# Check if the argument is an absolute or relative path
|
||||||
|
elif [ -f "$1" ]; then
|
||||||
|
IMAGE_PATH=$(realpath "$1")
|
||||||
|
else
|
||||||
|
echo "Error: File '$1' does not exist in $WALLPAPER_DIR or as a valid path."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
IMAGE_PATH=$(realpath "$1")
|
echo "Extracting colors using Pywal..."
|
||||||
|
wal -i "$IMAGE_PATH" -n -q 2> >(grep -v "deprecated in IMv7" >&2)
|
||||||
|
|
||||||
if [ ! -f "$IMAGE_PATH" ]; then
|
# Enhance colors if the palette is too monotone
|
||||||
echo "Error: File '$IMAGE_PATH' does not exist."
|
python3 "$HOME/scripts/blob_color_fixer.py" "$HOME/.cache/wal/colors"
|
||||||
exit 1
|
|
||||||
|
# Clear old backgrounds and copy the new one into the dynamic theme
|
||||||
|
rm -f "$THEME_DIR/backgrounds/"*
|
||||||
|
cp "$IMAGE_PATH" "$THEME_DIR/backgrounds/"
|
||||||
|
|
||||||
|
# Parse pywal colors and write to colors.toml in the dynamic theme
|
||||||
|
cat <<EOF > "$THEME_DIR/colors.toml"
|
||||||
|
accent = "$(sed -n '2p' ~/.cache/wal/colors)"
|
||||||
|
cursor = "$(sed -n '8p' ~/.cache/wal/colors)"
|
||||||
|
foreground = "$(sed -n '8p' ~/.cache/wal/colors)"
|
||||||
|
background = "$(sed -n '1p' ~/.cache/wal/colors)"
|
||||||
|
selection_foreground = "$(sed -n '1p' ~/.cache/wal/colors)"
|
||||||
|
selection_background = "$(sed -n '2p' ~/.cache/wal/colors)"
|
||||||
|
|
||||||
|
color0 = "$(sed -n '1p' ~/.cache/wal/colors)"
|
||||||
|
color1 = "$(sed -n '2p' ~/.cache/wal/colors)"
|
||||||
|
color2 = "$(sed -n '3p' ~/.cache/wal/colors)"
|
||||||
|
color3 = "$(sed -n '4p' ~/.cache/wal/colors)"
|
||||||
|
color4 = "$(sed -n '5p' ~/.cache/wal/colors)"
|
||||||
|
color5 = "$(sed -n '6p' ~/.cache/wal/colors)"
|
||||||
|
color6 = "$(sed -n '7p' ~/.cache/wal/colors)"
|
||||||
|
color7 = "$(sed -n '8p' ~/.cache/wal/colors)"
|
||||||
|
color8 = "$(sed -n '9p' ~/.cache/wal/colors)"
|
||||||
|
color9 = "$(sed -n '10p' ~/.cache/wal/colors)"
|
||||||
|
color10 = "$(sed -n '11p' ~/.cache/wal/colors)"
|
||||||
|
color11 = "$(sed -n '12p' ~/.cache/wal/colors)"
|
||||||
|
color12 = "$(sed -n '13p' ~/.cache/wal/colors)"
|
||||||
|
color13 = "$(sed -n '14p' ~/.cache/wal/colors)"
|
||||||
|
color14 = "$(sed -n '15p' ~/.cache/wal/colors)"
|
||||||
|
color15 = "$(sed -n '16p' ~/.cache/wal/colors)"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Write neovim.lua to satisfy LazyVim's theme symlink requirement
|
||||||
|
cat <<EOF > "$THEME_DIR/neovim.lua"
|
||||||
|
return {
|
||||||
|
{
|
||||||
|
"LazyVim/LazyVim",
|
||||||
|
opts = {
|
||||||
|
colorscheme = "tokyonight",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Apply the Blob-Dynamic theme
|
||||||
|
# omarchy-theme-set manages the background and reloads waybar and AGS
|
||||||
|
omarchy-theme-set "blob-dynamic"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Start awww daemon if not running
|
||||||
|
if ! pgrep -x awww-daemon >/dev/null; then
|
||||||
|
awww-daemon >/dev/null 2>&1 &
|
||||||
|
sleep 1
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Point the Omarchy background link to your custom image
|
echo "Wallpaper and dynamic theme applied successfully: $IMAGE_PATH"
|
||||||
ln -nsf "$IMAGE_PATH" "$HOME/.config/omarchy/current/background"
|
|
||||||
|
|
||||||
# Relaunch swaybg smoothly
|
|
||||||
pkill -x swaybg
|
|
||||||
setsid uwsm-app -- swaybg -i "$HOME/.config/omarchy/current/background" -m fill >/dev/null 2>&1 &
|
|
||||||
|
|
||||||
echo "Wallpaper updated to: $IMAGE_PATH"
|
|
||||||
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 728 KiB |
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 663 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 347 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.8 MiB |
|
After Width: | Height: | Size: 6.2 MiB |
|
After Width: | Height: | Size: 854 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 502 KiB |
|
After Width: | Height: | Size: 132 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 61 KiB |