Skip to main content

Overview

Kanagawa dotfiles make it easy to add custom scripts to your system. By placing scripts in ~/.local/bin, they become available system-wide and can integrate with Hyprland keybindings, Waybar, and the theme system.
All scripts in ~/.local/bin are automatically in your PATH and can be executed from anywhere.

Script Installation

The installation process uses GNU Stow to symlink scripts from the dotfiles repository to your system:
# From install.sh
mkdir -p ~/.local/bin
stow -v -R -t ~/.local/bin scripts
What this does:
  • Creates ~/.local/bin if it doesnโ€™t exist
  • Symlinks all files from scripts/ directory to ~/.local/bin
  • Makes scripts available system-wide

Basic Script Template

Simple Bash Script

#!/bin/bash

# Script description and purpose
# Author: Your Name
# Date: 2026-03-08

# Enable error handling
set -e

# Define variables
CONFIG_DIR="$HOME/.config/myapp"
OUTPUT_FILE="$CONFIG_DIR/output.txt"

# Main logic
main() {
  # Your code here
  echo "Script executed successfully"
}

# Run main function
main "$@"

Shell Script (POSIX Compatible)

#!/bin/sh

# Faster startup for simple scripts
# More portable across systems

# Define variables
CONFIG="$HOME/.config/myapp/config"

# Main logic
if [ -f "$CONFIG" ]; then
  echo "Config found"
else
  echo "Config missing"
fi

Integrating with the Theme System

Reading Current Theme

Scripts can read the current theme from configuration files:
#!/bin/bash

# Extract current theme from Hyprland config
get_current_theme() {
  local colors_conf="$HOME/.config/hypr/colors/colors.conf"
  
  if [ -f "$colors_conf" ]; then
    # Parse: source = ~/.config/hypr/colors/custom/kanagawa.conf
    grep "source" "$colors_conf" | sed 's/.*custom\/\(.*\)\.conf/\1/'
  else
    echo "unknown"
  fi
}

CURRENT_THEME=$(get_current_theme)
echo "Current theme: $CURRENT_THEME"

Theme-Aware Script Example

#!/bin/bash

# Script that adapts behavior based on current theme

get_theme_icon() {
  local theme=$1
  
  case "$theme" in
    kanagawa) echo "๐ŸŒŠ" ;;
    gruvbox) echo "๐Ÿ“ฆ" ;;
    catppuccin) echo "โ˜•" ;;
    everforest) echo "๐ŸŒฒ" ;;
    *) echo "๐ŸŽจ" ;;
  esac
}

# Read current theme
CURRENT_THEME=$(grep "source" "$HOME/.config/hypr/colors/colors.conf" | sed 's/.*custom\/\(.*\)\.conf/\1/')
ICON=$(get_theme_icon "$CURRENT_THEME")

# Use in notifications
notify-send "$ICON Script Running" "Using $CURRENT_THEME theme"

Using hyprctl for Window Management

Getting Active Window Info

#!/bin/bash

# Get information about the currently focused window

# Get active window class
ACTIVE_CLASS=$(hyprctl activewindow -j | jq -r '.class')

# Get active window title
ACTIVE_TITLE=$(hyprctl activewindow -j | jq -r '.title')

# Get workspace
ACTIVE_WORKSPACE=$(hyprctl activewindow -j | jq -r '.workspace.id')

echo "Class: $ACTIVE_CLASS"
echo "Title: $ACTIVE_TITLE"
echo "Workspace: $ACTIVE_WORKSPACE"

Window Manipulation Script

#!/bin/bash

# Script to toggle floating mode for active window

toggle_floating() {
  # Get current floating state
  IS_FLOATING=$(hyprctl activewindow -j | jq -r '.floating')
  
  if [ "$IS_FLOATING" = "true" ]; then
    hyprctl dispatch togglefloating
    notify-send "Window" "Tiled"
  else
    hyprctl dispatch togglefloating
    notify-send "Window" "Floating"
  fi
}

toggle_floating

Focus Script Example

#!/bin/bash

# Focus or launch an application

APP_CLASS="$1"
APP_EXEC="$2"

if [ -z "$APP_CLASS" ] || [ -z "$APP_EXEC" ]; then
  echo "Usage: $0 <window-class> <command>"
  exit 1
fi

# Check if window exists
if hyprctl clients -j | jq -e ".[] | select(.class == \"$APP_CLASS\")" >/dev/null; then
  # Focus existing window
  hyprctl dispatch focuswindow "class:$APP_CLASS"
else
  # Launch application
  $APP_EXEC &
fi
Usage:
# Focus or launch Firefox
focus-or-launch "firefox" "firefox"

# Focus or launch terminal
focus-or-launch "ghostty" "ghostty"

Waybar Integration

Create scripts that output JSON for Waybar custom modules:

Basic Waybar Module Script

#!/bin/bash

# Simple text module
echo '{"text": "Hello", "tooltip": "World"}'

Weather Module (Existing Example)

Hereโ€™s the actual weather script from the dotfiles:
#!/bin/sh

# Ciudad/Ubicaciรณn
LOCATION="Jaen"

# Obtener datos (filtramos errores de red con --fail)
WEATHER_DATA=$(curl -s --fail "wttr.in/${LOCATION}?format=j1")

if [ $? -ne 0 ] || [ -z "$WEATHER_DATA" ]; then
  echo '{"text": "Error", "tooltip": "No se pudo conectar con wttr.in"}'
  exit 1
fi

# Extraer datos con jq
TEMP=$(echo "$WEATHER_DATA" | jq -r '.current_condition[0].temp_C')
WEATHER_CODE=$(echo "$WEATHER_DATA" | jq -r '.current_condition[0].weatherCode')
HORA=$(date +%H)

# Funciรณn de mapeo
get_emoji() {
  code=$1
  hora=$2

  # Lรณgica de dรญa: de 07:00 a 19:59
  if [ "$hora" -ge 7 ] && [ "$hora" -lt 20 ]; then
    case "$code" in
      113) echo "โ˜€๏ธ" ;;
      116) echo "๐ŸŒค๏ธ" ;;
      119 | 122) echo "โ˜๏ธ" ;;
      143 | 248 | 260 | 263 | 266 | 281 | 284 | 293 | 296 | 299 | 302 | 305 | 308 | 311 | 314 | 317 | 320 | 323 | 326 | 329 | 332 | 335 | 338 | 350 | 353 | 356 | 359 | 362 | 365 | 368 | 371 | 374 | 377 | 386 | 389 | 392 | 395) echo "๐ŸŒง๏ธ" ;;
      *) echo "โ“" ;;
    esac
  else
    # Lรณgica de noche
    case "$code" in
      113) echo "๐ŸŒ™" ;;
      116 | 119 | 122) echo "โ˜๏ธ" ;;
      143 | 248 | 260 | 263 | 266 | 281 | 284 | 293 | 296 | 299 | 302 | 305 | 308 | 311 | 314 | 317 | 320 | 323 | 326 | 329 | 332 | 335 | 338 | 350 | 353 | 356 | 359 | 362 | 365 | 368 | 371 | 374 | 377 | 386 | 389 | 392 | 395) echo "๐ŸŒง๏ธ" ;;
      *) echo "โ“" ;;
    esac
  fi
}

EMOJI=$(get_emoji "$WEATHER_CODE" "$HORA")

echo "${EMOJI} ${TEMP}ยฐC"

Advanced Waybar Module with Click Actions

#!/bin/bash

# Module that responds to clicks

# Handle click events from Waybar
if [ "$1" = "--toggle" ]; then
  # Do something on click
  notify-send "Module Clicked" "Performing action"
  exit 0
fi

# Normal output
VALUE=$(some_command)
echo '{"text": "'"$VALUE"'", "tooltip": "Click to toggle", "class": "custom-module"}'
Waybar config:
"custom/mymodule": {
  "exec": "~/.local/bin/my-waybar-script",
  "interval": 30,
  "on-click": "~/.local/bin/my-waybar-script --toggle",
  "return-type": "json"
}

Notification Scripts

Basic Notification

#!/bin/bash

notify-send "Title" "Message body" -t 3000

Advanced Notification with Icons and Actions

#!/bin/bash

# Send notification with icon and urgency
notify-send \
  --icon="dialog-information" \
  --urgency=normal \
  --expire-time=5000 \
  --app-name="My Script" \
  "Title" \
  "Message with details"

Progress Notification

#!/bin/bash

# Show progress in notification
for i in {0..100..10}; do
  notify-send \
    --hint=int:value:$i \
    --hint=string:synchronous:progress \
    "Processing" \
    "$i% complete"
  sleep 1
done

notify-send "Complete" "Task finished" -t 2000

Script Templates

Application Launcher

#!/bin/bash

# Launch application with specific settings

APP_NAME="MyApp"
APP_EXEC="/usr/bin/myapp"
APP_ARGS="--config $HOME/.config/myapp/config.conf"

# Check if already running
if pgrep -x "myapp" >/dev/null; then
  notify-send "$APP_NAME" "Already running"
  # Focus window
  hyprctl dispatch focuswindow "class:myapp"
else
  # Launch application
  $APP_EXEC $APP_ARGS &
  notify-send "$APP_NAME" "Launched"
fi

System Information Script

#!/bin/bash

# Collect and display system information

# CPU usage
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)

# Memory usage
MEM=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}')

# Disk usage
DISK=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')

# Display
notify-send "System Info" \
  "CPU: ${CPU}%\nMemory: ${MEM}%\nDisk: ${DISK}%" \
  -t 5000

Screenshot Script

#!/bin/bash

# Screenshot script with Hyprland integration

SCREENSHOT_DIR="$HOME/Pictures/Screenshots"
mkdir -p "$SCREENSHOT_DIR"

FILENAME="screenshot_$(date +%Y%m%d_%H%M%S).png"
FULL_PATH="$SCREENSHOT_DIR/$FILENAME"

# Take screenshot of region
grim -g "$(slurp)" "$FULL_PATH"

if [ $? -eq 0 ]; then
  # Copy to clipboard
  wl-copy < "$FULL_PATH"
  
  notify-send \
    --icon="$FULL_PATH" \
    "Screenshot Saved" \
    "$FILENAME\nCopied to clipboard"
else
  notify-send "Screenshot Failed" "Could not capture screen"
fi

Rofi/Wofi Menu Script

#!/bin/bash

# Create a custom menu with actions

OPTIONS="Lock\nLogout\nReboot\nShutdown"

CHOICE=$(echo -e "$OPTIONS" | wofi --dmenu --prompt "Power Menu:" --width 200 --height 250)

case "$CHOICE" in
  "Lock")
    swaylock
    ;;
  "Logout")
    hyprctl dispatch exit
    ;;
  "Reboot")
    systemctl reboot
    ;;
  "Shutdown")
    systemctl poweroff
    ;;
esac

Best Practices

Error Handling

#!/bin/bash

# Enable strict error handling
set -euo pipefail

# Trap errors
trap 'echo "Error on line $LINENO"' ERR

# Check dependencies
check_deps() {
  for cmd in jq curl hyprctl; do
    if ! command -v "$cmd" &>/dev/null; then
      echo "Error: $cmd is not installed"
      exit 1
    fi
  done
}

check_deps

Logging

#!/bin/bash

LOG_FILE="$HOME/.local/share/myscript.log"

log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}

log "Script started"
# Your code here
log "Script completed"

Configuration Files

#!/bin/bash

CONFIG_FILE="$HOME/.config/myscript/config.conf"

# Load configuration
if [ -f "$CONFIG_FILE" ]; then
  source "$CONFIG_FILE"
else
  # Create default config
  mkdir -p "$(dirname "$CONFIG_FILE")"
  cat > "$CONFIG_FILE" <<EOF
# My Script Configuration
OPTION1="value1"
OPTION2="value2"
EOF
  echo "Created default config: $CONFIG_FILE"
fi

Performance Considerations

Avoid running expensive operations in scripts that execute frequently (like Waybar modules).
#!/bin/bash

# Cache expensive operations
CACHE_FILE="/tmp/myscript_cache"
CACHE_DURATION=300  # 5 minutes

get_data() {
  if [ -f "$CACHE_FILE" ]; then
    # Check if cache is still valid
    AGE=$(($(date +%s) - $(stat -c %Y "$CACHE_FILE")))
    
    if [ "$AGE" -lt "$CACHE_DURATION" ]; then
      cat "$CACHE_FILE"
      return
    fi
  fi
  
  # Fetch fresh data
  DATA=$(expensive_operation)
  echo "$DATA" > "$CACHE_FILE"
  echo "$DATA"
}

get_data

Making Scripts Executable

After creating a script:
# Make executable
chmod +x ~/.local/bin/my-script

# Test execution
my-script

Hyprland Keybindings

Add custom script keybindings to ~/.config/hypr/hyprland.conf:
# Custom scripts
bind = SUPER, P, exec, ~/.local/bin/my-power-menu
bind = SUPER_SHIFT, S, exec, ~/.local/bin/my-screenshot
bind = SUPER, I, exec, ~/.local/bin/system-info

# With arguments
bind = SUPER, F, exec, ~/.local/bin/focus-or-launch "firefox" "firefox"

Debugging Scripts

Enable Debug Output

#!/bin/bash

# Enable debug mode
set -x  # Print commands before executing

# Your script here

Run with Debug

# Run script with bash debug
bash -x ~/.local/bin/my-script

# Check for syntax errors
bash -n ~/.local/bin/my-script

Log Output

# Redirect all output to log file
my-script &> /tmp/script-debug.log

# View log
cat /tmp/script-debug.log

Example: Complete Custom Script

Hereโ€™s a complete example combining multiple concepts:
#!/bin/bash

# ==========================================
# Workspace Manager Script
# Quickly switch to common workspace layouts
# ==========================================

set -euo pipefail

CONFIG_FILE="$HOME/.config/workspace-manager/config.conf"
LOG_FILE="$HOME/.local/share/workspace-manager.log"

# Logging function
log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}

# Get current theme
get_theme() {
  grep "source" "$HOME/.config/hypr/colors/colors.conf" 2>/dev/null | \
    sed 's/.*custom\/\(.*\)\.conf/\1/' || echo "unknown"
}

# Workspace layouts
setup_dev_workspace() {
  log "Setting up development workspace"
  
  # Switch to workspace 1
  hyprctl dispatch workspace 1
  
  # Launch terminal
  ghostty &
  sleep 0.5
  
  # Launch editor
  hyprctl dispatch workspace 1
  code &
  sleep 0.5
  
  # Launch browser on workspace 2
  hyprctl dispatch workspace 2
  firefox &
  
  # Return to workspace 1
  hyprctl dispatch workspace 1
  
  THEME=$(get_theme)
  notify-send "๐Ÿš€ Development Workspace" "Setup complete ($THEME theme)" -t 2000
}

setup_media_workspace() {
  log "Setting up media workspace"
  
  hyprctl dispatch workspace 3
  spotify &
  sleep 0.5
  
  # Launch visualizer in floating mode
  ghostty -e cava &
  sleep 0.5
  hyprctl dispatch togglefloating
  
  notify-send "๐ŸŽต Media Workspace" "Setup complete" -t 2000
}

# Menu
OPTIONS="Development\nMedia\nGaming\nCancel"
CHOICE=$(echo -e "$OPTIONS" | wofi --dmenu --prompt "Setup Workspace:" --width 250 --height 300)

case "$CHOICE" in
  "Development")
    setup_dev_workspace
    ;;
  "Media")
    setup_media_workspace
    ;;
  "Gaming")
    log "Gaming workspace selected"
    notify-send "๐ŸŽฎ Gaming" "Not implemented yet"
    ;;
  *)
    log "Workspace setup cancelled"
    ;;
esac

log "Script completed"

Theme Selector

Example of interactive menu script

Theme Switcher

Example of system configuration script

Hyprland Configuration

Keybindings and window management

Waybar

Custom module integration

Build docs developers (and LLMs) love