Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .config/hypr/conf/keybinds.conf
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ bind = $mainMod, G, exec, kitty --class floating-terminal -e lazygit
bind = $mainMod SHIFT, Escape, exec, kitty --class floating-terminal -e btop

# Hyprland Scripts
bind = $mainMod, A, exec, ~/.config/hypr/scripts/wallpaper.sh
bind = $mainMod, A, exec, ~/.config/hypr/scripts/wallpaper.sh --once
bind = $mainMod SHIFT, R, exec, ~/.config/hypr/scripts/reload.sh

# Waybar Scripts
Expand Down
90 changes: 56 additions & 34 deletions .config/hypr/scripts/wallpaper.sh
Original file line number Diff line number Diff line change
@@ -1,41 +1,63 @@
#!/bin/bash
#!/usr/bin/env bash
# Apply workspace wallpapers once or listen with one owner per Hyprland session.
set -euo pipefail

# Transition settings: clean bezier fade matching monitor refresh rate (144Hz)
TRANSITION_OPTS="--transition-type fade --transition-bezier .23,1,.32,1 --transition-duration 0.8 --transition-fps 144"

change_wallpaper(){
workspace=$(hyprctl activeworkspace -j | jq -r '.id')

# Set wallpapers for each workspace
case $workspace in
1)
awww img /home/samhb/Pictures/Wallpapers/csm_wallpaper.jpg $TRANSITION_OPTS
;;
2)
awww img /home/samhb/Pictures/Wallpapers/reze_wallpaper.png $TRANSITION_OPTS
;;
3)
awww img /home/samhb/Pictures/Wallpapers/dandadan_wallpaper.png $TRANSITION_OPTS
;;
4)
awww img /home/samhb/Pictures/Wallpapers/goku_black_wallpaper.jpg $TRANSITION_OPTS
;;
5)
awww img /home/samhb/Pictures/Wallpapers/evangelion_wallpaper.png $TRANSITION_OPTS
;;
esac
mode=${1:-listen}
if (( $# > 1 )) || [[ $mode != listen && $mode != --once ]]; then
echo 'Usage: wallpaper.sh [--once]' >&2
exit 2
fi
for command in hyprctl jq awww; do
command -v "$command" >/dev/null || { echo "Missing command: $command" >&2; exit 1; }
done

# Apply the current workspace image; unmapped workspaces retain the last image.
change_wallpaper() {
local workspace image
if ! workspace=$(hyprctl activeworkspace -j | jq -er '.id'); then
echo 'Could not read active workspace.' >&2
return 1
fi
case "$workspace" in
1) image=csm_wallpaper.jpg ;;
2) image=reze_wallpaper.png ;;
3) image=dandadan_wallpaper.png ;;
4) image=goku_black_wallpaper.jpg ;;
5) image=evangelion_wallpaper.png ;;
*) return 0 ;;
esac
image="$HOME/Pictures/Wallpapers/$image"
if [[ ! -f $image ]]; then
echo "Wallpaper not found: $image" >&2
return 1
fi
awww img "$image" --transition-type fade --transition-bezier .23,1,.32,1 \
--transition-duration 0.8 --transition-fps 144
}

# Set initial wallpaper
change_wallpaper
if [[ $mode == listen ]]; then
: "${XDG_RUNTIME_DIR:?Missing XDG_RUNTIME_DIR}" "${HYPRLAND_INSTANCE_SIGNATURE:?Missing Hyprland session}"
for command in socat flock; do
command -v "$command" >/dev/null || { echo "Missing command: $command" >&2; exit 1; }
done
socket_path="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"
[[ -S $socket_path ]] || { echo "Hyprland socket not found: $socket_path" >&2; exit 1; }
exec 9>"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/wallpaper.lock"
flock -n 9 || { echo 'Wallpaper listener already running.'; exit 0; }
fi

# Socket Path:
SOCKET_PATH="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"
# Autostart order does not guarantee awww is ready; bound the wait to five seconds.
ready=false
for ((attempt=0; attempt<20; attempt++)); do
if awww query >/dev/null 2>&1; then ready=true; break; fi
sleep 0.25
done
[[ $ready == true ]] || { echo 'Wallpaper daemon unavailable after five seconds.' >&2; exit 1; }
change_wallpaper
[[ $mode == --once ]] && exit 0

# Listen for workspace events
socat -U - UNIX-CONNECT:${SOCKET_PATH} | while read -r line; do
if echo "$line" | grep -q "workspace>>"; then
change_wallpaper
fi
socat -U - "UNIX-CONNECT:$socket_path" | while IFS= read -r line; do
case "$line" in
workspace\>\>*) change_wallpaper || echo 'Could not update workspace wallpaper.' >&2 ;;
esac
done
48 changes: 38 additions & 10 deletions .config/rofi/scripts/clipboard_history.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,41 @@
#!/usr/bin/env bash
# Copy, delete, or confirm clearing clipboard history without changing it on cancel.
set -euo pipefail
trap 'notify-send "Clipboard" "Clipboard operation failed." >&2 || true' ERR
payload=$(mktemp)
trap 'rm -f "$payload"' EXIT

# Show clipboard history with custom message using dedicated theme
cliphist list | rofi -dmenu -i \
-p " CLIPBOARD HISTORY" \
-mesg " [ ENTER ] PASTE [ DEL ] DELETE ENTRY [ CTRL+DEL ] CLEAR ALL" \
-theme ~/.config/rofi/themes/clipboard.rasi | cliphist decode | wl-copy

# Notify if copied
if [ -n "$(wl-paste 2>/dev/null || true)" ]; then
notify-send "Clipboard" "Content copied!" -t 2000
fi
while true; do
history=$(cliphist list)
[[ -n $history ]] || { notify-send 'Clipboard' 'History is empty.'; exit 0; }
mapfile -t entries <<< "$history"
status=0
selected=$(printf '%s\n' "${entries[@]}" | rofi -dmenu -i -no-custom -format i \
-kb-remove-char-forward Control+d -kb-remove-word-forward Control+Alt+d \
-kb-custom-1 Delete -kb-custom-2 Control+Delete \
-p 'CLIPBOARD HISTORY' -mesg '[ ENTER ] COPY [ DEL ] DELETE ENTRY [ CTRL+DEL ] CLEAR ALL' \
-theme "$HOME/.config/rofi/themes/clipboard.rasi") || status=$?
[[ $status != 1 ]] || exit 0
case $status in
0|10)
[[ $selected =~ ^(0|[1-9][0-9]*)$ ]] && (( selected < ${#entries[@]} )) || exit 1
if [[ $status == 10 ]]; then
printf '%s\n' "${entries[selected]}" | cliphist delete
continue
fi
# Decode first: a failed lookup must not replace the current clipboard.
printf '%s\n' "${entries[selected]}" | cliphist decode > "$payload"
wl-copy < "$payload"
notify-send 'Clipboard' 'Content copied!' -t 2000
exit 0
;;
11)
confirm_status=0
confirmation=$(printf 'Cancel\nClear all\n' | rofi -dmenu -no-custom -format i \
-p 'Clear clipboard history?' -theme "$HOME/.config/rofi/themes/current.rasi") || confirm_status=$?
if [[ $confirm_status == 0 && $confirmation == 1 ]]; then cliphist wipe; fi
[[ $confirm_status == 0 || $confirm_status == 1 ]] || exit 1
;;
*) exit "$status" ;;
esac
done
59 changes: 30 additions & 29 deletions .config/rofi/scripts/wallpaper.sh
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
#!/bin/bash

WALLPAPER_DIR="$HOME/Pictures/Wallpapers"
[ -d "$WALLPAPER_DIR" ] || WALLPAPER_DIR="$HOME/Pictures/Wallpaper"

# Get all wallpapers
WALLPAPERS=$(find "$WALLPAPER_DIR" -type f \( -iname "*.jpg" -o -iname "*.png" -o -iname "*.jpeg" \) | sort)

# Check if directory has wallpapers
if [ -z "$WALLPAPERS" ]; then
notify-send "Wallpaper Selector" "No wallpapers found in $WALLPAPER_DIR"
exit 1
fi

# Create rofi menu with image names
SELECTED=$(echo "$WALLPAPERS" | while read -r wallpaper; do
basename "$wallpaper"
done | rofi -dmenu -i -theme ~/.config/rofi/themes/current.rasi -p "Select Wallpaper" \
-theme-str 'window {width: 600px;} listview {lines: 10;}')

# If something was selected
if [ -n "$SELECTED" ]; then
WALLPAPER_PATH=$(echo "$WALLPAPERS" | grep "$SELECTED$")

if [ -f "$WALLPAPER_PATH" ]; then
awww img "$WALLPAPER_PATH" --transition-type fade --transition-bezier .23,1,.32,1 --transition-duration 0.8 --transition-fps 144
notify-send "Wallpaper Changed" "$SELECTED" -i "$WALLPAPER_PATH" -t 3000
fi
fi
#!/usr/bin/env bash
# Select wallpaper paths by index, preserving punctuation and duplicate basenames.
set -euo pipefail
trap 'notify-send "Wallpaper" "Could not select or apply wallpaper." >&2 || true' ERR
wallpaper_dir="$HOME/Pictures/Wallpapers"
[[ -d $wallpaper_dir ]] || wallpaper_dir="$HOME/Pictures/Wallpaper"
[[ -d $wallpaper_dir ]] || { notify-send 'Wallpaper' 'Wallpaper directory not found.'; exit 1; }
# A temporary null-delimited list preserves paths and lets discovery failures surface.
list=$(mktemp)
trap 'rm -f "$list"' EXIT
find "$wallpaper_dir" -type f \( -iname '*.jpg' -o -iname '*.png' -o -iname '*.jpeg' \) -print0 | sort -z > "$list"
mapfile -d '' -t wallpapers < "$list"
(( ${#wallpapers[@]} )) || { notify-send 'Wallpaper' 'No wallpapers found.'; exit 0; }
names=()
for wallpaper in "${wallpapers[@]}"; do
name=${wallpaper#"$wallpaper_dir/"}
# Display control characters safely; the index still resolves the original path.
printf -v name '%q' "$name"
names+=("$name")
done
status=0
selected=$(printf '%s\n' "${names[@]}" | rofi -dmenu -i -no-custom -format i \
-theme "$HOME/.config/rofi/themes/current.rasi" -p 'Select Wallpaper' \
-theme-str 'window {width: 600px;} listview {lines: 10;}') || status=$?
[[ $status != 1 ]] || exit 0
[[ $status == 0 ]] || exit "$status"
[[ $selected =~ ^(0|[1-9][0-9]*)$ ]] && (( selected < ${#wallpapers[@]} )) || exit 1
awww img "${wallpapers[selected]}" --transition-type fade --transition-bezier .23,1,.32,1 \
--transition-duration 0.8 --transition-fps 144
notify-send 'Wallpaper Changed' "${names[selected]}" -i "${wallpapers[selected]}" -t 3000
32 changes: 21 additions & 11 deletions .config/rofi/scripts/wifi.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
#!/bin/bash
notify-send "WiFi" "Scanning networks..."
WIFI_LIST=$(nmcli -f SSID,SECURITY,SIGNAL device wifi list | tail -n +2)
CHOSEN=$(echo "$WIFI_LIST" | rofi -dmenu -i -theme ~/.config/rofi/themes/current.rasi -p "WiFi Networks" | awk '{print $1}')

if [ -n "$CHOSEN" ]; then
PASSWORD=$(rofi -dmenu -theme ~/.config/rofi/themes/current.rasi -password -p "Password for $CHOSEN")
nmcli device wifi connect "$CHOSEN" password "$PASSWORD" && \
notify-send "WiFi" "Connected to $CHOSEN" || \
notify-send "WiFi" "Failed to connect"
fi
#!/usr/bin/env bash
# Select an intact SSID and delegate credentials to NetworkManager in Kitty.
set -euo pipefail
trap 'notify-send "Wi-Fi" "Could not open the network connection prompt." >&2 || true' ERR
networks=$(nmcli --escape no --get-values SSID device wifi list)
declare -A seen=()
ssids=()
# shortcut: SSIDs containing newlines are not representable in this menu; use nmcli directly for those.
while IFS= read -r ssid; do
[[ -n $ssid ]] || continue
if [[ ! ${seen["$ssid"]+present} ]]; then ssids+=("$ssid"); seen["$ssid"]=1; fi
done <<< "$networks"
(( ${#ssids[@]} )) || { notify-send 'Wi-Fi' 'No named networks found.'; exit 0; }
status=0
selected=$(printf '%s\n' "${ssids[@]}" | rofi -dmenu -i -no-custom -format i \
-theme "$HOME/.config/rofi/themes/current.rasi" -p 'Wi-Fi Networks') || status=$?
[[ $status != 1 ]] || exit 0
[[ $status == 0 ]] || exit "$status"
[[ $selected =~ ^(0|[1-9][0-9]*)$ ]] && (( selected < ${#ssids[@]} )) || exit 1
# No password is collected or placed in command arguments by this script.
kitty --hold -e nmcli --ask device wifi connect "${ssids[selected]}"
14 changes: 14 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Validate shell syntax, regressions, and shell usage on pull requests.
name: Dotfiles checks
on: [push, pull_request]
permissions:
contents: read
jobs:
shell:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y shellcheck jq
- run: bash tests/run.sh
- name: ShellCheck changed shell scripts
run: shellcheck .config/hypr/scripts/wallpaper.sh .config/rofi/scripts/{wifi,clipboard_history,wallpaper}.sh scripts/apply-system-optimizations.sh tests/*.sh
34 changes: 23 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,18 +164,30 @@ Existing conflicting configuration files or directories will be safely backed up

## System optimizations

An automated tuning script is included in `scripts/apply-system-optimizations.sh`. It configures:

- DNF parallel downloads (capped at 2), fastest mirror selection, and default yes prompts.
- Tuned power profile set to `desktop` for responsive CPU governor scaling.
- Disables `NetworkManager-wait-online.service` to trim cold boot time.
- Inotify user watch limit raised to 524,288 for large development projects.
- Hardware video decode via RPM Fusion `mesa-va-drivers-freeworld` on AMD Radeon GPUs.
- Disables idle background daemons (`ModemManager`, `thermald`, `rsyslog`, `abrtd`, and `lvm2-monitor`).
- Installs modern developer CLI tools (`fzf`, `zoxide`, `eza`, `git-delta`, `duf`).

To apply these tweaks:
The default script changes only the developer file-watch limit:

```bash
sudo ./scripts/apply-system-optimizations.sh
```

It preserves unrelated sysctl settings and higher watch limits, backs up changed
files, verifies the kernel value, and restores the previous setting if application
fails. It does not install packages, alter power profiles, or disable services.
See [optional system steps](docs/system-tuning.md) for individually verified
changes and rollback. Previously applied system settings are left in place.

## Verification

Run `bash tests/run.sh` for shell syntax and isolated regression checks. Tests use
mock commands and temporary homes; they do not alter the running desktop.

The workspace wallpaper listener has one owner per Hyprland session. `SUPER+A`
runs `wallpaper.sh --once` to refresh without creating another listener. Workspaces
6–10 retain the last wallpaper. Missing images and daemon failures go to stderr.

Rofi Wi-Fi selection opens Kitty with NetworkManager's `nmcli --ask` prompt;
saved credentials and authentication remain NetworkManager's responsibility.
Clipboard history supports Enter to copy, Delete to delete the selected entry,
and Ctrl+Delete to clear history after confirmation. Escape leaves the clipboard
and history alone. Wallpaper selection displays relative paths and handles
punctuation and duplicate filenames without regex matching.
67 changes: 67 additions & 0 deletions docs/system-tuning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Optional workstation changes

## TL;DR

The default script only raises the developer file-watch limit. Apply the steps
below separately after recording current settings. These commands are for the
Fedora workstation; they are not automatic optimizations or measured wins.

## DNF preferences

Inspect `/etc/dnf/dnf.conf` and back it up with `sudo cp -a --backup=numbered
/etc/dnf/dnf.conf /etc/dnf/dnf.conf.before-dotfiles` before editing. Keep
`max_parallel_downloads=2` as the existing preference until download measurements
justify another value. `fastestmirror=True` selects by connection latency, not
throughput. `defaultyes=True` changes the default confirmation choice, not speed.
For Fedora defaults remove those overrides only after reviewing the file. Verify
with `dnf --dump-main-config` (check the installed DNF version's help first).
Restore the saved file to roll back.

Reference: [DNF5 configuration](https://dnf5.readthedocs.io/en/latest/dnf5.conf.5.html).

## Power profile

Run `tuned-adm active` and `tuned-adm verify` in the actual host session. Record the
active profile before testing another. Keep the current profile by default.
If testing desktop scaling, first confirm `desktop` appears in `tuned-adm list`,
then run `sudo tuned-adm profile desktop`; verify and compare responsiveness and
idle power. Roll back with `sudo tuned-adm profile PREVIOUS_PROFILE`. A file in
`/etc/tuned` is not proof of live activation; do not disable other power managers
without understanding the host's integration.

## Services and boot

Measure `systemd-analyze critical-chain` and `systemd-analyze blame` first. Inspect
`systemctl list-dependencies --reverse NetworkManager-wait-online.service` and
whether network-dependent mounts/services need it. If demonstrated unnecessary,
record its enabled state, then use `sudo systemctl disable
NetworkManager-wait-online.service`; compare subsequent boots. Restore its
previous enabled state to roll back. Never blindly disable ModemManager, thermald,
rsyslog, abrtd, or lvm2-monitor; identify actual use and dependencies first.
Previously disabled services require individual review, not blanket re-enabling.

## AMD hardware video decoding

Inspect `rpm -q mesa-va-drivers mesa-va-drivers-freeworld` and `vainfo`; confirm
which codecs and driver the running application uses. A package's presence alone
does not establish active browser decoding. If needed, follow the current
[RPM Fusion codec instructions](https://rpmfusion.org/Howto/Multimedia) for the
installed Fedora release. Review a proposed DNF swap interactively; do not add
repositories or import keys silently. Record the transaction ID and package
versions. Review `sudo dnf history undo TRANSACTION_ID` to roll back; availability
of old packages can limit rollback. Do not claim codec acceleration until verified
in the application.

## Developer tools

Review and run `sudo dnf install fzf zoxide eza git-delta duf` separately. Verify
with `command -v` for each executable (`delta` is the git-delta executable).
Record the DNF transaction for rollback; avoid removing pre-existing tools.

## File-watch rollback

On success, the watch-limit helper prints any backup path. Restore that exact
file and apply just its watch-limit value with `sudo sysctl -w
fs.inotify.max_user_watches=PREVIOUS_VALUE`. If no file previously existed, remove
only the newly created workstation file and restore the previously recorded
kernel value. Do not apply every sysctl file as a shortcut to rollback.
Loading
Loading