4 |
5 | **A productive and clean [Niri](https://github.com/YaLTeR/niri) configuration setup**
6 | _Dynamic theming • Borderless layouts • Minimal_
7 |
8 | ---
9 |
10 | ### Gallery
11 |
12 |
13 |
14 |  |
15 |
16 |
17 |  |
18 |
19 |
20 |  |
21 |
22 |
23 |
24 | ---
25 |
26 |
27 |
28 | ## Contents
29 |
30 | - [Features](#features)
31 | - [Automatic Installation](#automatic-installation-recommended)
32 | - [What Gets Installed](#what-gets-installed)
33 | - [Themes](#themes)
34 | - [Preconfigured Tools](#preconfigured-tools)
35 | - [Keybinds](#keybinds)
36 | - [System & Shortcuts](#system--shortcuts)
37 | - [Applications](#applications)
38 | - [Media Controls](#media-controls)
39 | - [Window Management](#window-management)
40 | - [Workspace Management](#workspace-management)
41 | - [Monitor Management](#monitor-management)
42 | - [Layout Controls](#layout-controls)
43 | - [Window Modes](#window-modes)
44 | - [Utilities](#utilities)
45 |
46 | ## Features
47 |
48 | - Clean borderless, gapless minimal look
49 | - Dynamic theme switching system-wide
50 | - Out-of-Box preconfigured for all popular themes and applications
51 | - Rust-powered tooling and packages (rust go brrr...)
52 |
53 | ## Automatic Installation (Recommended)
54 |
55 | For Arch Linux and Arch-based distributions (Manjaro, EndeavourOS, etc.):
56 |
57 | ```bash
58 | curl -fsSL https://raw.githubusercontent.com/saatvik333/niri-dotfiles/main/install.sh | sh
59 | ```
60 |
61 | **Important Requirements:**
62 |
63 | ```
64 | Fresh or minimal Arch Linux installation recommended
65 | Active internet connection required
66 | Sudo privileges needed
67 | At least 5GB free disk space
68 | ```
69 |
70 | What the Script Does
71 |
72 | The automated installer will:
73 |
74 | ```
75 | Verify system compatibility (Arch-based only)
76 | Update your system packages
77 | Install base development tools (git, base-devel, curl)
78 | Set up AUR helper (yay)
79 | Configure Rust toolchain
80 | Install all required packages (niri, waybar, fish, etc.)
81 | Install AUR packages (vicinae, wallust, etc.)
82 | Install GTK themes (Colloid, Rose Pine, Osaka)
83 | Install icon themes (Colloid icons)
84 | Clone and configure dotfiles
85 | Set up shell configuration (Fish/Zsh)
86 | Create systemd services
87 | Install wallpapers
88 | Backup existing configurations
89 | ```
90 |
91 | Installation Time: Approximately 15-30 minutes depending on your internet speed.
92 |
93 | # What Gets Installed
94 |
95 | Core Components
96 |
97 | Window Manager: Niri (Scrollable-tiling Wayland compositor)
98 | Status Bar: Waybar (Highly customizable)
99 | Terminal: Alacritty, Kitty
100 | Shell: Fish (with optional Zsh)
101 | Notification Daemon: Mako
102 | Application Launcher: Rofi
103 | Screen Locker: GTKLock
104 | Wallpaper Manager: awww
105 |
106 | Additional Tools
107 |
108 | Editor: Neovim (preconfigured)
109 | File Manager: Yazi (TUI), Thunar (GUI)
110 | PDF Viewer: Zathura
111 | System Info: Fastfetch
112 | Theme Manager: Wallust
113 | Prompt: Starship
114 | Authentication: Polkit-gnome
115 | Utilities: dust, eza, niri-switch
116 |
117 | Development Tools
118 |
119 | Rust toolchain (rustup, cargo)
120 | Base development packages
121 | Git and build essentials
122 |
123 | ## Themes
124 |
125 | [Wallust](https://codeberg.org/explosion-mental/wallust) is used for the theming using it's color palettes and it's palette generation using wallpaper.
126 |
127 | | Theme | GTK Theme | Icon Theme |
128 | | ---------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
129 | | Catppuccin | [Colloid (Light/Dark) Catppuccin](https://github.com/vinceliuice/Colloid-gtk-theme) | [Colloid Catppuccin (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
130 | | Everforest | [Colloid (Light/Dark) Everforest](https://github.com/vinceliuice/Colloid-gtk-theme) | [Colloid Everforest (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
131 | | Gruvbox | [Colloid (Light/Dark) Gruvbox](https://github.com/vinceliuice/Colloid-gtk-theme) | [Colloid Gruvbox (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
132 | | Nord | [Colloid (Light/Dark) Nord](https://github.com/vinceliuice/Colloid-gtk-theme) | [Colloid Nord (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
133 | | Rosé Pine | [Rose Pine GTK Theme (Light/Dark)](https://github.com/Fausto-Korpsvart/Rose-Pine-GTK-Theme) | [Colloid Catppuccin (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
134 | | Dracula | [Colloid (Light/Dark) Dracula](https://github.com/vinceliuice/Colloid-gtk-theme) | [Colloid Dracula (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
135 | | Material | [Colloid Grey (Light/Dark)](https://github.com/vinceliuice/Colloid-gtk-theme) | [Colloid (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
136 | | Solarized | [Osaka GTK Theme (Light/Dark)](https://github.com/Fausto-Korpsvart/Osaka-GTK-Theme) | [Colloid Everforest (Light/Dark)](https://github.com/vinceliuice/Colloid-icon-theme) |
137 |
138 | Thanks to [vinceliuice](https://github.com/vinceliuice) and [Fausto-Korpsvart](https://github.com/Fausto-Korpsvart) for providing awesome GTK themes.
139 |
140 | ## Preconfigured Tools
141 |
142 | - Neovim
143 | - Yazi
144 | - Rofi
145 | - Waybar
146 | - Fish
147 | - Fastfetch
148 | - Mako
149 | - Alacritty
150 | - Kitty
151 | - Starship
152 |
153 | ## Keybinds
154 |
155 | > **Note:** `MOD` key is the Super/Windows key by default.
156 |
157 | ### System & Shortcuts
158 |
159 | | Keybind | Action |
160 | | ---------------------- | ------------------------------------- |
161 | | `MOD + Shift + Escape` | Show hotkey overlay (shortcuts panel) |
162 |
163 | ### Applications
164 |
165 | | Keybind | Action |
166 | | -------------------- | ------------------------------------------------ |
167 | | `MOD + Return` | Open terminal (Alacritty) |
168 | | `MOD + Alt + Return` | Open terminal (Kitty) |
169 | | `MOD + B` | Open primary browser (Firefox Developer Edition) |
170 | | `MOD + Alt + B` | Open secondary browser (Google Chrome) |
171 | | `MOD + A` | Toggle application launcher (Vicinae) |
172 | | `MOD + E` | Open file manager (Thunar) |
173 | | `MOD + Alt + E` | Open TUI file manager (Yazi) |
174 | | `MOD + W` | Open wallpaper selector |
175 | | `MOD + Shift + Q` | Lock screen (GTKLock) |
176 |
177 | ### Media Controls
178 |
179 | | Keybind | Action |
180 | | ----------------------- | -------------------------- |
181 | | `XF86AudioRaiseVolume` | Increase volume |
182 | | `XF86AudioLowerVolume` | Decrease volume |
183 | | `XF86AudioMute` | Mute/unmute audio |
184 | | `XF86AudioMicMute` | Mute/unmute microphone |
185 | | `XF86MonBrightnessUp` | Increase screen brightness |
186 | | `XF86MonBrightnessDown` | Decrease screen brightness |
187 | | `XF86AudioPlay` | Play/pause media |
188 | | `XF86AudioPause` | Play/pause media |
189 | | `XF86AudioNext` | Next track |
190 | | `XF86AudioPrev` | Previous track |
191 |
192 | > **Note:** All media keys work even when the screen is locked.
193 |
194 | ### Window Management
195 |
196 | #### Focus Windows
197 |
198 | | Keybind | Action |
199 | | -------------------------- | ---------------------------------- |
200 | | `MOD + H` or `MOD + Left` | Focus window/column left |
201 | | `MOD + J` or `MOD + Down` | Focus workspace down / window down |
202 | | `MOD + K` or `MOD + Up` | Focus workspace up / window up |
203 | | `MOD + L` or `MOD + Right` | Focus window/column right |
204 | | `MOD + Home` | Focus first column |
205 | | `MOD + End` | Focus last column |
206 | | `MOD + Q` | Close focused window |
207 |
208 | #### Move Windows
209 |
210 | | Keybind | Action |
211 | | ------------------------------------------ | ------------------------------------------------ |
212 | | `MOD + Shift + H` or `MOD + Shift + Left` | Move column left |
213 | | `MOD + Shift + J` or `MOD + Shift + Down` | Move column to workspace down / move window down |
214 | | `MOD + Shift + K` or `MOD + Shift + Up` | Move column to workspace up / move window up |
215 | | `MOD + Shift + L` or `MOD + Shift + Right` | Move column right |
216 | | `MOD + Shift + Home` | Move column to first position |
217 | | `MOD + Shift + End` | Move column to last position |
218 |
219 | #### Mouse Navigation
220 |
221 | | Keybind | Action |
222 | | --------------------------- | ----------------------------- |
223 | | `MOD + Scroll Down` | Focus workspace down |
224 | | `MOD + Scroll Up` | Focus workspace up |
225 | | `MOD + Scroll Right` | Focus column right |
226 | | `MOD + Scroll Left` | Focus column left |
227 | | `MOD + Shift + Scroll Down` | Move column to workspace down |
228 | | `MOD + Shift + Scroll Up` | Move column to workspace up |
229 | | `MOD + Ctrl + Scroll Right` | Move column right |
230 | | `MOD + Ctrl + Scroll Left` | Move column left |
231 |
232 | ### Workspace Management
233 |
234 | #### Navigate Workspaces
235 |
236 | | Keybind | Action |
237 | | -------------- | ----------------------------- |
238 | | `MOD + 1-9` | Switch to workspace 1-9 |
239 | | `MOD + Tab` | Switch to previous workspace |
240 | | `MOD + Escape` | Toggle overview mode |
241 | | `Alt + Tab` | Window switcher (niri-switch) |
242 |
243 | #### Move Windows to Workspaces
244 |
245 | | Keybind | Action |
246 | | ------------------- | ---------------------------- |
247 | | `MOD + Shift + 1-9` | Move window to workspace 1-9 |
248 |
249 | ### Monitor Management
250 |
251 | #### Focus Monitors
252 |
253 | | Keybind | Action |
254 | | ---------------------------------------- | ------------------- |
255 | | `MOD + Ctrl + H` or `MOD + Ctrl + Left` | Focus monitor left |
256 | | `MOD + Ctrl + L` or `MOD + Ctrl + Right` | Focus monitor right |
257 | | `MOD + Ctrl + K` or `MOD + Ctrl + Up` | Focus monitor up |
258 | | `MOD + Ctrl + J` or `MOD + Ctrl + Down` | Focus monitor down |
259 |
260 | #### Move Windows to Monitors
261 |
262 | | Keybind | Action |
263 | | -------------------------------------------------------- | ---------------------------- |
264 | | `MOD + Shift + Ctrl + H` or `MOD + Shift + Ctrl + Left` | Move window to monitor left |
265 | | `MOD + Shift + Ctrl + L` or `MOD + Shift + Ctrl + Right` | Move window to monitor right |
266 | | `MOD + Shift + Ctrl + K` or `MOD + Shift + Ctrl + Up` | Move window to monitor up |
267 | | `MOD + Shift + Ctrl + J` or `MOD + Shift + Ctrl + Down` | Move window to monitor down |
268 |
269 | ### Layout Controls
270 |
271 | | Keybind | Action |
272 | | -------------------------- | ----------------------------- |
273 | | `MOD + C` | Center focused column |
274 | | `MOD + Ctrl + C` | Center all visible columns |
275 | | `MOD + [` | Decrease column width by 10% |
276 | | `MOD + ]` | Increase column width by 10% |
277 | | `MOD + Shift + [` | Decrease window height by 10% |
278 | | `MOD + Shift + ]` | Increase window height by 10% |
279 | | `MOD + Ctrl + Scroll Down` | Decrease window height by 5% |
280 | | `MOD + Ctrl + Scroll Up` | Increase window height by 5% |
281 |
282 | ### Window Modes
283 |
284 | | Keybind | Action |
285 | | --------- | ---------------------- |
286 | | `MOD + T` | Toggle window floating |
287 | | `MOD + F` | Toggle fullscreen |
288 | | `MOD + M` | Maximize column |
289 |
290 | ### Utilities
291 |
292 | | Keybind | Action |
293 | | ----------------- | --------------------------- |
294 | | `MOD + S` | Take screenshot (selection) |
295 | | `MOD + Shift + S` | Screenshot entire screen |
296 | | `MOD + Ctrl + S` | Screenshot current window |
297 | | `MOD + P` | Color picker (hyprpicker) |
298 | | `MOD + Alt + W` | Restart Waybar |
299 |
300 | ---
301 |
--------------------------------------------------------------------------------
/niri/config.kdl:
--------------------------------------------------------------------------------
1 | // Startup Applications
2 | spawn-sh-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1 &"
3 | spawn-at-startup "awww-daemon"
4 | spawn-at-startup "waybar"
5 | spawn-at-startup "sh" "-c" "vicinae server &"
6 |
7 | // General Settings
8 | prefer-no-csd
9 | screenshot-path "~/Pictures/Screenshots/%Y-%m-%d %H-%M-%S.png"
10 |
11 | output "eDP-1" {
12 | mode "1920x1080@60.024"
13 | scale 1
14 | }
15 |
16 | input {
17 | keyboard {
18 | xkb {
19 | layout "us"
20 | options "caps:escape"
21 | }
22 | }
23 |
24 | touchpad {
25 | tap
26 | natural-scroll
27 | }
28 |
29 | focus-follows-mouse
30 | workspace-auto-back-and-forth
31 | }
32 |
33 | hotkey-overlay {
34 | skip-at-startup
35 | }
36 |
37 | cursor {
38 | hide-when-typing
39 | hide-after-inactive-ms 5000
40 | }
41 |
42 | overview {
43 | backdrop-color "#1E1E2E"
44 | workspace-shadow {
45 | off
46 | }
47 | }
48 |
49 | layer-rule {
50 | match namespace="^awww-daemon$"
51 | place-within-backdrop true
52 | }
53 |
54 | binds {
55 | // Shortcuts Panel
56 | MOD+SHIFT+ESCAPE { show-hotkey-overlay; }
57 |
58 | // Application Shortcuts
59 | MOD+RETURN hotkey-overlay-title="Open Terminal: alacritty" { spawn-sh "alacritty"; }
60 | MOD+ALT+RETURN hotkey-overlay-title="Open Terminal: kitty" { spawn-sh "kitty"; }
61 | MOD+ALT+B hotkey-overlay-title="Open Secondary Browser: chrome" { spawn-sh "google-chrome-stable --enable-features=TouchpadOverscrollHistoryNavigation"; }
62 | MOD+SHIFT+Q hotkey-overlay-title="Lock Screen: gtklock" { spawn-sh "gtklock"; }
63 | MOD+A hotkey-overlay-title="Open App Launcher: vicinae" { spawn-sh "vicinae toggle"; }
64 | MOD+B hotkey-overlay-title="Open Browser: firefox" { spawn-sh "firefox-developer-edition"; }
65 | MOD+E hotkey-overlay-title="File Manager: Thunar" { spawn-sh "thunar"; }
66 | MOD+ALT+E hotkey-overlay-title="File Manager: Yazi" { spawn-sh "yazi"; }
67 | MOD+W hotkey-overlay-title="Open Wallpaper Selector" { spawn-sh "$HOME/.config/scripts/bgselector.sh"; }
68 |
69 | // Media Keys
70 | XF86AudioRaiseVolume allow-when-locked=true { spawn-sh "mediactl volume_up"; }
71 | XF86AudioLowerVolume allow-when-locked=true { spawn-sh "mediactl volume_down"; }
72 | XF86MonBrightnessUp allow-when-locked=true { spawn-sh "mediactl brightness_up"; }
73 | XF86MonBrightnessDown allow-when-locked=true { spawn-sh "mediactl brightness_down"; }
74 | XF86AudioMute allow-when-locked=true { spawn-sh "mediactl volume_mute"; }
75 | XF86AudioMicMute allow-when-locked=true { spawn-sh "mediactl mic_mute"; }
76 | XF86AudioNext allow-when-locked=true { spawn-sh "mediactl next_track"; }
77 | XF86AudioPause allow-when-locked=true { spawn-sh "mediactl play_pause"; }
78 | XF86AudioPlay allow-when-locked=true { spawn-sh "mediactl play_pausse"; }
79 | XF86AudioPrev allow-when-locked=true { spawn-sh "mediactl prev_track"; }
80 |
81 | // Window Management
82 | MOD+Q { close-window; }
83 |
84 | MOD+H { focus-column-left; }
85 | MOD+J { focus-workspace-down; }
86 | MOD+K { focus-workspace-up; }
87 | MOD+L { focus-column-right; }
88 |
89 | MOD+LEFT { focus-column-left; }
90 | MOD+DOWN { focus-window-down; }
91 | MOD+UP { focus-window-up; }
92 | MOD+RIGHT { focus-column-right; }
93 |
94 | // Move windows within workspace
95 | MOD+SHIFT+LEFT { move-column-left; }
96 | MOD+SHIFT+DOWN { move-window-down; }
97 | MOD+SHIFT+UP { move-window-up; }
98 | MOD+SHIFT+RIGHT { move-column-right; }
99 |
100 | MOD+SHIFT+H { move-column-left; }
101 | MOD+SHIFT+J { move-column-to-workspace-down; }
102 | MOD+SHIFT+K { move-column-to-workspace-up; }
103 | MOD+SHIFT+L { move-column-right; }
104 |
105 | // Move to workspace edges
106 | MOD+SHIFT+HOME { move-column-to-first; }
107 | MOD+SHIFT+END { move-column-to-last; }
108 |
109 | MOD+HOME { focus-column-first; }
110 | MOD+END { focus-column-last; }
111 |
112 | // Monitor Management - Move between monitors
113 | MOD+CTRL+LEFT { focus-monitor-left; }
114 | MOD+CTRL+RIGHT { focus-monitor-right; }
115 | MOD+CTRL+UP { focus-monitor-up; }
116 | MOD+CTRL+DOWN { focus-monitor-down; }
117 |
118 | MOD+CTRL+H { focus-monitor-left; }
119 | MOD+CTRL+L { focus-monitor-right; }
120 | MOD+CTRL+K { focus-monitor-up; }
121 | MOD+CTRL+J { focus-monitor-down; }
122 |
123 | // Move windows to different monitors
124 | MOD+SHIFT+CTRL+LEFT { move-column-to-monitor-left; }
125 | MOD+SHIFT+CTRL+RIGHT { move-column-to-monitor-right; }
126 | MOD+SHIFT+CTRL+UP { move-column-to-monitor-up; }
127 | MOD+SHIFT+CTRL+DOWN { move-column-to-monitor-down; }
128 |
129 | MOD+SHIFT+CTRL+H { move-column-to-monitor-left; }
130 | MOD+SHIFT+CTRL+L { move-column-to-monitor-right; }
131 | MOD+SHIFT+CTRL+K { move-column-to-monitor-up; }
132 | MOD+SHIFT+CTRL+J { move-column-to-monitor-down; }
133 |
134 | // Workspace Management
135 | MOD+ESCAPE hotkey-overlay-title="Open Overview" repeat=false { toggle-overview; }
136 | MOD+WHEELSCROLLDOWN cooldown-ms=100 { focus-workspace-down; }
137 | MOD+WHEELSCROLLUP cooldown-ms=100 { focus-workspace-up; }
138 |
139 | // Move windows to different workspaces - using wheel matches K/J pattern
140 | MOD+SHIFT+WHEELSCROLLDOWN cooldown-ms=100 { move-column-to-workspace-down; }
141 | MOD+SHIFT+WHEELSCROLLUP cooldown-ms=100 { move-column-to-workspace-up; }
142 |
143 | // Column navigation with mouse
144 | MOD+WHEELSCROLLRIGHT { focus-column-right; }
145 | MOD+WHEELSCROLLLEFT { focus-column-left; }
146 | MOD+CTRL+WHEELSCROLLRIGHT { move-column-right; }
147 | MOD+CTRL+WHEELSCROLLLEFT { move-column-left; }
148 |
149 | // Numbered Workspaces
150 | MOD+1 { focus-workspace 1; }
151 | MOD+2 { focus-workspace 2; }
152 | MOD+3 { focus-workspace 3; }
153 | MOD+4 { focus-workspace 4; }
154 | MOD+5 { focus-workspace 5; }
155 | MOD+6 { focus-workspace 6; }
156 | MOD+7 { focus-workspace 7; }
157 | MOD+8 { focus-workspace 8; }
158 | MOD+9 { focus-workspace 9; }
159 |
160 | // Move windows to numbered workspaces with MOD+SHIFT
161 | MOD+SHIFT+1 { move-column-to-workspace 1; }
162 | MOD+SHIFT+2 { move-column-to-workspace 2; }
163 | MOD+SHIFT+3 { move-column-to-workspace 3; }
164 | MOD+SHIFT+4 { move-column-to-workspace 4; }
165 | MOD+SHIFT+5 { move-column-to-workspace 5; }
166 | MOD+SHIFT+6 { move-column-to-workspace 6; }
167 | MOD+SHIFT+7 { move-column-to-workspace 7; }
168 | MOD+SHIFT+8 { move-column-to-workspace 8; }
169 | MOD+SHIFT+9 { move-column-to-workspace 9; }
170 |
171 | MOD+TAB { focus-workspace-previous; }
172 |
173 | // Layout Controls
174 | MOD+C { center-column; }
175 | MOD+CTRL+C { center-visible-columns; }
176 | MOD+BRACKETLEFT { set-column-width "-10%"; }
177 | MOD+BRACKETRIGHT { set-column-width "+10%"; }
178 | MOD+SHIFT+BRACKETLEFT { set-window-height "-10%"; }
179 | MOD+SHIFT+BRACKETRIGHT { set-window-height "+10%"; }
180 |
181 | // Window resizing with mouse
182 | MOD+CTRL+WHEELSCROLLDOWN { set-window-height "-5%"; }
183 | MOD+CTRL+WHEELSCROLLUP { set-window-height "+5%"; }
184 |
185 | // Window Modes
186 | MOD+T { toggle-window-floating; }
187 | MOD+F { fullscreen-window; }
188 | MOD+M { maximize-column; }
189 | // MOD+W { toggle-column-tabbed-display; }
190 |
191 | // Utils
192 | MOD+S { screenshot; }
193 | MOD+SHIFT+S { screenshot-screen write-to-disk=true; }
194 | MOD+CTRL+S { screenshot-window write-to-disk=true; }
195 | MOD+P { spawn-sh "pgrep -x hyprpicker >/dev/null || hyprpicker"; }
196 |
197 | // Application Restarts
198 | MOD+ALT+W { spawn "sh" "-c" "pgrep -x waybar >/dev/null && killall waybar || waybar &"; }
199 | // use with toggle in waybar (doens't restart if not running)
200 | // MOD+ALT+W { spawn "killall" "-SIGUSR1" "waybar"; }
201 | }
202 |
203 | // Layout Settings
204 | layout {
205 | gaps 0
206 | center-focused-column "on-overflow"
207 | background-color "transparent"
208 |
209 | preset-column-widths {
210 | proportion 0.33333
211 | proportion 0.5
212 | proportion 0.66667
213 | }
214 |
215 | focus-ring {
216 | width 0
217 | active-color "#1E1E2E"
218 | }
219 |
220 | insert-hint {
221 | off
222 | color "#1E1E2E"
223 | }
224 |
225 | struts {}
226 |
227 | }
228 |
229 | // Animation Settings
230 | animations {
231 | workspace-switch {
232 | spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001
233 | }
234 | window-open {
235 | duration-ms 200
236 | curve "ease-out-quad"
237 | }
238 | window-close {
239 | duration-ms 200
240 | curve "ease-out-cubic"
241 | }
242 | horizontal-view-movement {
243 | spring damping-ratio=1.0 stiffness=900 epsilon=0.0001
244 | }
245 | window-movement {
246 | spring damping-ratio=1.0 stiffness=800 epsilon=0.0001
247 | }
248 | window-resize {
249 | spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001
250 | }
251 | config-notification-open-close {
252 | spring damping-ratio=0.6 stiffness=1200 epsilon=0.001
253 | }
254 | screenshot-ui-open {
255 | duration-ms 300
256 | curve "ease-out-quad"
257 | }
258 | overview-open-close {
259 | spring damping-ratio=1.0 stiffness=900 epsilon=0.0001
260 | }
261 | }
262 |
263 | // Window Rules
264 | // Application-specific rules
265 | window-rule {
266 | match app-id=r#"firefox$"# title="^Picture-in-Picture$"
267 | open-floating true
268 | }
269 |
270 | window-rule {
271 | match app-id=r#"firefox"#
272 | match app-id=r#"google-chrome"#
273 | match app-id=r#"code"#
274 | match app-id=r#"obsidian$"#
275 | open-maximized true
276 | }
277 |
278 | // File dialogs - Open/Save/Select
279 | window-rule {
280 | match title=r#".*(Open|Save|Select).*"#
281 | open-floating true
282 | default-column-width { proportion 0.0; }
283 | max-width 800
284 | max-height 1000
285 | }
286 |
287 | window-rule {
288 | match title=r#".*File.*"#
289 | open-floating true
290 | default-column-width { proportion 0.0; }
291 | max-width 800
292 | max-height 1000
293 | }
294 |
295 | window-rule {
296 | match app-id=r#"org\.gtk\.FileChooserDialog"#
297 | open-floating true
298 | default-column-width { proportion 0.0; }
299 | max-width 800
300 | max-height 1000
301 | }
302 |
303 | window-rule {
304 | match title=r#".*Sign in - Google Accounts — Mozilla Firefox"#
305 | open-floating true
306 | }
307 |
308 | // System dialogs
309 | window-rule {
310 | match title=r#".*(Dialog|Properties|Preferences|Settings|Rename).*"#
311 | open-floating true
312 | }
313 |
314 | window-rule {
315 | match app-id=r#"zenity"#
316 | open-floating true
317 | }
318 |
319 | // Authentication dialogs
320 | window-rule {
321 | match app-id=r#"org\.kde\.polkit-kde-authentication-agent-1"#
322 | open-floating true
323 | }
324 |
325 | window-rule {
326 | match title=r#".*Authentication.*"#
327 | open-floating true
328 | }
329 |
330 | // Password managers
331 | window-rule {
332 | match app-id=r#"org\.keepassxc\.KeePassXC"# title=r#".*Auto-Type.*"#
333 | open-floating true
334 | }
335 |
336 | window-rule {
337 | match app-id=r#"Bitwarden"# title=r#".*unlock.*"#
338 | open-floating true
339 | }
340 |
341 | // Notification and system utilities
342 | window-rule {
343 | match app-id=r#"nm-connection-editor"#
344 | open-floating true
345 | }
346 |
347 | window-rule {
348 | match app-id=r#"blueman-manager"#
349 | open-floating true
350 | }
351 |
352 | window-rule {
353 | match app-id=r#"pavucontrol"#
354 | open-floating true
355 | }
356 |
357 | // Steam dialogs
358 | window-rule {
359 | match app-id=r#"steam"# title=r#".*(Friends|Settings|Properties).*"#
360 | open-floating true
361 | }
362 |
363 | // Global window appearance
364 | window-rule {
365 | geometry-corner-radius 0
366 | clip-to-geometry true
367 | }
368 |
369 | // Inactive window transparency
370 | window-rule {
371 | match is-active=false
372 | opacity 0.9
373 | }
374 |
375 | // Environment Variables
376 | environment {
377 | DISPLAY null
378 | ELECTRON_OZONE_PLATFORM_HINT "auto"
379 | XDG_SESSION_TYPE "wayland"
380 | XDG_CURRENT_DESKTOP "niri"
381 |
382 | // Wayland Platform Settings
383 | QT_QPA_PLATFORM "wayland"
384 | QT_QPA_PLATFORMTHEME "gtk3"
385 | QT_QPA_PLATFORMTHEME_QT6 "gtk3"
386 | QT_WAYLAND_DISABLE_WINDOWDECORATION "1"
387 | QT_AUTO_SCREEN_SCALE_FACTOR "1"
388 |
389 | // GTK Settings
390 | GDK_BACKEND "wayland,x11"
391 |
392 | // Firefox Wayland
393 | MOZ_ENABLE_WAYLAND "1"
394 | MOZ_DBUS_REMOTE "1"
395 |
396 | // Java Applications
397 | _JAVA_AWT_WM_NONREPARENTING "1"
398 |
399 | // SDL Applications
400 | SDL_VIDEODRIVER "wayland"
401 |
402 | // CLUTTER Applications
403 | CLUTTER_BACKEND "wayland"
404 |
405 | // Performance
406 | VDPAU_DRIVER "va_gl"
407 | LIBVA_DRIVER_NAME "iHD"
408 | }
409 |
410 |
--------------------------------------------------------------------------------
/scripts/theme-sync.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -uo pipefail
4 |
5 | sleep 0.8 # let awww set the wallpaper
6 |
7 | # Source common utilities if available
8 | if [[ -f "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh" ]]; then
9 | source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
10 | else
11 | # Fallback logging functions if common.sh is not available
12 | log_info() {
13 | local -r timestamp=$(date '+%Y-%m-%d %H:%M:%S')
14 | echo -e "\\033[1;34m[$timestamp] INFO: $*\\033[0m" >&2
15 | }
16 |
17 | log_error() {
18 | local -r timestamp=$(date '+%Y-%m-%d %H:%M:%S')
19 | echo -e "\\033[1;31m[$timestamp] ERROR: $*\\033[0m" >&2
20 | }
21 |
22 | log_success() {
23 | local -r timestamp=$(date '+%Y-%m-%d %H:%M:%S')
24 | echo -e "\\033[1;32m[$timestamp] SUCCESS: $*\\033[0m" >&2
25 | }
26 |
27 | log_warn() {
28 | local -r timestamp=$(date '+%Y-%m-%d %H:%M:%S')
29 | echo -e "\\033[1;33m[$timestamp] WARN: $*\\033[0m" >&2
30 | }
31 |
32 | log_debug() {
33 | local -r timestamp=$(date '+%Y-%m-%d %H:%M:%S')
34 | echo -e "\\033[1;36m[$timestamp] DEBUG: $*\\033[0m" >&2
35 | }
36 |
37 | die() {
38 | log_error "$*"
39 | exit 1
40 | }
41 |
42 | validate_dependencies() {
43 | local -ra required_deps=("$@")
44 | local missing_deps=()
45 |
46 | for dep in "${required_deps[@]}"; do
47 | command -v "$dep" > /dev/null 2>&1 || missing_deps+=("$dep")
48 | done
49 |
50 | if [[ ${#missing_deps[@]} -gt 0 ]]; then
51 | die "Missing required dependencies: ${missing_deps[*]}"
52 | fi
53 | }
54 | fi
55 |
56 | # --- Configuration ---
57 | readonly SCRIPT_NAME="${0##*/}"
58 | readonly WALLPAPERS_DIR="$HOME/Pictures/Wallpapers"
59 | readonly DEFAULT_GTK_THEME="Colloid-Dark"
60 | readonly DEFAULT_ICON_THEME="Colloid-Dark"
61 | readonly THEME_STATE_FILE="$HOME/.cache/theme-sync-state"
62 |
63 | # Get theme based on directory and variation
64 | map_to_gtk_theme() {
65 | local theme_name="$1"
66 | local variation="$2"
67 |
68 | case "${theme_name}" in
69 | "catppuccin")
70 | if [[ "$variation" == "light" ]]; then
71 | echo "Colloid-Light-Catppuccin"
72 | else
73 | echo "Colloid-Dark-Catppuccin"
74 | fi
75 | ;;
76 | "dracula")
77 | if [[ "$variation" == "light" ]]; then
78 | echo "Colloid-Light-Dracula"
79 | else
80 | echo "Colloid-Dark-Dracula"
81 | fi
82 | ;;
83 | "everforest")
84 | if [[ "$variation" == "light" ]]; then
85 | echo "Colloid-Light-Everforest"
86 | else
87 | echo "Colloid-Dark-Everforest"
88 | fi
89 | ;;
90 | "gruvbox")
91 | if [[ "$variation" == "light" ]]; then
92 | echo "Colloid-Light-Gruvbox"
93 | else
94 | echo "Colloid-Dark-Gruvbox"
95 | fi
96 | ;;
97 | "material")
98 | if [[ "$variation" == "light" ]]; then
99 | echo "Colloid-Light"
100 | else
101 | echo "Colloid-Grey-Dark"
102 | fi
103 | ;;
104 | "nord")
105 | if [[ "$variation" == "light" ]]; then
106 | echo "Colloid-Light-Nord"
107 | else
108 | echo "Colloid-Dark-Nord"
109 | fi
110 | ;;
111 | "solarized")
112 | if [[ "$variation" == "light" ]]; then
113 | echo "Osaka-Light-Solarized"
114 | else
115 | echo "Osaka-Dark-Solarized"
116 | fi
117 | ;;
118 | "rose-pine")
119 | if [[ "$variation" == "light" ]]; then
120 | echo "Rosepine-Light"
121 | else
122 | echo "Rosepine-Dark"
123 | fi
124 | ;;
125 | "tokyo-night")
126 | if [[ "$variation" == "light" ]]; then
127 | echo "Tokyonight-Light"
128 | else
129 | echo "Tokyonight-Dark"
130 | fi
131 | ;;
132 | *)
133 | log_warn "Unknown theme: $theme_name, using default theme"
134 | if [[ "$variation" == "light" ]]; then
135 | echo "Colloid-Light"
136 | else
137 | echo "Colloid-Dark"
138 | fi
139 | ;;
140 | esac
141 | }
142 |
143 | map_to_icon_theme() {
144 | local theme_name="$1"
145 | local variation="$2"
146 |
147 | case "${theme_name}" in
148 | "catppuccin")
149 | if [[ "$variation" == "light" ]]; then
150 | echo "Colloid-Catppuccin-Light"
151 | else
152 | echo "Colloid-Catppuccin-Dark"
153 | fi
154 | ;;
155 | "dracula")
156 | if [[ "$variation" == "light" ]]; then
157 | echo "Colloid-Dracula-Light"
158 | else
159 | echo "Colloid-Dracula-Dark"
160 | fi
161 | ;;
162 | "everforest")
163 | if [[ "$variation" == "light" ]]; then
164 | echo "Colloid-Everforest-Light"
165 | else
166 | echo "Colloid-Everforest-Dark"
167 | fi
168 | ;;
169 | "gruvbox")
170 | if [[ "$variation" == "light" ]]; then
171 | echo "Colloid-Gruvbox-Light"
172 | else
173 | echo "Colloid-Gruvbox-Dark"
174 | fi
175 | ;;
176 | "material")
177 | # Base colloid-dark for material as requested
178 | if [[ "$variation" == "light" ]]; then
179 | echo "Colloid-Light"
180 | else
181 | echo "Colloid-Dark"
182 | fi
183 | ;;
184 | "nord")
185 | if [[ "$variation" == "light" ]]; then
186 | echo "Colloid-Nord-Light"
187 | else
188 | echo "Colloid-Nord-Dark"
189 | fi
190 | ;;
191 | "solarized")
192 | # everforest for osaka (solarized) as requested
193 | if [[ "$variation" == "light" ]]; then
194 | echo "Colloid-Everforest-Light"
195 | else
196 | echo "Colloid-Everforest-Dark"
197 | fi
198 | ;;
199 | "rose-pine" | "tokyo-night")
200 | # catppuccin for rose-pine and tokyo-night as requested
201 | if [[ "$variation" == "light" ]]; then
202 | echo "Colloid-Catppuccin-Light"
203 | else
204 | echo "Colloid-Catppuccin-Dark"
205 | fi
206 | ;;
207 | *)
208 | log_warn "Unknown theme: $theme_name, using default icon theme"
209 | if [[ "$variation" == "light" ]]; then
210 | echo "Colloid-Light"
211 | else
212 | echo "Colloid-Dark"
213 | fi
214 | ;;
215 | esac
216 | }
217 |
218 | map_to_wallust_theme() {
219 | local theme_name="$1"
220 | local variation="$2"
221 |
222 | case "${theme_name}" in
223 | "catppuccin")
224 | if [[ "$variation" == "light" ]]; then
225 | echo "Catppuccin-Latte"
226 | else
227 | echo "Catppuccin-Mocha"
228 | fi
229 | ;;
230 | "dracula")
231 | if [[ "$variation" == "light" ]]; then
232 | echo "base16-default-light"
233 | else
234 | echo "base16-dracula"
235 | fi
236 | ;;
237 | "everforest")
238 | if [[ "$variation" == "light" ]]; then
239 | echo "Everforest-Light-Medium"
240 | else
241 | echo "Everforest-Dark-Medium"
242 | fi
243 | ;;
244 | "gruvbox")
245 | if [[ "$variation" == "light" ]]; then
246 | echo "Gruvbox"
247 | else
248 | echo "Gruvbox-Dark"
249 | fi
250 | ;;
251 | "material")
252 | if [[ "$variation" == "light" ]]; then
253 | echo "base16-default-light"
254 | else
255 | echo "base16-black-metal-funeral"
256 | fi
257 | ;;
258 | "nord")
259 | if [[ "$variation" == "light" ]]; then
260 | echo "Nord-Light"
261 | else
262 | echo "Nord"
263 | fi
264 | ;;
265 | "solarized")
266 | if [[ "$variation" == "light" ]]; then
267 | echo "Solarized-Light"
268 | else
269 | echo "Solarized-Dark"
270 | fi
271 | ;;
272 | "rose-pine")
273 | if [[ "$variation" == "light" ]]; then
274 | echo "Rosé-Pine-Dawn"
275 | else
276 | echo "Rosé-Pine"
277 | fi
278 | ;;
279 | "tokyo-night")
280 | if [[ "$variation" == "light" ]]; then
281 | echo "Tokyo-Night-Light"
282 | else
283 | echo "Tokyo-Night"
284 | fi
285 | ;;
286 | *)
287 | log_warn "Unknown theme: $theme_name, using random theme"
288 | echo "random"
289 | ;;
290 | esac
291 | }
292 |
293 | # --- Functions ---
294 |
295 | detect_theme_from_wallpaper() {
296 | log_info "Detecting theme from current wallpaper directory"
297 |
298 | local wallpaper_path
299 | wallpaper_path=$(awww query 2> /dev/null | grep -oP '(?<=image: ).*' | head -n1 | tr -d '\n\r')
300 |
301 | if [[ -z "$wallpaper_path" ]]; then
302 | die "No wallpaper detected from awww query"
303 | fi
304 |
305 | if [[ ! -f "$wallpaper_path" ]]; then
306 | die "Wallpaper file does not exist: $wallpaper_path"
307 | fi
308 |
309 | log_debug "Found wallpaper: $wallpaper_path"
310 |
311 | local theme_dir
312 | theme_dir=$(dirname "$wallpaper_path")
313 | local parent_dir
314 | parent_dir=$(dirname "$theme_dir")
315 |
316 | local theme_name
317 | theme_name=$(basename "$parent_dir" | tr '[:upper:]' '[:lower:]')
318 |
319 | local variation
320 | variation=$(basename "$theme_dir" | tr '[:upper:]' '[:lower:]')
321 |
322 | if [[ "$theme_name" == "osaka" ]]; then
323 | theme_name="solarized"
324 | fi
325 |
326 | log_debug "Detected theme: $theme_name, variation: $variation"
327 |
328 | WALLPAPER_PATH="$wallpaper_path"
329 | WALLPAPER_VARIATION="$variation"
330 | DETECTED_THEME="$theme_name"
331 |
332 | export WALLPAPER_PATH WALLPAPER_VARIATION
333 | }
334 |
335 | check_theme_changed() {
336 | local -r current_theme="$1"
337 | local -r current_variation="$2"
338 |
339 | # Create cache directory if it doesn't exist
340 | mkdir -p "$(dirname "$THEME_STATE_FILE")"
341 |
342 | if [[ ! -f "$THEME_STATE_FILE" ]]; then
343 | log_info "No previous theme state found, this is first run or cache was cleared"
344 | return 0 # Theme changed (first run)
345 | fi
346 |
347 | local previous_theme previous_variation
348 | read -r previous_theme previous_variation < "$THEME_STATE_FILE"
349 |
350 | if [[ "$current_theme" == "$previous_theme" && "$current_variation" == "$previous_variation" ]]; then
351 | log_info "Theme unchanged: $current_theme ($current_variation)"
352 | return 1 # Theme did not change
353 | else
354 | log_info "Theme changed: $previous_theme ($previous_variation) → $current_theme ($current_variation)"
355 | return 0 # Theme changed
356 | fi
357 | }
358 |
359 | save_theme_state() {
360 | local -r theme="$1"
361 | local -r variation="$2"
362 |
363 | mkdir -p "$(dirname "$THEME_STATE_FILE")"
364 | echo "$theme $variation" > "$THEME_STATE_FILE"
365 | log_info "Saved theme state: $theme ($variation)"
366 | }
367 |
368 | # Function to set values in INI files
369 | set_ini_value() {
370 | local -r file="$1"
371 | local -r section="$2"
372 | local -r key="$3"
373 | local -r value="$4"
374 |
375 | [[ -f "$file" ]] || touch "$file"
376 |
377 | if grep -q "^\\[$section\\]" "$file"; then
378 | if grep -q "^$key=" "$file"; then
379 | sed -i "/^\\[$section\\]/,/^\\[/ s/^$key=.*/$key=$value/" "$file"
380 | else
381 | sed -i "/^\\[$section\\]/a $key=$value" "$file"
382 | fi
383 | else
384 | printf '\\n[%s]\\n%s=%s\\n' "$section" "$key" "$value" >> "$file"
385 | fi
386 | }
387 |
388 | manage_gtk_config() {
389 | local -r version="$1"
390 | local -r theme="$2"
391 | local -r variation="${3:-dark}"
392 | local -r config_file="$HOME/.config/gtk-$version/settings.ini"
393 |
394 | # Create directory if it doesn't exist
395 | mkdir -p "$(dirname "$config_file")"
396 |
397 | set_ini_value "$config_file" "Settings" "gtk-theme-name" "$theme"
398 |
399 | # Set prefer-dark-theme based on variation
400 | if [[ "$variation" == "light" ]]; then
401 | set_ini_value "$config_file" "Settings" "gtk-application-prefer-dark-theme" "0"
402 | else
403 | set_ini_value "$config_file" "Settings" "gtk-application-prefer-dark-theme" "1"
404 | fi
405 | }
406 |
407 | update_xsettingsd() {
408 | local -r theme="$1"
409 | local -r icon_theme="$2"
410 | local -r config_file="$HOME/.config/xsettingsd/xsettingsd.conf"
411 |
412 | # Create directory if it doesn't exist
413 | mkdir -p "$(dirname "$config_file")"
414 |
415 | # Check if file exists, create it with proper format if it doesn't
416 | if [[ ! -f "$config_file" ]]; then
417 | printf 'Net/ThemeName "%s"
418 | Net/IconThemeName "%s"
419 | ' "$theme" "$icon_theme" > "$config_file"
420 | else
421 | sed -i "s/Net\/ThemeName \".*\"/Net\/ThemeName \"$theme\"/; s/Net\/IconThemeName \".*\"/Net\/IconThemeName \"$icon_theme\"/" "$config_file" 2> /dev/null ||
422 | log_warn "Failed to update xsettingsd config for theme name"
423 | fi
424 | }
425 |
426 | update_gtk_settings() {
427 | local -r gtk_theme="$1"
428 | local -r variation="$2"
429 |
430 | # Set the GTK theme using gsettings
431 | gsettings set org.gnome.desktop.interface gtk-theme "$gtk_theme" 2> /dev/null || {
432 | log_warn "Failed to set GTK theme via gsettings, may not be available"
433 | }
434 |
435 | # Set color scheme based on variation
436 | if [[ "$variation" == "light" ]]; then
437 | gsettings set org.gnome.desktop.interface color-scheme "prefer-light" 2> /dev/null || {
438 | log_warn "Failed to set light color scheme via gsettings"
439 | }
440 | else
441 | gsettings set org.gnome.desktop.interface color-scheme "prefer-dark" 2> /dev/null || {
442 | log_warn "Failed to set dark color scheme via gsettings"
443 | }
444 | fi
445 |
446 | # Force reload of GTK settings for running applications
447 | if command -v dbus-send > /dev/null 2>&1; then
448 | dbus-send --session --dest=org.gtk.Settings --type=method_call \
449 | /org/gtk/Settings org.gtk.Settings.NotifyThemeChange 2> /dev/null || true
450 | fi
451 |
452 | # Reload xsettingsd if running
453 | if command -v pgrep > /dev/null 2>&1 && command -v pkill > /dev/null 2>&1; then
454 | if pgrep -x xsettingsd > /dev/null; then
455 | pkill -HUP xsettingsd
456 | fi
457 | else
458 | log_warn "pgrep/pkill not available, skipping xsettingsd reload"
459 | fi
460 | }
461 |
462 | manage_symlinks() {
463 | local -r theme="$1"
464 | local target_dir=""
465 |
466 | # Find theme directory
467 | local -ra theme_paths=(
468 | "$HOME/.themes/$theme"
469 | "$HOME/.local/share/themes/$theme"
470 | "/usr/share/themes/$theme"
471 | )
472 |
473 | for path in "${theme_paths[@]}"; do
474 | if [[ -d "$path" ]]; then
475 | target_dir="$path"
476 | break
477 | fi
478 | done
479 |
480 | [[ -n "$target_dir" ]] || {
481 | log_warn "Theme assets not found: $theme, skipping symlinks"
482 | return 1
483 | }
484 |
485 | # Create symlinks for GTK 4.0
486 | local -r gtk4_dir="$HOME/.config/gtk-4.0"
487 | mkdir -p "$gtk4_dir"
488 |
489 | declare -A links=(
490 | ["$gtk4_dir/gtk.css"]="gtk-4.0/gtk.css"
491 | ["$gtk4_dir/gtk-dark.css"]="gtk-4.0/gtk-dark.css"
492 | ["$gtk4_dir/assets"]="gtk-4.0/assets"
493 | )
494 |
495 | # Create symlinks
496 | for link in "${!links[@]}"; do
497 | local target="$target_dir/${links[$link]}"
498 | [[ -e "$target" ]] || continue
499 |
500 | mkdir -p "$(dirname "$link")"
501 | ln -sf "$target" "$link" && log_info "Created symlink: ${link##*/}"
502 | done
503 | }
504 |
505 | set_gtk_theme() {
506 | local -r gtk_theme="$1"
507 | local -r variation="${2:-dark}"
508 | local -r icon_theme="${3:-$DEFAULT_ICON_THEME}"
509 |
510 | log_info "Setting GTK theme to: $gtk_theme"
511 |
512 | # Check if theme directory exists
513 | local theme_found=0
514 | local -ra theme_paths=(
515 | "$HOME/.themes/$gtk_theme"
516 | "$HOME/.local/share/themes/$gtk_theme"
517 | "/usr/share/themes/$gtk_theme"
518 | )
519 |
520 | for path in "${theme_paths[@]}"; do
521 | if [[ -d "$path" ]]; then
522 | theme_found=1
523 | break
524 | fi
525 | done
526 |
527 | if [[ $theme_found -eq 0 ]]; then
528 | log_warn "GTK theme not found: $gtk_theme, skipping theme change"
529 | return
530 | fi
531 |
532 | # Apply theme through multiple methods to ensure coverage
533 | update_gtk_settings "$gtk_theme" "$variation"
534 | manage_gtk_config "3.0" "$gtk_theme" "$variation"
535 | manage_gtk_config "4.0" "$gtk_theme" "$variation"
536 | manage_symlinks "$gtk_theme"
537 | update_xsettingsd "$gtk_theme" "$icon_theme"
538 |
539 | log_success "GTK theme set to: $gtk_theme with comprehensive configuration"
540 | }
541 |
542 | set_icon_theme() {
543 | local -r icon_theme="$1"
544 |
545 | log_info "Setting icon theme to: $icon_theme"
546 |
547 | # Check if icon theme directory exists
548 | local theme_found=0
549 | local -ra icon_theme_paths=(
550 | "$HOME/.icons/$icon_theme"
551 | "$HOME/.local/share/icons/$icon_theme"
552 | "/usr/share/icons/$icon_theme"
553 | )
554 |
555 | for path in "${icon_theme_paths[@]}"; do
556 | if [[ -d "$path" ]]; then
557 | theme_found=1
558 | break
559 | fi
560 | done
561 |
562 | if [[ $theme_found -eq 0 ]]; then
563 | log_warn "Icon theme not found: $icon_theme, skipping icon theme change"
564 | return
565 | fi
566 |
567 | # Set the icon theme
568 | gsettings set org.gnome.desktop.interface icon-theme "$icon_theme" || {
569 | log_warn "Failed to set icon theme, gsettings may not be available"
570 | }
571 |
572 | log_success "Icon theme set to: $icon_theme"
573 | }
574 |
575 | run_wallust_theme() {
576 | local -r wallust_theme="$1"
577 | local -r wallpaper_path="$2"
578 |
579 | log_info "Running wallust with theme: $wallust_theme for wallpaper: $wallpaper_path"
580 |
581 | if [[ "$wallust_theme" == "random" ]]; then
582 | # Just run wallust on the wallpaper without a specific theme if unknown
583 | log_info "Running wallust in auto mode for: $wallpaper_path"
584 | if ! wallust run "$wallpaper_path" --dynamic-threshold 2> /dev/null; then
585 | log_warn "Wallust theme generation failed, continuing..."
586 | else
587 | log_success "Wallust theme generation completed"
588 | fi
589 | else
590 | # Try to apply the specific theme if available
591 | log_info "Applying specific wallust theme: $wallust_theme"
592 | if ! wallust theme "$wallust_theme" 2> /dev/null; then
593 | log_warn "Specific wallust theme failed, falling back to auto-generation for: $wallpaper_path"
594 | # Fallback to running wallust on the wallpaper directly
595 | if ! wallust run "$wallpaper_path" --dynamic-threshold 2> /dev/null; then
596 | log_warn "Wallust generation failed completely, continuing..."
597 | else
598 | log_success "Wallust fallback theme generation completed"
599 | fi
600 | else
601 | log_success "Wallust specific theme applied: $wallust_theme"
602 | fi
603 | fi
604 | }
605 |
606 | update_niri_config() {
607 | local -r niri_config_file="$HOME/.config/niri/config.kdl"
608 | local -r wallust_colors_file="$HOME/.cache/wallust/colors.json"
609 |
610 | if [[ ! -f "$wallust_colors_file" ]]; then
611 | log_warn "Wallust color cache not found, skipping niri config update"
612 | return
613 | fi
614 |
615 | local background_color
616 | background_color=$(jq -r '.special.background' "$wallust_colors_file")
617 |
618 | if [[ -z "$background_color" ]]; then
619 | log_warn "Could not extract background color from wallust cache"
620 | return
621 | fi
622 |
623 | log_info "Updating niri config with background color: $background_color"
624 |
625 | # Only change active-color within the focus-ring block
626 | sed -i "/focus-ring {/,/}/ s/active-color \".*\"/active-color \"$background_color\"/" "$niri_config_file"
627 | # Only change color within the insert-hint block
628 | sed -i "/insert-hint {/,/}/ s/color \".*\"/color \"$background_color\"/" "$niri_config_file"
629 |
630 | log_success "Niri config updated successfully"
631 | }
632 |
633 | update_vscode_theme() {
634 | local -r vscode_settings_file="$HOME/.config/Code/User/settings.json"
635 | local theme
636 |
637 | if [[ ! -f "$vscode_settings_file" ]]; then
638 | log_warn "VSCode settings file not found, skipping theme update"
639 | return
640 | fi
641 |
642 | if [[ "$WALLPAPER_VARIATION" == "light" ]]; then
643 | theme="Wallust-Bordered-Light"
644 | else
645 | theme="Wallust-Bordered-Dark"
646 | fi
647 |
648 | log_info "Updating VSCode theme to: $theme"
649 |
650 | if ! sed -i "s/\"workbench.colorTheme\": \".*\"/\"workbench.colorTheme\": \"$theme\"/" "$vscode_settings_file"; then
651 | log_error "Failed to update VSCode theme with sed"
652 | return 1
653 | fi
654 |
655 | log_success "VSCode theme updated successfully"
656 | }
657 |
658 | main() {
659 | log_info "Starting dynamic theme synchronization"
660 |
661 | # Validate dependencies
662 | validate_dependencies "awww" "wallust" "jq" "sed" "grep" "head" "tr"
663 |
664 | # Detect theme from current wallpaper
665 | detect_theme_from_wallpaper
666 |
667 | local detected_theme="${DETECTED_THEME:-}"
668 | local wallpaper_path="${WALLPAPER_PATH:-}"
669 | local wallpaper_variation="${WALLPAPER_VARIATION:-}"
670 |
671 | if [[ -z "$detected_theme" ]]; then
672 | die "Unable to determine theme from current wallpaper"
673 | fi
674 |
675 | if [[ -z "$wallpaper_path" || ! -f "$wallpaper_path" ]]; then
676 | die "Wallpaper file does not exist: ${wallpaper_path:-unknown}"
677 | fi
678 |
679 | if [[ -z "$wallpaper_variation" ]]; then
680 | die "Unable to determine wallpaper variation"
681 | fi
682 |
683 | log_info "Detected theme: $detected_theme, variation: $wallpaper_variation"
684 |
685 | # Check if theme/variation changed
686 | local theme_changed=0
687 | if check_theme_changed "$detected_theme" "$wallpaper_variation"; then
688 | theme_changed=1
689 | fi
690 |
691 | # Map to appropriate themes using both theme and variation
692 | local gtk_theme
693 | gtk_theme=$(map_to_gtk_theme "$detected_theme" "$wallpaper_variation")
694 |
695 | local icon_theme
696 | icon_theme=$(map_to_icon_theme "$detected_theme" "$wallpaper_variation")
697 |
698 | local wallust_theme
699 | wallust_theme=$(map_to_wallust_theme "$detected_theme" "$wallpaper_variation")
700 |
701 | # Only apply themes if theme/variation changed
702 | if [[ $theme_changed -eq 1 ]]; then
703 | set_gtk_theme "$gtk_theme" "$wallpaper_variation" "$icon_theme"
704 | set_icon_theme "$icon_theme"
705 | run_wallust_theme "$wallust_theme" "$wallpaper_path"
706 | update_niri_config
707 | update_vscode_theme
708 |
709 | if command -v vicinae > /dev/null 2>&1; then
710 | vicinae theme set wallust || log_warn "Failed to set vicinae theme"
711 | else
712 | log_warn "vicinae not found, skipping vicinae theme update"
713 | fi
714 |
715 | if command -v makoctl > /dev/null 2>&1; then
716 | makoctl reload 2> /dev/null || log_warn "Failed to reload mako"
717 | else
718 | log_warn "makoctl not available, skipping notification daemon reload"
719 | fi
720 |
721 | save_theme_state "$detected_theme" "$wallpaper_variation"
722 |
723 | log_success "Dynamic theme synchronization completed successfully"
724 | send_notification "Theme Manager" "Theme Synchronization Complete" "" "normal" "preferences-desktop-theme"
725 | else
726 | log_info "Theme unchanged, skipping all theming operations"
727 | log_success "Wallpaper applied, no theme changes needed"
728 | fi
729 | }
730 |
731 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
732 | main "$@"
733 | fi
734 |
735 |
--------------------------------------------------------------------------------