initial commit

This commit is contained in:
2026-09-14 15:31:20 -05:00
commit 438546d84f
44 changed files with 3292 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
# generated wallpapers should not be committed back
wallpapers/*.png
wallpapers/*.jpg
wallpapers/*.jpeg
wallpapers/*.webp
!wallpapers/.gitkeep
*.log
*.swp
.DS_Store
+273
View File
@@ -0,0 +1,273 @@
# arch-i3
A one-shot bootstrap for a complete i3 desktop on a **fresh Arch Linux install**
starting from a system with no X server at all.
Everything is themed with **Catppuccin Macchiato** and typeset in **Inconsolata Nerd Font**.
```
i3-wm · polybar · rofi · dunst · polkit-gnome · thunar · kitty
i3lock-color + betterlockscreen · feh · picom
```
---
## Quick start
From a TTY on a fresh install, as your **normal user** (not root — the scripts
call `sudo` where they need to):
```sh
sudo pacman -S --needed git
git clone <this-repo> ~/arch-i3
cd ~/arch-i3
./install.sh
```
Then log out, log back in (so the new group memberships apply) and:
```sh
startx
```
### Look before you leap
```sh
./install.sh --dry-run # print every action, change nothing
./install.sh --list # show the stages
```
---
## What the installer does
| Stage | Name | What happens |
|-------|------|--------------|
| `00` | preflight | Verifies Arch + network, enables pacman colour and parallel downloads, refreshes mirrors with `reflector`, runs a full `pacman -Syu` |
| `10` | packages | Installs everything in `packages/pacman.txt`; detects your GPU and adds the matching Vulkan/VA-API driver |
| `20` | aur | Builds `yay-bin`, swaps `i3lock``i3lock-color`, installs `packages/aur.txt` |
| `30` | dotfiles | Copies configs into `~`, helper scripts into `~/.local/bin`, generates wallpapers |
| `40` | services | Enables NetworkManager/bluetooth/pipewire, adds you to `video input audio storage`, rebuilds the font cache, primes the betterlockscreen cache |
| `50` | theme | Applies the GTK/Qt/cursor/icon theme and reloads a running session |
Run a subset with `--only` / `--skip`, by number or by name:
```sh
./install.sh --only dotfiles,theme
./install.sh --skip 00,10 # configs only, no package churn
./install.sh -y # never prompt
./install.sh --multilib # also enable the [multilib] repo
```
**Re-running is safe.** Package installs use `--needed`, and any config file the
installer would overwrite is first *moved* — never deleted — into
`~/.config-backup-<timestamp>/`.
```sh
scripts/restore-backup.sh --list # see what's been backed up
scripts/restore-backup.sh # put the most recent backup back
```
---
## Keybindings
`$mod` is the **Super** (Windows) key.
### Launching
| Keys | Action |
|------|--------|
| `$mod + Return` | kitty |
| `$mod + Shift + Return` | kitty, floating and centred |
| `$mod + e` | Thunar |
| `$mod + d` | rofi — application launcher |
| `$mod + Shift + d` | rofi — run a binary |
| `$mod + Tab` | rofi — window switcher |
| `$mod + Shift + e` | power menu (lock / logout / suspend / reboot / shutdown) |
| `$mod + q` | close window |
| `$mod + Shift + q` | `xkill` — click a window to kill it |
### Session
| Keys | Action |
|------|--------|
| `$mod + Shift + x` | lock the screen |
| `$mod + Shift + c` | reload the i3 config |
| `$mod + Shift + r` | restart i3 in place |
### Focus and movement
| Keys | Action |
|------|--------|
| `$mod + h/j/k/l` or arrows | move focus |
| `$mod + Shift + h/j/k/l` | move the window |
| `$mod + 1…0` | switch workspace |
| `$mod + Shift + 1…0` | send window to workspace |
| `Alt + Tab` | last workspace |
| `$mod + Ctrl + ←/→` | previous / next workspace |
| `$mod + Ctrl + Shift + ←/→` | move the workspace to another monitor |
### Layout
| Keys | Action |
|------|--------|
| `$mod + b` / `$mod + v` | split horizontally / vertically |
| `$mod + f` | fullscreen |
| `$mod + s` / `$mod + w` / `$mod + g` | stacking / tabbed / toggle split |
| `$mod + Shift + space` | float this window |
| `$mod + space` | focus tiling ↔ floating |
| `$mod + r` | resize mode (`h/j/k/l`, `Escape` to leave) |
| `$mod + minus` / `$mod + Shift + minus` | show / send to scratchpad |
| `$mod + plus` / `$mod + Shift + plus` | grow / shrink gaps |
### Hardware and utilities
| Keys | Action |
|------|--------|
| Volume / brightness / media keys | handled, with a dunst OSD |
| `Print` | full-screen screenshot |
| `Shift + Print` | select a region |
| `$mod + Print` | active window |
| `$mod + p` | display mode: `a`uto / `d`uplicate / `e`xtend / `l`aptop-only |
| `$mod + n` | pop the last notification back up |
| `$mod + Shift + n` | dismiss all notifications |
| `$mod + Ctrl + n` | pause/resume notifications |
| `$mod + Shift + w` | random wallpaper |
---
## Layout of this repo
```
install.sh orchestrator — stage runner, flags, summary
packages/
pacman.txt official repo packages
aur.txt AUR packages
scripts/
lib.sh logging, dry-run, backup-aware file installer
00-preflight.sh … 50-theme.sh
make-wallpaper.sh synthesises the Catppuccin wallpapers
restore-backup.sh undo
dotfiles/
xinitrc, xprofile, Xresources, gtkrc-2.0
config/ → ~/.config/…
i3/config window manager
polybar/config.ini bar; launch.sh detects your hardware
rofi/ config.rasi + Macchiato themes + powermenu.sh
dunst/dunstrc notifications
kitty/ kitty.conf + Macchiato colours
picom/picom.conf compositor
betterlockscreen/ lock screen colours
gtk-3.0/, gtk-4.0/, qt5ct/, xfce4/ toolkit theming
local/bin/ → ~/.local/bin/…
set-wallpaper volume brightness screenshot display restart-daemon
wallpapers/ drop your own images here; they win over generated ones
```
---
## Things worth knowing
### `i3lock` becomes `i3lock-color`
`betterlockscreen` depends on **i3lock-color**, an AUR fork that adds the
`--*-color` flags the Macchiato lock screen needs. It *conflicts with* and
*provides* the repo `i3lock`, so stage 20 removes the repo package first. You
still get an `i3lock` binary at the usual path — it just understands more flags.
### The lock screen needs a cached image
`betterlockscreen` pre-renders blurred wallpapers instead of blurring at lock
time (that's why it's instant). Stage 40 builds that cache. After changing your
wallpaper, refresh it:
```sh
betterlockscreen -u ~/.local/share/wallpapers/your-image.png --blur 0.5
```
`set-wallpaper <path>` does this for you automatically.
Locking is wired up three ways: `$mod+Shift+x`, `xss-lock` (10-minute idle timer
and lid close, started from the i3 config), and a `betterlockscreen@.service`
sleep hook for suspends that happen outside the X session.
### polybar adapts to the machine
`~/.config/polybar/launch.sh` detects your battery, AC adapter, backlight device
and network interfaces at launch and passes them in as environment variables, so
`config.ini` has nothing machine-specific in it. Modules with no matching
hardware (battery and backlight on a desktop) are dropped from the bar rather
than left to log errors. One bar spawns per monitor; the system tray goes on the
primary one. Logs land in `~/.cache/polybar.log`.
### NVIDIA
Stage 10 installs Mesa and, for Intel/AMD, the matching Vulkan driver. NVIDIA is
left to you deliberately — the right package depends on your card's generation
and kernel:
```sh
sudo pacman -S nvidia nvidia-utils # current cards, linux kernel
sudo pacman -S nvidia-lts nvidia-utils # linux-lts kernel
```
If you get tearing, uncomment nothing — `picom.conf` already sets
`backend = "glx"` and `vsync = true`. On a weak GPU, delete the `blur` block.
### No display manager
The setup logs in from a TTY with `startx`, which keeps the boot lean. If you'd
rather have a greeter:
```sh
sudo pacman -S ly && sudo systemctl enable ly.service # minimal, TUI
# or
sudo pacman -S lightdm lightdm-gtk-greeter && sudo systemctl enable lightdm
```
Both read `~/.xprofile`, so the environment stays identical either way.
### Making it yours
Put personal i3 tweaks in `~/.config/i3/config.d/*.conf`. They're included at the
end of the main config, so they override anything above them, and the installer
never touches that directory.
For everything else, edit the files in `dotfiles/` and re-run:
```sh
./install.sh --only dotfiles
```
### Changing the accent colour
The Catppuccin accent is **mauve** (`#c6a0f6`) throughout. To switch to, say,
teal, change it in five places:
```
dotfiles/config/i3/config client.focused
dotfiles/config/polybar/config.ini primary = / label-focused-*
dotfiles/config/rofi/themes/… selected:
dotfiles/config/dunst/dunstrc frame_color / [urgency_normal]
dotfiles/config/*/settings.ini catppuccin-macchiato-<accent>-standard+default
```
---
## Troubleshooting
**The bar is missing.** `cat ~/.cache/polybar.log`. The most common cause is a
font that didn't install — check with `fc-list | grep -i inconsolata`.
**Boxes instead of icons.** The Nerd Font glyphs aren't being found. Run
`fc-cache -fv`, then confirm `ttf-inconsolata-nerd` and `ttf-nerd-fonts-symbols-mono`
are installed.
**GTK apps look light/wrong.** The AUR theme package may name its accent
directory differently. `ls /usr/share/themes | grep -i catppuccin`, then put the
exact name into `~/.config/gtk-3.0/settings.ini`. Stage 50 tries to detect and
patch this automatically.
**No sound.** `systemctl --user status pipewire wireplumber`. A reboot after the
first install is usually what's missing.
**Lock screen shows a plain grey box.** The betterlockscreen cache isn't built —
run `betterlockscreen -u <image>`.
**i3 won't start.** `i3 -C` validates the config and prints the offending line.
+64
View File
@@ -0,0 +1,64 @@
! ==========================================================================
! ~/.Xresources — Catppuccin Macchiato for legacy X clients
! (xterm, i3lock's fallback rendering, dmenu, urxvt …)
! ==========================================================================
! --- DPI: uncomment and set for HiDPI panels ------------------------------
! Xft.dpi: 144
Xft.antialias: 1
Xft.hinting: 1
Xft.hintstyle: hintslight
Xft.rgba: rgb
Xft.lcdfilter: lcddefault
Xft.autohint: 0
! --- cursor ----------------------------------------------------------------
Xcursor.theme: catppuccin-macchiato-dark-cursors
Xcursor.size: 24
! --- Catppuccin Macchiato palette -----------------------------------------
#define base #24273a
#define mantle #1e2030
#define crust #181926
#define text #cad3f5
#define rosewater #f4dbd6
#define surface1 #494d64
#define surface2 #5b6078
#define red #ed8796
#define green #a6da95
#define yellow #eed49f
#define blue #8aadf4
#define pink #f5bde6
#define teal #8bd5ca
#define subtext1 #b8c0e0
#define subtext0 #a5adcb
*.foreground: text
*.background: base
*.cursorColor: rosewater
*.color0: surface1
*.color8: surface2
*.color1: red
*.color9: red
*.color2: green
*.color10: green
*.color3: yellow
*.color11: yellow
*.color4: blue
*.color12: blue
*.color5: pink
*.color13: pink
*.color6: teal
*.color14: teal
*.color7: subtext1
*.color15: subtext0
! --- xterm -----------------------------------------------------------------
XTerm*faceName: Inconsolata Nerd Font
XTerm*faceSize: 12
XTerm*termName: xterm-256color
XTerm*selectToClipboard: true
XTerm*saveLines: 10000
XTerm*scrollBar: false
@@ -0,0 +1,50 @@
# ===========================================================================
# betterlockscreen — Catppuccin Macchiato
# All colours are RRGGBBAA (i3lock-color format, no leading '#').
# Docs: https://github.com/betterlockscreen/betterlockscreen
# ===========================================================================
# Which cached image variant `betterlockscreen -l` uses when given no argument
display_on=0
span_image=false
# --- ring ------------------------------------------------------------------
insidecolor=24273a00
ringcolor=c6a0f6ff
insidevercolor=8aadf41c
insidewrongcolor=ed879633
ringvercolor=8aadf4ff
ringwrongcolor=ed8796ff
# --- keypress highlight ----------------------------------------------------
keyhlcolor=a6da95ff
bshlcolor=f5a97fff
separatorcolor=00000000
# --- text ------------------------------------------------------------------
verifcolor=cad3f5ff
timecolor=cad3f5ff
datecolor=a5adcbff
layoutcolor=a5adcbff
loginbox=24273aaa
locktext="Type password to unlock"
font="Inconsolata Nerd Font"
time_format="%H:%M"
date_format="%A, %d %B"
# --- ring geometry ---------------------------------------------------------
ring_width=6
radius=120
# --- greeter ---------------------------------------------------------------
greetercolor=c6a0f6ff
greeter_text="Welcome back, $USER"
# --- blur strength used by `betterlockscreen -u <img> --blur <n>` ----------
blur_level=1
# --- behaviour -------------------------------------------------------------
# Show the failed-attempt count and blank the screen after 60s of lock idle
lock_timeout=60
quiet=true
+127
View File
@@ -0,0 +1,127 @@
# ===========================================================================
# dunst — Catppuccin Macchiato
# Font: Inconsolata Nerd Font
# Docs: man 5 dunst
# ===========================================================================
[global]
monitor = 0
follow = mouse
# --- geometry ---------------------------------------------------------
width = (280, 440)
height = (0, 300)
origin = top-right
offset = (12, 42) # 42 clears the 30pt polybar
scale = 0
notification_limit = 6
progress_bar = true
progress_bar_height = 8
progress_bar_frame_width = 0
progress_bar_min_width = 150
progress_bar_max_width = 400
progress_bar_corner_radius = 4
indicate_hidden = yes
transparency = 5
separator_height = 4
padding = 12
horizontal_padding = 14
text_icon_padding = 10
frame_width = 2
frame_color = "#c6a0f6"
gap_size = 6
separator_color = frame
sort = yes
corner_radius = 10
# --- text -------------------------------------------------------------
font = Inconsolata Nerd Font 11
line_height = 0
markup = full
format = "<b>%s</b>\n%b"
alignment = left
vertical_alignment = center
show_age_threshold = 60
ellipsize = middle
ignore_newline = no
stack_duplicates = true
hide_duplicate_count = false
show_indicators = yes
# --- icons ------------------------------------------------------------
enable_recursive_icon_lookup = true
icon_theme = "Papirus-Dark, Adwaita"
icon_position = left
min_icon_size = 32
max_icon_size = 64
# --- history ----------------------------------------------------------
sticky_history = yes
history_length = 25
# --- misc -------------------------------------------------------------
dmenu = /usr/bin/rofi -dmenu -p dunst
browser = /usr/bin/xdg-open
always_run_script = true
title = Dunst
class = Dunst
ignore_dbusclose = false
# --- wayland (unused under i3, harmless) -------------------------------
force_xwayland = false
# --- mouse ------------------------------------------------------------
mouse_left_click = do_action, close_current
mouse_middle_click = close_all
mouse_right_click = close_current
[experimental]
per_monitor_dpi = false
# ---------------------------------------------------------------------------
# Urgency styling
# ---------------------------------------------------------------------------
[urgency_low]
background = "#24273a"
foreground = "#a5adcb"
frame_color = "#8bd5ca"
highlight = "#8bd5ca"
timeout = 5
[urgency_normal]
background = "#24273a"
foreground = "#cad3f5"
frame_color = "#c6a0f6"
highlight = "#c6a0f6"
timeout = 8
[urgency_critical]
background = "#24273a"
foreground = "#cad3f5"
frame_color = "#ed8796"
highlight = "#ed8796"
timeout = 0
# ---------------------------------------------------------------------------
# Rules
# ---------------------------------------------------------------------------
# Volume / brightness OSD: replace in place instead of stacking up.
[osd]
appname = "osd"
format = "<b>%s</b>\n%b"
timeout = 2
history_ignore = yes
urgency = low
# Screenshot notifications get a longer read and a click-to-open action.
[screenshot]
appname = "screenshot"
timeout = 6
urgency = normal
# Battery warnings should never disappear on their own.
[battery-critical]
appname = "battery"
urgency = critical
+17
View File
@@ -0,0 +1,17 @@
[Settings]
gtk-theme-name=catppuccin-macchiato-mauve-standard+default
gtk-icon-theme-name=Papirus-Dark
gtk-cursor-theme-name=catppuccin-macchiato-dark-cursors
gtk-cursor-theme-size=24
gtk-font-name=Inconsolata Nerd Font 11
gtk-application-prefer-dark-theme=1
gtk-enable-animations=1
gtk-primary-button-warps-slider=1
gtk-toolbar-style=GTK_TOOLBAR_ICONS
gtk-menu-images=1
gtk-button-images=1
gtk-decoration-layout=appmenu:none
gtk-xft-antialias=1
gtk-xft-hinting=1
gtk-xft-hintstyle=hintslight
gtk-xft-rgba=rgb
+11
View File
@@ -0,0 +1,11 @@
[Settings]
gtk-theme-name=catppuccin-macchiato-mauve-standard+default
gtk-icon-theme-name=Papirus-Dark
gtk-cursor-theme-name=catppuccin-macchiato-dark-cursors
gtk-cursor-theme-size=24
gtk-font-name=Inconsolata Nerd Font 11
gtk-application-prefer-dark-theme=1
gtk-xft-antialias=1
gtk-xft-hinting=1
gtk-xft-hintstyle=hintslight
gtk-xft-rgba=rgb
+333
View File
@@ -0,0 +1,333 @@
# ===========================================================================
# i3 window manager — Catppuccin Macchiato
# Font: Inconsolata Nerd Font
# Docs: https://i3wm.org/docs/userguide.html
# ===========================================================================
set $mod Mod4
set $alt Mod1
# --- Catppuccin Macchiato palette -----------------------------------------
set $rosewater #f4dbd6
set $flamingo #f0c6c6
set $pink #f5bde6
set $mauve #c6a0f6
set $red #ed8796
set $maroon #ee99a0
set $peach #f5a97f
set $yellow #eed49f
set $green #a6da95
set $teal #8bd5ca
set $sky #91d7e3
set $sapphire #7dc4e4
set $blue #8aadf4
set $lavender #b7bdf8
set $text #cad3f5
set $subtext1 #b8c0e0
set $subtext0 #a5adcb
set $overlay2 #939ab7
set $overlay1 #8087a2
set $overlay0 #6e738d
set $surface2 #5b6078
set $surface1 #494d64
set $surface0 #363a4f
set $base #24273a
set $mantle #1e2030
set $crust #181926
# --- fonts -----------------------------------------------------------------
font pango:Inconsolata Nerd Font 11
# --- applications ----------------------------------------------------------
set $term kitty
set $filemanager thunar
set $menu rofi -show drun
set $window_menu rofi -show window
set $runner rofi -show run
set $powermenu ~/.config/rofi/scripts/powermenu.sh
set $lock betterlockscreen --lock dimblur
# ---------------------------------------------------------------------------
# Appearance
# ---------------------------------------------------------------------------
# class border backgr. text indicator child_border
client.focused $mauve $mauve $crust $lavender $mauve
client.focused_inactive $surface0 $surface0 $text $surface0 $surface0
client.unfocused $surface0 $base $subtext0 $surface0 $surface0
client.urgent $red $red $crust $red $red
client.placeholder $base $base $text $base $base
client.background $base
# Gaps + borders (i3 >= 4.22 has gaps built in)
default_border pixel 2
default_floating_border pixel 2
hide_edge_borders smart
gaps inner 8
gaps outer 4
smart_gaps on
smart_borders on
# Focus behaviour
focus_follows_mouse no
mouse_warping none
focus_on_window_activation smart
popup_during_fullscreen smart
workspace_auto_back_and_forth yes
# ---------------------------------------------------------------------------
# Autostart
# ---------------------------------------------------------------------------
# exec_always => re-run on config reload; exec => only on i3 start.
# Wallpaper (feh restores whatever set-wallpaper.sh last applied)
exec_always --no-startup-id ~/.local/bin/set-wallpaper
# Compositor
exec_always --no-startup-id ~/.local/bin/restart-daemon picom
# Status bar
exec_always --no-startup-id ~/.config/polybar/launch.sh
# Notification daemon
exec_always --no-startup-id ~/.local/bin/restart-daemon dunst
# XDG autostart entries (nm-applet etc. that ship .desktop files)
exec --no-startup-id dex --autostart --environment i3
# PolicyKit authentication agent (GUI password prompts for gparted, etc.)
exec --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
# Tray applets
exec --no-startup-id nm-applet
exec --no-startup-id blueman-applet
exec --no-startup-id udiskie --tray --no-automount --notify
# Auto-lock: lock on suspend and after 10 min of idle time
exec --no-startup-id xss-lock --transfer-sleep-lock -- $lock --nofork
exec --no-startup-id xset s 600 600
exec --no-startup-id numlockx on
# Cursor theme for the root window
exec_always --no-startup-id xsetroot -cursor_name left_ptr
# ---------------------------------------------------------------------------
# Key bindings — launching
# ---------------------------------------------------------------------------
bindsym $mod+Return exec --no-startup-id $term
bindsym $mod+Shift+Return exec --no-startup-id $term --class kitty-float
bindsym $mod+e exec --no-startup-id $filemanager
bindsym $mod+d exec --no-startup-id $menu
bindsym $mod+Shift+d exec --no-startup-id $runner
bindsym $mod+Tab exec --no-startup-id $window_menu
bindsym $mod+Shift+e exec --no-startup-id $powermenu
bindsym $mod+q kill
bindsym $mod+Shift+q exec --no-startup-id xkill
# Lock / session
bindsym $mod+Shift+x exec --no-startup-id $lock
bindsym $mod+Shift+c reload
bindsym $mod+Shift+r restart
# ---------------------------------------------------------------------------
# Key bindings — focus & movement
# ---------------------------------------------------------------------------
# vim keys …
bindsym $mod+h focus left
bindsym $mod+j focus down
bindsym $mod+k focus up
bindsym $mod+l focus right
# … and arrows
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right
bindsym $mod+Shift+h move left
bindsym $mod+Shift+j move down
bindsym $mod+Shift+k move up
bindsym $mod+Shift+l move right
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
# ---------------------------------------------------------------------------
# Key bindings — layout
# ---------------------------------------------------------------------------
bindsym $mod+b split h
bindsym $mod+v split v
bindsym $mod+f fullscreen toggle
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+g layout toggle split
bindsym $mod+Shift+space floating toggle
bindsym $mod+space focus mode_toggle
bindsym $mod+a focus parent
bindsym $mod+Shift+a focus child
bindsym $mod+minus scratchpad show
bindsym $mod+Shift+minus move scratchpad
# Gap tweaks on the fly
bindsym $mod+plus gaps inner current plus 4
bindsym $mod+Shift+plus gaps inner current minus 4
bindsym $mod+Shift+g gaps inner current set 8; gaps outer current set 4
# Drag floating windows with the modifier held
floating_modifier $mod
tiling_drag modifier titlebar
# ---------------------------------------------------------------------------
# Workspaces
# ---------------------------------------------------------------------------
set $ws1 "1: "
set $ws2 "2: "
set $ws3 "3: "
set $ws4 "4: "
set $ws5 "5: "
set $ws6 "6: "
set $ws7 "7: "
set $ws8 "8: "
set $ws9 "9: "
set $ws10 "10: "
bindsym $mod+1 workspace number $ws1
bindsym $mod+2 workspace number $ws2
bindsym $mod+3 workspace number $ws3
bindsym $mod+4 workspace number $ws4
bindsym $mod+5 workspace number $ws5
bindsym $mod+6 workspace number $ws6
bindsym $mod+7 workspace number $ws7
bindsym $mod+8 workspace number $ws8
bindsym $mod+9 workspace number $ws9
bindsym $mod+0 workspace number $ws10
bindsym $mod+Shift+1 move container to workspace number $ws1
bindsym $mod+Shift+2 move container to workspace number $ws2
bindsym $mod+Shift+3 move container to workspace number $ws3
bindsym $mod+Shift+4 move container to workspace number $ws4
bindsym $mod+Shift+5 move container to workspace number $ws5
bindsym $mod+Shift+6 move container to workspace number $ws6
bindsym $mod+Shift+7 move container to workspace number $ws7
bindsym $mod+Shift+8 move container to workspace number $ws8
bindsym $mod+Shift+9 move container to workspace number $ws9
bindsym $mod+Shift+0 move container to workspace number $ws10
# Cycle workspaces
bindsym $mod+Ctrl+Right workspace next
bindsym $mod+Ctrl+Left workspace prev
bindsym $alt+Tab workspace back_and_forth
# Move workspace between monitors
bindsym $mod+Ctrl+Shift+Right move workspace to output right
bindsym $mod+Ctrl+Shift+Left move workspace to output left
# ---------------------------------------------------------------------------
# Media / hardware keys
# ---------------------------------------------------------------------------
bindsym XF86AudioRaiseVolume exec --no-startup-id ~/.local/bin/volume up
bindsym XF86AudioLowerVolume exec --no-startup-id ~/.local/bin/volume down
bindsym XF86AudioMute exec --no-startup-id ~/.local/bin/volume mute
bindsym XF86AudioMicMute exec --no-startup-id ~/.local/bin/volume micmute
bindsym XF86AudioPlay exec --no-startup-id playerctl play-pause
bindsym XF86AudioNext exec --no-startup-id playerctl next
bindsym XF86AudioPrev exec --no-startup-id playerctl previous
bindsym XF86MonBrightnessUp exec --no-startup-id ~/.local/bin/brightness up
bindsym XF86MonBrightnessDown exec --no-startup-id ~/.local/bin/brightness down
# Screenshots (maim)
bindsym Print exec --no-startup-id ~/.local/bin/screenshot full
bindsym Shift+Print exec --no-startup-id ~/.local/bin/screenshot area
bindsym $mod+Print exec --no-startup-id ~/.local/bin/screenshot window
# Notification control
bindsym $mod+n exec --no-startup-id dunstctl history-pop
bindsym $mod+Shift+n exec --no-startup-id dunstctl close-all
bindsym $mod+Ctrl+n exec --no-startup-id dunstctl set-paused toggle
# Wallpaper shuffle
bindsym $mod+Shift+w exec --no-startup-id ~/.local/bin/set-wallpaper --random
# ---------------------------------------------------------------------------
# Modes
# ---------------------------------------------------------------------------
mode "resize" {
bindsym h resize shrink width 4 px or 4 ppt
bindsym j resize grow height 4 px or 4 ppt
bindsym k resize shrink height 4 px or 4 ppt
bindsym l resize grow width 4 px or 4 ppt
bindsym Left resize shrink width 4 px or 4 ppt
bindsym Down resize grow height 4 px or 4 ppt
bindsym Up resize shrink height 4 px or 4 ppt
bindsym Right resize grow width 4 px or 4 ppt
bindsym Return mode "default"
bindsym Escape mode "default"
bindsym $mod+r mode "default"
}
bindsym $mod+r mode "resize"
set $mode_display Display: [a]uto [d]uplicate [e]xtend-right [l]aptop-only
mode "$mode_display" {
bindsym a exec --no-startup-id "~/.local/bin/display auto", mode "default"
bindsym d exec --no-startup-id "~/.local/bin/display duplicate", mode "default"
bindsym e exec --no-startup-id "~/.local/bin/display extend", mode "default"
bindsym l exec --no-startup-id "~/.local/bin/display internal", mode "default"
bindsym Return mode "default"
bindsym Escape mode "default"
}
bindsym $mod+p mode "$mode_display"
# ---------------------------------------------------------------------------
# Window rules
# ---------------------------------------------------------------------------
for_window [class="^.*"] title_format "<span foreground='#cad3f5'>%title</span>"
# Float dialogs and small utilities
for_window [window_role="pop-up"] floating enable
for_window [window_role="bubble"] floating enable
for_window [window_role="dialog"] floating enable
for_window [window_type="dialog"] floating enable
for_window [window_type="menu"] floating enable
for_window [window_type="utility"] floating enable
for_window [window_type="toolbar"] floating enable
for_window [window_type="splash"] floating enable
for_window [class="Pavucontrol"] floating enable, resize set 900 600, move position center
for_window [class="Blueman-manager"] floating enable, resize set 800 560, move position center
for_window [class="Nm-connection-editor"] floating enable, resize set 800 560, move position center
for_window [class="Lxappearance"] floating enable, move position center
for_window [class="Thunar" instance="^dialog$"] floating enable
for_window [class="File-roller"] floating enable, move position center
for_window [class="Xarchiver"] floating enable, move position center
for_window [class="kitty-float"] floating enable, resize set 1000 620, move position center
for_window [title="^Picture-in-Picture$"] floating enable, sticky enable, resize set 640 360, move position 1250 700
# Assign apps to workspaces
assign [class="(?i)firefox|chromium|Brave-browser"] $ws2
assign [class="Thunar"] $ws4
# No focus stealing from these
no_focus [class="Steam"]
# ---------------------------------------------------------------------------
# i3bar — disabled; polybar handles the status bar.
# Kept as a commented fallback in case polybar ever fails to start.
# ---------------------------------------------------------------------------
# bar {
# status_command i3status
# colors {
# background $base
# statusline $text
# focused_workspace $mauve $mauve $crust
# active_workspace $surface0 $surface0 $text
# inactive_workspace $base $base $subtext0
# urgent_workspace $red $red $crust
# }
# }
# ---------------------------------------------------------------------------
# Local overrides — anything in this directory wins, and it is never
# overwritten by the bootstrap script.
# ---------------------------------------------------------------------------
include ~/.config/i3/config.d/*.conf
+117
View File
@@ -0,0 +1,117 @@
# ===========================================================================
# kitty — Catppuccin Macchiato
# Font: Inconsolata Nerd Font
# Docs: https://sw.kovidgoyal.net/kitty/conf/
# ===========================================================================
# --- font ------------------------------------------------------------------
font_family Inconsolata Nerd Font
bold_font Inconsolata Nerd Font Bold
italic_font auto
bold_italic_font auto
font_size 12.0
# Inconsolata is a touch tight vertically; give the lines a little air.
adjust_line_height 110%
adjust_column_width 0
disable_ligatures never
# Fall back to the symbol font for glyphs Inconsolata Nerd Font lacks
symbol_map U+E000-U+E00A,U+EA60-U+EBEB,U+E0A0-U+E0C8,U+E0CA,U+E0CC-U+E0D7,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E6B1,U+E700-U+E7C5,U+F000-U+F2FF,U+F300-U+F375,U+F400-U+F533,U+F0001-U+F1AF0 Symbols Nerd Font Mono
# --- cursor ----------------------------------------------------------------
cursor_shape beam
cursor_beam_thickness 1.6
cursor_blink_interval -1
cursor_stop_blinking_after 15.0
# --- scrollback ------------------------------------------------------------
scrollback_lines 10000
scrollback_pager less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER
wheel_scroll_multiplier 3.0
# --- mouse -----------------------------------------------------------------
mouse_hide_wait 2.0
url_color #8aadf4
url_style curly
open_url_with default
detect_urls yes
copy_on_select no
strip_trailing_spaces smart
focus_follows_mouse no
# --- performance -----------------------------------------------------------
repaint_delay 8
input_delay 2
sync_to_monitor yes
# --- bell ------------------------------------------------------------------
enable_audio_bell no
visual_bell_duration 0.0
window_alert_on_bell yes
bell_on_tab " "
# --- window ----------------------------------------------------------------
remember_window_size no
initial_window_width 110c
initial_window_height 30c
window_padding_width 10
window_margin_width 0
placement_strategy center
hide_window_decorations yes
confirm_os_window_close 0
# --- tabs ------------------------------------------------------------------
tab_bar_edge bottom
tab_bar_style powerline
tab_powerline_style slanted
tab_bar_min_tabs 2
tab_title_template "{index}: {title}"
active_tab_font_style bold
inactive_tab_font_style normal
# --- layouts ---------------------------------------------------------------
enabled_layouts splits,stack,tall,fat,grid
# i3 does the tiling; kitty splits are here for the occasional full-screen use.
# --- misc ------------------------------------------------------------------
shell .
editor .
allow_remote_control yes
listen_on unix:@kitty
term xterm-kitty
update_check_interval 0
clipboard_control write-clipboard write-primary read-clipboard read-primary
# ===========================================================================
# Keybindings
# ===========================================================================
kitty_mod ctrl+shift
map kitty_mod+c copy_to_clipboard
map kitty_mod+v paste_from_clipboard
map kitty_mod+t new_tab_with_cwd
map kitty_mod+w close_tab
map kitty_mod+right next_tab
map kitty_mod+left previous_tab
map kitty_mod+n new_os_window_with_cwd
map kitty_mod+enter launch --location=split --cwd=current
map kitty_mod+h neighboring_window left
map kitty_mod+j neighboring_window down
map kitty_mod+k neighboring_window up
map kitty_mod+l neighboring_window right
map kitty_mod+equal change_font_size all +1.0
map kitty_mod+minus change_font_size all -1.0
map kitty_mod+backspace change_font_size all 0
map kitty_mod+f show_scrollback
map kitty_mod+u scroll_page_up
map kitty_mod+d scroll_page_down
map kitty_mod+e open_url_with_hints
# ===========================================================================
# Colours
# ===========================================================================
include themes/catppuccin-macchiato.conf
@@ -0,0 +1,76 @@
# vim:ft=kitty
## name: Catppuccin Kitty Macchiato
## author: Catppuccin Org
## license: MIT
## upstream: https://github.com/catppuccin/kitty
# The basic colors
foreground #CAD3F5
background #24273A
selection_foreground #24273A
selection_background #F4DBD6
# Cursor colors
cursor #F4DBD6
cursor_text_color #24273A
# URL underline color when hovering with mouse
url_color #F4DBD6
# Kitty window border colors
active_border_color #B7BDF8
inactive_border_color #6E738D
bell_border_color #EED49F
# OS Window titlebar colors
wayland_titlebar_color system
macos_titlebar_color system
# Tab bar colors
active_tab_foreground #181926
active_tab_background #C6A0F6
inactive_tab_foreground #CAD3F5
inactive_tab_background #1E2030
tab_bar_background #181926
# Colors for marks (marked text in the terminal)
mark1_foreground #24273A
mark1_background #B7BDF8
mark2_foreground #24273A
mark2_background #C6A0F6
mark3_foreground #24273A
mark3_background #7DC4E4
# The 16 terminal colors
# black
color0 #494D64
color8 #5B6078
# red
color1 #ED8796
color9 #ED8796
# green
color2 #A6DA95
color10 #A6DA95
# yellow
color3 #EED49F
color11 #EED49F
# blue
color4 #8AADF4
color12 #8AADF4
# magenta
color5 #F5BDE6
color13 #F5BDE6
# cyan
color6 #8BD5CA
color14 #8BD5CA
# white
color7 #B8C0E0
color15 #A5ADCB
+108
View File
@@ -0,0 +1,108 @@
# ===========================================================================
# picom — compositor
# Tuned to be subtle: no wobbly nonsense, just tear-free rendering,
# soft shadows and a hint of transparency on unfocused terminals.
# Docs: man 5 picom
# ===========================================================================
# --- backend ---------------------------------------------------------------
backend = "glx";
vsync = true;
use-damage = true;
glx-no-stencil = true;
glx-no-rebind-pixmap = false;
# --- shadows ---------------------------------------------------------------
shadow = true;
shadow-radius = 14;
shadow-opacity = 0.45;
shadow-offset-x = -14;
shadow-offset-y = -12;
shadow-color = "#181926";
shadow-exclude = [
"name = 'Notification'",
"class_g = 'Conky'",
"class_g ?= 'Notify-osd'",
"class_g = 'Cairo-clock'",
"class_g = 'Polybar'",
"class_g = 'i3-frame'",
"_GTK_FRAME_EXTENTS@:c",
"window_type = 'dock'",
"window_type = 'desktop'"
];
# --- fading ----------------------------------------------------------------
fading = true;
fade-in-step = 0.06;
fade-out-step = 0.06;
fade-delta = 6;
fade-exclude = [];
# --- transparency ----------------------------------------------------------
inactive-opacity = 0.94;
active-opacity = 1.0;
frame-opacity = 1.0;
inactive-opacity-override = false;
inactive-dim = 0.0;
opacity-rule = [
"100:class_g = 'firefox'",
"100:class_g = 'Chromium'",
"100:class_g = 'mpv'",
"100:fullscreen",
"95:class_g = 'kitty' && focused",
"88:class_g = 'kitty' && !focused",
"95:class_g = 'Rofi'",
"100:class_g = 'Polybar'",
"100:class_g = 'Dunst'"
];
# --- corners (picom 9+) ----------------------------------------------------
corner-radius = 10;
rounded-corners-exclude = [
"window_type = 'dock'",
"window_type = 'desktop'",
"class_g = 'Polybar'",
"class_g = 'i3-frame'"
];
# --- blur (comment out on weak GPUs) ---------------------------------------
blur: {
method = "dual_kawase";
strength = 4;
background = false;
background-frame = false;
background-fixed = false;
}
blur-background-exclude = [
"window_type = 'dock'",
"window_type = 'desktop'",
"class_g = 'Polybar'",
"_GTK_FRAME_EXTENTS@:c"
];
# --- general ---------------------------------------------------------------
mark-wmwin-focused = true;
mark-ovredir-focused = true;
detect-rounded-corners = true;
detect-client-opacity = true;
detect-transient = true;
detect-client-leader = true;
use-ewmh-active-win = true;
unredir-if-possible = false;
dbe = false;
log-level = "warn";
wintypes:
{
tooltip = { fade = true; shadow = true; opacity = 0.95; focus = true; full-shadow = false; };
dock = { shadow = false; clip-shadow-above = true; };
dnd = { shadow = false; };
popup_menu = { opacity = 0.98; shadow = true; };
dropdown_menu= { opacity = 0.98; shadow = true; };
utility = { shadow = false; };
notification = { shadow = true; };
};
+318
View File
@@ -0,0 +1,318 @@
; ==========================================================================
; polybar — Catppuccin Macchiato
; Font: Inconsolata Nerd Font
; Docs: https://github.com/polybar/polybar/wiki
;
; Hardware-specific values (battery, adapter, network interfaces, backlight)
; are injected as environment variables by launch.sh, with sane fallbacks.
; ==========================================================================
[colors]
rosewater = #f4dbd6
flamingo = #f0c6c6
pink = #f5bde6
mauve = #c6a0f6
red = #ed8796
maroon = #ee99a0
peach = #f5a97f
yellow = #eed49f
green = #a6da95
teal = #8bd5ca
sky = #91d7e3
sapphire = #7dc4e4
blue = #8aadf4
lavender = #b7bdf8
text = #cad3f5
subtext1 = #b8c0e0
subtext0 = #a5adcb
overlay2 = #939ab7
overlay1 = #8087a2
overlay0 = #6e738d
surface2 = #5b6078
surface1 = #494d64
surface0 = #363a4f
base = #24273a
mantle = #1e2030
crust = #181926
background = ${colors.base}
background-alt = ${colors.surface0}
foreground = ${colors.text}
primary = ${colors.mauve}
secondary = ${colors.lavender}
alert = ${colors.red}
disabled = ${colors.overlay0}
transparent = #00000000
; --------------------------------------------------------------------------
; Bar
; --------------------------------------------------------------------------
[bar/main]
monitor = ${env:MONITOR:}
monitor-strict = false
override-redirect = false
width = 100%
height = 30pt
radius = 0
background = ${colors.background}
foreground = ${colors.foreground}
line-size = 3pt
border-size = 0pt
border-color = ${colors.transparent}
padding-left = 1
padding-right = 1
module-margin = 1
separator = "│"
separator-foreground = ${colors.surface2}
font-0 = Inconsolata Nerd Font:style=Regular:size=11;2
font-1 = Inconsolata Nerd Font:style=Bold:size=11;2
font-2 = Symbols Nerd Font Mono:size=13;3
font-3 = Noto Color Emoji:scale=10;2
modules-left = i3 xwindow
modules-center = date
modules-right = ${env:MODULES_RIGHT:filesystem memory cpu temperature pulseaudio systray}
cursor-click = pointer
cursor-scroll = ns-resize
enable-ipc = true
wm-restack = i3
; --------------------------------------------------------------------------
; Modules
; --------------------------------------------------------------------------
[module/i3]
type = internal/i3
pin-workspaces = true
show-urgent = true
strip-wsnumbers = true
index-sort = true
enable-click = true
enable-scroll = true
wrapping-scroll = false
reverse-scroll = false
fuzzy-match = true
format = <label-state> <label-mode>
label-mode = %mode%
label-mode-padding = 1
label-mode-background = ${colors.peach}
label-mode-foreground = ${colors.crust}
label-focused = %name%
label-focused-background = ${colors.surface0}
label-focused-foreground = ${colors.mauve}
label-focused-underline = ${colors.mauve}
label-focused-padding = 1
label-unfocused = %name%
label-unfocused-foreground = ${colors.overlay1}
label-unfocused-padding = 1
label-visible = %name%
label-visible-foreground = ${colors.lavender}
label-visible-padding = 1
label-urgent = %name%
label-urgent-background = ${colors.red}
label-urgent-foreground = ${colors.crust}
label-urgent-padding = 1
[module/xwindow]
type = internal/xwindow
label = %title:0:80:...%
label-foreground = ${colors.subtext0}
label-empty =
[module/date]
type = internal/date
interval = 1
date = %a %d %b
time = %H:%M
date-alt = %A, %d %B %Y
time-alt = %H:%M:%S
format-prefix = " "
format-prefix-foreground = ${colors.lavender}
label = %date% %time%
label-foreground = ${colors.text}
[module/filesystem]
type = internal/fs
interval = 60
mount-0 = /
warn-percentage = 85
format-mounted-prefix = " "
format-mounted-prefix-foreground = ${colors.green}
format-warn-prefix = " "
format-warn-prefix-foreground = ${colors.peach}
label-mounted = %percentage_used%%
label-warn = %percentage_used%%
label-unmounted =
click-left = kitty --class kitty-float -e btop
[module/memory]
type = internal/memory
interval = 3
warn-percentage = 90
format-prefix = " "
format-prefix-foreground = ${colors.yellow}
format-warn-prefix = " "
format-warn-prefix-foreground = ${colors.red}
label = %percentage_used%%
label-warn = %percentage_used%%
label-warn-foreground = ${colors.red}
[module/cpu]
type = internal/cpu
interval = 3
warn-percentage = 90
format-prefix = " "
format-prefix-foreground = ${colors.blue}
format-warn-prefix = " "
format-warn-prefix-foreground = ${colors.red}
label = %percentage:2%%
label-warn = %percentage:2%%
label-warn-foreground = ${colors.red}
[module/temperature]
type = internal/temperature
interval = 5
thermal-zone = 0
base-temperature = 20
warn-temperature = 80
units = true
format = <ramp> <label>
format-warn = <ramp> <label-warn>
label = %temperature-c%
label-warn = %temperature-c%
label-warn-foreground = ${colors.red}
ramp-0 =
ramp-1 =
ramp-2 =
ramp-3 =
ramp-0-foreground = ${colors.sky}
ramp-1-foreground = ${colors.green}
ramp-2-foreground = ${colors.yellow}
ramp-3-foreground = ${colors.peach}
[module/backlight]
type = internal/backlight
card = ${env:BACKLIGHT:intel_backlight}
use-actual-brightness = true
enable-scroll = true
format = <ramp> <label>
label = %percentage%%
ramp-0 =
ramp-1 =
ramp-2 =
ramp-3 =
ramp-foreground = ${colors.yellow}
[module/pulseaudio]
type = internal/pulseaudio
use-ui-max = false
interval = 5
format-volume = <ramp-volume> <label-volume>
label-volume = %percentage%%
label-muted = " muted"
label-muted-foreground = ${colors.overlay0}
ramp-volume-0 =
ramp-volume-1 =
ramp-volume-2 =
ramp-volume-foreground = ${colors.teal}
click-right = pavucontrol
scroll-up = ~/.local/bin/volume up
scroll-down = ~/.local/bin/volume down
[module/wlan]
type = internal/network
interface = ${env:WLAN_IFACE:}
interface-type = wireless
interval = 5
format-connected = <ramp-signal> <label-connected>
format-disconnected = <label-disconnected>
label-connected = %essid:0:14%
label-connected-foreground = ${colors.text}
label-disconnected = " offline"
label-disconnected-foreground = ${colors.overlay0}
ramp-signal-0 =
ramp-signal-1 =
ramp-signal-2 =
ramp-signal-3 =
ramp-signal-foreground = ${colors.sapphire}
click-left = kitty --class kitty-float -e nmtui
[module/eth]
type = internal/network
interface = ${env:ETH_IFACE:}
interface-type = wired
interval = 5
format-connected = <label-connected>
format-connected-prefix = " "
format-connected-prefix-foreground = ${colors.green}
format-disconnected =
label-connected = %local_ip%
[module/battery]
type = internal/battery
full-at = 98
low-at = 15
battery = ${env:BATTERY:BAT0}
adapter = ${env:ADAPTER:AC}
poll-interval = 5
format-charging = <animation-charging> <label-charging>
format-discharging = <ramp-capacity> <label-discharging>
format-full = <label-full>
format-low = <animation-low> <label-low>
label-charging = %percentage%%
label-discharging = %percentage%%
label-full = " full"
label-full-foreground = ${colors.green}
label-low = %percentage%%
label-low-foreground = ${colors.red}
ramp-capacity-0 =
ramp-capacity-1 =
ramp-capacity-2 =
ramp-capacity-3 =
ramp-capacity-4 =
ramp-capacity-0-foreground = ${colors.red}
ramp-capacity-1-foreground = ${colors.peach}
ramp-capacity-2-foreground = ${colors.yellow}
ramp-capacity-3-foreground = ${colors.green}
ramp-capacity-4-foreground = ${colors.green}
animation-charging-0 =
animation-charging-1 =
animation-charging-2 =
animation-charging-3 =
animation-charging-4 =
animation-charging-foreground = ${colors.green}
animation-charging-framerate = 750
animation-low-0 =
animation-low-1 =
animation-low-foreground = ${colors.red}
animation-low-framerate = 500
[module/systray]
type = internal/tray
format-margin = 6pt
tray-spacing = 8pt
tray-background = ${colors.background}
; --------------------------------------------------------------------------
; Global settings
; --------------------------------------------------------------------------
[settings]
screenchange-reload = true
pseudo-transparency = false
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Launch one polybar instance per connected monitor.
# Hardware-specific values are detected here and passed to polybar as
# environment variables, so config.ini stays machine-independent.
# ---------------------------------------------------------------------------
set -uo pipefail
CONFIG="$HOME/.config/polybar/config.ini"
LOG="${XDG_CACHE_HOME:-$HOME/.cache}/polybar.log"
mkdir -p "$(dirname "$LOG")"
# --- stop any running instances and wait for them to die -------------------
pkill -x polybar >/dev/null 2>&1 || true
for _ in {1..20}; do
pgrep -x polybar >/dev/null || break
sleep 0.1
done
# Anything still alive gets the hammer.
pgrep -x polybar >/dev/null && pkill -9 -x polybar
# --- hardware detection ----------------------------------------------------
# Battery + AC adapter
BATTERY="$(ls -1 /sys/class/power_supply 2>/dev/null | grep -m1 '^BAT' || true)"
ADAPTER="$(ls -1 /sys/class/power_supply 2>/dev/null | grep -m1 -E '^(AC|ADP|ACAD)' || true)"
export BATTERY="${BATTERY:-BAT0}"
export ADAPTER="${ADAPTER:-AC}"
# Backlight device (intel_backlight, amdgpu_bl0, nvidia_0, …)
BACKLIGHT="$(ls -1 /sys/class/backlight 2>/dev/null | head -n1 || true)"
export BACKLIGHT="${BACKLIGHT:-intel_backlight}"
# Network interfaces: wireless ones have a /sys/class/net/*/wireless dir
WLAN_IFACE=""
ETH_IFACE=""
for iface in /sys/class/net/*; do
name="$(basename "$iface")"
[[ "$name" == "lo" ]] && continue
if [[ -d "$iface/wireless" ]]; then
[[ -z "$WLAN_IFACE" ]] && WLAN_IFACE="$name"
else
[[ -z "$ETH_IFACE" ]] && ETH_IFACE="$name"
fi
done
export WLAN_IFACE ETH_IFACE
# --- module pruning --------------------------------------------------------
# Desktops have no battery/backlight; leaving those modules enabled makes
# polybar spam the log, so build the right-hand module list dynamically.
MODULES_RIGHT="filesystem memory cpu temperature"
[[ -d "/sys/class/backlight/$BACKLIGHT" ]] && MODULES_RIGHT+=" backlight"
MODULES_RIGHT+=" pulseaudio"
[[ -n "$WLAN_IFACE" ]] && MODULES_RIGHT+=" wlan"
[[ -n "$ETH_IFACE" ]] && MODULES_RIGHT+=" eth"
[[ -d "/sys/class/power_supply/$BATTERY" ]] && MODULES_RIGHT+=" battery"
MODULES_RIGHT+=" systray"
export MODULES_RIGHT
# --- launch ----------------------------------------------------------------
{
echo
echo "=== polybar launch $(date -Is) ==="
echo "battery=$BATTERY adapter=$ADAPTER backlight=$BACKLIGHT wlan=${WLAN_IFACE:-none} eth=${ETH_IFACE:-none}"
echo "modules-right=$MODULES_RIGHT"
} >>"$LOG"
if command -v polybar >/dev/null && polybar --list-monitors >/dev/null 2>&1; then
# The tray can only live on one bar; give it to the primary monitor.
primary="$(polybar --list-monitors | grep -m1 '(primary)' | cut -d: -f1)"
while IFS= read -r line; do
m="${line%%:*}"
[[ -z "$m" ]] && continue
if [[ -n "$primary" && "$m" != "$primary" ]]; then
MONITOR="$m" MODULES_RIGHT="${MODULES_RIGHT/ systray/}" \
polybar --reload main --config="$CONFIG" >>"$LOG" 2>&1 &
else
MONITOR="$m" polybar --reload main --config="$CONFIG" >>"$LOG" 2>&1 &
fi
done < <(polybar --list-monitors)
else
polybar --reload main --config="$CONFIG" >>"$LOG" 2>&1 &
fi
echo "polybar launched" >>"$LOG"
+22
View File
@@ -0,0 +1,22 @@
[Appearance]
custom_palette=false
icon_theme=Papirus-Dark
standard_dialogs=default
style=Fusion
[Fonts]
fixed="Inconsolata Nerd Font,11,-1,5,50,0,0,0,0,0"
general="Inconsolata Nerd Font,11,-1,5,50,0,0,0,0,0"
[Interface]
activate_item_on_single_click=1
buttonbox_layout=0
cursor_flash_time=1000
dialog_buttons_have_icons=1
double_click_interval=400
keyboard_scheme=2
menus_have_icons=true
show_shortcuts_in_context_menus=true
toolbutton_style=4
underline_shortcut=1
wheel_scroll_lines=3
+41
View File
@@ -0,0 +1,41 @@
/* =========================================================================
* rofi — Catppuccin Macchiato
* Font: Inconsolata Nerd Font
* Docs: man 5 rofi-theme
* ========================================================================= */
configuration {
modes: "drun,run,window,ssh";
font: "Inconsolata Nerd Font 12";
show-icons: true;
icon-theme: "Papirus-Dark";
display-drun: " Apps";
display-run: " Run";
display-window: " Window";
display-ssh: " SSH";
drun-display-format: "{name}";
window-format: "{w} · {c} · {t}";
terminal: "kitty";
ssh-client: "ssh";
ssh-command: "{terminal} -e {ssh-client} {host}";
matching: "fuzzy";
sort: true;
sorting-method: "fzf";
drun-match-fields: "name,generic,exec,categories,keywords";
drun-use-desktop-cache: false;
hover-select: true;
me-select-entry: "";
me-accept-entry: "MousePrimary";
kb-mode-next: "Shift+Right,Control+Tab";
kb-mode-previous: "Shift+Left,Control+ISO_Left_Tab";
kb-row-up: "Up,Control+k";
kb-row-down: "Down,Control+j";
kb-remove-to-eol: "";
}
@theme "~/.config/rofi/themes/catppuccin-macchiato.rasi"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# rofi power menu — lock / logout / suspend / hibernate / reboot / shutdown
# Destructive entries ask for confirmation before firing.
# ---------------------------------------------------------------------------
set -euo pipefail
THEME="$HOME/.config/rofi/themes/powermenu.rasi"
LOCK_CMD=(betterlockscreen --lock dimblur)
lock=" Lock"
logout=" Log out"
suspend=" Suspend"
hibernate=" Hibernate"
reboot=" Reboot"
shutdown=" Shut down"
menu() {
printf '%s\n' "$lock" "$logout" "$suspend" "$hibernate" "$reboot" "$shutdown" |
rofi -dmenu -i \
-theme "$THEME" \
-p " $(whoami)@$(hostname)" \
-select "$lock"
}
confirm() {
local answer
answer=$(printf ' No\n Yes\n' |
rofi -dmenu -i -theme "$THEME" -p "$1" -select " No")
[[ "$answer" == *Yes* ]]
}
case "$(menu)" in
"$lock") "${LOCK_CMD[@]}" ;;
"$logout") confirm "Log out?" && i3-msg exit ;;
"$suspend") "${LOCK_CMD[@]}" & sleep 0.3; systemctl suspend ;;
"$hibernate") confirm "Hibernate?" && { "${LOCK_CMD[@]}" & sleep 0.3; systemctl hibernate; } ;;
"$reboot") confirm "Reboot?" && systemctl reboot ;;
"$shutdown") confirm "Shut down?" && systemctl poweroff ;;
*) exit 0 ;;
esac
@@ -0,0 +1,195 @@
/* =========================================================================
* Catppuccin Macchiato theme for rofi
* ========================================================================= */
* {
rosewater: #f4dbd6ff;
flamingo: #f0c6c6ff;
pink: #f5bde6ff;
mauve: #c6a0f6ff;
red: #ed8796ff;
maroon: #ee99a0ff;
peach: #f5a97fff;
yellow: #eed49fff;
green: #a6da95ff;
teal: #8bd5caff;
sky: #91d7e3ff;
sapphire: #7dc4e4ff;
blue: #8aadf4ff;
lavender: #b7bdf8ff;
text: #cad3f5ff;
subtext1: #b8c0e0ff;
subtext0: #a5adcbff;
overlay2: #939ab7ff;
overlay1: #8087a2ff;
overlay0: #6e738dff;
surface2: #5b6078ff;
surface1: #494d64ff;
surface0: #363a4fff;
base: #24273aff;
mantle: #1e2030ff;
crust: #181926ff;
background: @base;
background-alt: @surface0;
foreground: @text;
selected: @mauve;
active: @green;
urgent: @red;
margin: 0;
padding: 0;
spacing: 0;
}
window {
transparency: "real";
location: center;
anchor: center;
fullscreen: false;
width: 640px;
x-offset: 0px;
y-offset: 0px;
enabled: true;
border-radius: 12px;
border: 2px solid;
border-color: @mauve;
background-color: @background;
cursor: "default";
}
mainbox {
enabled: true;
spacing: 0px;
padding: 0px;
background-color: transparent;
children: [ "inputbar", "message", "listview" ];
}
inputbar {
enabled: true;
spacing: 10px;
padding: 14px 16px;
background-color: @mantle;
text-color: @foreground;
children: [ "prompt", "entry" ];
}
prompt {
enabled: true;
background-color: transparent;
text-color: @mauve;
}
entry {
enabled: true;
background-color: transparent;
text-color: @foreground;
cursor: text;
placeholder: "Search…";
placeholder-color: @overlay0;
}
message {
enabled: true;
margin: 0px;
padding: 10px 16px;
background-color: @surface0;
text-color: @foreground;
}
textbox {
background-color: transparent;
text-color: @foreground;
vertical-align: 0.5;
horizontal-align: 0.0;
}
listview {
enabled: true;
columns: 1;
lines: 10;
cycle: true;
dynamic: true;
scrollbar: true;
layout: vertical;
reverse: false;
fixed-height: true;
fixed-columns: true;
spacing: 2px;
padding: 10px;
background-color: transparent;
cursor: "default";
}
scrollbar {
handle-width: 4px;
handle-color: @surface2;
border-radius: 8px;
background-color: @mantle;
}
element {
enabled: true;
spacing: 10px;
padding: 8px 12px;
border-radius: 8px;
background-color: transparent;
text-color: @foreground;
cursor: pointer;
}
element normal.normal { background-color: transparent; text-color: @foreground; }
element normal.urgent { background-color: transparent; text-color: @urgent; }
element normal.active { background-color: transparent; text-color: @active; }
element selected.normal { background-color: @selected; text-color: @crust; }
element selected.urgent { background-color: @urgent; text-color: @crust; }
element selected.active { background-color: @active; text-color: @crust; }
element alternate.normal { background-color: transparent; text-color: @foreground; }
element alternate.urgent { background-color: transparent; text-color: @urgent; }
element alternate.active { background-color: transparent; text-color: @active; }
element-icon {
background-color: transparent;
text-color: inherit;
size: 24px;
cursor: inherit;
}
element-text {
background-color: transparent;
text-color: inherit;
highlight: inherit;
cursor: inherit;
vertical-align: 0.5;
horizontal-align: 0.0;
}
mode-switcher {
enabled: true;
spacing: 0px;
background-color: transparent;
}
button {
padding: 8px;
background-color: @surface0;
text-color: @overlay1;
cursor: pointer;
}
button selected {
background-color: @mauve;
text-color: @crust;
}
error-message {
padding: 16px;
border-radius: 12px;
background-color: @base;
text-color: @red;
}
@@ -0,0 +1,25 @@
/* Compact variant of the Macchiato theme, used by powermenu.sh */
@import "catppuccin-macchiato.rasi"
window {
width: 360px;
border-color: @red;
}
listview {
lines: 6;
columns: 1;
}
element {
padding: 10px 14px;
}
element selected.normal {
background-color: @red;
text-color: @crust;
}
prompt {
text-color: @red;
}
+2
View File
@@ -0,0 +1,2 @@
TerminalEmulator=kitty
FileManager=thunar
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="thunar" version="1.0">
<property name="last-view" type="string" value="ThunarDetailsView"/>
<property name="last-icon-view-zoom-level" type="string" value="THUNAR_ZOOM_LEVEL_100_PERCENT"/>
<property name="last-details-view-zoom-level" type="string" value="THUNAR_ZOOM_LEVEL_38_PERCENT"/>
<property name="last-window-width" type="int" value="1100"/>
<property name="last-window-height" type="int" value="700"/>
<property name="last-window-maximized" type="bool" value="false"/>
<property name="last-separator-position" type="int" value="180"/>
<property name="last-show-hidden" type="bool" value="false"/>
<property name="misc-single-click" type="bool" value="false"/>
<property name="misc-date-style" type="string" value="THUNAR_DATE_STYLE_YYYYMMDD"/>
<property name="misc-thumbnail-mode" type="string" value="THUNAR_THUMBNAIL_MODE_ONLY_LOCAL"/>
<property name="misc-folders-first" type="bool" value="true"/>
<property name="misc-text-beside-icons" type="bool" value="false"/>
<property name="misc-middle-click-in-tab" type="bool" value="true"/>
<property name="misc-open-new-window-as-tab" type="bool" value="true"/>
<property name="misc-full-path-in-title" type="bool" value="false"/>
<property name="shortcuts-icon-emblems" type="bool" value="true"/>
<property name="tree-icon-emblems" type="bool" value="true"/>
<property name="last-location-bar" type="string" value="ThunarLocationButtons"/>
<property name="last-side-pane" type="string" value="ThunarShortcutsPane"/>
</channel>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<channel name="xsettings" version="1.0">
<property name="Net" type="empty">
<property name="ThemeName" type="string" value="catppuccin-macchiato-mauve-standard+default"/>
<property name="IconThemeName" type="string" value="Papirus-Dark"/>
<property name="EnableEventSounds" type="bool" value="false"/>
<property name="EnableInputFeedbackSounds" type="bool" value="false"/>
</property>
<property name="Xft" type="empty">
<property name="DPI" type="int" value="-1"/>
<property name="Antialias" type="int" value="1"/>
<property name="Hinting" type="int" value="1"/>
<property name="HintStyle" type="string" value="hintslight"/>
<property name="RGBA" type="string" value="rgb"/>
</property>
<property name="Gtk" type="empty">
<property name="FontName" type="string" value="Inconsolata Nerd Font 11"/>
<property name="MonospaceFontName" type="string" value="Inconsolata Nerd Font 11"/>
<property name="CursorThemeName" type="string" value="catppuccin-macchiato-dark-cursors"/>
<property name="CursorThemeSize" type="int" value="24"/>
<property name="DecorationLayout" type="string" value="menu:minimize,maximize,close"/>
</property>
</channel>
+13
View File
@@ -0,0 +1,13 @@
# ~/.gtkrc-2.0 — GTK2 apps (lxappearance, some older utilities)
gtk-theme-name="catppuccin-macchiato-mauve-standard+default"
gtk-icon-theme-name="Papirus-Dark"
gtk-cursor-theme-name="catppuccin-macchiato-dark-cursors"
gtk-cursor-theme-size=24
gtk-font-name="Inconsolata Nerd Font 11"
gtk-toolbar-style=GTK_TOOLBAR_ICONS
gtk-menu-images=1
gtk-button-images=1
gtk-xft-antialias=1
gtk-xft-hinting=1
gtk-xft-hintstyle="hintslight"
gtk-xft-rgba="rgb"
+30
View File
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Backlight control with a dunst OSD. usage: brightness up|down|set <n>
set -uo pipefail
STEP="${BRIGHTNESS_STEP:-5}"
MIN=1 # never go fully dark — that looks like a crash
ID=2594
command -v brightnessctl >/dev/null || { echo "brightnessctl not found" >&2; exit 1; }
pct() { brightnessctl -m | awk -F, '{gsub(/%/,"",$4); print $4}'; }
case "${1:-}" in
up) brightnessctl -q set "${STEP}%+" ;;
down) brightnessctl -q set "${STEP}%-" ;;
set) brightnessctl -q set "${2:?need a percentage}%" ;;
""|-h|--help) echo "usage: brightness up|down|set <percent>"; exit 0 ;;
*) echo "unknown action: $1" >&2; exit 2 ;;
esac
# Enforce the floor
current="$(pct)"
if (( current < MIN )); then
brightnessctl -q set "${MIN}%"
current="$MIN"
fi
command -v dunstify >/dev/null && \
dunstify -a osd -u low -r "$ID" -h "int:value:$current" \
-i display-brightness "Brightness ${current}%"
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Minimal xrandr multi-monitor switcher.
# display auto | duplicate | extend | internal | external | list
# "internal" is whichever output name looks like a laptop panel.
# ---------------------------------------------------------------------------
set -uo pipefail
command -v xrandr >/dev/null || { echo "xrandr not installed" >&2; exit 1; }
mapfile -t CONNECTED < <(xrandr --query | awk '/ connected/{print $1}')
INTERNAL="$(printf '%s\n' "${CONNECTED[@]}" | grep -m1 -E '^(eDP|LVDS|DSI)' || true)"
[[ -z "$INTERNAL" ]] && INTERNAL="${CONNECTED[0]:-}"
mapfile -t EXTERNALS < <(printf '%s\n' "${CONNECTED[@]}" | grep -v "^${INTERNAL}$" || true)
reload_bar() { "$HOME/.config/polybar/launch.sh" >/dev/null 2>&1 & }
restore_bg() { "$HOME/.local/bin/set-wallpaper" >/dev/null 2>&1 & }
case "${1:-auto}" in
list)
xrandr --query | grep -E ' connected|disconnected' ; exit 0 ;;
internal)
args=(--output "$INTERNAL" --auto --primary)
for e in "${EXTERNALS[@]}"; do args+=(--output "$e" --off); done
xrandr "${args[@]}" ;;
external)
if [[ ${#EXTERNALS[@]} -eq 0 ]]; then echo "no external display connected" >&2; exit 1; fi
args=(--output "${EXTERNALS[0]}" --auto --primary --output "$INTERNAL" --off)
for e in "${EXTERNALS[@]:1}"; do args+=(--output "$e" --off); done
xrandr "${args[@]}" ;;
duplicate)
if [[ ${#EXTERNALS[@]} -eq 0 ]]; then echo "no external display connected" >&2; exit 1; fi
args=(--output "$INTERNAL" --auto --primary)
for e in "${EXTERNALS[@]}"; do args+=(--output "$e" --auto --same-as "$INTERNAL"); done
xrandr "${args[@]}" ;;
extend)
if [[ ${#EXTERNALS[@]} -eq 0 ]]; then echo "no external display connected" >&2; exit 1; fi
args=(--output "$INTERNAL" --auto --primary)
prev="$INTERNAL"
for e in "${EXTERNALS[@]}"; do
args+=(--output "$e" --auto --right-of "$prev")
prev="$e"
done
xrandr "${args[@]}" ;;
auto)
# Extend if something external is plugged in, otherwise laptop panel only.
if [[ ${#EXTERNALS[@]} -gt 0 ]]; then exec "$0" extend; else exec "$0" internal; fi ;;
*)
echo "usage: display auto|duplicate|extend|internal|external|list" >&2; exit 2 ;;
esac
sleep 0.5
restore_bg
reload_bar
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Restart a daemon idempotently — safe to call from i3's exec_always.
# usage: restart-daemon <name> [args…]
set -uo pipefail
name="${1:?usage: restart-daemon <name> [args...]}"
shift || true
command -v "$name" >/dev/null 2>&1 || { echo "restart-daemon: $name not installed" >&2; exit 0; }
pkill -x "$name" >/dev/null 2>&1 || true
for _ in {1..20}; do
pgrep -x "$name" >/dev/null || break
sleep 0.1
done
pgrep -x "$name" >/dev/null && pkill -9 -x "$name"
log="${XDG_CACHE_HOME:-$HOME/.cache}/$name.log"
mkdir -p "$(dirname "$log")"
exec "$name" "$@" >>"$log" 2>&1 &
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Screenshots with maim. usage: screenshot full|area|window [--clip-only]
# Images land in ~/Pictures/Screenshots and are copied to the clipboard.
# ---------------------------------------------------------------------------
set -uo pipefail
DIR="${SCREENSHOT_DIR:-$(xdg-user-dir PICTURES 2>/dev/null || echo "$HOME/Pictures")/Screenshots}"
STAMP="$(date +%Y-%m-%d_%H-%M-%S)"
FILE="$DIR/screenshot_$STAMP.png"
MODE="${1:-full}"
CLIP_ONLY=0
[[ "${2:-}" == "--clip-only" ]] && CLIP_ONLY=1
command -v maim >/dev/null || { echo "maim not installed" >&2; exit 1; }
mkdir -p "$DIR"
# Selection colour = Catppuccin mauve (maim wants floats 0-1)
SELECT_COLOR="0.776,0.627,0.965,0.5"
case "$MODE" in
full)
maim --hidecursor --quality 10 "$FILE" ;;
area)
maim --select --hidecursor --quality 10 --color="$SELECT_COLOR" \
--bordersize=2 --nodrag "$FILE" ;;
window)
maim --window "$(xdotool getactivewindow)" --hidecursor --quality 10 "$FILE" ;;
*)
echo "usage: screenshot full|area|window [--clip-only]" >&2; exit 2 ;;
esac
# maim exits non-zero when the user cancels an area selection with Escape.
if [[ ! -s "$FILE" ]]; then
rm -f "$FILE"
exit 0
fi
command -v xclip >/dev/null && xclip -selection clipboard -t image/png -i "$FILE"
if [[ "$CLIP_ONLY" == "1" ]]; then
rm -f "$FILE"
command -v dunstify >/dev/null && \
dunstify -a screenshot -i camera-photo "Screenshot" "Copied to clipboard"
exit 0
fi
command -v dunstify >/dev/null && \
dunstify -a screenshot -i "$FILE" "Screenshot saved" "$(basename "$FILE")\nCopied to clipboard"
+65
View File
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Apply a wallpaper with feh and remember the choice.
#
# set-wallpaper # restore the remembered wallpaper
# set-wallpaper /path/img.png # set a specific image
# set-wallpaper --random # pick a random one from the wallpaper dir
# set-wallpaper --cache # (re)build the betterlockscreen cache
#
# The current selection is stored in $XDG_STATE_HOME/current-wallpaper.
# ---------------------------------------------------------------------------
set -uo pipefail
WALLPAPER_DIR="${WALLPAPER_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers}"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}"
STATE_FILE="$STATE_DIR/current-wallpaper"
MODE="${FEH_MODE:---bg-fill}"
mkdir -p "$STATE_DIR" "$WALLPAPER_DIR"
pick_random() {
find -L "$WALLPAPER_DIR" -type f \
\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' \) \
| shuf -n1
}
first_available() {
find -L "$WALLPAPER_DIR" -type f \
\( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' \) \
| sort | head -n1
}
cache_lockscreen=0
target=""
case "${1:-}" in
--random) target="$(pick_random)" ;;
--cache) cache_lockscreen=1 ;;
"") target="$(cat "$STATE_FILE" 2>/dev/null || true)" ;;
-*) echo "unknown option: $1" >&2; exit 2 ;;
*) target="$1"; cache_lockscreen=1 ;;
esac
# Fall back through: remembered -> anything in the dir -> a flat Macchiato base
if [[ -z "$target" || ! -f "$target" ]]; then
target="$(first_available)"
fi
if [[ -z "$target" || ! -f "$target" ]]; then
echo "set-wallpaper: no image found in $WALLPAPER_DIR — using a flat colour" >&2
command -v xsetroot >/dev/null && xsetroot -solid "#24273a"
exit 0
fi
command -v feh >/dev/null || { echo "set-wallpaper: feh is not installed" >&2; exit 1; }
feh --no-fehbg "$MODE" "$target"
printf '%s\n' "$target" > "$STATE_FILE"
# Keep the lock screen in sync with the desktop. Caching is slow (imagemagick
# blurs the image at every resolution), so only do it when the image changed.
if [[ "$cache_lockscreen" == "1" ]] && command -v betterlockscreen >/dev/null; then
( betterlockscreen --update "$target" --blur 0.5 >/dev/null 2>&1 &
disown ) 2>/dev/null || true
fi
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Volume / mic control with a dunst OSD. usage: volume up|down|mute|micmute|set <n>
set -uo pipefail
STEP="${VOLUME_STEP:-5}"
MAX="${VOLUME_MAX:-100}"
SINK="@DEFAULT_AUDIO_SINK@"
SOURCE="@DEFAULT_AUDIO_SOURCE@"
ID=2593 # fixed notification id => the OSD replaces itself instead of stacking
have() { command -v "$1" >/dev/null 2>&1; }
have wpctl || { echo "wpctl (wireplumber) not found" >&2; exit 1; }
get_volume() {
wpctl get-volume "$SINK" | awk '{printf "%d", $2 * 100}'
}
is_muted() {
wpctl get-volume "$SINK" | grep -q MUTED
}
osd() {
have dunstify || return 0
local icon="$1" text="$2" value="$3"
dunstify -a osd -u low -r "$ID" -h "int:value:$value" -i "$icon" "$text"
}
case "${1:-}" in
up)
wpctl set-mute "$SINK" 0
wpctl set-volume -l "$(awk "BEGIN{print $MAX/100}")" "$SINK" "${STEP}%+"
;;
down)
wpctl set-mute "$SINK" 0
wpctl set-volume "$SINK" "${STEP}%-"
;;
set)
wpctl set-volume -l "$(awk "BEGIN{print $MAX/100}")" "$SINK" "${2:?need a percentage}%"
;;
mute)
wpctl set-mute "$SINK" toggle
;;
micmute)
wpctl set-mute "$SOURCE" toggle
if wpctl get-volume "$SOURCE" | grep -q MUTED; then
osd microphone-sensitivity-muted "Microphone muted" 0
else
osd microphone-sensitivity-high "Microphone live" 100
fi
exit 0
;;
""|-h|--help)
echo "usage: volume up|down|mute|micmute|set <percent>"; exit 0 ;;
*)
echo "unknown action: $1" >&2; exit 2 ;;
esac
vol="$(get_volume)"
if is_muted; then
osd audio-volume-muted "Muted" 0
elif (( vol >= 66 )); then osd audio-volume-high "Volume ${vol}%" "$vol"
elif (( vol >= 33 )); then osd audio-volume-medium "Volume ${vol}%" "$vol"
else osd audio-volume-low "Volume ${vol}%" "$vol"
fi
# Nudge polybar so the bar updates instantly rather than on its 5s poll
pkill -RTMIN+1 polybar 2>/dev/null || true
+27
View File
@@ -0,0 +1,27 @@
#!/bin/sh
# ---------------------------------------------------------------------------
# ~/.xinitrc — executed by startx / xinit
# ---------------------------------------------------------------------------
userresources=$HOME/.Xresources
usermodmap=$HOME/.Xmodmap
sysresources=/etc/X11/xinit/.Xresources
sysmodmap=/etc/X11/xinit/.Xmodmap
[ -f "$sysresources" ] && xrdb -merge "$sysresources"
[ -f "$sysmodmap" ] && xmodmap "$sysmodmap"
[ -f "$userresources" ] && xrdb -merge "$userresources"
[ -f "$usermodmap" ] && xmodmap "$usermodmap"
# Run everything in /etc/X11/xinit/xinitrc.d (dbus, gnome-keyring, …)
if [ -d /etc/X11/xinit/xinitrc.d ]; then
for f in /etc/X11/xinit/xinitrc.d/?*.sh; do
[ -x "$f" ] && . "$f"
done
unset f
fi
# Shared environment for both startx and display-manager logins
[ -f "$HOME/.xprofile" ] && . "$HOME/.xprofile"
exec i3
+48
View File
@@ -0,0 +1,48 @@
# ---------------------------------------------------------------------------
# ~/.xprofile — sourced by .xinitrc and by most display managers.
# Put environment variables here, not in .xinitrc, so both login paths agree.
# ---------------------------------------------------------------------------
# --- XDG -------------------------------------------------------------------
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
export XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
export XDG_CURRENT_DESKTOP=i3
export XDG_SESSION_DESKTOP=i3
export XDG_SESSION_TYPE=x11
# --- PATH ------------------------------------------------------------------
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
# --- defaults --------------------------------------------------------------
export TERMINAL=kitty
export EDITOR=nvim
export VISUAL=nvim
export PAGER=less
export BROWSER=xdg-open
# --- toolkit theming -------------------------------------------------------
export GTK_THEME="catppuccin-macchiato-mauve-standard+default"
export QT_QPA_PLATFORMTHEME=qt5ct
export QT_AUTO_SCREEN_SCALE_FACTOR=1
export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
export XCURSOR_THEME="catppuccin-macchiato-dark-cursors"
export XCURSOR_SIZE=24
# --- Java apps need this to render under a non-reparenting WM --------------
export _JAVA_AWT_WM_NONREPARENTING=1
export AWT_TOOLKIT=MToolkit
# --- Firefox / Electron: touchpad smooth scrolling + wayland off -----------
export MOZ_USE_XINPUT2=1
# --- keyboard --------------------------------------------------------------
# Uncomment and adapt if you want caps-lock as an extra escape:
# setxkbmap -option caps:escape
# --- create the standard user directories the first time round ------------
command -v xdg-user-dirs-update >/dev/null 2>&1 && xdg-user-dirs-update
Executable
+135
View File
@@ -0,0 +1,135 @@
#!/usr/bin/env bash
# ===========================================================================
# arch-i3 — bootstrap a complete i3 desktop on a fresh Arch Linux install
#
# ./install.sh run every stage
# ./install.sh --dry-run print what would happen, change nothing
# ./install.sh --only 30,50 run just those stages
# ./install.sh --skip 20 run everything except that stage
# ./install.sh --list show the stages and exit
# ./install.sh -y never prompt
#
# Safe to re-run: package installs use --needed, and every config file that
# would be overwritten is first moved into ~/.config-backup-<timestamp>/.
# ===========================================================================
set -Eeuo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export DRY_RUN=0 ASSUME_YES=0 ENABLE_MULTILIB=0
ONLY="" SKIP=""
usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 0; }
while [[ $# -gt 0 ]]; do
case "$1" in
-n|--dry-run) DRY_RUN=1 ;;
-y|--yes) ASSUME_YES=1 ;;
--multilib) ENABLE_MULTILIB=1 ;;
--only) ONLY="${2:?--only needs a stage list}"; shift ;;
--skip) SKIP="${2:?--skip needs a stage list}"; shift ;;
--list) LIST=1 ;;
-h|--help) usage ;;
*) echo "unknown option: $1 (try --help)" >&2; exit 2 ;;
esac
shift
done
source "$REPO_ROOT/scripts/lib.sh"
STAGES=(
"00:preflight:00-preflight.sh:sanity checks, mirrors, pacman -Syu"
"10:packages:10-packages.sh:install everything from the official repos"
"20:aur:20-aur.sh:bootstrap yay, install i3lock-color + betterlockscreen"
"30:dotfiles:30-dotfiles.sh:deploy configs, scripts and wallpapers"
"40:services:40-services.sh:enable services, groups, font + lock cache"
"50:theme:50-theme.sh:apply the Catppuccin Macchiato theme"
)
if [[ -n "${LIST:-}" ]]; then
printf '%sStages%s\n' "$C_BOLD" "$C_RESET"
for s in "${STAGES[@]}"; do
IFS=: read -r num name _ desc <<<"$s"
printf ' %s%-4s%s %-10s %s\n' "$C_MAUVE" "$num" "$C_RESET" "$name" "$desc"
done
exit 0
fi
selected() {
local num="$1" name="$2"
if [[ -n "$ONLY" ]]; then
[[ ",$ONLY," == *",$num,"* || ",$ONLY," == *",$name,"* ]] || return 1
fi
if [[ -n "$SKIP" ]]; then
[[ ",$SKIP," == *",$num,"* || ",$SKIP," == *",$name,"* ]] && return 1
fi
return 0
}
# --- banner ----------------------------------------------------------------
cat <<'BANNER'
___ _ _ ____
/ _ | ________ / / (_) __/
/ __ |/ __/ __/ / _ \ / /_ |
/_/ |_|_/ \__/ /_//_/ /_/____/ i3 · polybar · rofi · dunst · kitty
Catppuccin Macchiato · Inconsolata Nerd Font
BANNER
[[ "$DRY_RUN" == "1" ]] && warn "DRY RUN — nothing will be changed"
require_arch
require_not_root
# --- confirmation ----------------------------------------------------------
echo
log "This will install $(read_pkg_list "$PACKAGES_DIR/pacman.txt" | wc -l | tr -d ' ') repo packages and"
log "$(read_pkg_list "$PACKAGES_DIR/aur.txt" | wc -l | tr -d ' ') AUR packages, then write configs into ~/.config."
log "Existing configs are backed up, never deleted."
echo
confirm "Proceed?" || die "aborted by user"
# --- run stages ------------------------------------------------------------
START="$SECONDS"
FAILED=()
for s in "${STAGES[@]}"; do
IFS=: read -r num name script desc <<<"$s"
if ! selected "$num" "$name"; then
skip "stage $num ($name) — skipped"
continue
fi
echo
printf '%s┌─ stage %s · %s %s%s\n' "$C_BOLD$C_MAUVE" "$num" "$name" "─────────────────────" "$C_RESET"
if ! bash "$REPO_ROOT/scripts/$script"; then
warn "stage $num ($name) failed"
FAILED+=("$num:$name")
confirm "continue with the remaining stages?" || die "aborted after stage $num"
fi
done
# --- summary ---------------------------------------------------------------
echo
ELAPSED=$(( SECONDS - START ))
if [[ ${#FAILED[@]} -eq 0 ]]; then
log "${C_GREEN}Done${C_RESET} in ${ELAPSED}s."
else
warn "finished in ${ELAPSED}s with ${#FAILED[@]} failed stage(s): ${FAILED[*]}"
fi
cat <<'NEXT'
Next steps
──────────
1. Log out of the TTY and back in so the new groups take effect.
2. Start the session with: startx
(or install a display manager — see the README)
3. First keys to try:
Super + Return kitty
Super + d rofi app launcher
Super + e thunar
Super + Shift + e power menu
Super + Shift + x lock the screen
Super + Shift + r restart i3 after editing the config
4. Personal tweaks belong in ~/.config/i3/config.d/*.conf — that
directory is never overwritten by this installer.
NEXT
+17
View File
@@ -0,0 +1,17 @@
# ---------------------------------------------------------------------------
# AUR packages (installed with yay)
# ---------------------------------------------------------------------------
# i3lock-color provides & conflicts with i3lock — it is the fork that
# betterlockscreen depends on. Installing it gives you the `i3lock` binary
# with all the extra --*-color flags.
i3lock-color
betterlockscreen
# Catppuccin Macchiato theming for GTK apps, cursors and folder icons
catppuccin-gtk-theme-macchiato
catppuccin-cursors-macchiato
papirus-folders-catppuccin-git
# Nice-to-haves
xcursor-themes
+123
View File
@@ -0,0 +1,123 @@
# ---------------------------------------------------------------------------
# Official repository packages (pacman)
# Lines starting with '#' and blank lines are ignored.
# ---------------------------------------------------------------------------
# --- build tooling (needed for the AUR helper) -----------------------------
base-devel
git
# --- X server & friends ----------------------------------------------------
xorg-server
xorg-xinit
xorg-xrandr
xorg-xrdb
xorg-xset
xorg-xsetroot
xorg-xprop
xorg-xkill
xorg-xinput
xclip
xdotool
xsettingsd
# --- graphics drivers (mesa covers intel/amd; nvidia users: see README) ----
mesa
xf86-video-vesa
# --- window manager & core desktop ----------------------------------------
i3-wm
polybar
rofi
dunst
libnotify
polkit
polkit-gnome
picom
feh
xss-lock
numlockx
# NOTE: i3lock is intentionally NOT listed here.
# betterlockscreen needs i3lock-color (AUR), which provides/replaces i3lock.
# See packages/aur.txt.
# --- terminal --------------------------------------------------------------
kitty
# --- file manager stack ----------------------------------------------------
thunar
thunar-volman
thunar-archive-plugin
tumbler
ffmpegthumbnailer
gvfs
gvfs-mtp
gvfs-afc
gvfs-smb
udisks2
udiskie
file-roller
xarchiver
# --- audio (pipewire) ------------------------------------------------------
pipewire
pipewire-alsa
pipewire-pulse
pipewire-jack
wireplumber
pavucontrol
alsa-utils
playerctl
# --- networking ------------------------------------------------------------
networkmanager
network-manager-applet
nm-connection-editor
# --- bluetooth (harmless if the machine has no radio) ----------------------
bluez
bluez-utils
blueman
# --- theming / fonts -------------------------------------------------------
ttf-inconsolata-nerd
ttf-nerd-fonts-symbols
ttf-nerd-fonts-symbols-mono
noto-fonts
noto-fonts-emoji
noto-fonts-cjk
ttf-liberation
papirus-icon-theme
lxappearance
qt5ct
gtk-engine-murrine
# --- utilities you will miss on day one ------------------------------------
imagemagick
maim
brightnessctl
lm_sensors
htop
btop
neovim
man-db
man-pages
less
tree
unzip
zip
p7zip
unrar
wget
curl
jq
rsync
fastfetch
xdg-user-dirs
xdg-utils
ntfs-3g
reflector
zsh
zsh-completions
starship
+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Stage 00 — sanity checks, mirror refresh, pacman quality-of-life.
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
log "Preflight"
require_arch
require_not_root
require_sudo
step "checking network connectivity"
if ! ping -c1 -W3 archlinux.org >/dev/null 2>&1; then
warn "cannot reach archlinux.org — package installation will fail"
confirm "continue anyway?" || die "aborted"
else
ok "network reachable"
fi
# --- pacman.conf niceties --------------------------------------------------
step "enabling pacman colour output + parallel downloads"
if [[ "$DRY_RUN" != "1" ]]; then
sudo sed -i \
-e 's/^#\(Color\)$/\1/' \
-e 's/^#\(VerbosePkgLists\)$/\1/' \
-e 's/^#\?ParallelDownloads.*/ParallelDownloads = 8/' \
/etc/pacman.conf
grep -q '^ParallelDownloads' /etc/pacman.conf || \
sudo sed -i '/^\[options\]/a ParallelDownloads = 8' /etc/pacman.conf
fi
ok "pacman.conf tuned"
# --- multilib (optional, off by default) ----------------------------------
if [[ "${ENABLE_MULTILIB:-0}" == "1" ]]; then
step "enabling [multilib]"
run sudo sed -i '/^#\[multilib\]$/,+1 s/^#//' /etc/pacman.conf
fi
# --- mirrors ---------------------------------------------------------------
if have reflector; then
step "refreshing mirrorlist with reflector (this can take a minute)"
run sudo reflector --protocol https --latest 20 --sort rate \
--save /etc/pacman.d/mirrorlist || warn "reflector failed; keeping existing mirrorlist"
else
skip "reflector not installed yet — mirrors will be refreshed on the next run"
fi
step "synchronising package databases"
run sudo pacman -Syu --noconfirm
ok "preflight complete"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Stage 10 — install everything available from the official repositories.
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
log "Installing official packages"
require_arch
require_not_root
require_sudo
mapfile -t PKGS < <(read_pkg_list "$PACKAGES_DIR/pacman.txt")
[[ ${#PKGS[@]} -gt 0 ]] || die "pacman.txt yielded no packages"
# Split into "already installed" and "to install" so re-runs are quiet.
TO_INSTALL=()
for p in "${PKGS[@]}"; do
if pacman -Qq "$p" >/dev/null 2>&1; then
continue
fi
TO_INSTALL+=("$p")
done
if [[ ${#TO_INSTALL[@]} -eq 0 ]]; then
ok "all ${#PKGS[@]} packages already installed"
else
step "installing ${#TO_INSTALL[@]} of ${#PKGS[@]} packages"
printf '%s %s%s\n' "$C_DIM" "${TO_INSTALL[*]}" "$C_RESET"
# --needed keeps reinstalls off the table; failures on a single package
# should not nuke the whole run, so retry one-by-one if the batch fails.
if ! run sudo pacman -S --needed --noconfirm "${TO_INSTALL[@]}"; then
warn "batch install failed — retrying package by package"
for p in "${TO_INSTALL[@]}"; do
run sudo pacman -S --needed --noconfirm "$p" || warn "could not install: $p"
done
fi
fi
# --- GPU driver hint -------------------------------------------------------
if lspci 2>/dev/null | grep -qi 'vga.*intel'; then
step "Intel GPU detected — installing vulkan-intel"
run sudo pacman -S --needed --noconfirm vulkan-intel intel-media-driver || true
elif lspci 2>/dev/null | grep -qi 'vga.*amd\|vga.*ati'; then
step "AMD GPU detected — installing vulkan-radeon"
run sudo pacman -S --needed --noconfirm vulkan-radeon libva-mesa-driver || true
elif lspci 2>/dev/null | grep -qi 'vga.*nvidia'; then
warn "NVIDIA GPU detected — install 'nvidia' or 'nvidia-open' yourself; see README"
fi
ok "official packages done"
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Stage 20 — bootstrap the yay AUR helper and install AUR packages.
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
log "AUR packages"
require_arch
require_not_root
require_sudo
# --- yay -------------------------------------------------------------------
if have yay; then
ok "yay already installed"
else
step "building yay-bin from the AUR"
build_dir="$(mktemp -d)"
if [[ "$DRY_RUN" == "1" ]]; then
skip "would clone + makepkg yay-bin in $build_dir"
else
git clone --depth 1 https://aur.archlinux.org/yay-bin.git "$build_dir/yay-bin"
( cd "$build_dir/yay-bin" && makepkg -si --noconfirm )
rm -rf "$build_dir"
fi
ok "yay installed"
fi
# --- i3lock -> i3lock-color swap ------------------------------------------
# betterlockscreen needs i3lock-color; it conflicts with plain i3lock.
if pacman -Qq i3lock >/dev/null 2>&1 && ! pacman -Qq i3lock-color >/dev/null 2>&1; then
step "removing repo i3lock so i3lock-color can take over"
run sudo pacman -Rdd --noconfirm i3lock || warn "could not remove i3lock; yay will prompt about the conflict"
fi
mapfile -t AUR_PKGS < <(read_pkg_list "$PACKAGES_DIR/aur.txt")
TO_INSTALL=()
for p in "${AUR_PKGS[@]}"; do
pacman -Qq "$p" >/dev/null 2>&1 || TO_INSTALL+=("$p")
done
if [[ ${#TO_INSTALL[@]} -eq 0 ]]; then
ok "all AUR packages already installed"
else
step "installing: ${TO_INSTALL[*]}"
for p in "${TO_INSTALL[@]}"; do
run yay -S --needed --noconfirm --answerclean None --answerdiff None "$p" \
|| warn "AUR package failed to build: $p (continuing)"
done
fi
ok "AUR stage done"
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
# Stage 30 — deploy dotfiles into $HOME (backing up whatever was there).
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
log "Deploying dotfiles"
require_not_root
CFG="${XDG_CONFIG_HOME:-$HOME/.config}"
# --- home-level files ------------------------------------------------------
install_file "$DOTFILES_DIR/xinitrc" "$HOME/.xinitrc" 755
install_file "$DOTFILES_DIR/xprofile" "$HOME/.xprofile" 644
install_file "$DOTFILES_DIR/Xresources" "$HOME/.Xresources" 644
install_file "$DOTFILES_DIR/gtkrc-2.0" "$HOME/.gtkrc-2.0" 644
# --- ~/.config/* -----------------------------------------------------------
# Copy each tracked file individually so untracked files the user added
# (e.g. their own polybar module) survive a re-run.
while IFS= read -r -d '' src; do
rel="${src#"$DOTFILES_DIR"/config/}"
install_file "$src" "$CFG/$rel" 644
done < <(find "$DOTFILES_DIR/config" -type f -print0)
# --- executables -----------------------------------------------------------
step "installing helper scripts to ~/.local/bin"
run mkdir -p "$HOME/.local/bin"
while IFS= read -r -d '' src; do
install_file "$src" "$HOME/.local/bin/$(basename "$src")" 755
done < <(find "$DOTFILES_DIR/local/bin" -type f -print0)
# Scripts inside ~/.config need the exec bit too
for s in "$CFG/polybar/launch.sh" "$CFG/rofi/scripts/powermenu.sh"; do
[[ -f "$s" ]] && run chmod 755 "$s"
done
if [[ -d "$CFG/i3/scripts" ]]; then
run find "$CFG/i3/scripts" -type f -name '*.sh' -exec chmod 755 {} +
fi
# --- user-owned override directories (never overwritten) -------------------
run mkdir -p "$CFG/i3/config.d"
# The include glob in the main config must match at least one file, otherwise
# i3 complains on every reload — so ship a comment-only placeholder.
if [[ ! -f "$CFG/i3/config.d/00-local.conf" && "$DRY_RUN" != "1" ]]; then
cat > "$CFG/i3/config.d/00-local.conf" <<'TXT'
# Personal i3 overrides.
#
# Every *.conf in this directory is included at the very END of
# ~/.config/i3/config, so anything you put here wins over the defaults:
# re-binding a key here silently replaces the earlier bindsym.
#
# This directory is never overwritten by the arch-i3 installer.
#
# Examples:
# bindsym $mod+Shift+b exec --no-startup-id firefox
# gaps inner 12
# for_window [class="Spotify"] move to workspace $ws9
TXT
fi
# --- wallpapers ------------------------------------------------------------
step "generating wallpapers"
if [[ "$DRY_RUN" == "1" ]]; then
skip "would generate wallpapers into $WALLPAPER_DIR"
else
mkdir -p "$WALLPAPER_DIR"
# Prefer any images the user dropped into the repo's wallpapers/ dir
shopt -s nullglob
repo_walls=("$REPO_ROOT"/wallpapers/*.{png,jpg,jpeg,webp})
shopt -u nullglob
if [[ ${#repo_walls[@]} -gt 0 ]]; then
cp -n "${repo_walls[@]}" "$WALLPAPER_DIR/" || true
ok "copied ${#repo_walls[@]} wallpaper(s) from the repo"
fi
# Generate the Catppuccin set if the directory is still (nearly) empty
if [[ $(find "$WALLPAPER_DIR" -maxdepth 1 -type f | wc -l) -lt 2 ]]; then
# pipefail would turn a missing/failing xrandr into a stage abort
geom=""
if have xrandr; then
geom="$(xrandr --query 2>/dev/null | awk '/\*/{print $1; exit}' || true)"
fi
"$REPO_ROOT/scripts/make-wallpaper.sh" "$WALLPAPER_DIR" "${geom:-2560x1440}" \
|| warn "wallpaper generation failed (ImageMagick missing?)"
fi
fi
# --- default applications --------------------------------------------------
step "setting default applications"
if [[ "$DRY_RUN" != "1" ]] && have xdg-mime; then
xdg-mime default thunar.desktop inode/directory 2>/dev/null || true
xdg-settings set default-web-browser firefox.desktop 2>/dev/null || true
fi
if [[ -d "$BACKUP_DIR" ]]; then
warn "previous configs were moved to: $(tildify "$BACKUP_DIR")"
fi
ok "dotfiles deployed"
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
# Stage 40 — enable system services, user services and groups.
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
log "Services & system integration"
require_arch
require_not_root
require_sudo
enable_system() {
local unit="$1"
if ! systemctl list-unit-files --no-legend | grep -q "^${unit}"; then
skip "$unit not present"
return 0
fi
if systemctl is-enabled --quiet "$unit" 2>/dev/null; then
skip "$unit already enabled"
else
step "enabling $unit"
run sudo systemctl enable --now "$unit"
fi
}
enable_user() {
local unit="$1"
if ! systemctl --user list-unit-files | grep -q "^${unit}"; then
skip "user unit $unit not present"
return 0
fi
if systemctl --user is-enabled --quiet "$unit" 2>/dev/null; then
skip "user unit $unit already enabled"
else
step "enabling user unit $unit"
run systemctl --user enable "$unit"
fi
}
# --- system services -------------------------------------------------------
enable_system NetworkManager.service
enable_system bluetooth.service
enable_system systemd-timesyncd.service
# --- user services ---------------------------------------------------------
# pipewire is socket-activated; enabling the sockets is enough.
enable_user pipewire.socket
enable_user pipewire-pulse.socket
enable_user wireplumber.service
# --- betterlockscreen on suspend ------------------------------------------
# xss-lock (started from the i3 config) already handles lid-close and suspend.
# The systemd unit below is the belt-and-braces version for logind-initiated
# sleeps that happen outside the X session.
if have betterlockscreen; then
step "installing betterlockscreen@.service hook"
if [[ "$DRY_RUN" != "1" ]]; then
sudo tee /etc/systemd/system/betterlockscreen@.service >/dev/null <<UNIT
[Unit]
Description=Lock the screen with betterlockscreen before sleep
Before=sleep.target
[Service]
User=%i
Type=forking
Environment=DISPLAY=:0
ExecStart=/usr/bin/betterlockscreen --lock dimblur
TimeoutSec=infinity
[Install]
WantedBy=sleep.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable "betterlockscreen@${USER}.service"
fi
ok "betterlockscreen sleep hook enabled"
fi
# --- groups ----------------------------------------------------------------
for grp in video input audio storage; do
if getent group "$grp" >/dev/null && ! id -nG "$USER" | tr ' ' '\n' | grep -qx "$grp"; then
step "adding $USER to group '$grp'"
run sudo usermod -aG "$grp" "$USER"
fi
done
# --- font cache ------------------------------------------------------------
step "rebuilding the font cache"
run fc-cache -f >/dev/null
if fc-list | grep -qi 'inconsolata.*nerd'; then
ok "Inconsolata Nerd Font is available to X clients"
else
warn "Inconsolata Nerd Font not found by fontconfig — check that ttf-inconsolata-nerd installed"
fi
# --- lock screen cache -----------------------------------------------------
if have betterlockscreen; then
wall="$(cat "${XDG_STATE_HOME:-$HOME/.local/state}/current-wallpaper" 2>/dev/null || true)"
[[ -z "$wall" ]] && wall="$(find "$WALLPAPER_DIR" -maxdepth 1 -type f 2>/dev/null | sort | head -n1 || true)"
if [[ -n "$wall" && -f "$wall" ]]; then
step "building the betterlockscreen image cache (this takes a moment)"
run betterlockscreen --update "$wall" --blur 0.5 >/dev/null 2>&1 \
|| warn "betterlockscreen cache build failed; run 'betterlockscreen -u <image>' by hand"
else
skip "no wallpaper available yet — run 'betterlockscreen -u <image>' after first login"
fi
fi
ok "services configured"
+83
View File
@@ -0,0 +1,83 @@
#!/usr/bin/env bash
# Stage 50 — apply the Catppuccin Macchiato theme to everything that needs
# a runtime nudge rather than a config file.
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
log "Theming"
require_not_root
GTK_THEME_NAME="catppuccin-macchiato-mauve-standard+default"
ICON_THEME="Papirus-Dark"
CURSOR_THEME="catppuccin-macchiato-dark-cursors"
# --- verify the themes actually landed ------------------------------------
theme_exists() {
local name="$1" kind="$2" # kind: themes | icons
[[ -d "/usr/share/$kind/$name" || -d "$HOME/.local/share/$kind/$name" || -d "$HOME/.$kind/$name" ]]
}
if theme_exists "$GTK_THEME_NAME" themes; then
ok "GTK theme found: $GTK_THEME_NAME"
else
warn "GTK theme '$GTK_THEME_NAME' not installed"
# The AUR package name for the accent may differ; fall back to whatever
# catppuccin-macchiato theme is present so apps are not left on Adwaita.
found="$(find /usr/share/themes "$HOME/.local/share/themes" -maxdepth 1 \
-iname 'catppuccin-macchiato*' -printf '%f\n' 2>/dev/null | sort | head -n1 || true)"
if [[ -n "$found" ]]; then
warn "using '$found' instead — updating the GTK settings files"
GTK_THEME_NAME="$found"
if [[ "$DRY_RUN" != "1" ]]; then
sed -i "s/^gtk-theme-name=.*/gtk-theme-name=$GTK_THEME_NAME/" \
"$HOME/.config/gtk-3.0/settings.ini" "$HOME/.config/gtk-4.0/settings.ini" 2>/dev/null || true
sed -i "s/^gtk-theme-name=.*/gtk-theme-name=\"$GTK_THEME_NAME\"/" \
"$HOME/.gtkrc-2.0" 2>/dev/null || true
fi
fi
fi
theme_exists "$ICON_THEME" icons || warn "icon theme '$ICON_THEME' not installed"
theme_exists "$CURSOR_THEME" icons || warn "cursor theme '$CURSOR_THEME' not installed"
# --- Papirus folders in Catppuccin colours --------------------------------
if have papirus-folders; then
step "recolouring Papirus folders (mauve)"
run sudo papirus-folders -C cat-macchiato-mauve --theme Papirus-Dark >/dev/null 2>&1 \
|| warn "papirus-folders failed; folders keep the stock colour"
fi
# --- default cursor theme (used by the root window and non-GTK apps) ------
step "setting the default X cursor theme"
if [[ "$DRY_RUN" != "1" ]]; then
mkdir -p "$HOME/.icons/default"
cat > "$HOME/.icons/default/index.theme" <<INI
[Icon Theme]
Name=Default
Comment=Default cursor theme
Inherits=$CURSOR_THEME
INI
fi
ok "cursor theme set to $CURSOR_THEME"
# --- dconf/gsettings, for the handful of apps that ignore settings.ini ----
if have gsettings; then
step "applying gsettings desktop preferences"
run gsettings set org.gnome.desktop.interface gtk-theme "$GTK_THEME_NAME" 2>/dev/null || true
run gsettings set org.gnome.desktop.interface icon-theme "$ICON_THEME" 2>/dev/null || true
run gsettings set org.gnome.desktop.interface cursor-theme "$CURSOR_THEME" 2>/dev/null || true
run gsettings set org.gnome.desktop.interface font-name "Inconsolata Nerd Font 11" 2>/dev/null || true
run gsettings set org.gnome.desktop.interface monospace-font-name "Inconsolata Nerd Font 11" 2>/dev/null || true
run gsettings set org.gnome.desktop.interface color-scheme "prefer-dark" 2>/dev/null || true
fi
# --- reload anything already running --------------------------------------
if [[ -n "${DISPLAY:-}" && "$DRY_RUN" != "1" ]]; then
step "reloading the live session"
xrdb -merge "$HOME/.Xresources" 2>/dev/null || true
have i3-msg && i3-msg reload >/dev/null 2>&1 || true
have dunstctl && dunstctl reload >/dev/null 2>&1 || true
[[ -x "$HOME/.config/polybar/launch.sh" ]] && "$HOME/.config/polybar/launch.sh" || true
fi
ok "theming applied"
Executable
+117
View File
@@ -0,0 +1,117 @@
#!/usr/bin/env bash
# Shared helpers for the arch-i3 bootstrap scripts.
# Sourced, never executed directly.
set -Eeuo pipefail
# --- paths -----------------------------------------------------------------
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
DOTFILES_DIR="$REPO_ROOT/dotfiles"
PACKAGES_DIR="$REPO_ROOT/packages"
WALLPAPER_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers"
BACKUP_DIR="$HOME/.config-backup-$(date +%Y%m%d-%H%M%S)"
# --- flags (exported by install.sh) ---------------------------------------
: "${DRY_RUN:=0}"
: "${ASSUME_YES:=0}"
# --- colours ---------------------------------------------------------------
if [[ -t 1 ]]; then
C_RESET=$'\e[0m'; C_BOLD=$'\e[1m'; C_DIM=$'\e[2m'
C_BLUE=$'\e[38;5;111m'; C_GREEN=$'\e[38;5;115m'
C_YELLOW=$'\e[38;5;222m'; C_RED=$'\e[38;5;210m'; C_MAUVE=$'\e[38;5;140m'
else
C_RESET=''; C_BOLD=''; C_DIM=''
C_BLUE=''; C_GREEN=''; C_YELLOW=''; C_RED=''; C_MAUVE=''
fi
log() { printf '%s==>%s %s\n' "$C_BLUE$C_BOLD" "$C_RESET" "$*"; }
step() { printf '%s ->%s %s\n' "$C_MAUVE" "$C_RESET" "$*"; }
ok() { printf '%s ok%s %s\n' "$C_GREEN" "$C_RESET" "$*"; }
warn() { printf '%s !!%s %s\n' "$C_YELLOW" "$C_RESET" "$*" >&2; }
die() { printf '%serror%s %s\n' "$C_RED$C_BOLD" "$C_RESET" "$*" >&2; exit 1; }
skip() { printf '%s --%s %s\n' "$C_DIM" "$C_RESET" "$*"; }
# Run a command, honouring DRY_RUN.
run() {
if [[ "$DRY_RUN" == "1" ]]; then
printf '%s dry%s %s\n' "$C_DIM" "$C_RESET" "$*"
return 0
fi
"$@"
}
confirm() {
[[ "$ASSUME_YES" == "1" ]] && return 0
[[ "$DRY_RUN" == "1" ]] && return 0
local reply
read -rp "$(printf '%s ??%s %s [y/N] ' "$C_YELLOW" "$C_RESET" "$1")" reply
[[ "$reply" =~ ^[Yy]$ ]]
}
have() { command -v "$1" >/dev/null 2>&1; }
# Print a path with $HOME collapsed to '~'. Done as a function because the
# obvious ${path/#$HOME/~} keeps a literal backslash in the replacement.
tildify() {
local p="$1"
if [[ "$p" == "$HOME"/* ]]; then
printf '~/%s' "${p#"$HOME"/}"
elif [[ "$p" == "$HOME" ]]; then
printf '~'
else
printf '%s' "$p"
fi
}
# Read a package list file, dropping comments and blanks.
read_pkg_list() {
local file="$1"
[[ -f "$file" ]] || die "package list not found: $file"
sed -e 's/#.*$//' -e 's/[[:space:]]\+$//' "$file" | grep -v '^[[:space:]]*$' || true
}
# Guard rails common to every stage.
require_arch() {
[[ -f /etc/arch-release ]] || die "this bootstrap targets Arch Linux (no /etc/arch-release found)"
}
require_not_root() {
[[ "$(id -u)" -ne 0 ]] || die "run this as your normal user, not root — sudo is invoked where needed"
}
require_sudo() {
have sudo || die "sudo is not installed; install it and add your user to the wheel group first"
if [[ "$DRY_RUN" != "1" ]]; then
sudo -v || die "sudo authentication failed"
# Keep the sudo timestamp alive for the length of the run.
( while true; do sudo -n true; sleep 50; kill -0 "$$" 2>/dev/null || exit; done ) &
SUDO_KEEPALIVE_PID=$!
trap 'kill "$SUDO_KEEPALIVE_PID" 2>/dev/null || true' EXIT
fi
}
# Back up a path (file, dir or symlink) into $BACKUP_DIR, preserving structure.
backup_path() {
local target="$1"
[[ -e "$target" || -L "$target" ]] || return 0
local rel="${target#"$HOME"/}"
local dest="$BACKUP_DIR/$rel"
step "backing up $(tildify "$target") -> $(tildify "$dest")"
run mkdir -p "$(dirname "$dest")"
run mv "$target" "$dest"
}
# Copy a file from the repo into place, backing up whatever was there.
install_file() {
local src="$1" dest="$2" mode="${3:-644}"
[[ -f "$src" ]] || die "missing source file: $src"
if [[ -f "$dest" ]] && cmp -s "$src" "$dest"; then
skip "$(tildify "$dest") already up to date"
return 0
fi
backup_path "$dest"
run mkdir -p "$(dirname "$dest")"
run install -m "$mode" "$src" "$dest"
ok "installed $(tildify "$dest")"
}
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Generate Catppuccin Macchiato wallpapers with ImageMagick.
# Shipping binary images in a config repo is rude, so we synthesise them.
# usage: make-wallpaper.sh [output-dir] [WIDTHxHEIGHT]
# ---------------------------------------------------------------------------
set -euo pipefail
OUT_DIR="${1:-${XDG_DATA_HOME:-$HOME/.local/share}/wallpapers}"
GEOM="${2:-3840x2160}"
W="${GEOM%x*}"
H="${GEOM#*x}"
# ImageMagick 7 uses `magick`; 6 uses `convert`.
if command -v magick >/dev/null 2>&1; then IM=(magick)
elif command -v convert >/dev/null 2>&1; then IM=(convert)
else
echo "make-wallpaper: ImageMagick not found — skipping wallpaper generation" >&2
exit 0
fi
mkdir -p "$OUT_DIR"
base="#24273a"
mantle="#1e2030"
crust="#181926"
mauve="#c6a0f6"
blue="#8aadf4"
teal="#8bd5ca"
pink="#f5bde6"
# --- 1. soft diagonal gradient --------------------------------------------
"${IM[@]}" -size "${W}x${H}" \
"gradient:${mantle}-${crust}" \
-rotate 45 -gravity center -extent "${W}x${H}" \
-blur 0x24 \
"$OUT_DIR/macchiato-gradient.png"
# --- 2. radial glow over the base colour ----------------------------------
"${IM[@]}" -size "${W}x${H}" "xc:${base}" \
\( -size "${W}x${H}" "radial-gradient:${mauve}-${base}" -alpha set -channel A -evaluate multiply 0.22 +channel \) \
-compose over -composite \
\( -size "${W}x${H}" "radial-gradient:${teal}-${base}" -alpha set -channel A -evaluate multiply 0.12 +channel -roll +$((W/3))+$((H/3)) \) \
-compose over -composite \
-blur 0x40 \
"$OUT_DIR/macchiato-glow.png"
# --- 3. subtle triangle/mosaic texture ------------------------------------
"${IM[@]}" -size "$((W/12))x$((H/12))" "xc:${base}" \
+noise Gaussian -blur 0x2 -normalize \
-level 40%,60% \
-fill "${base}" -colorize 82% \
-resize "${W}x${H}!" -blur 0x3 \
"$OUT_DIR/macchiato-texture.png"
# --- 4. flat base, for people who want zero distraction -------------------
"${IM[@]}" -size "${W}x${H}" "xc:${base}" "$OUT_DIR/macchiato-flat.png"
# --- 5. logo-ish centred mark on the gradient -----------------------------
"${IM[@]}" "$OUT_DIR/macchiato-gradient.png" \
-gravity center \
-fill "${mauve}" -stroke none \
-draw "circle $((W/2)),$((H/2)) $((W/2)),$((H/2 - H/9))" \
-fill "${base}" \
-draw "circle $((W/2)),$((H/2)) $((W/2)),$((H/2 - H/9 + H/90))" \
-blur 0x1 \
"$OUT_DIR/macchiato-ring.png"
echo "wallpapers written to $OUT_DIR:"
ls -1 "$OUT_DIR"
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# ---------------------------------------------------------------------------
# Put back the configs that install.sh moved aside.
# scripts/restore-backup.sh # restore the most recent backup
# scripts/restore-backup.sh <dir> # restore a specific backup
# scripts/restore-backup.sh --list # show available backups
# ---------------------------------------------------------------------------
source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
mapfile -t BACKUPS < <(find "$HOME" -maxdepth 1 -type d -name '.config-backup-*' | sort -r)
if [[ "${1:-}" == "--list" ]]; then
if [[ ${#BACKUPS[@]} -eq 0 ]]; then
echo "no backups found in $HOME"
else
printf '%s\n' "${BACKUPS[@]}"
fi
exit 0
fi
SRC="${1:-${BACKUPS[0]:-}}"
[[ -n "$SRC" ]] || die "no backup directories found (looked for ~/.config-backup-*)"
[[ -d "$SRC" ]] || die "not a directory: $SRC"
log "Restoring from ${SRC/#$HOME/\~}"
step "the following files will be copied back over your current configs:"
(cd "$SRC" && find . -type f | sed 's|^\./| ~/|')
confirm "Restore these files?" || die "aborted"
# -a preserves modes; the trailing /. copies the contents, not the directory.
run cp -a "$SRC/." "$HOME/"
ok "restored — restart i3 (Super+Shift+R) or log out and back in"
View File
+21
View File
@@ -0,0 +1,21 @@
# wallpapers
Drop your own images here (`.png`, `.jpg`, `.jpeg`, `.webp`).
Stage `30-dotfiles` copies everything in this directory into
`~/.local/share/wallpapers/`. If the directory is empty, the installer
synthesises a small set of Catppuccin Macchiato wallpapers instead, via
`scripts/make-wallpaper.sh` and ImageMagick — sized to your primary display.
Generate them by hand at any time:
```sh
scripts/make-wallpaper.sh ~/.local/share/wallpapers 3840x2160
```
Apply one, which also refreshes the betterlockscreen cache:
```sh
set-wallpaper ~/.local/share/wallpapers/macchiato-glow.png
set-wallpaper --random # or Super+Shift+W
```