Compare commits

..

18 Commits

Author SHA1 Message Date
sam_oneal 2d5a4e9f4c updated waybar power menu list 2026-09-07 20:13:04 -06:00
sam_oneal 8d2aa49668 updated to latest submodule 2026-09-07 20:01:50 -06:00
sam_oneal f60b7e263b hyprland: SUPER+Right creates a new workspace past the end of a monitor
m+1/m-1 only cycle existing workspaces on the active monitor and wrap at
the edges, so SUPER+Right never created a workspace. Replace them with a
Lua helper: advancing past the last workspace on the monitor now creates
a fresh (empty) workspace there; going before the first wraps to the
monitor's last workspace.
2026-09-07 18:26:27 -06:00
sam_oneal b148fcc256 hyprland: add SUPER+arrow workspace cycling and overview binds
SUPER+Left/Right cycle existing workspaces on the active monitor
(m+1/m-1 with cycling enabled), SUPER+Up opens the hyprshell overview
scoped to the current monitor (create/manage workspaces there), and
SUPER+Down closes the overview again. Adds binds-help.sh cheat sheet
(SHIFT+SUPER+H) and overview-close.sh helper.
2026-09-07 18:14:27 -06:00
sam_oneal 09c8706f35 fixed issue with lid closing was not being detected 2026-09-07 15:45:44 -06:00
sam_oneal 9d788d42e1 changed how closing windows worked; updated how the cloudflare access service works 2026-08-31 19:54:42 -06:00
sam_oneal 086097f2a9 waybar: add weather module to bar
Add custom/weather module between battery and clock, polling
weather.sh every 10 minutes.
2026-08-18 08:03:33 -06:00
sam_oneal 1755d17558 hyprlock: disable fingerprint, add battery/weather/notifications
- Disable fingerprint auth (GDM holds the device; hyprlock can't
  claim it, blocking the auth thread).
- Fix date format escaping (single quotes).
- Add battery, weather, and latest mako notification labels.
- Add hyprlock wrapper (crash guard), battery script, notifications
  script, and weather script.
2026-08-18 08:02:50 -06:00
sam_oneal 9e310d2f58 hyprland: prepend PATH, gate idle inhibitors, allow lock restore
- Prepend ~/.local/bin to PATH so user scripts (hyprlock wrapper,
  weather.sh) resolve before system binaries.
- Gate dpms-off and suspend on lock screen running (condition_cmd)
  so idle inhibitors from apps work while unlocked but screen still
  blanks and machine suspends after lock.
- Enable allow_session_lock_restore so a crashed lock client can be
  replaced.
- Temporarily enable debug logging to diagnose re-lock issues.
2026-08-18 08:02:16 -06:00
sam_oneal 21dbb16e8b zsh: add pv-backed mv/cp wrappers with progress and dir creation
Override mv and cp to pipe large transfers through pv for progress.
Directories are streamed via tar+pv; single files go through pv
directly. Parent directories are created (mkdir -p) when the
destination path doesn't exist yet.
2026-08-18 08:01:05 -06:00
sam_oneal 5cfa02ea6b make: exclude local-only packages and use --restow for idempotency
Filter out resources and wluma from PACKAGES since they are
local-only and should not be stowed. Use stow --restow so make link
can be run repeatedly without conflicts from pre-existing symlinks.
2026-08-18 08:00:40 -06:00
sam_oneal f57dc1db3c added ai sessions to gitignore 2026-08-14 23:43:44 -06:00
sam_oneal 260b202d6f update readme with current packages; add AGENTS.md for AI guidance 2026-08-14 21:35:39 -06:00
renovate 804ec54090 Add renovate.json 2026-08-15 03:28:59 +00:00
sam_oneal 6e1ed76eed additional work to setup hyprland 2026-08-14 21:27:58 -06:00
sam_oneal 2b7b18c66d manage monitors with monique; reduce hypr-lid to lock/suspend timers
Add the monique GUI/daemon for monitor arrangement, hotplug profiles and
clamshell handling (writes monitors.lua, keybind SUPER+SHIFT+M, stowed
settings). hypr-lid is trimmed to hypr-lid-power: display management moved
to moniqued, it now only runs the delayed lock/suspend timers when the lid
is closed undocked. Update logind drop-in comments accordingly.
2026-08-14 21:25:38 -06:00
sam_oneal db72a5a635 add hypr-lid service for clamshell and delayed lid sleep 2026-08-14 18:16:31 -06:00
sam_oneal 96c4d36714 migrated hyprland config from hyprlang to lua; added mocha palette as lua 2026-08-14 17:33:25 -06:00
53 changed files with 1453 additions and 283 deletions
+1
View File
@@ -1 +1,2 @@
sunsetr/.config/sunsetr/geo.toml
resources/ai/sessions
+74
View File
@@ -0,0 +1,74 @@
# AGENTS.md
Guidance for AI agents (and humans) working on this repo. This file documents
how AI assistance has been used on this project and the expectations for future
sessions.
## What this repo is
A set of GNU-stow packages containing the user's machine configuration
(Hyprland desktop, terminal, shell, systemd units, scripts, wallpapers). Each
top-level directory is a stow package rooted at `$HOME` (or `/` for system
dirs like `etc`). See `README.md` for the full package table and quick start.
## How AI has been used here
All prior work is captured in session transcripts under
`resources/ai/sessions/`. The main efforts so far:
- **Hyprland migration to Lua** (`session-ses_ffd7.md`): converted the
hyprlang `.conf` configs to Lua configs for Hyprland 0.56.2, migrated
plugins and themes (Catppuccin Mocha palette lives in `hyprmocha/`).
- **uwsm/sddm sign-in debugging** (`session-ses_ffd3.md`): diagnosed why the
uwsm Hyprland session would not allow sign-in; added a `sudo-askpass.sh`
(zenity prompt via `SUDO_ASKPASS`) and disabled linger.
- **Performance work** (`session-ses_ffcb.md`): profiled Hyprland CPU usage,
identified the animated `borderangle` loop as the culprit; added mako
notifications and waybar modules.
- **Monitor management** (`session-ses_ffc9.md`): adopted
[monique](https://github.com/notwidow/monique) for arranging multiple
monitors, including clamshell mode and the `hypr-lid-power` service
(`hyprland/.config/systemd/user/hypr-lid-power.service`).
- **Cloudflare tunnel + secrets** (`session-ses_0098.md`): wired the
`cloudflared-k8s` service to pull the Cloudflare Access service token from
the Homelab 1Password vault at runtime via `op run` — it is never written to
disk.
## Rules for agents
1. **Never commit secrets.** No API keys, tokens, passwords, or private keys
may be committed. Use 1Password references (`op://Vault/Item/...`) and
inject at runtime with `op run -- ...`. `.secrets.baseline` + `detect-secrets`
(via pre-commit) is the guardrail; keep it updated (`detect-secrets scan`
and re-baseline if a benign false positive is added).
2. **Fit every change into the stow layout.** Config files must live under a
package directory mirroring their destination (e.g. a new app config goes
under `<pkg>/.config/<app>/`). After changes, the package must be
committable and stowable (`make link` / `make unlink` must keep working).
3. **`resources/` is untracked and must stay untracked.** Session transcripts
under `resources/ai/sessions/` can contain sensitive values (e.g. a
Cloudflare client secret shown during a session). Do not stage, add, or
commit anything under `resources/`. Note `wluma/` is likewise local-only.
4. **Hyprland config is Lua, not hyprlang.** All `hyprland/.config/hypr/`
configs are `.lua` files driven by `hyprland.lua`. Do not reintroduce
`.conf`/hyprlang syntax.
5. **`nvim/` is a private submodule** pointing at a self-hosted Gitea/GitLab
repo; never replace it with a public fork and don't commit submodule state
changes unless intended. Pull with `make pull-updates`.
6. **Behave like the user's own assistant on this machine.** These dotfiles
reference real hosts (`sams_laptop`, `k8s.samoneal.io`, `git-ssh.samoneal.io`)
and real 1Password vaults — work with them, but keep the repo portable by
leaving secrets and machine-specific absolute paths in comments rather than
committed values where practical.
7. **Verify before finishing.** Run `git status`/`git diff` to confirm only
intended files changed and nothing sensitive is staged. If unsure whether a
value is a secret, treat it as one.
## Workflow
- Pre-commit hooks: `pre-commit install` (runs trailing-whitespace +
detect-secrets).
- Lint/typecheck: this repo is plain config + shell; shell scripts should stay
`sh`/`bash`-compatible and pass `shellcheck` if available.
- Record substantial AI sessions: if a session materially changes this repo,
save the transcript under `resources/ai/sessions/` (keep it untracked).
+3 -3
View File
@@ -1,6 +1,6 @@
HOME_TARGET ?= $(HOME)
SYSTEM_DIRS := etc usr var opt boot
PACKAGES := $(patsubst %/,%,$(wildcard */))
PACKAGES := $(filter-out resources wluma,$(patsubst %/,%,$(wildcard */)))
.PHONY: check-stow pull-updates unlink link
@@ -77,10 +77,10 @@ link: check-stow
done; \
if [ "$$is_sys" -eq 1 ]; then \
echo " [System] Stowing $$pkg/$$sub to $$sub"; \
sudo stow -d "$$pkg" -t "/$$sub" "$$sub"; \
sudo stow --restow -d "$$pkg" -t "/$$sub" "$$sub"; \
else \
echo " [USER] Stowing $$pkg/$$sub to $(HOME_TARGET)/$$sub"; \
stow -d "$$pkg" -t "$(HOME_TARGET)/$$sub" "$$sub"; \
stow --restow -d "$$pkg" -t "$(HOME_TARGET)/$$sub" "$$sub"; \
fi; \
done; \
done
+76 -9
View File
@@ -1,13 +1,80 @@
# Dotfiles
All the configuration files for my current machine setup.
All the configuration files for my current machine setup. Managed with GNU
stow so every package maps directly onto the relevant `~/.config`, `~/.local`,
`/etc`, etc. paths.
## Configurations
## Requirements
- Ghostty
- Hyprland
- Local
- NVIM
- SSH
- SystemD
- ZSH
- [GNU stow](https://www.gnu.org/software/stow/)
- `make`
- [op (1Password CLI)](https://developer.1password.com/docs/cli/) for anything
that needs a secret
## Quick start
```sh
# Install stow if missing (auto-detects Arch/Debian/macOS)
make check-stow
# Link every package into $HOME (system dirs like /etc are stowed with sudo)
make link
# Unlink everything again
make unlink
# Pull main plus submodules
make pull-updates
```
## Packages
| Package | Path | Description |
| ----------- | -------------- | ------------------------------------------------------- |
| `backgrounds` | `~/.config/backgrounds` | Wallpaper collection (incl. Dynamic-Wallpapers) |
| `ghostty` | `~/.config/ghostty` | Ghostty terminal config |
| `hosts` | `~/.config/hosts` | Custom `HOSTALIASES` file (`HOSTALIASES=~/.config/hosts`) |
| `hyprland` | `~/.config/hypr`, `~/.local/bin` | Hyprland config (Lua), scripts, `hypr-lid-power` service |
| `hyprlock` | `~/.config/hypr` | Lockscreen config |
| `hyprmocha` | `~/.config/hypr` | Catppuccin Mocha palette as Lua |
| `hyprpaper` | `~/.config/hypr` | Wallpaper daemon config |
| `hyprshell` | `~/.config/hyprshell` | Launcher/overview (Rust, RON config + CSS) |
| `local` | `~/.local/bin` | Local scripts (e.g. `fetch-nas-credentials.sh`) |
| `mako` | `~/.config/mako` | Notifications |
| `monique` | `~/.config/monique` | Monitor/clamshell arrangement |
| `nvim` | `~/.config/nvim` | Git submodule (private repo) |
| `rofi` | `~/.config/rofi` | Launcher (Catppuccin Mocha) |
| `ssh` | `~/.ssh/config` | SSH config (1Password agent + cloudflared proxy) |
| `sunsetr` | `~/.config/sunsetr` | Screen color temperature (`geo.toml` is gitignored) |
| `systemd` | `~/.config/systemd/user`, `/etc/systemd` | User + system units (cloudflared, rclone/GDrive, NAS mounts, 1Password sign-in) |
| `waybar` | `~/.config/waybar` | Top bar |
| `wofi` | `~/.config/wofi` | App menu |
| `yay` | `~/.config/yay` | AUR helper config |
| `zsh` | `~/.zshrc` | Zsh config |
`wluma/` and `resources/` exist locally but are **not** committed (see
`AGENTS.md`).
## Secrets management
No secrets are committed to this repo. Anything secret goes through
[1Password](https://1password.com):
- References look like `op://Vault/Item/field[/section]` (e.g. the Cloudflare
service token in `systemd/.config/systemd/user/cloudflared-k8s.service`).
- Services inject secrets at runtime with `op run -- ...`, so values never hit
disk or git.
- `fetch-nas-credentials.sh` writes fetched NAS credentials to
`~/.nas/.smbcredentials` (chmod 600) at boot.
- `detect-secrets` runs via `pre-commit` against `.secrets.baseline`.
## Submodules
- `nvim/.config/nvim``git@git-ssh.samoneal.io:2222/sam_oneal/nvim-config.git`
(private). Pull with `make pull-updates`.
## AI assistance
This repo is developed with AI assistance. See `AGENTS.md` for how AI has been
used, the conventions it must follow, and where session transcripts are kept
(`resources/ai/sessions/`).
-76
View File
@@ -1,76 +0,0 @@
# Sound through pactl
bind = , XF86AudioRaiseVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ +10%
bind = , XF86AudioLowerVolume, exec, pactl set-sink-volume @DEFAULT_SINK@ -10%
bind = , XF86AudioMut, exec, pactl set-sink-mute @DEFAULT_SINK@ toggle
bind = , XF86AudioPlay, exec, playerctl play-pause
bind = , XF86AudioPrev, exec, playerctl previous
bind = , XF86AudioNext, exec, playerctl next
# Brightness through brightnessctl
bind = , XF86MonBrightnessUp, exec, brightnessctl set +5%
bind = , XF86MonBrightnessDown, exec, brightnessctl set 5%-
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
bind = $mainMod, T, exec, $terminal
bind = $mainMod, B, exec, $browser
bind = $mainMod, F, exec, $fileManager
bind = $mainMod, C, killactive,
bind = $mainMod, M, exit,
bind = $mainMod, V, togglefloating,
bind = $mainMod, space, exec, $menu
bind = $SUPER_SHIFT, l, exec, hyprlock
bind = $SUPER_SHIFT, R, exec, hyprctl reload
bind = $SUPER_SHIFT, D, exec, hyprctl keyword monitor eDP-1, disable
bind = $SUPER_SHIFT, F, exec, hyprctl keyword monitor eDP-1, enable
bind = , Print, exec, grim -c -o "$(hyprctl activeworkspace -j | jq -r '.monitor')" $(xdg-user-dir PICTURES)/Screenshots/$(date +'%s_grim.png')
bind = shift, Print, exec, grim -g "$(slurp)" $(xdg-user-dir PICTURES)/Screenshots/$(date +'%s_grim.png')
bind = $mainMod, P, pseudo, # dwindle
bind = $mainMod, E, togglesplit, # dwindle
bind = $mainMod CTRL, F, fullscreen, # dwindle
bind = $mainMod, W, togglegroup, # dwindle
# Move focus with mainMod + arrow keys
bind = $mainMod, h, movefocus, l
bind = $mainMod, l, movefocus, r
bind = $mainMod, k, movefocus, u
bind = $mainMod, j, movefocus, d
# Switch workspaces with mainMod + [0-9]
bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
bind = $mainMod, 4, workspace, 4
bind = $mainMod, 5, workspace, 5
bind = $mainMod, 6, workspace, 6
bind = $mainMod, 7, workspace, 7
bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move active window to a workspace with mainMod + SHIFT + [0-9]
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3
bind = $mainMod SHIFT, 4, movetoworkspace, 4
bind = $mainMod SHIFT, 5, movetoworkspace, 5
bind = $mainMod SHIFT, 6, movetoworkspace, 6
bind = $mainMod SHIFT, 7, movetoworkspace, 7
bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic
bind = $mainMod SHIFT, S, movetoworkspace, special:magic
# Scroll through existing workspaces with mainMod + scroll
bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1
# Move/resize windows with mainMod + LMB/RMB and dragging
bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow
# Laptop Lid for opening and close
bindl = , switch:on:Lid Switch, exec, hyprctl dpms off && hyprctl dispatch exec hyprlock
bindl = , switch:off:Lid Switch, exec, hyprctl dispatch dpms on
+119
View File
@@ -0,0 +1,119 @@
-- See https://wiki.hypr.land/Configuring/Basics/Binds/ for more
-- Sound through pactl
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("pactl set-sink-volume @DEFAULT_SINK@ +10%"), { desc = "Raise volume" })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("pactl set-sink-volume @DEFAULT_SINK@ -10%"), { desc = "Lower volume" })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("pactl set-sink-mute @DEFAULT_SINK@ toggle"), { desc = "Mute audio" })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"), { desc = "Play/pause media" })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"), { desc = "Previous track" })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"), { desc = "Next track" })
-- Brightness through brightnessctl
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl set +5%"), { desc = "Increase brightness" })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl set 5%-"), { desc = "Decrease brightness" })
-- Main modifier binds
hl.bind(mainMod .. " + T", hl.dsp.exec_cmd(terminal), { desc = "Open terminal" })
hl.bind(mainMod .. " + B", hl.dsp.exec_cmd(browser), { desc = "Open browser" })
hl.bind(mainMod .. " + F", hl.dsp.exec_cmd(fileManager), { desc = "Open file manager" })
hl.bind(mainMod .. " + C", hl.dsp.window.close(), { desc = "Close active window" })
hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd("hyprctl dispatch closewindow \"class:$(hyprctl activewindow -j | jq -r .class)\""), { desc = "Close active window (by class)" })
hl.bind(mainMod .. " + M", hl.dsp.exit(), { desc = "Exit Hyprland" })
hl.bind(mainMod .. " + V", hl.dsp.window.float({ action = "toggle" }), { desc = "Toggle floating" })
hl.bind(mainMod .. " + space", hl.dsp.exec_cmd(menu), { desc = "Open app menu" })
hl.bind(SUPER_SHIFT .. " + L", hl.dsp.exec_cmd("hyprlock"), { desc = "Lock screen" })
hl.bind(SUPER_SHIFT .. " + R", hl.dsp.exec_cmd("hyprctl reload"), { desc = "Reload config" })
-- Monique: GUI monitor arrangement/display settings
hl.bind(SUPER_SHIFT .. " + M", hl.dsp.exec_cmd("monique"), { desc = "Open monique (monitor settings)" })
hl.bind(SUPER_SHIFT .. " + D", hl.dsp.exec_cmd("hyprctl keyword monitor eDP-1, disable"), { desc = "Disable eDP-1 (clamshell)" })
hl.bind(SUPER_SHIFT .. " + F", hl.dsp.exec_cmd("hyprctl keyword monitor eDP-1, enable"), { desc = "Enable eDP-1" })
-- Screenshots
hl.bind("Print", hl.dsp.exec_cmd("grim -c -o \"$(hyprctl activeworkspace -j | jq -r '.monitor')\" $(xdg-user-dir PICTURES)/Screenshots/$(date +'%s_grim.png')"), { desc = "Screenshot active monitor" })
hl.bind("SHIFT + Print", hl.dsp.exec_cmd("grim -g \"$(slurp)\" $(xdg-user-dir PICTURES)/Screenshots/$(date +'%s_grim.png')"), { desc = "Screenshot region" })
-- Dwindle layout
hl.bind(mainMod .. " + P", hl.dsp.window.pseudo(), { desc = "Toggle pseudo tiling" }) -- dwindle
hl.bind(mainMod .. " + E", hl.dsp.layout("togglesplit"), { desc = "Toggle split" }) -- dwindle
hl.bind(mainMod .. " + CTRL + F", hl.dsp.window.fullscreen({ action = "toggle" }), { desc = "Toggle fullscreen" }) -- dwindle
hl.bind(mainMod .. " + W", hl.dsp.group.toggle(), { desc = "Toggle window group" }) -- dwindle
-- Move focus with mainMod + vim keys
hl.bind(mainMod .. " + H", hl.dsp.focus({ direction = "l" }), { desc = "Focus left" })
hl.bind(mainMod .. " + L", hl.dsp.focus({ direction = "r" }), { desc = "Focus right" })
hl.bind(mainMod .. " + K", hl.dsp.focus({ direction = "u" }), { desc = "Focus up" })
hl.bind(mainMod .. " + J", hl.dsp.focus({ direction = "d" }), { desc = "Focus down" })
-- Cycle workspaces on the active monitor with mainMod + arrows. Advancing past
-- the last workspace on the monitor creates a fresh (empty) workspace there;
-- going before the first wraps around to the last one on the monitor.
local function step_workspace(dir)
local mon = hl.get_active_monitor()
if not mon then return end
local current = mon.active_workspace.id
local mon_ids, all_max = {}, 0
for _, ws in ipairs(hl.get_workspaces()) do
if ws.id > all_max then all_max = ws.id end
if ws.monitor and ws.monitor.id == mon.id then
table.insert(mon_ids, ws.id)
end
end
table.sort(mon_ids)
local target
if dir > 0 then
for _, id in ipairs(mon_ids) do
if id > current then target = id break end
end
-- No next workspace on this monitor: create one right here.
if not target then target = all_max + 1 end
else
for i = #mon_ids, 1, -1 do
if mon_ids[i] < current then target = mon_ids[i] break end
end
-- No previous workspace on this monitor: wrap to the last one.
if not target and #mon_ids > 0 then target = mon_ids[#mon_ids] end
end
if target then hl.dispatch(hl.dsp.focus({ workspace = target })) end
end
hl.bind(mainMod .. " + Left", function() step_workspace(-1) end, { desc = "Previous workspace (active monitor)" })
hl.bind(mainMod .. " + Right", function() step_workspace(1) end, { desc = "Next workspace (active monitor); creates one past the end" })
-- Switch workspaces with mainMod + [0-9]
for i = 1, 10 do
local key = i % 10 -- 10 maps to key 0
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }), { desc = "Go to workspace " .. i })
end
-- Move active window to a workspace with mainMod + SHIFT + [0-9]
for i = 1, 10 do
local key = i % 10 -- 10 maps to key 0
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }), { desc = "Move window to workspace " .. i })
end
-- Example special workspace (scratchpad)
hl.bind(mainMod .. " + S", hl.dsp.workspace.toggle_special("magic"), { desc = "Toggle scratchpad" })
hl.bind(mainMod .. " + SHIFT + S", hl.dsp.window.move({ workspace = "special:magic" }), { desc = "Move window to scratchpad" })
-- Scroll through existing workspaces with mainMod + scroll
hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }), { desc = "Next workspace (scroll)" })
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }), { desc = "Previous workspace (scroll)" })
-- Keybind cheat sheet
hl.bind(SUPER_SHIFT .. " + H", hl.dsp.exec_cmd("binds-help.sh"), { desc = "Keybind cheat sheet" })
-- Workspace overview (macOS Mission Control style). SUPER + Up is registered by
-- the hyprshell daemon itself ("Open Overview with Super + Up"); SUPER + Down
-- closes the overview again without switching.
hl.bind(mainMod .. " + Down", hl.dsp.exec_cmd("overview-close.sh"), { desc = "Close workspace overview" })
-- Move/resize windows with mainMod + LMB/RMB and dragging
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true, desc = "Drag window" })
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true, desc = "Resize window" })
-- Laptop lid is handled by hypr-lid-power.service (delayed lock/suspend when
-- closed undocked; display/clamshell handling is done by moniqued) instead of
-- these binds; see .local/bin/hypr-lid-power.sh.
-6
View File
@@ -1,6 +0,0 @@
# Example per-device config
# See https://wiki.hyprland.org/Configuring/Keywords/#per-device-input-configs for more
device {
name = epic-mouse-v1
sensitivity = -0.5
}
+6
View File
@@ -0,0 +1,6 @@
-- Example per-device config
-- See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Devices/ for more
hl.device({
name = "epic-mouse-v1",
sensitivity = -0.5,
})
-2
View File
@@ -1,2 +0,0 @@
env = XCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt5ct # change to qt6ct if you have that
+8
View File
@@ -0,0 +1,8 @@
-- See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Environment-variables/
hl.env("XCURSOR_SIZE", "24")
hl.env("QT_QPA_PLATFORMTHEME", "qt5ct") -- change to qt6ct if you have that
hl.env("SUDO_ASKPASS", os.getenv("HOME") .. "/.local/bin/sudo-askpass.sh")
-- Prepend ~/.local/bin so `hyprlock` (and other user scripts) resolve to the
-- crash-guard wrapper instead of /usr/bin/hyprlock. Hyprland's exec/keybind
-- PATH is otherwise the system PATH and misses user-local binaries.
hl.env("PATH", os.getenv("HOME") .. "/.local/bin:" .. (os.getenv("PATH") or "/usr/bin:/bin"))
-9
View File
@@ -1,9 +0,0 @@
exec-once = hyprctl setcursor catppuccin-mocha-dark-cursors 28 &
# exec-once = /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
exec-once = systemctl --user start hyprpolkitagent &
exec-once = waybar & mako & hyprpaper & hypridle & wluma & hyprshell run &
exec-once = blueman-applet & # systemctl --user stop wireplumber.service &
exec-once = 1password --silent & op signin & sunsetr &
+14
View File
@@ -0,0 +1,14 @@
-- Autostart. See https://wiki.hypr.land/Configuring/Basics/Autostart/
hl.on("hyprland.start", function()
hl.exec_cmd("hyprctl setcursor catppuccin-mocha-dark-cursors 28 &")
hl.exec_cmd("systemctl --user start plasma-polkit-agent &")
hl.exec_cmd("waybar & mako & hyprpaper & hypridle & wluma & hyprshell run &")
hl.exec_cmd("1password --silent &")
hl.exec_cmd("systemctl --user start hyprland-session.target &")
hl.exec_cmd("blueman-applet &")
hl.exec_cmd("sunsetr &")
end)
hl.on("hyprland.shutdown", function()
os.execute("systemctl --user stop hyprland-session.target && sleep 0.1")
end)
+12
View File
@@ -23,14 +23,26 @@ listener {
on-timeout = loginctl lock-session # lock screen when timeout has passed
}
# The dpms-off and suspend listeners ignore idle inhibitors (Steam, browsers,
# etc. hold them) so the screen still blanks and the machine still suspends
# after the screen is locked -- otherwise a held inhibitor would keep the
# locked screen on forever. `condition_cmd` gates them on the lock screen
# actually running, so inhibitors keep working as intended while unlocked
# (e.g. a video keeps the screen awake).
listener {
timeout = 330 # 5.5min
ignore_inhibit = true # fire even while apps hold idle inhibitors
condition_cmd = pidof hyprlock > /dev/null # ...but only once the session is locked
condition_retry = 30 # if not locked yet, re-check every 30s while idle
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 after timeout has fired.
}
listener {
timeout = 1800 # 30min
ignore_inhibit = true # fire even while apps hold idle inhibitors
condition_cmd = pidof hyprlock > /dev/null # ...but only while the session is locked
condition_retry = 60 # if not locked yet, re-check every 60s while idle
on-timeout = systemctl suspend # suspend pc
}
-93
View File
@@ -1,93 +0,0 @@
source=~/.config/hypr/monitors.conf
source=~/.config/hypr/mocha.conf
source=~/.config/hypr/keywords.conf
source=~/.config/hypr/exec.conf
source=~/.config/hypr/env.conf
source=~/.config/hypr/variable.conf
source=~/.config/hypr/input.conf
# See https://wiki.hyprland.org/Configuring/Keywords/ for more
xwayland {
force_zero_scaling = true
}
general {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
border_size = 3
col.active_border = $mauve $flamingo 90deg
col.inactive_border = $subtext0
resize_on_border = true
gaps_in = 2
gaps_out = 4
layout = dwindle
# Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on
allow_tearing = false
}
decoration {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
rounding = 4
blur {
enabled = true
size = 3
passes = 1
new_optimizations = true
}
shadow {
enabled = true
range = 4
render_power = 3
color = rgba(1a1a1aee)
}
dim_inactive = true
dim_strength = 0.05
dim_special = 0.07
}
animations {
enabled = true
bezier = linear, 0.0, 0.0, 1, 1
animation = borderangle, 1, 50, linear, loop
animation=workspaces,1,0.5,default
animation=windows,0,0.1,default
animation=fade,0,0.1,default
}
dwindle {
# See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more
pseudotile = yes # master switch for pseudotiling. Enableing is bound to mainMod + P in the keybinds section
preserve_split = yes
}
# master {
# # See https://wiki.hyprland.org/Configuring/Master-Layout/ for more
# new_is_master = true
# }
misc {
# See https://wiki.hyprland.org/Configuring/Variables/ for more
force_default_wallpaper = 0 # Set to 0 or 1 to disable the anime mascot wallpapers
middle_click_paste = false
}
# Device Config
soruce=~/.config/hypr/device.conf
# Windows Rules (v2)
source=~/.config/hypr/windowrulev2.conf
# Keyboard Bindings
source=~/.config/hypr/binding.conf
+121
View File
@@ -0,0 +1,121 @@
-- Hyprland config (Lua)
-- Converted from hyprlang .conf files. See https://wiki.hypr.land/Configuring/Start/
-- This file is a stow symlink; Hyprland resolves it to the repo path, so make
-- sure requires (e.g. mocha.lua from the hyprmocha package) resolve through
-- the real config dir.
package.path = os.getenv("HOME") .. "/.config/hypr/?.lua;" .. package.path
require("keywords")
require("variables")
require("mocha")
require("monitors")
require("env")
require("input")
require("device")
require("window_rules")
require("binds")
require("exec")
hl.config({
xwayland = {
force_zero_scaling = true,
},
})
hl.config({
general = {
border_size = 3,
col = {
active_border = { colors = { mauve, flamingo }, angle = 90 },
inactive_border = subtext0,
},
resize_on_border = true,
gaps_in = 2,
gaps_out = 4,
layout = "dwindle",
-- Please see https://wiki.hypr.land/Configuring/Advanced-and-Cool/Tearing/ before you turn this on
allow_tearing = false,
},
})
hl.config({
binds = {
-- Let SUPER+scroll workspace cycling wrap around instead of stopping at
-- the edges. (SUPER+Left/Right wrapping is handled directly in binds.lua.)
allow_workspace_cycles = true,
},
})
hl.config({
decoration = {
rounding = 4,
blur = {
enabled = true,
size = 3,
passes = 1,
new_optimizations = true,
},
shadow = {
enabled = true,
range = 4,
render_power = 3,
color = "rgba(1a1a1aee)",
},
dim_inactive = true,
dim_strength = 0.05,
dim_special = 0.07,
},
})
hl.config({
animations = {
enabled = true,
},
})
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
-- borderangle disabled: the old "loop" style animated continuously, forcing
-- constant redraws and spiking idle CPU usage even with VFR enabled.
hl.animation({ leaf = "borderangle", enabled = false, speed = 50, bezier = "linear", style = "loop" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 0.5, bezier = "default" })
hl.animation({ leaf = "windows", enabled = false })
hl.animation({ leaf = "fade", enabled = false })
hl.config({
dwindle = {
-- pseudotile had no Lua config-table equivalent; toggled via the P bind
preserve_split = true,
},
})
-- master {
-- new_is_master = true
-- }
-- CPU/power efficiency. See https://wiki.hypr.land/Configuring/Variables/#misc
hl.config({
misc = {
force_default_wallpaper = 0, -- Set to 0 or 1 to disable the anime mascot wallpapers
middle_click_paste = false,
-- Don't wake/re-render just because the cursor crossed a monitor edge.
mouse_move_focuses_monitor = false,
-- Skip drawing the Hyprland logo in the corner of empty workspaces.
disable_hyprland_logo = true,
-- Allow a new hyprlock to take over the lock after a crash; without this a
-- dead lock client leaves the session permanently locked (no lockscreen).
allow_session_lock_restore = true,
},
})
-- Variable framerate: only redraw when the scene actually changes instead of
-- running at a fixed rate. This is the single biggest idle-CPU saver; it is
-- true by default, but set explicitly (note it lives under `debug:`).
hl.config({
debug = {
vfr = true,
-- Temporarily on to diagnose the mystery re-lock (disable_logs=true
-- default hides dispatch exec lines from hyprland.log).
disable_logs = false,
},
})
-26
View File
@@ -1,26 +0,0 @@
# For all categories, see https://wiki.hyprland.org/Configuring/Variables/
input {
kb_layout = us
kb_variant =
kb_model =
kb_options =
kb_rules =
kb_options = ctrl:nocaps
follow_mouse = 1
touchpad {
natural_scroll = true
clickfinger_behavior = true
disable_while_typing = true
scroll_factor = 0.5
}
sensitivity = 0 # -1.0 to 1.0, 0 means no modification
}
gesture = 3, horizontal, workspace
gestures {
workspace_swipe_direction_lock = true
workspace_swipe_direction_lock_threshold = 10
}
+31
View File
@@ -0,0 +1,31 @@
-- For all categories, see https://wiki.hypr.land/Configuring/Basics/Variables/
hl.config({
input = {
kb_layout = "us",
kb_variant = "",
kb_model = "",
kb_options = "ctrl:nocaps",
kb_rules = "",
follow_mouse = 1,
touchpad = {
natural_scroll = true,
clickfinger_behavior = true,
disable_while_typing = true,
scroll_factor = 0.5,
},
sensitivity = 0, -- -1.0 to 1.0, 0 means no modification
},
})
hl.gesture({
fingers = 3,
direction = "horizontal",
action = "workspace",
})
hl.config({
gestures = {
workspace_swipe_direction_lock = true,
workspace_swipe_direction_lock_threshold = 10,
},
})
-2
View File
@@ -1,2 +0,0 @@
# See https://wiki.hyprland.org/Configuring/Keywords/ for more
$mainMod = super
+4
View File
@@ -0,0 +1,4 @@
-- See https://wiki.hypr.land/Configuring/Basics/Keywords/ for more
-- Defined as globals so every required module can use them.
mainMod = "SUPER" -- Sets the "Windows" key as main modifier
SUPER_SHIFT = "SUPER + SHIFT"
-6
View File
@@ -1,6 +0,0 @@
# Generated by nwg-displays on 2026-05-12 at 10:26:18. Do not edit manually.
monitor=desc:BOE 0x0BC9,disable
monitor=desc:Viewteck Co. Ltd. GNV34DB,3440x1440@50.0,3795x1785,1.0
monitor=desc:Dell Inc. DELL U2515HX GMDK46C4053L,2560x1440@59.95,5488x345,1.0
monitor=desc:Dell Inc. DELL U2518D 0WG2J9C5B3WL,2560x1440@59.95,2928x345,1.0
+11
View File
@@ -0,0 +1,11 @@
-- Generated by Monique - https://github.com/ToRvaLDz/monique
hl.monitor({
output = "desc:BOE 0x0BC9",
mode = "2560x1600@165",
position = "0x0",
scale = 1,
cm = "srgb",
sdr_min_luminance = 0.2,
sdr_max_luminance = 80,
})
-4
View File
@@ -1,4 +0,0 @@
$terminal = ghostty
$fileManager = dolphin
$menu = wofi --show drun
$browser = zen-browser
+4
View File
@@ -0,0 +1,4 @@
terminal = "ghostty"
fileManager = "dolphin"
menu = "wofi --show drun"
browser = "zen-browser"
+8
View File
@@ -0,0 +1,8 @@
-- Example window rules
-- See https://wiki.hypr.land/Configuring/Basics/Window-Rules/ for more
hl.layer_rule({
name = "no_anim_for_wofi",
match = { namespace = "wofi" },
no_anim = true,
})
-11
View File
@@ -1,11 +0,0 @@
# Example windowrule v1
# windowrule = float, ^(kitty)$
# Example windowrule v2
# windowrulev2 = float,class:^(kitty)$,title:^(kitty)$
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
# windowrulev2 = suppressevent maximize, class:.* # You'll probably like this.
layerrule {
name = no_anim_for_wofi
no_anim = on
match:namespace = wofi
}
@@ -0,0 +1,13 @@
[Unit]
Description=Hyprland lid lock/suspend timers (display/clamshell handled by moniqued)
After=graphical-session.target
PartOf=graphical-session.target
[Service]
Type=simple
ExecStart=%h/.local/bin/hypr-lid-power.sh
Restart=on-failure
RestartSec=3
[Install]
WantedBy=graphical-session.target
@@ -0,0 +1,6 @@
[Unit]
Description=Hyprland session
BindsTo=graphical-session.target
Wants=graphical-session-pre.target
After=graphical-session-pre.target
PropagatesStopTo=graphical-session.target
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Searchable cheat sheet of Hyprland keybindings, shown in wofi.
#
# The descriptions shown here come from the `desc` option passed to hl.bind()
# in ~/.config/hypr/binds.lua, read back at runtime via `hyprctl binds -j`,
# so the cheat sheet always matches the live config.
set -u
mods_from_mask() {
local mask="$1" out=""
(( mask & 1 )) && out="${out:+$out + }SHIFT"
(( mask & 4 )) && out="${out:+$out + }CTRL"
(( mask & 8 )) && out="${out:+$out + }ALT"
(( mask & 64 )) && out="${out:+$out + }SUPER"
(( mask & 128 )) && out="${out:+$out + }MOD5"
printf "%s" "$out"
}
hyprctl binds -j |
jq -rc '.[] | select(.has_description) | select(.description != "") | [.modmask, .key, .description] | @tsv' |
while IFS=$'\t' read -r mask key desc; do
if (( mask )); then
mods="$(mods_from_mask "$mask")"
printf '%s + %s\t%s\n' "$mods" "$key" "$desc"
else
printf '%s\t%s\n' "$key" "$desc"
fi
done |
sort -k1,1 |
column -t -s $'\t' |
wofi --dmenu --prompt "Hyprland keybinds" >/dev/null
+13
View File
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Open btop fullscreen in a ghostty window; focus it if it is already open.
# Intended as the on-click action for the waybar cpu/memory modules.
set -uo pipefail
if hyprctl clients -j 2>/dev/null | jq -e 'any(.[]; ((.title // "") + " " + (.class // "")) | ascii_downcase | contains("btop"))' >/dev/null 2>&1; then
hyprctl dispatch 'hl.dsp.focus({ window = "title:btop" })' 2>/dev/null || true
exit 0
fi
ghostty -e btop &
sleep 0.5
hyprctl dispatch 'hl.dsp.window.fullscreen({ action = "set" })' 2>/dev/null || true
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# hypr-lid-power: delayed lock/suspend for the laptop lid.
#
# Display/clamshell/hotplug handling is owned by moniqued (see
# ~/.config/hypr/monitors.lua and moniqued's clamshell mode). This service only
# runs the power timers while the lid is closed with no external display
# attached:
# - lock after LOCK_SECONDS
# - suspend after SUSPEND_SECONDS
#
# Runs as a systemd user service (hypr-lid-power.service) and polls the lid +
# monitor state, so connecting/disconnecting a dock while the lid is closed is
# handled too. Lock/suspend use transient systemd timers, which pause during
# suspend and are recreated afterwards, so if the machine wakes while the lid
# stays closed it will sleep again after the full timeout.
set -uo pipefail
RUNTIME="/run/user/$(id -u)"
LOCK_SECONDS=60
SUSPEND_SECONDS=600
POLL_SECONDS=2
log() { printf '[hypr-lid-power] %s %s\n' "$(date '+%H:%M:%S')" "$*"; }
# Run hyprctl against the active Hyprland instance, if any.
hypr() {
local sig=""
for d in "$RUNTIME"/hypr/*/; do
if [ -S "$d/.socket.sock" ]; then
sig="$(basename "$d")"
break
fi
done
[ -n "$sig" ] || return 1
HYPRLAND_INSTANCE_SIGNATURE="$sig" command hyprctl "$@"
}
lid_closed() {
local state
state="$(cat /proc/acpi/button/lid/*/state 2>/dev/null)" || return 1
[[ "$state" == *closed* ]]
}
# True when Hyprland has any output up that is not the built-in panel/headless.
has_external() {
hypr monitors -j 2>/dev/null | grep '"name": "' | grep -qvE '"(eDP|HEADLESS)-'
}
stop_timers() {
systemctl --user stop hypr-lid-lock.timer hypr-lid-suspend.timer 2>/dev/null || true
}
# Re-create the named timer unless it is already waiting. Transient timers are
# collected after they fire, so this also restarts them after a suspend.
ensure_timer() {
local name="$1" seconds="$2"
shift 2
if ! systemctl --user --quiet is-active "hypr-lid-$name.timer"; then
systemd-run --user --quiet --unit="hypr-lid-$name" --on-active="${seconds}s" --collect "$@"
fi
}
powering=no
while true; do
if ! hypr monitors >/dev/null 2>&1; then
# No Hyprland running (e.g. another compositor, or before first launch).
powering=no
sleep 3
continue
fi
want_power="no"
if lid_closed && ! has_external; then
want_power="yes"
fi
if [ "$want_power" != "$powering" ]; then
log "power mode: $powering -> $want_power"
if [ "$want_power" = "yes" ]; then
ensure_timer lock "$LOCK_SECONDS" loginctl lock-sessions
ensure_timer suspend "$SUSPEND_SECONDS" systemctl suspend
else
stop_timers
fi
powering="$want_power"
fi
sleep "$POLL_SECONDS"
done
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# macOS-style SUPER+Down: close the hyprshell workspace overview if it is open,
# returning to the current workspace without switching.
#
# hyprshell's overview is a gtk4-layer-shell surface whose namespace is
# "hyprshell_overview", and the OpenOverview IPC message toggles the overview
# closed when it is already open (see OverviewRootInput::OpenOverview).
set -u
if ! hyprctl layers -j | jq -e '
.. | objects | .namespace? // empty | select(. == "hyprshell_overview")
' >/dev/null; then
exit 0
fi
hyprshell socat '"OpenOverview"'
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# sudo-askpass: GUI password prompt for sudo via $SUDO_ASKPASS.
#
# sudo calls the program pointed to by $SUDO_ASKPASS when it cannot prompt on a
# terminal (or with `sudo -A`) and reads the password from its stdout. zenity's
# --password dialog does exactly that. If the dialog is cancelled or times out
# zenity exits non-zero and sudo aborts.
set -uo pipefail
exec zenity --title="sudo authentication" \
--password \
--text="Enter password for $(id -un):" \
--timeout=120 2>/dev/null
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Print the current weather as a short icon+temp string, e.g. "🌦️ +83°F".
# Caches the wttr.in response for CACHE_MAX_AGE seconds so the lock screen and
# waybar don't hit the network on every refresh. Override the location with
# WEATHER_CITY or edit CITY below.
set -u
CITY="${WEATHER_CITY:-Denver}"
CACHE_DIR="${XDG_RUNTIME_DIR:-/tmp}"
CACHE="${CACHE_DIR}/wttr.cache"
CACHE_MAX_AGE=1800 # 30 minutes
URL="https://wttr.in/${CITY}?format=%c+%t"
cache_age() {
[ -f "${CACHE}" ] || return 1
now=$(date +%s)
mtime=$(stat -c %Y "${CACHE}")
[ "$(( now - mtime ))" -lt "${CACHE_MAX_AGE}" ]
}
if cache_age; then
cat "${CACHE}"
exit 0
fi
TMP="${CACHE}.tmp.$$"
if curl -fsS --max-time 10 "${URL}" > "${TMP}" 2>/dev/null; then
mv "${TMP}" "${CACHE}"
cat "${CACHE}"
else
rm -f "${TMP}" || true
# Offline: fall back to whatever we cached last, even if stale.
[ -f "${CACHE}" ] && cat "${CACHE}"
fi
+42 -3
View File
@@ -7,14 +7,17 @@ $font = JetBrainsMono Nerd Font
# General
general {
disable_loading_bar = false
hide_cursor = false
}
# Authentication
# Fingerprint is disabled: GDM's login worker (pam/gdm-fingerprint) holds the
# device for the whole session, so hyprlock can never claim it -- the failed
# claim blocked the auth thread and made unlocks appear stuck. Fingerprint is
# still available at the GDM login screen; hyprlock is password-only.
auth {
fingerprint {
enabled = true
enabled = false
ready_message = Scan fingerprint to unlock
present_message = Scanning...
retry_delay = 250 # in milliseconds
@@ -45,7 +48,7 @@ label {
# Date
label {
monitor =
text = cmd[update:43200000] echo "$(date + "%A, %d %B %Y")"
text = cmd[update:43200000] echo "$(date +'%A, %d %B %Y')"
color = $text
font_size = 25
font_family = $font
@@ -54,6 +57,30 @@ label {
valign = top
}
# Battery
label {
monitor =
text = cmd[update:60000] $HOME/.local/bin/hyprlock-battery.sh
color = $subtext1
font_size = 20
font_family = $font
position = -30, -190
halign = right
valign = top
}
# Weather
label {
monitor =
text = cmd[update:600000] $HOME/.local/bin/weather.sh
color = $subtext1
font_size = 20
font_family = $font
position = -30, -215
halign = right
valign = top
}
# User Avatar
image {
monitor =
@@ -66,6 +93,18 @@ image {
valign = center
}
# Notifications (latest mako notification; empty when none)
label {
monitor =
text = cmd[update:5000] $HOME/.local/bin/hyprlock-notifications.sh
color = $subtext0
font_size = 16
font_family = $font
position = 0, 60
halign = center
valign = bottom
}
# Input Field
input-field {
monitor =
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Guard wrapper for hyprlock (0.9.x): upstream has crashes on DPMS-off / monitor
# power transitions while the lock screen is running (hyprlock#991, #953, #695).
# If the real hyprlock dies, this restarts it so the session doesn't end up in a
# "lockscreen app died" state. Requires misc:allow_session_lock_restore = true
# in Hyprland config for the replacement lock to be accepted.
set -u
REAL_HYPRLOCK=/usr/bin/hyprlock
MAX_RESTARTS=20
restart=0
while true; do
"$REAL_HYPRLOCK" "$@"
code=$?
# 0: exited cleanly (e.g. unlocked) -- done.
# 1: auth failed, retry -- let the loop back off and try again.
if [ "$code" -eq 0 ]; then
exit 0
fi
restart=$((restart + 1))
if [ "$restart" -ge "$MAX_RESTARTS" ]; then
exit "$code"
fi
# Brief pause so a crash loop doesn't burn CPU (fade back in quickly).
sleep 0.5
done
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Battery status for the hyprlock screen, e.g. " 18%".
# Uses the first battery found under /sys/class/power_supply (BAT0/BAT1, ...);
# prints nothing on desktops without a battery.
set -u
bat=""
for b in /sys/class/power_supply/BAT*; do
if [ -r "${b}/capacity" ]; then
bat="${b}"
break
fi
done
[ -z "${bat}" ] && exit 0
cap="$(cat "${bat}/capacity" 2>/dev/null || echo 0)"
status="$(cat "${bat}/status" 2>/dev/null || echo Unknown)"
case "${status}" in
Charging|Full)
icon=""
;;
*)
if [ "${cap}" -ge 80 ]; then
icon=""
elif [ "${cap}" -ge 60 ]; then
icon=""
elif [ "${cap}" -ge 40 ]; then
icon=""
elif [ "${cap}" -ge 20 ]; then
icon=""
else
icon=""
fi
;;
esac
printf '%s %s%%' "${icon}" "${cap}"
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Latest mako notification for the hyprlock screen, Pango-escaped, e.g.
# "Battery test: Battery at 18%". Prints nothing when there are no
# notifications, so the label renders empty.
set -u
makoctl list -j 2>/dev/null | jq -r '
if length == 0 then
empty
else
.[-1] as $n |
(if $n.app_name != null and $n.app_name != "" then $n.app_name + ": " else "" end) +
($n.summary // "") +
(if $n.body != null and $n.body != "" then " — " + $n.body[0:80] else "" end)
end' 2>/dev/null | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g'
+81
View File
@@ -0,0 +1,81 @@
-- Catppuccin Mocha palette for the Lua Hyprland config.
-- Colors are "rgba(rrggbbaa)" strings; the *Alpha globals are bare hex for
-- building custom colors. Kept alongside mocha.conf, which hyprlock still sources.
rosewater = "rgba(f5e0dcff)"
rosewaterAlpha = "f5e0dc"
flamingo = "rgba(f2cdcdff)"
flamingoAlpha = "f2cdcd"
pink = "rgba(f5c2e7ff)"
pinkAlpha = "f5c2e7"
mauve = "rgba(cba6f7ff)"
mauveAlpha = "cba6f7"
red = "rgba(f38ba8ff)"
redAlpha = "f38ba8"
maroon = "rgba(eba0acff)"
maroonAlpha = "eba0ac"
peach = "rgba(fab387ff)"
peachAlpha = "fab387"
yellow = "rgba(f9e2afff)"
yellowAlpha = "f9e2af"
green = "rgba(a6e3a1ff)"
greenAlpha = "a6e3a1"
teal = "rgba(94e2d5ff)"
tealAlpha = "94e2d5"
sky = "rgba(89dcebff)"
skyAlpha = "89dceb"
sapphire = "rgba(74c7ecff)"
sapphireAlpha = "74c7ec"
blue = "rgba(89b4faff)"
blueAlpha = "89b4fa"
lavendar = "rgba(b4befeff)"
lavendarAlpha = "b4befe"
text = "rgba(cdd6f4ff)"
textAlpha = "cdd6f4"
subtext1 = "rgba(bac2deff)"
subtext1Alpha = "bac2de"
subtext0 = "rgba(a6adc8ff)"
subtext0Alpha = "a6adc8"
overlay2 = "rgba(9399b2ff)"
overlay2Alpha = "9399b2"
overlay1 = "rgba(7f849cff)"
overlay1Alpha = "7f849c"
overlay0 = "rgba(6c7086ff)"
overlay0Alpha = "6c7086"
surface2 = "rgba(585b70ff)"
surface2Alpha = "585b70"
surface1 = "rgba(45475aff)"
surface1Alpha = "45475a"
surface0 = "rgba(313244ff)"
surface0Alpha = "313244"
base = "rgba(1e1e2eff)"
baseAlpha = "1e1e2e"
mantle = "rgba(181825ff)"
mantleAlpha = "181825"
crust = "rgba(11111bff)"
crustAlpha = "11111b"
+10 -8
View File
@@ -1,6 +1,6 @@
// Edit with `hyprshell config edit`
(
version: 3,
version: 4,
windows: (
scale: 8.5,
items_per_row: 5,
@@ -43,7 +43,6 @@
actions: (
actions: [
lock_screen,
hibernate,
logout,
reboot,
shutdown,
@@ -69,11 +68,13 @@
),
),
),
key: "Tab",
modifier: "alt",
filter_by: [],
hide_filtered: false,
exclude_special_workspaces: "",
key: "Up",
top_offset: 430,
modifier: "super",
filter_by: [
current_monitor,
],
exclude_workspaces: "special:.*",
),
switch: (
modifier: "alt",
@@ -82,7 +83,8 @@
current_monitor,
],
switch_workspaces: false,
exclude_special_workspaces: "",
exclude_workspaces: "",
kill_key: 'q',
),
switch_2: None,
),
+30
View File
@@ -1,5 +1,35 @@
# https://github.com/emersion/mako/wiki/Configuration
max-history=10
# Style
default-timeout=5000
layer=overlay
anchor=top-right
border-radius=10
font="MesloLGS Nerd Font Mono Bold 12"
icons=1
max-icon-size=48
# Catppuccin Mocha palette (matches waybar mocha.css)
background-color=#1e1e2e
text-color=#cdd6f4
border-color=#cba6f7
progress-color=#a6e3a1
# Grouping
group-by=app-name
sort=-time
[grouped]
format=(<b>%g</b>) <b>%s</b>\n%b
[urgency=low]
border-color=#94e2d5
default-timeout=8000
[urgency=normal]
[urgency=critical]
border-color=#f38ba8
default-timeout=0
+279
View File
@@ -0,0 +1,279 @@
{
"name": "desk",
"monitors": [
{
"name": "eDP-2",
"description": "BOE 0x0BC9",
"make": "BOE",
"model": "0x0BC9",
"serial": "",
"width": 2560,
"height": 1600,
"refresh_rate": 165.0,
"resolution_mode": "explicit",
"available_modes": [
"2560x1600@165.00Hz",
"2560x1600@60.00Hz",
"1920x1200@165.00Hz",
"1920x1080@165.00Hz",
"1600x1200@165.00Hz",
"1680x1050@165.00Hz",
"1280x1024@165.00Hz",
"1440x900@165.00Hz",
"1280x800@165.00Hz",
"1280x720@165.00Hz",
"1024x768@165.00Hz",
"800x600@165.00Hz",
"640x480@165.00Hz"
],
"x": 0,
"y": 0,
"position_mode": "explicit",
"scale": 1,
"scale_mode": "explicit",
"transform": 0,
"mirror_of": "",
"bitdepth": 8,
"vrr": 0,
"color_management": "srgb",
"icc_profile": "",
"sdr_brightness": 1,
"sdr_saturation": 1,
"sdr_eotf": 0,
"supports_hdr": 0,
"supports_wide_color": 0,
"sdr_min_luminance": 0.2,
"sdr_max_luminance": 80,
"min_luminance": 0.0,
"max_luminance": 0.0,
"max_avg_luminance": 0.0,
"reserved_top": 0,
"reserved_bottom": 0,
"reserved_left": 0,
"reserved_right": 0,
"enabled": true,
"focus_at_startup": false
},
{
"name": "DP-7",
"description": "Dell Inc. DELL U2515HX GMDK46C4053L",
"make": "Dell Inc.",
"model": "DELL U2515HX",
"serial": "GMDK46C4053L",
"width": 2560,
"height": 1440,
"refresh_rate": 59.951,
"resolution_mode": "explicit",
"available_modes": [
"2560x1440@59.95Hz",
"2048x1152@60.00Hz",
"1920x1200@59.88Hz",
"1920x1080@60.00Hz",
"1920x1080@60.00Hz",
"1920x1080@59.94Hz",
"1920x1080@50.00Hz",
"1920x1080@30.00Hz",
"1920x1080@29.97Hz",
"1920x1080@25.00Hz",
"1920x1080@24.00Hz",
"1920x1080@23.98Hz",
"1600x1200@60.00Hz",
"1680x1050@59.95Hz",
"1280x1024@75.03Hz",
"1280x1024@60.02Hz",
"1200x960@59.99Hz",
"1152x864@75.00Hz",
"1280x720@60.00Hz",
"1280x720@60.00Hz",
"1280x720@59.94Hz",
"1280x720@50.00Hz",
"1024x768@75.03Hz",
"1024x768@60.00Hz",
"800x600@75.00Hz",
"800x600@60.32Hz",
"720x576@50.00Hz",
"720x480@60.00Hz",
"720x480@60.00Hz",
"720x480@59.94Hz",
"720x480@59.94Hz",
"720x480@59.94Hz",
"640x480@75.00Hz",
"640x480@60.00Hz",
"640x480@59.94Hz",
"640x480@59.94Hz",
"720x400@70.08Hz"
],
"x": 5488,
"y": 345,
"position_mode": "explicit",
"scale": 1,
"scale_mode": "explicit",
"transform": 0,
"mirror_of": "",
"bitdepth": 8,
"vrr": 0,
"color_management": "srgb",
"icc_profile": "",
"sdr_brightness": 1,
"sdr_saturation": 1,
"sdr_eotf": 0,
"supports_hdr": 0,
"supports_wide_color": 0,
"sdr_min_luminance": 0.2,
"sdr_max_luminance": 80,
"min_luminance": 0.0,
"max_luminance": 0.0,
"max_avg_luminance": 0.0,
"reserved_top": 0,
"reserved_bottom": 0,
"reserved_left": 0,
"reserved_right": 0,
"enabled": true,
"focus_at_startup": false
},
{
"name": "DP-10",
"description": "Dell Inc. DELL U2518D 0WG2J9C5B3WL",
"make": "Dell Inc.",
"model": "DELL U2518D",
"serial": "0WG2J9C5B3WL",
"width": 2560,
"height": 1440,
"refresh_rate": 59.951,
"resolution_mode": "explicit",
"available_modes": [
"2560x1440@59.95Hz",
"2048x1080@60.00Hz",
"2048x1080@24.00Hz",
"1920x1080@60.00Hz",
"1920x1080@60.00Hz",
"1920x1080@59.94Hz",
"1920x1080@50.00Hz",
"1600x1200@60.00Hz",
"1280x1024@75.03Hz",
"1280x1024@60.02Hz",
"1152x864@75.00Hz",
"1280x720@60.00Hz",
"1280x720@60.00Hz",
"1280x720@59.94Hz",
"1280x720@50.00Hz",
"1024x768@75.03Hz",
"1024x768@60.00Hz",
"800x600@75.00Hz",
"800x600@60.32Hz",
"720x576@50.00Hz",
"720x576@50.00Hz",
"720x480@60.00Hz",
"720x480@60.00Hz",
"720x480@59.94Hz",
"720x480@59.94Hz",
"640x480@75.00Hz",
"640x480@60.00Hz",
"640x480@59.94Hz",
"640x480@59.94Hz",
"720x400@70.08Hz"
],
"x": 2928,
"y": 345,
"position_mode": "explicit",
"scale": 1,
"scale_mode": "explicit",
"transform": 0,
"mirror_of": "",
"bitdepth": 8,
"vrr": 0,
"color_management": "srgb",
"icc_profile": "",
"sdr_brightness": 1,
"sdr_saturation": 1,
"sdr_eotf": 0,
"supports_hdr": 0,
"supports_wide_color": 0,
"sdr_min_luminance": 0.2,
"sdr_max_luminance": 80,
"min_luminance": 0.0,
"max_luminance": 0.0,
"max_avg_luminance": 0.0,
"reserved_top": 0,
"reserved_bottom": 0,
"reserved_left": 0,
"reserved_right": 0,
"enabled": true,
"focus_at_startup": false
},
{
"name": "DP-4",
"description": "Viewteck Co. Ltd. GNV34DB",
"make": "Viewteck Co., Ltd.",
"model": "GNV34DB",
"serial": "",
"width": 3440,
"height": 1440,
"refresh_rate": 50.0,
"resolution_mode": "explicit",
"available_modes": [
"3440x1440@30.00Hz",
"3440x1440@50.00Hz",
"3440x1440@50.00Hz",
"2560x1080@60.00Hz",
"2560x1080@59.94Hz",
"1720x1440@60.00Hz",
"1920x1080@60.00Hz",
"1920x1080@60.00Hz",
"1920x1080@59.94Hz",
"1920x1080@50.00Hz",
"1680x1050@59.88Hz",
"1600x900@60.00Hz",
"1280x1024@75.03Hz",
"1280x1024@60.02Hz",
"1440x900@59.90Hz",
"1280x720@60.00Hz",
"1280x720@60.00Hz",
"1280x720@59.94Hz",
"1280x720@50.00Hz",
"1024x768@75.03Hz",
"1024x768@60.00Hz",
"800x600@75.00Hz",
"800x600@60.32Hz",
"720x480@60.00Hz",
"720x480@60.00Hz",
"720x480@59.94Hz",
"720x480@59.94Hz",
"640x480@75.00Hz",
"640x480@60.00Hz",
"640x480@59.94Hz",
"640x480@59.94Hz",
"720x400@70.08Hz"
],
"x": 3795,
"y": 1785,
"position_mode": "explicit",
"scale": 1,
"scale_mode": "explicit",
"transform": 0,
"mirror_of": "",
"bitdepth": 8,
"vrr": 0,
"color_management": "srgb",
"icc_profile": "",
"sdr_brightness": 1,
"sdr_saturation": 1,
"sdr_eotf": 0,
"supports_hdr": 0,
"supports_wide_color": 0,
"sdr_min_luminance": 0.2,
"sdr_max_luminance": 80,
"min_luminance": 0.0,
"max_luminance": 0.0,
"max_avg_luminance": 0.0,
"reserved_top": 0,
"reserved_bottom": 0,
"reserved_left": 0,
"reserved_right": 0,
"enabled": true,
"focus_at_startup": false
}
],
"workspace_rules": [],
"last_applied_time": 1788832961.6364207
}
@@ -0,0 +1,60 @@
{
"name": "laptop",
"monitors": [
{
"name": "eDP-2",
"description": "BOE 0x0BC9",
"make": "BOE",
"model": "0x0BC9",
"serial": "",
"width": 2560,
"height": 1600,
"refresh_rate": 165.0,
"resolution_mode": "explicit",
"available_modes": [
"2560x1600@165.00Hz",
"2560x1600@60.00Hz",
"1920x1200@165.00Hz",
"1920x1080@165.00Hz",
"1600x1200@165.00Hz",
"1680x1050@165.00Hz",
"1280x1024@165.00Hz",
"1440x900@165.00Hz",
"1280x800@165.00Hz",
"1280x720@165.00Hz",
"1024x768@165.00Hz",
"800x600@165.00Hz",
"640x480@165.00Hz"
],
"x": 0,
"y": 0,
"position_mode": "explicit",
"scale": 1,
"scale_mode": "explicit",
"transform": 0,
"mirror_of": "",
"bitdepth": 8,
"vrr": 0,
"color_management": "srgb",
"icc_profile": "",
"sdr_brightness": 1,
"sdr_saturation": 1,
"sdr_eotf": 0,
"supports_hdr": 0,
"supports_wide_color": 0,
"sdr_min_luminance": 0.2,
"sdr_max_luminance": 80,
"min_luminance": 0.0,
"max_luminance": 0.0,
"max_avg_luminance": 0.0,
"reserved_top": 0,
"reserved_bottom": 0,
"reserved_left": 0,
"reserved_right": 0,
"enabled": true,
"focus_at_startup": false
}
],
"workspace_rules": [],
"last_applied_time": 1788832965.6726744
}
+6
View File
@@ -0,0 +1,6 @@
{
"clamshell_mode": true,
"update_sddm": true,
"migrate_workspaces": true,
"active_profile": "laptop"
}
@@ -2,22 +2,29 @@
# systemctl --user daemon-reload
# systemctl --user enable --now cloudflared-k8s.service
# systemctl --user status cloudflared-k8s.service
#
# The Cloudflare Access service token for k8s-api lives in the "Homelab"
# 1Password vault (item "K8s API Service Token", section "Service Token") and
# is injected at runtime via `op run` -- the secret is never written to disk.
[Unit]
Description=Cloudflare Tunnel to K8s API
After=network-online.target graphical-session.target
BindsTo=graphical-session.target
After=network-online.target graphical-session.target op-cli-signin.service
Wants=network-online.target systemd-networkd-wait-online.service
BindsTo=graphical-session.target
StartLimitIntervalSec=500
StartLimitBurst=5
[Service]
PassEnvironment=DISPLAY WAYLAND_DISPLAY DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR
ExecStart=uwsm app -- /usr/bin/cloudflared access tcp --hostname k8s.samoneal.io --url localhost:6443
Environment="OP_BIOMETRIC_UNLOCK_ENABLED=true"
Environment="TUNNEL_SERVICE_TOKEN_ID=op://Homelab/K8s API Service Token/Service Token/client-id"
Environment="TUNNEL_SERVICE_TOKEN_SECRET=op://Homelab/K8s API Service Token/Service Token/client-secret"
ExecStart=/usr/bin/op run -- /usr/bin/cloudflared access tcp --hostname k8s.samoneal.io --url localhost:6443
Restart=on-failure
RestartSec=60
[Install]
WantedBy=default.target
WantedBy=graphical-session.target
@@ -1,8 +1,8 @@
[Unit]
Description=Connect to GDrive via RClone
After=network-online.target graphical-session.target
Wants=network-online.target
BindsTo=graphical-session.target
Wants=network-online.target graphical-session.target
[Service]
PassEnvironment=DISPLAY WAYLAND_DISPLAY DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR
@@ -11,4 +11,4 @@ Restart=on-failure
RestartSec=60
[Install]
WantedBy=default.target
WantedBy=graphical-session.target
@@ -1,17 +1,18 @@
[Unit]
Description=Fetch NAS credentials from 1Password
After=op-cli-signin.service
After=graphical-session.target op-cli-signin.service
BindsTo=graphical-session.target
[Service]
Type=oneshot
Environment="DISPLAY=:0"
Environment="WAYLAND_DISPLAY=wayland-0"
PassEnvironment=DISPLAY WAYLAND_DISPLAY DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR
Environment="OP_BIOMETRIC_UNLOCK_ENABLED=true"
ExecStart=sudo -u sammieo -E /home/sammieo/.local/bin/fetch-nas-credentials.sh
ExecStartPost=sudo -u sammieo /usr/bin/systemctl start nas-mounts.target
Environment="SUDO_ASKPASS=/home/sammieo/.local/bin/sudo-askpass.sh"
ExecStart=sudo -A -u sammieo -E /home/sammieo/.local/bin/fetch-nas-credentials.sh
ExecStartPost=sudo -A -u sammieo /usr/bin/systemctl start nas-mounts.target
RemainAfterExit=yes
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
WantedBy=graphical-session.target
@@ -1,13 +1,19 @@
[Unit]
Description=1Password CLI SignIn
After=graphical-session.target
BindsTo=graphical-session.target
StartLimitIntervalSec=300
StartLimitBurst=10
[Service]
Type=oneshot
Environment="DISPLAY=:0"
Environment="WAYLAND_DISPLAY=wayland-0"
Environment="OP_BIOMETRIC_UNLOCK_ENABLED=true"
ExecStart=/bin/sh op signin
ExecStart=/usr/bin/op signin
Restart=on-failure
RestartSec=5
[Install]
WantedBy=default.target
WantedBy=graphical-session.target
@@ -0,0 +1,8 @@
# Hyprland handles the laptop lid itself: moniqued owns the display side
# (clamshell when external displays are attached) and hypr-lid-power runs the
# delayed lock/suspend timers otherwise. logind must not suspend immediately
# on lid close.
[Login]
HandleLidSwitch=ignore
HandleLidSwitchExternalPower=ignore
HandleLidSwitchDocked=ignore
+29 -4
View File
@@ -2,7 +2,7 @@
"position": "top",
"modules-left": ["hyprland/workspaces", "mpris"],
"modules-center": ["hyprland/window"],
"modules-right": ["cpu", "memory", "bluetooth", "network", "pulseaudio", "battery", "clock", "power-profiles-daemon", "custom/power"],
"modules-right": ["cpu", "memory", "tray", "bluetooth", "network", "pulseaudio", "battery", "custom/weather", "clock", "power-profiles-daemon", "custom/power"],
"clock": {
"format": "<span foreground='#f5c2e7'> </span>{:%a %d %H:%M}",
"tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>"
@@ -22,6 +22,30 @@
"format-icons": ["", "", "", "", ""],
"tooltip-format": "{time}"
},
"cpu": {
"on-click": "~/.local/bin/btop.sh"
},
"memory": {
"on-click": "~/.local/bin/btop.sh"
},
"custom/weather": {
"format": "{}",
"exec": "~/.local/bin/weather.sh",
"interval": 600,
"tooltip": false
},
"tray": {
"icon-size": 16,
"icon-theme": "Papirus-Dark",
"spacing": 8,
"tooltip": true,
"menu": {
"enabled": true,
"arrow": false,
"separator": true,
"size": 16
}
},
"network": {
"format-wifi": "<span size='13000' foreground='#f5e0dc'> </span>{essid}",
"format-ethernet": "<span size='13000' foreground='#f5e0dc'>󰤭 </span> Disconnected",
@@ -65,10 +89,11 @@
"menu": "on-click",
"menu-file": "~/.config/waybar/power_menu.xml",
"menu-actions": {
"shutdown": "shutdown",
"reboot": "reboot",
"suspend": "systemctl suspend",
"hibernate": "systemctl hibernate",
"lock": "hyprlock",
"logout": "hyprctl dispatch exit",
"reboot": "reboot",
"shutdown": "shutdown",
},
"escape": true
},
+11 -6
View File
@@ -7,21 +7,26 @@
</object>
</child>
<child>
<object class="GtkMenuItem" id="hibernate">
<property name="label">Hibernate</property>
<object class="GtkMenuItem" id="lock">
<property name="label">Lock</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property>
<object class="GtkMenuItem" id="logout">
<property name="label">Logout</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="reboot">
<property name="label">Restart</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="delimiter1" />
</child>
<child>
<object class="GtkMenuItem" id="reboot">
<property name="label">Reboot</property>
<object class="GtkMenuItem" id="shutdown">
<property name="label">Shutdown</property>
</object>
</child>
</object>
+14
View File
@@ -122,6 +122,20 @@ button:hover {
border-bottom: 2px solid @mauve;
}
#tray {
color: @lavender;
}
#tray > .passive {
opacity: 0.7;
}
#tray > .needs-attention {
background-color: rgba(0, 0, 0, 0.3);
color: @red;
border-bottom: 2px solid @red;
}
#custom-power {
color: #ec9974;
}
+59
View File
@@ -139,3 +139,62 @@ alias pip=pip3
. "/home/sammieo/.deno/env"
export LC_ALL="en_US.UTF-8"
# Make sure no alias hides the real mv / cp
unalias mv 2>/dev/null
unalias cp 2>/dev/null
# Custom 'move' command that incorporates the pv command
mv() {
if (( $# > 2 )); then
command mv "$@"
return
fi
local src=$1 dest=$2
if [ -d "$src" ]; then
mkdir -p "$dest"
# Total size in bytes (du -sb)
local total=$(du -sb "$src" | awk '{print $1}')
# Pipe through pv and extract at destination
tar -cf - -C "$(dirname "$src")" "$(basename "$src")" |
pv -s "$total" |
tar -xf - -C "$dest"
# Remove the original directory
rm -rf "$src"
else
mkdir -p "$(dirname "$dest")"
command mv "$@"
fi
}
# Custom 'cp' command that incorporates the pv command
cp() {
if (( $# > 2 )); then
command cp "$@"
return
fi
local src=$1 dst=$2
if [ -d "$src" ]; then
mkdir -p "$dst"
# ---- 1️⃣ Total size of the directory ---------------------------------
local total=$(du -sb "$src" | awk '{print $1}')
# ---- 2️⃣ Stream the dir through pv into the destination ---------------
tar -cf - -C "$(dirname "$src")" "$(basename "$src")" |
pv -s "$total" |
tar -xf - -C "$dst"
else
# ---- 3️⃣ Single file -------------------------------------------------
mkdir -p "$(dirname "$dst")"
local fsize=$(stat -c%s "$src" 2>/dev/null || wc -c <"$src")
pv -s "$fsize" < "$src" > "$dst"
fi
}