diff --git a/.bashrc b/.bashrc
index 9d4649d..2065bb1 100755
--- a/.bashrc
+++ b/.bashrc
@@ -158,3 +158,13 @@ alias bat=batcat
[ -f "$HOME/.local/bin/env" ] && . "$HOME/.local/bin/env"
. "$HOME/.cargo/env"
+
+# Sway / Wayland environment
+export XDG_CURRENT_DESKTOP=sway
+export XDG_SESSION_TYPE=wayland
+export QT_QPA_PLATFORM=wayland
+export QT_WAYLAND_DISABLE_WINDOWDECORATION=1
+export GDK_BACKEND=wayland
+export MOZ_ENABLE_WAYLAND=1
+export SDL_VIDEODRIVER=wayland
+export _JAVA_AWT_WM_NONREPARENTING=1
diff --git a/.config/nvim/lua/config/terminal.lua b/.config/nvim/lua/config/terminal.lua
new file mode 100644
index 0000000..e536b2c
--- /dev/null
+++ b/.config/nvim/lua/config/terminal.lua
@@ -0,0 +1,118 @@
+-- Terminal helpers layered on top of the native LazyVim/Snacks terminal.
+--
+-- Native behaviour is kept intact and untouched:
+-- / toggle a bottom terminal at the project root (supports a
+-- count, e.g. 2 opens a *separate* terminal #2)
+-- fT terminal in the current working directory
+--
+-- This module adds:
+-- a *dedicated* floating terminal (stable toggle, one-key close)
+-- ft a picker that lists every live terminal so any contextual
+-- terminal can be focused or restored even after its window was
+-- closed/hidden.
+local M = {}
+
+-- The floating terminal. Tracked as an upvalue so is a real
+-- open/close toggle regardless of which directory the editor is in.
+---@type snacks.win?
+local float
+
+--- Open or focus the floating terminal, or hide it if already focused.
+function M.toggle_float()
+ -- Reuse the existing instance: hide if visible, re-show if hidden.
+ if float and float:buf_valid() then
+ if float:win_valid() then
+ float:hide()
+ else
+ float:show()
+ vim.cmd("startinsert")
+ end
+ return
+ end
+
+ -- First open: a high fixed count guarantees this terminal never collides with
+ -- the count-keyed bottom terminals (1, 2, 3 ...), so it is always the *same*
+ -- float for no matter the context.
+ float = Snacks.terminal(nil, {
+ count = 99,
+ win = { position = "float", border = "rounded" },
+ })
+
+ -- Close it with a single keystroke from inside the shell: hides the
+ -- window (buffer-local, so it only affects this floating terminal and never
+ -- the bottom split, where shell tab-completion must keep working).
+ if float and float.buf then
+ vim.keymap.set("t", "", function()
+ if float and float:win_valid() then
+ float:hide()
+ end
+ end, { buffer = float.buf, silent = true, desc = "Hide floating terminal" })
+ end
+end
+
+--- List every live Snacks terminal in a picker. Restore/focus one with ,
+--- kill one with .
+function M.picker()
+ local terms = Snacks.terminal.list()
+ if #terms == 0 then
+ Snacks.notify.info("No open terminals")
+ return
+ end
+
+ local items = {} ---@type snacks.picker.finder.Item[]
+ for _, term in ipairs(terms) do
+ local meta = vim.b[term.buf] and vim.b[term.buf].snacks_terminal or {}
+ items[#items + 1] = {
+ term = term,
+ buf = term.buf,
+ id = meta.id,
+ cwd = meta.cwd or "?",
+ cmd = meta.cmd or vim.o.shell,
+ visible = term:win_valid(),
+ text = ("#%s %s %s"):format(meta.id or "?", meta.cwd or "?", meta.cmd or vim.o.shell),
+ }
+ end
+
+ Snacks.picker({
+ title = "Terminals",
+ items = items,
+ format = function(item)
+ local ret = {} ---@type snacks.picker.Highlight[]
+ ret[#ret + 1] = {
+ item.visible and "● " or "○ ",
+ item.visible and "SnacksPickerSpecial" or "SnacksPickerComment",
+ }
+ ret[#ret + 1] = { ("#%-2s"):format(item.id or "?"), "Number" }
+ ret[#ret + 1] = { " " .. vim.fn.fnamemodify(item.cwd, ":~"), "SnacksPickerPath" }
+ ret[#ret + 1] = { " " .. vim.fn.fnamemodify(item.cmd, ":t"), "SnacksPickerComment" }
+ return ret
+ end,
+ confirm = function(picker, item)
+ picker:close()
+ local term = item.term
+ if not term:buf_valid() then
+ Snacks.notify.warn("Terminal no longer exists")
+ return
+ end
+ if term:win_valid() then
+ vim.api.nvim_set_current_win(term.win)
+ else
+ term:show()
+ vim.cmd("startinsert")
+ end
+ end,
+ win = { input = { keys = { ["d"] = "term_delete" } } },
+ actions = {
+ term_delete = function(picker, item)
+ local term = item.term
+ picker:close()
+ if term and term:buf_valid() then
+ term:close()
+ pcall(vim.api.nvim_buf_delete, term.buf, { force = true })
+ end
+ end,
+ },
+ })
+end
+
+return M
diff --git a/.config/nvim/lua/plugins/conform.lua b/.config/nvim/lua/plugins/conform.lua
new file mode 100644
index 0000000..0dc9d18
--- /dev/null
+++ b/.config/nvim/lua/plugins/conform.lua
@@ -0,0 +1,10 @@
+return {
+ "stevearc/conform.nvim",
+ opts = {
+ formatters = {
+ shfmt = {
+ args = { "-i", "2", "-ci", "-bn", "-sr" },
+ },
+ },
+ },
+}
diff --git a/.config/nvim/lua/plugins/markdown.lua b/.config/nvim/lua/plugins/markdown.lua
new file mode 100644
index 0000000..6f22818
--- /dev/null
+++ b/.config/nvim/lua/plugins/markdown.lua
@@ -0,0 +1,8 @@
+return {
+ {
+ "MeanderingProgrammer/render-markdown.nvim",
+ opts = {
+ enabled = false,
+ },
+ },
+}
diff --git a/.config/nvim/lua/plugins/snacks.lua b/.config/nvim/lua/plugins/snacks.lua
index 9e894d0..f7ef138 100644
--- a/.config/nvim/lua/plugins/snacks.lua
+++ b/.config/nvim/lua/plugins/snacks.lua
@@ -24,6 +24,8 @@ return {
keys = {
["Y"] = "copy_relative",
["gy"] = "copy_absolute",
+ -- pick a window
+ ["L"] = { { "pick_win", "jump" }, mode = { "n", "i" } },
},
},
},
diff --git a/.config/nvim/lua/plugins/terminal.lua b/.config/nvim/lua/plugins/terminal.lua
new file mode 100644
index 0000000..fdc2f4c
--- /dev/null
+++ b/.config/nvim/lua/plugins/terminal.lua
@@ -0,0 +1,23 @@
+-- Terminal UX layered on Snacks (replaces toggleterm).
+-- Helpers live in lua/config/terminal.lua; keys are declared here so lazy.nvim
+-- registers the which-key labels with the rest of the snacks spec.
+return {
+ "folke/snacks.nvim",
+ keys = {
+ {
+ "",
+ function()
+ require("config.terminal").toggle_float()
+ end,
+ desc = "Terminal (float)",
+ mode = "n",
+ },
+ {
+ "ft",
+ function()
+ require("config.terminal").picker()
+ end,
+ desc = "Terminals (list/restore)",
+ },
+ },
+}
diff --git a/.config/sway/config b/.config/sway/config
new file mode 100644
index 0000000..eb742d6
--- /dev/null
+++ b/.config/sway/config
@@ -0,0 +1,606 @@
+#----------------------------------------------------------------
+# Sway Config — Arch + Wayland
+#
+# Migrated from i3/X11 config. All X11-specific tools have been
+# replaced with Wayland-native equivalents (see migration notes
+# throughout this file).
+#
+# Install script: arch-init.sh
+#----------------------------------------------------------------
+
+
+#################################################################
+# Variables
+#################################################################
+
+# Mod key: Mod4 = Super/Windows key, Mod1 = Alt
+set $mod Mod4
+
+# Helper scripts directory (Wayland replacements for ratflow scripts)
+set $scriptsDir ~/.config/sway/scripts
+
+# Config directory
+set $configDir ~/.config/sway
+
+
+#################################################################
+# Workspaces
+#################################################################
+
+set $workspace1 1: >_
+set $workspace2 2: dev
+set $workspace3 3: www
+set $workspace4 4: files
+set $workspace5 5: e-mail
+set $workspace6 6: IM
+set $workspace7 7: video
+set $workspace8 8: workspace
+set $workspace9 9: workspace
+set $workspace10 10: Music
+set $workspace11 V: VM
+
+# Switch to workspace
+bindsym $mod+1 workspace $workspace1
+bindsym $mod+2 workspace $workspace2
+bindsym $mod+3 workspace $workspace3
+bindsym $mod+4 workspace $workspace4
+bindsym $mod+5 workspace $workspace5
+bindsym $mod+6 workspace $workspace6
+bindsym $mod+7 workspace $workspace7
+bindsym $mod+8 workspace $workspace8
+bindsym $mod+9 workspace $workspace9
+bindsym $mod+0 workspace $workspace10
+
+# Move focused container to workspace
+bindsym $mod+Shift+1 move container to workspace $workspace1
+bindsym $mod+Shift+2 move container to workspace $workspace2
+bindsym $mod+Shift+3 move container to workspace $workspace3
+bindsym $mod+Shift+4 move container to workspace $workspace4
+bindsym $mod+Shift+5 move container to workspace $workspace5
+bindsym $mod+Shift+6 move container to workspace $workspace6
+bindsym $mod+Shift+7 move container to workspace $workspace7
+bindsym $mod+Shift+8 move container to workspace $workspace8
+bindsym $mod+Shift+9 move container to workspace $workspace9
+bindsym $mod+Shift+0 move container to workspace $workspace10
+
+# Next/prev workspace
+bindsym $mod+x workspace next
+bindsym $mod+Shift+x move container to workspace next
+bindsym $mod+z workspace prev
+bindsym $mod+Shift+z move container to workspace prev
+
+# Moving workspace across monitors
+# MIGRATION: xrandr replaced by swaymsg output commands
+bindsym $mod+Control+Left move workspace to output left
+bindsym $mod+Control+Right move workspace to output right
+
+
+#################################################################
+# Output / Monitor Management
+# MIGRATION: xrandr → swaymsg output / kanshi for auto profiles
+#################################################################
+
+# Define your outputs — run `swaymsg -t get_outputs` to get names
+set $leftOutput eDP-1
+set $rightOutput HDMI-A-1
+
+# Default monitor setup
+# sway handles output layout natively, no xrandr needed
+output $rightOutput pos 1920 0
+output $leftOutput pos 0 0
+
+# Mode for toggling outputs (replaces i3 "outputs" mode)
+mode "outputs" {
+ # $mod+Shift+1: turn off left monitor; $mod+1: turn it on
+ bindsym $mod+Shift+1 output $leftOutput disable; mode "default"
+ bindsym $mod+1 output $leftOutput enable; mode "default"
+
+ # $mod+Shift+2: turn off right monitor; $mod+2: turn it on
+ bindsym $mod+Shift+2 output $rightOutput disable; mode "default"
+ bindsym $mod+2 output $rightOutput enable; mode "default"
+
+ bindsym Return mode "default"
+ bindsym Escape mode "default"
+}
+
+bindsym $mod+m mode "outputs"
+
+
+#################################################################
+# Wallpaper
+# MIGRATION: feh → swaybg (Wayland-native wallpaper setter)
+# Omarchy also uses swaybg for this purpose.
+#################################################################
+
+set $wallpaper1 /usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png
+set $wallpaper2 /usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png
+
+# swaybg is a native Wayland wallpaper setter
+output $leftOutput bg $wallpaper1 fill
+output $rightOutput bg $wallpaper2 fill
+
+
+#################################################################
+# Input Device Configuration
+# MIGRATION: synclient → sway input {} blocks
+# X11 touchpad/synaptics config replaced by libinput via sway.
+#################################################################
+
+input "type:touchpad" {
+ tap enabled
+ click_method button_areas
+ scroll_method two_finger
+ dwt enabled
+ # natural_scroll enabled # uncomment if you prefer macOS-style scrolling
+}
+
+input "type:keyboard" {
+ xkb_layout pl
+ # xkb_variant ...
+ # xkb_options caps:escape # uncomment to remap Caps Lock to Escape
+}
+
+
+#################################################################
+# Application Launchers
+# MIGRATION: ulauncher works on Wayland but is GTK-based.
+# Alternatives: wofi (native), rofi-wayland, tofi.
+# dmenu_run replaced by wofi (or keep as dmenu_path | wofi -d).
+# Omarchy uses Walker — a modern Wayland-native launcher.
+#################################################################
+
+# Terminal
+bindsym $mod+Return exec kitty
+
+# App launcher: wofi (lightweight, Wayland-native)
+# Alternatives: rofi -show drun (rofi-wayland package), tofi
+bindsym $mod+d exec fuzzel
+bindsym Mod1+F2 exec wofi --show run
+
+# Clipboard manager — show history (Ctrl+Alt+H)
+# bindsym Control+Mod1+h exec copyq toggle
+bindsym Control+Mod1+h exec cliphist list | fuzzel --width 80 --dmenu | cliphist decode | wl-copy
+
+# Run app assigned to current workspace
+bindsym $mod+Shift+a exec $scriptsDir/autoapp
+
+
+#################################################################
+# Window Navigation
+# MIGRATION: i3-msg workspace back_and_forth → same in sway
+#################################################################
+
+bindsym $mod+Tab workspace back_and_forth
+
+
+#################################################################
+# Window Management
+# MIGRATION: nearly identical to i3 — sway is i3-compatible
+#################################################################
+
+# Use Mouse+$mod to drag floating windows
+floating_modifier $mod
+
+# Kill focused window
+bindsym $mod+Shift+q kill
+
+# Change focus (vim keys)
+bindsym $mod+j focus left
+bindsym $mod+k focus down
+bindsym $mod+l focus up
+bindsym $mod+semicolon focus right
+
+# Change focus (arrow keys)
+bindsym $mod+Left focus left
+bindsym $mod+Down focus down
+bindsym $mod+Up focus up
+bindsym $mod+Right focus right
+
+# Move focused window (vim keys)
+bindsym $mod+Shift+j move left
+bindsym $mod+Shift+k move down
+bindsym $mod+Shift+l move up
+bindsym $mod+Shift+semicolon move right
+
+# Move focused window (arrow keys)
+bindsym $mod+Shift+Left move left
+bindsym $mod+Shift+Down move down
+bindsym $mod+Shift+Up move up
+bindsym $mod+Shift+Right move right
+
+# Split orientation
+bindsym $mod+h split h
+bindsym $mod+v split v
+
+# Fullscreen
+bindsym $mod+f fullscreen
+
+# Layout modes
+bindsym $mod+s layout stacking
+bindsym $mod+w layout tabbed
+bindsym $mod+e layout toggle split
+
+# Toggle tiling / floating
+bindsym $mod+Shift+space floating toggle
+bindsym $mod+button2 floating toggle
+
+# Focus tiling/floating toggle
+bindsym $mod+space focus mode_toggle
+
+# Focus parent container
+bindsym $mod+a focus parent
+
+# Always-float toggle (custom script)
+bindsym $mod+Shift+f exec $scriptsDir/always-float
+
+
+#################################################################
+# Resize Mode
+#################################################################
+
+mode "resize" {
+ bindsym j resize shrink width 10 px or 10 ppt
+ bindsym k resize grow height 10 px or 10 ppt
+ bindsym l resize shrink height 10 px or 10 ppt
+ bindsym semicolon resize grow width 10 px or 10 ppt
+
+ bindsym Left resize shrink width 10 px or 10 ppt
+ bindsym Down resize grow height 10 px or 10 ppt
+ bindsym Up resize shrink height 10 px or 10 ppt
+ bindsym Right resize grow width 10 px or 10 ppt
+
+ bindsym Return mode "default"
+ bindsym Escape mode "default"
+}
+
+bindsym $mod+r mode "resize"
+
+
+#################################################################
+# Screenshots
+# MIGRATION: screengrab (X11) → grim + slurp (Wayland-native)
+# grim = screen capture (replaces scrot/import/screengrab)
+# slurp = region selector (Wayland-native)
+# wl-copy copies to Wayland clipboard
+# Omarchy also uses grim + slurp (+ satty for annotation).
+#################################################################
+
+# Screenshots — capture once, save to file + copy to clipboard
+bindsym Print exec $scriptsDir/screenshot full
+bindsym Shift+Print exec $scriptsDir/screenshot area
+bindsym Control+Print exec $scriptsDir/screenshot window
+
+
+#################################################################
+# Calendar
+# MIGRATION: osmo/gsimplecal (X11) → keep osmo if desired,
+# or use waybar clock module + rofi/wofi calendar script.
+# Omarchy uses waybar clock module only (no standalone calendar app).
+#################################################################
+
+bindsym $mod+c exec osmo -c
+# Alternative: wofi-based calendar popup
+# bindsym $mod+c exec $scriptsDir/calendar-popup
+
+
+#################################################################
+# Screen Color Temperature
+# MIGRATION: redshift (X11) → gammastep (Wayland fork of redshift)
+# or wlsunset (lighter alternative).
+# Omarchy uses hyprsunset (Hyprland-specific), but for sway
+# gammastep is the standard choice.
+# $scriptsDir/gammastep-adjust replaces redshift-adjust.
+#################################################################
+
+bindsym $mod+Control+KP_Next exec $scriptsDir/gammastep-adjust "-500"
+bindsym $mod+Control+KP_Prior exec $scriptsDir/gammastep-adjust "+500"
+bindsym $mod+Control+l exec gammastep -x
+
+
+#################################################################
+# Translation of Selected Text
+# MIGRATION: trans-xsel (X11 xsel) → translate-shell + wl-clipboard
+# Uses wl-paste / wl-copy instead of xsel/xclip.
+#################################################################
+
+bindsym $mod+t exec bash -c "$scriptsDir/translate-wl en:pl &"
+bindsym $mod+Shift+t exec bash -c "$scriptsDir/translate-wl pl:en &"
+
+
+#################################################################
+# Multimedia Keys & Audio
+# MIGRATION: PulseAudio volctl → PipeWire + wpctl (or pamixer)
+# Arch + Wayland standard is PipeWire with WirePlumber.
+# Volume OSD: SwayOSD (Omarchy approach) or swayosd-server.
+# The $scriptsDir/volctl wrapper uses wpctl/pamixer underneath.
+#################################################################
+
+# Volume controls (primary sink)
+bindsym XF86AudioRaiseVolume exec --no-startup-id $scriptsDir/volctl up
+bindsym XF86AudioLowerVolume exec --no-startup-id $scriptsDir/volctl down
+bindsym XF86AudioMute exec --no-startup-id $scriptsDir/volctl toggle
+
+# Volume controls (secondary sink, e.g. HDMI)
+bindsym Shift+XF86AudioRaiseVolume exec --no-startup-id SINK_NUM=1 $scriptsDir/volctl up
+bindsym Shift+XF86AudioLowerVolume exec --no-startup-id SINK_NUM=1 $scriptsDir/volctl down
+bindsym Shift+XF86AudioMute exec --no-startup-id SINK_NUM=1 $scriptsDir/volctl toggle
+
+# Screen brightness
+# MIGRATION: xbacklight / ratflow-backlight → brightnessctl
+bindsym XF86MonBrightnessUp exec --no-startup-id $scriptsDir/backlight up
+bindsym XF86MonBrightnessDown exec --no-startup-id $scriptsDir/backlight down
+
+# Media player controls
+# MIGRATION: clementine-specific → playerctl (agnostic MPRIS controller)
+# playerctl works with any MPRIS2-compatible player (Spotify, mpv, etc.)
+bindsym XF86AudioPlay exec playerctl play-pause
+bindsym XF86AudioPause exec playerctl pause
+bindsym XF86AudioNext exec playerctl next
+bindsym XF86AudioPrev exec playerctl previous
+
+
+#################################################################
+# Projector / Screen Layout
+# MIGRATION: xrandr shell scripts → swaymsg output commands
+# kanshi can auto-switch profiles on hotplug.
+#################################################################
+
+bindsym $mod+p exec $scriptsDir/output-profile projector
+bindsym $mod+Shift+p exec $scriptsDir/output-profile edp-only
+
+
+#################################################################
+# Session / Power Management
+# MIGRATION: i3-nagbar → swaynag, i3exit → loginctl-based script
+# swayidle handles DPMS/idle (replaces xset dpms).
+# swaylock replaces i3lock.
+# Omarchy uses hyprlock+hypridle, sway equivalents are swaylock+swayidle.
+#################################################################
+
+# Reload sway config
+bindsym $mod+Shift+c exec swaymsg reload
+
+# Suspend
+bindsym $mod+Escape exec systemctl suspend
+
+# Restart sway
+bindsym $mod+Shift+r exec swaymsg reload
+
+# Exit sway
+bindsym $mod+Shift+e exec swaynag -t warning -m 'Exit sway? This will end your Wayland session.' -b 'Yes, exit sway' 'swaymsg exit'
+
+# Session management mode (replaces i3exit)
+set $exitScript $scriptsDir/swayexit
+
+mode "session_management" {
+ bindsym l exec --no-startup-id $exitScript lock, mode "default"
+ bindsym e exec --no-startup-id $exitScript logout, mode "default"
+ bindsym s exec --no-startup-id $exitScript suspend, mode "default"
+ bindsym h exec --no-startup-id $exitScript hibernate, mode "default"
+ bindsym r exec --no-startup-id $exitScript reboot, mode "default"
+ bindsym Shift+s exec --no-startup-id $exitScript shutdown, mode "default"
+
+ bindsym Return mode "default"
+ bindsym Escape mode "default"
+}
+
+bindsym $mod+Pause mode "session_management"
+
+
+#################################################################
+# New Workspace
+# MIGRATION: yad → zenity (or rofi/wofi)
+# yad has some Wayland issues; zenity is GTK and works.
+# Alternatively, use wofi for a pure Wayland input dialog.
+#################################################################
+
+bindsym $mod+n exec name=$(wofi --dmenu --prompt "Workspace name") && swaymsg workspace "$name"
+
+
+#################################################################
+# Theme / Appearance
+# MIGRATION: No i3 client colors — sway has the same syntax.
+# compton → not needed (sway has built-in compositor with
+# basic effects). For blur/shadows, use swayfx (fork of sway).
+# Omarchy uses Hyprland which has built-in blur/animation;
+# for sway, swayfx adds these features.
+#################################################################
+
+# Font
+set $fontName "Source Sans 3 Semi-Bold"
+set $fontSize 10
+set $barFontSize 9
+
+font pango: $fontName $fontSize
+
+# Window colors
+# border background text indicator
+client.focused #252525 #252525 #D9D9D9 #101010
+client.focused_inactive #101010 #101010 #606060 #101010
+client.unfocused #101010 #000000 #606060 #101010
+client.urgent #F05000 #F05000 #F0F0F0 #101010
+
+# Default border style
+default_border pixel 2
+default_floating_border normal 2
+
+# Gaps (optional — sway supports gaps natively, unlike stock i3)
+# gaps inner 5
+# gaps outer 2
+
+# Window opacity (requires swayfx for per-window opacity, or use
+# sway's built-in opacity for all windows)
+# for_window [app_id="kitty"] opacity 0.95
+
+
+#################################################################
+# Bar — Waybar
+# MIGRATION: i3bar + py3status → waybar
+# waybar is the standard bar for wlroots-based compositors.
+# Omarchy uses waybar with extensive modules.
+# Config files: ~/.config/waybar/config.jsonc + style.css
+#################################################################
+
+bar {
+ swaybar_command waybar
+}
+
+
+#################################################################
+# Window Assignments
+# MIGRATION: class= → app_id= for native Wayland apps.
+# XWayland apps still use class=. Use `swaymsg -t get_tree`
+# to find the correct app_id for native Wayland apps.
+# Note: some apps may need both rules.
+#################################################################
+
+# Development
+assign [app_id="qtcreator"] $workspace2
+assign [class="Qtcreator"] $workspace2
+assign [app_id="eclipse"] $workspace2
+assign [class="Eclipse"] $workspace2
+
+# Browser
+assign [app_id="firefox"] $workspace3
+assign [class="Firefox"] $workspace3
+assign [app_id="chromium"] $workspace3
+assign [class="Chromium-browser"] $workspace3
+assign [class="Chrome"] $workspace3
+
+# File managers
+assign [app_id="pcmanfm-qt"] $workspace4
+assign [class="Pcmanfm"] $workspace4
+assign [app_id="dolphin"] $workspace4
+assign [class="Dolphin"] $workspace4
+assign [app_id="nautilus"] $workspace4
+assign [class="Nautilus"] $workspace4
+assign [app_id="thunar"] $workspace4
+assign [class="Thunar"] $workspace4
+
+# E-mail
+assign [app_id="thunderbird"] $workspace5
+assign [class="Thunderbird"] $workspace5
+
+# IM
+assign [app_id="telegram-desktop"] $workspace6
+assign [class="Telegram"] $workspace6
+assign [class="Skype"] $workspace6
+
+# Video
+assign [app_id="kodi"] $workspace7
+assign [class="Kodi"] $workspace7
+
+# Music
+assign [app_id="spotify"] $workspace10
+assign [class="Spotify"] $workspace10
+assign [class="Clementine"] $workspace10
+
+# VM
+assign [class="VirtualBox"] $workspace11
+assign [app_id="virt-manager"] $workspace11
+
+
+#################################################################
+# Floating Windows
+#################################################################
+
+for_window [app_id="screengrab"] floating enable
+for_window [app_id="osmo"] floating enable
+for_window [app_id="gsimplecal"] floating enable
+for_window [class="Osmo"] floating enable
+for_window [class="Skype"] floating enable
+for_window [app_id="yad"] floating enable
+for_window [class="Yad"] floating enable
+for_window [app_id="wofi"] floating enable
+for_window [app_id="swaync"] floating enable
+
+
+#################################################################
+# Idle Management
+# MIGRATION: xset dpms → swayidle
+# Locks screen after timeout, turns off display.
+# Omarchy uses hypridle; sway equivalent is swayidle.
+#################################################################
+
+set $lockCommand swaylock -f -c 000000
+
+exec swayidle -w \
+ timeout 600 '$lockCommand' \
+ timeout 900 'swaymsg "output * dpms off"' \
+ resume 'swaymsg "output * dpms on"' \
+ before-sleep '$lockCommand'
+
+
+#################################################################
+# Autostart
+# MIGRATION NOTES:
+# dunst → mako (Wayland-native notifier, used by Omarchy)
+# or swaync (Sway Notification Center, more feature-rich)
+# compton → NOT NEEDED (sway has built-in compositor)
+# For blur/rounded corners, use swayfx
+# synclient → sway input {} block (see above)
+# redshift → gammastep or wlsunset
+# nm-applet → nm-applet still works on Wayland, or use
+# waybar network module + impala TUI (Omarchy approach)
+# parcellite → cliphist + wl-clipboard (Wayland clipboard manager)
+# cliphist stores history, wl-copy/wl-paste for access
+# xset dpms → swayidle (see above)
+#################################################################
+
+# Notification daemon — mako (Omarchy uses mako)
+exec --no-startup-id mako
+
+# Alternative: swaync (Sway Notification Center) — more feature-rich
+# exec --no-startup-id swaync
+
+# Screen color temperature
+exec --no-startup-id gammastep -l 52.43:15.15 -m wayland
+
+# Network manager applet (still works on Wayland)
+exec --no-startup-id nm-applet --indicator
+
+# Clipboard manager
+# Use Ctrl+Alt+H to toggle the clipboard history window
+# exec --no-startup-id wl-paste --watch --type image cliphist store
+exec --no-startup-id wl-paste --type text --watch cliphist store
+exec --no-startup-id wl-paste --type image --watch cliphist store
+
+# Bluetooth manager (waybar module can handle this, or use blueman-applet)
+# exec --no-startup-id blueman-applet
+
+# Polkit authentication agent (needed for GUI apps that need root)
+exec --no-startup-id /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1
+
+# Background services for XDG desktop portals (screen sharing, file picker)
+exec --no-startup-id dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway
+
+
+#################################################################
+# Key repeat rate
+#################################################################
+
+# input "type:keyboard" {
+# xkb_layout pl
+# # xkb_variant "programmer"
+# xkb_options "grp:alt_shift_toggle, compose:ralt"
+# repeat_delay 300
+# repeat_rate 40
+# }
+# input type:keyboard { xkb_layout pl xkb_options grp:win_space_toggle }
+
+# input type:keyboard {
+# xkb_layout pl
+# xkb_options level3:ralt_switch
+# }
+
+# input type:keyboard {
+# xkb_layout pl
+# xkb_options grp:alt_shift_toggle
+# }
+
+input type:keyboard {
+ xkb_layout pl,us
+ # lv3:ralt_switch keeps Right Alt working for Polish chars
+ # grp:alt_shift_toggle allows Alt+Shift to switch layouts
+ xkb_options lv3:ralt_switch,grp:alt_shift_toggle
+}
diff --git a/.config/sway/scripts/always-float b/.config/sway/scripts/always-float
new file mode 100755
index 0000000..5a01c8a
--- /dev/null
+++ b/.config/sway/scripts/always-float
@@ -0,0 +1,11 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# always-float — Toggle floating on the currently focused window
+#
+# Adapted from the ratflow always-float script.
+# Uses swaymsg instead of i3-msg.
+#
+# Dependencies: sway
+#----------------------------------------------------------------
+
+swaymsg floating toggle
diff --git a/.config/sway/scripts/autoapp b/.config/sway/scripts/autoapp
new file mode 100755
index 0000000..f760fe4
--- /dev/null
+++ b/.config/sway/scripts/autoapp
@@ -0,0 +1,28 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# autoapp — Launch the application assigned to current workspace
+#
+# Adapted from the ratflow autoapp script.
+# Reads workspace assignments and launches the matching app.
+# Uses swaymsg instead of i3-msg.
+#
+# Dependencies: sway, jq
+#----------------------------------------------------------------
+
+# Get current workspace name
+WORKSPACE=$(swaymsg -t get_workspaces | jq -r '.[] | select(.focused) | .name')
+
+# Map workspace names to applications
+# Customize these to match your workflow
+case "$WORKSPACE" in
+ *">_"*) kitty ;;
+ *"dev"*) kitty ;;
+ *"www"*) firefox ;;
+ *"files"*) thunar ;;
+ *"e-mail"*) thunderbird ;;
+ *"IM"*) telegram-desktop ;;
+ *"video"*) mpv ;;
+ *"Music"*) spotify ;;
+ *"VM"*) virt-manager ;;
+ *) echo "No app assigned to workspace: $WORKSPACE" ;;
+esac
diff --git a/.config/sway/scripts/backlight b/.config/sway/scripts/backlight
new file mode 100755
index 0000000..5c9279d
--- /dev/null
+++ b/.config/sway/scripts/backlight
@@ -0,0 +1,36 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# backlight — Screen brightness control using brightnessctl
+#
+# Replaces the old ratflow backlight script (xbacklight-based).
+# brightnessctl works on Wayland without X11 dependencies.
+#
+# Usage:
+# backlight up — increase brightness by 5%
+# backlight down — decrease brightness by 5%
+# backlight set N — set brightness to N percent
+# backlight get — print current brightness percentage
+#
+# Dependencies: brightnessctl
+#----------------------------------------------------------------
+
+STEP=5
+
+case "$1" in
+ up)
+ brightnessctl set "${STEP}%+" -q
+ ;;
+ down)
+ brightnessctl set "${STEP}%-" -q
+ ;;
+ set)
+ brightnessctl set "${2}%" -q
+ ;;
+ get)
+ brightnessctl info | grep -oP '\d+%' | head -1 | tr -d '%'
+ ;;
+ *)
+ echo "Usage: backlight {up|down|set N|get}"
+ exit 1
+ ;;
+esac
diff --git a/.config/sway/scripts/gammastep-adjust b/.config/sway/scripts/gammastep-adjust
new file mode 100755
index 0000000..eddd412
--- /dev/null
+++ b/.config/sway/scripts/gammastep-adjust
@@ -0,0 +1,101 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# gammastep-adjust — Adjust gammastep color temperature
+#
+# Replaces the old ratflow redshift-adjust script.
+# gammastep is the Wayland fork of redshift; it uses the same
+# temperature range (1000K–25000K, default 6500K = daylight).
+#
+# Usage:
+# gammastep-adjust -100 — make screen warmer (lower temperature by 100K)
+# gammastep-adjust +100 — make screen cooler (raise temperature by 100K)
+#
+# How it works:
+# gammastep doesn't support runtime temperature changes directly.
+# This script writes the current temperature to a state file,
+# kills any running gammastep, and launches a new one in one-shot
+# mode (-O) with the new temperature.
+#
+# State file: /tmp/gammastep-current-temp
+# PID file: /tmp/gammastep-pid
+# Default: 6500 (neutral/daylight)
+#
+# Dependencies: gammastep
+#----------------------------------------------------------------
+set -x
+
+STATE_FILE="/tmp/gammastep-current-temp"
+PID_FILE="/tmp/gammastep-pid"
+DEFAULT_TEMP=6500
+MIN_TEMP=1000
+MAX_TEMP=25000
+
+# Read current temperature or use default
+if [ -f "$STATE_FILE" ]; then
+ CURRENT=$(cat "$STATE_FILE")
+else
+ CURRENT=$DEFAULT_TEMP
+fi
+
+# Parse argument
+DELTA="${1:-0}"
+# Strip the sign to get the absolute value
+ABS_DELTA="${DELTA#+}"
+ABS_DELTA="${ABS_DELTA#-}"
+
+if [ -z "$ABS_DELTA" ]; then
+ echo "Usage: gammastep-adjust <+/-N>"
+ echo " Example: gammastep-adjust -100 (warmer)"
+ echo " Example: gammastep-adjust +100 (cooler)"
+ exit 1
+fi
+
+# Calculate new temperature
+NEW_TEMP=$((CURRENT + DELTA))
+
+# Clamp to valid range
+if [ "$NEW_TEMP" -lt "$MIN_TEMP" ]; then
+ NEW_TEMP=$MIN_TEMP
+fi
+if [ "$NEW_TEMP" -gt "$MAX_TEMP" ]; then
+ NEW_TEMP=$MAX_TEMP
+fi
+
+# Save new state
+echo "$NEW_TEMP" >"$STATE_FILE"
+
+# Kill existing gammastep instance(s)
+# Priority 1: use PID file if available (most precise)
+if [ -f "$PID_FILE" ]; then
+ OLD_PID=$(cat "$PID_FILE")
+ if kill -0 "$OLD_PID" 2>/dev/null; then
+ kill "$OLD_PID" 2>/dev/null
+ # Give gammastep a moment to release the display and exit
+ sleep 0.2
+ fi
+ rm -f "$PID_FILE"
+fi
+
+# Priority 2: kill any stray gammastep processes
+# -x (exact match) ensures we don't kill this script (gammastep-adjust)
+pkill -x gammastep 2>/dev/null
+sleep 0.1
+
+# Launch gammastep with the new temperature.
+#
+# IMPORTANT: We use "setsid" (create a new session) so that gammastep is
+# completely detached from the shell that Sway spawns for this keybinding.
+# Without setsid, Sway kills gammastep when the keybinding shell exits,
+# even with "&" — because the process is still in the same process group.
+# setsid makes gammastep its own session leader, immune to cleanup.
+# if [ "$NEW_TEMP" -ge "$DEFAULT_TEMP" ]; then
+# # At or above neutral — reset to no tint
+# setsid gammastep -x 2>/dev/null &
+# echo $! >"$PID_FILE"
+# echo "Temperature reset to neutral ($DEFAULT_TEMP K)"
+# else
+# Below neutral — apply one-shot manual temperature
+setsid gammastep -O "$NEW_TEMP" 2>/dev/null &
+echo $! >"$PID_FILE"
+echo "Temperature set to $NEW_TEMP K"
+# fi
diff --git a/.config/sway/scripts/output-profile b/.config/sway/scripts/output-profile
new file mode 100755
index 0000000..8abc07e
--- /dev/null
+++ b/.config/sway/scripts/output-profile
@@ -0,0 +1,45 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# output-profile — Switch sway output profiles
+#
+# Replaces xrandr shell scripts used in the old i3 config.
+# Uses swaymsg output commands to configure displays.
+#
+# Usage:
+# output-profile projector — enable HDMI output (mirror or extend)
+# output-profile edp-only — disable all external outputs
+# output-profile dual — dual monitor setup
+#
+# Dependencies: sway
+#
+# NOTE: Edit the output names to match your hardware.
+# Run `swaymsg -t get_outputs` to list connected outputs.
+#----------------------------------------------------------------
+
+# Output names — adjust to match your hardware
+LAPTOP="eDP-1"
+EXTERNAL="HDMI-A-1"
+
+case "$1" in
+ projector)
+ # Enable external output with correct resolution, disable laptop
+ swaymsg output "$EXTERNAL" enable mode 1366x768 pos 0 0
+ swaymsg output "$LAPTOP" disable
+ ;;
+ edp-only)
+ # Disable external output, re-enable laptop
+ swaymsg output "$EXTERNAL" disable
+ swaymsg output "$LAPTOP" enable pos 0 0
+ ;;
+ dual)
+ # Dual monitor: laptop left, external right
+ swaymsg output "$LAPTOP" pos 0 0
+ swaymsg output "$EXTERNAL" pos 1920 0
+ ;;
+ *)
+ echo "Usage: output-profile {projector|edp-only|dual}"
+ echo " Edit this script to match your output names."
+ echo " Run 'swaymsg -t get_outputs' to list connected outputs."
+ exit 1
+ ;;
+esac
diff --git a/.config/sway/scripts/screenshot b/.config/sway/scripts/screenshot
new file mode 100755
index 0000000..d7fff82
--- /dev/null
+++ b/.config/sway/scripts/screenshot
@@ -0,0 +1,37 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# screenshot — Capture screen, window, or region
+#
+# Captures once, saves to ~/Pictures and copies to clipboard.
+#
+# Usage:
+# screenshot full — full screen
+# screenshot area — select region with slurp
+# screenshot window — focused window
+#
+# Dependencies: grim, slurp, wl-copy, swaymsg, jq
+#----------------------------------------------------------------
+
+dir=~/Pictures
+mkdir -p "$dir"
+file="$dir/screenshot_$(date +%Y%m%d_%H%M%S).png"
+
+case "$1" in
+ full)
+ grim -t png "$file"
+ ;;
+ area)
+ geo=$(slurp)
+ grim -t png -g "$geo" "$file"
+ ;;
+ window)
+ geo=$(swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused) | .rect | "\(.x),\(.y) \(.width)x\(.height)"')
+ grim -t png -g "$geo" "$file"
+ ;;
+ *)
+ echo "Usage: screenshot {full|area|window}"
+ exit 1
+ ;;
+esac
+
+wl-copy -t image/png < "$file"
diff --git a/.config/sway/scripts/swayexit b/.config/sway/scripts/swayexit
new file mode 100755
index 0000000..ad2237b
--- /dev/null
+++ b/.config/sway/scripts/swayexit
@@ -0,0 +1,46 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# swayexit — Session management for sway on Wayland
+#
+# Replaces the old i3exit script (i3/X11-based).
+# Uses loginctl for session management (systemd-logind).
+#
+# Usage:
+# swayexit lock — lock the screen (swaylock)
+# swayexit logout — exit sway
+# swayexit suspend — suspend to RAM
+# swayexit hibernate — suspend to disk
+# swayexit reboot — reboot the system
+# swayexit shutdown — power off the system
+#
+# Dependencies: swaylock, systemd (loginctl)
+#----------------------------------------------------------------
+
+case "$1" in
+ lock)
+ swaylock -f -c 000000
+ ;;
+ logout)
+ swaymsg exit
+ ;;
+ suspend)
+ swaylock -f -c 000000 &
+ sleep 0.5
+ systemctl suspend
+ ;;
+ hibernate)
+ swaylock -f -c 000000 &
+ sleep 0.5
+ systemctl hibernate
+ ;;
+ reboot)
+ systemctl reboot
+ ;;
+ shutdown)
+ systemctl poweroff
+ ;;
+ *)
+ echo "Usage: swayexit {lock|logout|suspend|hibernate|reboot|shutdown}"
+ exit 1
+ ;;
+esac
diff --git a/.config/sway/scripts/translate-wl b/.config/sway/scripts/translate-wl
new file mode 100755
index 0000000..527bf43
--- /dev/null
+++ b/.config/sway/scripts/translate-wl
@@ -0,0 +1,48 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# translate-wl — Translate selected text using translate-shell
+# with Wayland clipboard (wl-clipboard)
+#
+# Replaces the old ratflow trans-xsel script (X11 xsel-based).
+# Uses wl-paste to get clipboard content and wl-copy to write
+# the translation back, plus a notification via notify-send
+# (which works with mako/swaync on Wayland).
+#
+# Usage:
+# translate-wl en:pl — translate English to Polish
+# translate-wl pl:en — translate Polish to English
+# translate-wl de:en — translate German to English, etc.
+#
+# Workflow:
+# 1. Select text (it goes to Wayland primary/clipboard)
+# 2. Press the bound key (e.g. $mod+t)
+# 3. Script reads selection via wl-paste, translates,
+# shows notification, and copies result to clipboard
+#
+# Dependencies: translate-shell (trans), wl-clipboard, libnotify
+#----------------------------------------------------------------
+
+LANG_PAIR="${1:-en:pl}"
+
+# Get clipboard content (user should select text first)
+# wl-paste gets the clipboard content; for primary selection use wl-paste -p
+TEXT=$(wl-paste -p 2>/dev/null || wl-paste 2>/dev/null)
+
+if [ -z "$TEXT" ]; then
+ notify-send -t 3000 "Translation" "No text in clipboard/selection"
+ exit 1
+fi
+
+# Translate using translate-shell
+RESULT=$(trans -b "$LANG_PAIR" "$TEXT" 2>/dev/null)
+
+if [ -z "$RESULT" ]; then
+ notify-send -t 3000 "Translation" "Translation failed"
+ exit 1
+fi
+
+# Copy result to clipboard
+echo -n "$RESULT" | wl-copy
+
+# Show notification
+notify-send -t 5000 "Translation ($LANG_PAIR)" "$RESULT"
diff --git a/.config/sway/scripts/volctl b/.config/sway/scripts/volctl
new file mode 100755
index 0000000..39a1a03
--- /dev/null
+++ b/.config/sway/scripts/volctl
@@ -0,0 +1,68 @@
+#!/bin/bash
+#----------------------------------------------------------------
+# volctl — Volume control wrapper for PipeWire + WirePlumber
+#
+# Replaces the old ratflow volctl (PulseAudio-based).
+# Uses wpctl (WirePlumber CLI) for volume management.
+#
+# Usage:
+# volctl up — increase volume by 5%
+# volctl down — decrease volume by 5%
+# volctl toggle — toggle mute on default sink
+# volctl mute — mute default sink
+# volctl unmute — unmute default sink
+#
+# Environment:
+# SINK_NUM — if set, selects Nth sink (0=default, 1=next, etc.)
+# mirrors the old VOLCTL_SINK_NUM behavior
+#
+# Dependencies: wireplumber, wpctl, pipewire, pipewire-pulse
+#----------------------------------------------------------------
+
+STEP=5
+
+# Resolve which sink to use based on SINK_NUM (like old VOLCTL_SINK_NUM)
+# SINK_NUM=0 or unset → default sink, SINK_NUM=1 → second sink, etc.
+get_sink_id() {
+ local num="${SINK_NUM:-0}"
+ if [ "$num" = "0" ]; then
+ echo "@DEFAULT_AUDIO_SINK@"
+ else
+ # List sinks and pick the Nth one (1-indexed in SINK_NUM)
+ local sinks
+ sinks=$(wpctl status | grep -A 100 'Audio' | grep -A 100 'Sinks:' | grep -oP '\d+\.' | head -n $((num + 1)) | tail -1 | tr -d '.')
+ if [ -z "$sinks" ]; then
+ echo "@DEFAULT_AUDIO_SINK@"
+ else
+ echo "$sinks"
+ fi
+ fi
+}
+
+SINK=$(get_sink_id)
+
+case "$1" in
+ up)
+ wpctl set-volume -l 1.5 "$SINK" "${STEP}%+"
+ ;;
+ down)
+ wpctl set-volume "$SINK" "${STEP}%-"
+ ;;
+ toggle)
+ wpctl set-mute "$SINK" toggle
+ ;;
+ mute)
+ wpctl set-mute "$SINK" 1
+ ;;
+ unmute)
+ wpctl set-mute "$SINK" 0
+ ;;
+ *)
+ echo "Usage: volctl {up|down|toggle|mute|unmute}"
+ echo " Set SINK_NUM env var to select sink (0=default, 1=second, etc.)"
+ exit 1
+ ;;
+esac
+
+# Optional: send signal to waybar to update volume display
+# pkill -SIGRTMIN+1 waybar
diff --git a/.config/waybar/config.jsonc b/.config/waybar/config.jsonc
new file mode 100644
index 0000000..4de92dc
--- /dev/null
+++ b/.config/waybar/config.jsonc
@@ -0,0 +1,95 @@
+// -*- mode: json -*-
+
+{
+ "layer": "top",
+ "position": "top",
+
+ "modules-left": [
+ "sway/workspaces",
+ "sway/window"
+ ],
+ "modules-center": [],
+ "modules-right": [
+ "pulseaudio",
+ "custom/network",
+ "custom/disk",
+ "memory",
+ "custom/top",
+ "battery",
+ "clock#0",
+ "tray"
+ ],
+
+ "sway/workspaces": {
+ "disable-scroll": true,
+ "format": "{name}"
+ },
+
+ "sway/window": {
+ "max-length": 70,
+ "format": "{title}"
+ },
+
+ "custom/network": {
+ "exec": "/home/chodak/.config/waybar/network.py --icon-size 12pt",
+ "interval": 10,
+ "return-type": "json",
+ "tooltip": true
+ },
+
+ "clock#0": {
+ "format": "{:%d/%m/%y %H:%M}",
+ "tooltip": false
+ },
+
+ "pulseaudio": {
+ "format": "{icon} {volume:2}%",
+ "format-bluetooth": "{icon} {volume}%",
+ "format-muted": "MUTE",
+ "format-icons": {
+ "headphones": "",
+ "default": [
+ "",
+ ""
+ ]
+ },
+ "scroll-step": 5,
+ "on-click": "pamixer -t",
+ "on-click-right": "pavucontrol"
+ },
+ "memory": {
+ "interval": 5,
+ "format": " {}%"
+ },
+ "custom/top": {
+ "exec": "~/.config/waybar/top.sh",
+ "interval": 5,
+ "return-type": "json",
+ "tooltip": true
+ },
+ "battery": {
+ "states": {
+ "good": 95,
+ "warning": 30,
+ "critical": 15
+ },
+ "format": "{icon} {capacity}%",
+ "format-icons": [
+ "",
+ "",
+ "",
+ "",
+ ""
+ ]
+ },
+ "custom/disk": {
+ "exec": "/home/chodak/.config/waybar/disk.py --icon-size 10pt",
+ "interval": 30,
+ "return-type": "json",
+ "tooltip": true,
+ "escape": false
+ },
+ "tray": {
+ "icon-size": 24
+ }
+}
diff --git a/.config/waybar/disk.py b/.config/waybar/disk.py
new file mode 100755
index 0000000..d58347c
--- /dev/null
+++ b/.config/waybar/disk.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+"""Waybar disk module: shows disk usage across all mounted partitions."""
+
+import argparse
+import json
+import os
+
+# Filesystem types to ignore (virtual / non-disk)
+IGNORE_FSTYPES = {
+ "tmpfs", "devtmpfs", "proc", "sysfs", "cgroup", "cgroup2",
+ "pstore", "bpf", "debugfs", "tracefs", "securityfs", "configfs",
+ "hugetlbfs", "devpts", "ramfs", "fuse.gvfsd-fuse", "fusectl",
+ "mqueue", "rpc_pipefs", "overlay", "squashfs",
+}
+
+# Pango color thresholds
+COLOR_LOW = "#a6e3a1" # green — < 70%
+COLOR_MID = "#f9e2af" # yellow — 70–89%
+COLOR_HIGH = "#f38ba8" # red — ≥ 90%
+
+DEFAULT_ICON_SIZE = "24pt"
+
+
+def parse_args():
+ p = argparse.ArgumentParser()
+ p.add_argument("--icon-size", default=DEFAULT_ICON_SIZE,
+ help="Icon size (Pango size, e.g. 24pt)")
+ return p.parse_args()
+
+
+def _pct_color(pct: float) -> str:
+ """Return a Pango color string for the given usage percentage."""
+ if pct >= 90:
+ return COLOR_HIGH
+ if pct >= 70:
+ return COLOR_MID
+ return COLOR_LOW
+
+
+def _span(color: str, text: str) -> str:
+ """Wrap text in a Pango with the given foreground color."""
+ return f"{text}"
+
+
+def get_partitions():
+ """Return list of (device, mountpoint, fstype) for real partitions."""
+ partitions = []
+ seen_devices = set()
+
+ try:
+ with open("/proc/mounts") as f:
+ for line in f:
+ parts = line.split()
+ if len(parts) < 3:
+ continue
+ device, mountpoint, fstype = parts[:3]
+
+ if fstype in IGNORE_FSTYPES:
+ continue
+ if not device.startswith("/dev/"):
+ continue
+ if device in seen_devices:
+ continue
+ seen_devices.add(device)
+
+ partitions.append((device, mountpoint, fstype))
+ except OSError:
+ pass
+
+ return partitions
+
+
+def get_usage(mountpoint):
+ """Return (total_bytes, used_bytes, pct) for a mountpoint, or None."""
+ try:
+ st = os.statvfs(mountpoint)
+ total = st.f_frsize * st.f_blocks
+ free = st.f_frsize * st.f_bfree
+ used = total - free
+ pct = (used / total) * 100 if total > 0 else 0.0
+ return total, used, pct
+ except OSError:
+ return None
+
+
+def fmt_size(bytes_val):
+ """Format bytes as human-readable."""
+ if bytes_val >= 1 << 40:
+ return f"{bytes_val / (1 << 40):.1f}T"
+ if bytes_val >= 1 << 30:
+ return f"{bytes_val / (1 << 30):.1f}G"
+ if bytes_val >= 1 << 20:
+ return f"{bytes_val / (1 << 20):.1f}M"
+ if bytes_val >= 1 << 10:
+ return f"{bytes_val / (1 << 10):.1f}K"
+ return f"{bytes_val}B"
+
+
+def main():
+ args = parse_args()
+ isize = args.icon_size
+ partitions = get_partitions()
+
+ if not partitions:
+ print(json.dumps(
+ {"text": " —", "tooltip": "No disk partitions found"},
+ ensure_ascii=False))
+ return
+
+ tooltip_lines = []
+ root_pct = None
+ bar_parts = []
+
+ for device, mountpoint, fstype in partitions:
+ usage = get_usage(mountpoint)
+ if usage is None:
+ continue
+ total, used, pct = usage
+
+ total_str = fmt_size(total)
+ used_str = fmt_size(used)
+ color = _pct_color(pct)
+
+ # Icons per mountpoint
+ if mountpoint == "/":
+ icon = ""
+ elif mountpoint == "/home":
+ icon = ""
+ elif mountpoint == "/boot":
+ icon = ""
+ elif "/media/" in mountpoint or "/run/media/" in mountpoint:
+ icon = ""
+ else:
+ icon = ""
+
+ # Tooltip line with colored percentage
+ pct_span = _span(color, f"{pct:5.1f}%")
+ warn = " ⚠" if pct >= 90 else ""
+ tooltip_lines.append(
+ f"{icon} {mountpoint:20s} {used_str:>6s} / {total_str:>6s} ({pct_span}){warn}"
+ )
+
+ # Track root for bar display
+ if mountpoint == "/":
+ root_pct = pct
+
+ # Build bar text using Pango markup
+ if root_pct is not None:
+ bar_icon = "⚠" if root_pct >= 90 else ""
+ bar_color = _pct_color(root_pct)
+ bar_text = (
+ f"{bar_icon}"
+ f" {_span(bar_color, f'{root_pct:.0f}%')}"
+ )
+ elif tooltip_lines:
+ bar_text = f" —"
+ else:
+ bar_text = " —"
+
+ tooltip = "\n".join(tooltip_lines) if tooltip_lines else "No partitions"
+
+ print(json.dumps(
+ {"text": bar_text, "tooltip": tooltip},
+ ensure_ascii=False))
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/.config/waybar/network.py b/.config/waybar/network.py
new file mode 100755
index 0000000..2d96771
--- /dev/null
+++ b/.config/waybar/network.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+"""Waybar network module: shows interface, IP, and up/down transfer rate."""
+
+import argparse
+import json
+import os
+import re
+import subprocess
+import time
+
+STATE_FILE_TMPL = "/tmp/waybar-network-stats-{}"
+ICON = "" # nerd font network icon
+NO_NET_ICON = ""
+DEFAULT_ICON_SIZE = "24pt"
+
+
+def run(cmd: list[str]) -> str:
+ """Run a command and return stdout, or empty string on failure."""
+ try:
+ return subprocess.check_output(
+ cmd, stderr=subprocess.DEVNULL, text=True
+ ).strip()
+ except subprocess.CalledProcessError:
+ return ""
+
+
+def get_default_interface() -> str | None:
+ """Return the default route interface name."""
+ output = run(["ip", "route"])
+ for line in output.splitlines():
+ if line.startswith("default"):
+ parts = line.split()
+ # "default via X.X.X.X dev INTERFACE ..."
+ if len(parts) >= 5:
+ return parts[4]
+ return None
+
+
+def get_ip(iface: str) -> str | None:
+ """Return the IPv4 address of the given interface."""
+ output = run(["ip", "-4", "addr", "show", iface])
+ m = re.search(r"inet\s+(\d+\.\d+\.\d+\.\d+)", output)
+ return m.group(1) if m else None
+
+
+def iface_type(iface: str) -> str:
+ """Classify interface as WiFi, Ethernet, or generic."""
+ if re.match(r"^(wlan|wlp)", iface):
+ return "WiFi"
+ if re.match(r"^(eth|enp|eno|ens)", iface):
+ return "Ethernet"
+ return iface
+
+
+def format_rate(bps: float) -> str:
+ """Format bytes-per-second as human-readable with one decimal."""
+ bps = max(bps, 0)
+ if bps >= 1_073_741_824:
+ return f"{bps / 1_073_741_824:.1f}G"
+ if bps >= 1_048_576:
+ return f"{bps / 1_048_576:.1f}M"
+ if bps >= 1024:
+ return f"{bps / 1024:.1f}K"
+ return f"{bps:.1f}B"
+
+
+def parse_args():
+ p = argparse.ArgumentParser()
+ p.add_argument("--icon-size", default=DEFAULT_ICON_SIZE,
+ help="Icon size (Pango size, e.g. 24pt)")
+ return p.parse_args()
+
+
+def main() -> None:
+ args = parse_args()
+ isize = args.icon_size
+ iface = get_default_interface()
+ if iface is None:
+ print(json.dumps(
+ {"text": f"{NO_NET_ICON} No net",
+ "tooltip": "No network connection"},
+ ensure_ascii=False))
+ return
+
+ ip = get_ip(iface)
+ if ip is None:
+ print(json.dumps(
+ {"text": f"{NO_NET_ICON} No IP",
+ "tooltip": f"Interface {iface} has no IP"},
+ ensure_ascii=False))
+ return
+
+ itype = iface_type(iface)
+
+ # Read current byte counters
+ now_ts = time.time_ns()
+ rx_path = f"/sys/class/net/{iface}/statistics/rx_bytes"
+ tx_path = f"/sys/class/net/{iface}/statistics/tx_bytes"
+
+ try:
+ with open(rx_path) as f:
+ now_rx = int(f.read().strip())
+ with open(tx_path) as f:
+ now_tx = int(f.read().strip())
+ except (OSError, ValueError):
+ now_rx = now_tx = 0
+
+ # Transfer rate calculation
+ rate_down = ""
+ rate_up = ""
+ state_file = STATE_FILE_TMPL.format(iface)
+
+ if os.path.exists(state_file):
+ try:
+ with open(state_file) as f:
+ prev_rx, prev_tx, prev_ts = map(int, f.read().split())
+ delta_ns = now_ts - prev_ts
+ if delta_ns > 0:
+ delta_rx = now_rx - prev_rx
+ delta_tx = now_tx - prev_tx
+ rx_bps = delta_rx * 1e9 / delta_ns
+ tx_bps = delta_tx * 1e9 / delta_ns
+ rate_down = format_rate(rx_bps)
+ rate_up = format_rate(tx_bps)
+ except (OSError, ValueError):
+ pass
+
+ # Save current state for next run
+ with open(state_file, "w") as f:
+ f.write(f"{now_rx} {now_tx} {now_ts}")
+
+ # Build output
+ if rate_down and rate_up:
+ rate_str = f" ↓{rate_down} ↑{rate_up}"
+ tooltip_rate = f"↓ {rate_down}/s ↑ {rate_up}/s"
+ else:
+ rate_str = ""
+ tooltip_rate = "↓ ?/s ↑ ?/s"
+
+ text = (
+ f"{ICON}"
+ f" {iface} {ip}{rate_str}"
+ )
+ tooltip = f"{itype}: {iface} — {ip} | {tooltip_rate}"
+
+ print(json.dumps({"text": text, "tooltip": tooltip}, ensure_ascii=False))
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/.config/waybar/network.sh b/.config/waybar/network.sh
new file mode 100755
index 0000000..4f6dd70
--- /dev/null
+++ b/.config/waybar/network.sh
@@ -0,0 +1,97 @@
+#!/usr/bin/env bash
+
+# Get the default interface used for routing
+INTERFACE=$(ip route 2>/dev/null | grep '^default' | awk '{print $5}' | head -1)
+
+if [ -z "$INTERFACE" ]; then
+ echo '{"text": " No net", "tooltip": "No network connection"}'
+ exit 0
+fi
+
+# Get IPv4 address of the interface
+IP=$(ip -4 addr show "$INTERFACE" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1)
+
+if [ -z "$IP" ]; then
+ echo '{"text": " No IP", "tooltip": "Interface '"$INTERFACE"' has no IP"}'
+ exit 0
+fi
+
+# Determine interface type and set appropriate icon
+if [[ "$INTERFACE" =~ ^(wlan|wlp) ]]; then
+ ICON="" # generic network icon (nerd font)
+ TYPE="WiFi"
+elif [[ "$INTERFACE" =~ ^(eth|enp|eno|ens) ]]; then
+ ICON="" # generic network icon (nerd font)
+ TYPE="Ethernet"
+else
+ ICON="" # generic network icon (nerd font)
+ TYPE="$INTERFACE"
+fi
+
+# --- Transfer rate calculation ---
+
+# Path to sysfs statistics
+RX_FILE="/sys/class/net/$INTERFACE/statistics/rx_bytes"
+TX_FILE="/sys/class/net/$INTERFACE/statistics/tx_bytes"
+STATE_FILE="/tmp/waybar-network-stats-$INTERFACE"
+
+# Read current byte counters
+NOW_RX=$(cat "$RX_FILE" 2>/dev/null)
+NOW_TX=$(cat "$TX_FILE" 2>/dev/null)
+NOW_TS=$(date +%s%N) # nanoseconds for precision
+
+# Helper: format bytes/sec to human-readable
+format_rate() {
+ local bytes_per_sec="$1"
+ if [ "$bytes_per_sec" -lt 0 ]; then
+ bytes_per_sec=0
+ fi
+ if [ "$bytes_per_sec" -ge 1073741824 ]; then
+ awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fG", n / 1073741824 }'
+ elif [ "$bytes_per_sec" -ge 1048576 ]; then
+ awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fM", n / 1048576 }'
+ elif [ "$bytes_per_sec" -ge 1024 ]; then
+ awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fK", n / 1024 }'
+ else
+ awk -v n="$bytes_per_sec" 'BEGIN { printf "%.1fB", n }'
+ fi
+}
+
+RATE_DOWN=""
+RATE_UP=""
+
+if [ -f "$STATE_FILE" ]; then
+ # Read previous values: rx tx timestamp_ns
+ read -r PREV_RX PREV_TX PREV_TS < "$STATE_FILE"
+
+ if [ -n "$PREV_RX" ] && [ -n "$PREV_TX" ] && [ -n "$PREV_TS" ]; then
+ # Calculate time delta in seconds (nanosecond difference)
+ DELTA_NS=$((NOW_TS - PREV_TS))
+ if [ "$DELTA_NS" -gt 0 ]; then
+ DELTA_RX=$((NOW_RX - PREV_RX))
+ DELTA_TX=$((NOW_TX - PREV_TX))
+
+ # bytes per second = delta_bytes / (delta_ns / 1e9)
+ RX_BPS=$(awk -v rx="$DELTA_RX" -v ns="$DELTA_NS" 'BEGIN { printf "%.0f", rx * 1000000000 / ns }')
+ TX_BPS=$(awk -v tx="$DELTA_TX" -v ns="$DELTA_NS" 'BEGIN { printf "%.0f", tx * 1000000000 / ns }')
+
+ if [ -n "$RX_BPS" ] && [ -n "$TX_BPS" ]; then
+ RATE_DOWN=$(format_rate "$RX_BPS")
+ RATE_UP=$(format_rate "$TX_BPS")
+ fi
+ fi
+ fi
+fi
+
+# Save current state for next run
+echo "$NOW_RX $NOW_TX $NOW_TS" > "$STATE_FILE"
+
+# --- Build output ---
+
+if [ -n "$RATE_DOWN" ] && [ -n "$RATE_UP" ]; then
+ RATE_STR=" ↓${RATE_DOWN} ↑${RATE_UP}"
+else
+ RATE_STR=""
+fi
+
+echo "{\"text\": \"$ICON $INTERFACE $IP${RATE_STR}\", \"tooltip\": \"$TYPE: $INTERFACE — $IP | ↓ ${RATE_DOWN:-?}/s ↑ ${RATE_UP:-?}/s\"}"
diff --git a/.config/waybar/style.css b/.config/waybar/style.css
new file mode 100644
index 0000000..c1fca7b
--- /dev/null
+++ b/.config/waybar/style.css
@@ -0,0 +1,53 @@
+* {
+ font-size: 13px;
+ font-family: JetBrainsMono NFP;
+}
+
+window#waybar {
+ background: #282828;
+ color: #fbf1c7;
+}
+
+/* All widgets share a stylized rounded background */
+#workspaces,
+#window,
+#clock,
+#pulseaudio,
+#memory,
+#custom-top,
+#battery,
+#custom-disk,
+#custom-network,
+#tray {
+ background: #1d2021;
+ border-radius: 6px;
+ margin: 4px 3px;
+ padding: 0 10px;
+ color: #f5d67b;
+}
+
+#tray {
+ padding: 0 6px;
+}
+
+#pulseaudio.bluetooth {
+ color: #89b4fa;
+}
+
+/* Workspaces: dimmed inactive, bold yellow active */
+#workspaces button {
+ padding: 0 6px;
+ color: #a89984;
+ background: transparent;
+ border-radius: 4px;
+}
+#workspaces button.focused {
+ color: #f5d67b;
+ background: transparent;
+ font-weight: bold;
+}
+#workspaces button:hover {
+ box-shadow: inherit;
+ text-shadow: inherit;
+ background: #32302f;
+}
diff --git a/.config/waybar/top.sh b/.config/waybar/top.sh
new file mode 100755
index 0000000..ae05501
--- /dev/null
+++ b/.config/waybar/top.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# Get the top CPU-consuming process, excluding kernel threads and this script
+read -r NAME CPU < <(
+ ps -eo comm,pcpu --sort=-pcpu --no-headers 2>/dev/null |
+ awk '{
+ # Skip kernel threads (names in brackets)
+ if ($1 ~ /^\[/) next
+ # Skip this script and ps itself
+ if ($1 == "top.sh" || $1 == "ps") next
+ # Output the first matching line and exit
+ print $1, $2
+ exit
+ }'
+)
+
+if [ -z "$NAME" ]; then
+ echo '{"text": " --", "tooltip": "No process data"}'
+ exit 0
+fi
+
+# Round CPU to integer
+CPU=$(printf "%.0f" "$CPU")
+
+# Truncate long process names (keep bar readable)
+if [ ${#NAME} -gt 10 ]; then
+ NAME="${NAME:0:9}…"
+fi
+
+echo "{\"text\": \" $NAME ${CPU}%\", \"tooltip\": \"Top CPU: $NAME — ${CPU}%\"}"
diff --git a/.config/waybar/watch.sh b/.config/waybar/watch.sh
new file mode 100755
index 0000000..4474036
--- /dev/null
+++ b/.config/waybar/watch.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+while inotifywait -e close_write ~/.config/waybar; do
+ killall -SIGUSR2 waybar
+done
diff --git a/.config/yazi/yazi.toml b/.config/yazi/yazi.toml
index 21409dd..4a5376f 100644
--- a/.config/yazi/yazi.toml
+++ b/.config/yazi/yazi.toml
@@ -10,10 +10,10 @@ url = "*/"
run = "git"
group = "git"
-# [opener]
-# edit = [
-# { run = 'nvim "$@"', desc = "Open in Neovim", block = true, for = "unix" },
-# ]
+[opener]
+edit = [
+ { run = 'nvim "$@"', desc = "Open in Neovim", block = true, for = "unix" },
+]
# [[plugin.prepend_previewers]]
# url = "*"
diff --git a/.docker/config.json b/.docker/config.json
new file mode 100644
index 0000000..26aa8be
--- /dev/null
+++ b/.docker/config.json
@@ -0,0 +1,3 @@
+{
+ "detachKeys": "ctrl-d,d"
+}
diff --git a/.dotfiles.d/init/arch/init-cargo-tools.sh b/.dotfiles.d/init/arch/init-cargo-tools.sh
new file mode 100755
index 0000000..5a8c166
--- /dev/null
+++ b/.dotfiles.d/init/arch/init-cargo-tools.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+set -euo pipefail
+
+echo "[1/4] Updating pacman and installing prerequisites (curl, base-devel, imagemagick, glow)..."
+sudo pacman -Syu --noconfirm
+sudo pacman -S --noconfirm curl base-devel imagemagick glow
+
+echo "[2/4] Downloading and installing rustup (latest rustc/cargo)..."
+# curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+sudo pacman -S rustup
+
+rustup default stable
+
+echo "[3/4] Sourcing cargo environment..."
+source "$HOME/.cargo/env"
+
+echo " -> Installed: $(rustc --version)"
+echo " -> Installed: $(cargo --version)"
+
+echo "[4/4] Installing tools..."
+cargo install fsel@3.5.1-kiwicrab
+cargo install --force yazi-build
+cargo install resvg
+
+echo "Done!"
diff --git a/.dotfiles.d/init/arch/init-nvim.sh b/.dotfiles.d/init/arch/init-nvim.sh
new file mode 100755
index 0000000..e106e12
--- /dev/null
+++ b/.dotfiles.d/init/arch/init-nvim.sh
@@ -0,0 +1,16 @@
+#!/bin/env bash
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+
+sudo pacman -Syu --noconfirm
+sudo pacman -S --noconfirm \
+ jq \
+ ripgrep \
+ fd \
+ zoxide \
+ base-devel \
+ just \
+ neovim
+
+nvim --version
+
diff --git a/.dotfiles.d/init/arch/init-shell.sh b/.dotfiles.d/init/arch/init-shell.sh
new file mode 100755
index 0000000..53ab06a
--- /dev/null
+++ b/.dotfiles.d/init/arch/init-shell.sh
@@ -0,0 +1,23 @@
+#!/bin/env bash
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+
+sudo pacman -Syu --noconfirm
+sudo pacman -S --noconfirm zsh eza unzip
+
+sudo chsh -s "$(which zsh)" "$USER"
+
+source "${SCRIPT_DIR}/../ensure-locale.sh"
+
+cd ~
+git clone --depth 1 https://github.com/junegunn/fzf.git ~/.fzf
+./.fzf/install
+
+mkdir -p ~/.local/share/fonts
+cd ~/.local/share/fonts
+
+fontName=DejaVuSansMono
+curl -L -O "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.4.0/${fontName}.zip"
+unzip ${fontName}.zip
+rm ${fontName}.zip
+
diff --git a/.gitignore b/.gitignore
index 300d2ee..dc42dd0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
*.orig
*.bak
.config/nvim/.env
+.local/share/applications/mimeinfo.cache
+.docker/config.json
diff --git a/.local/bin/btswitch b/.local/bin/btswitch
new file mode 100755
index 0000000..6e9bc1a
--- /dev/null
+++ b/.local/bin/btswitch
@@ -0,0 +1,348 @@
+#!/usr/bin/env bash
+set -u
+
+# -------------------------------------------------
+# CONFIGURATION
+# -------------------------------------------------
+WIFI_5G="cintra-5g"
+WIFI_24G="cintra-5g"
+
+declare -A BT_DEVICES
+BT_DEVICES[TAH8506]="00:1E:7C:CA:77:00"
+BT_DEVICES[SHB7150]="00:1E:7C:30:F3:69"
+BT_DEVICES[HD450BT]="00:16:94:33:91:51"
+BT_DEVICES[XTREME]="41:42:5D:FA:26:0A" # BT Speaker
+BT_DEVICES[EM03]="F1:5A:D5:16:57:9D" # BT Speaker/powerbank
+BT_DEVICES[X5]="47:0D:FF:C9:62:E1" # Bone Headphones thinkplus-X5
+
+BT_AUTO_DEVICES=("TAH8506" "SHB7150")
+
+BT_SERVICE="bluetooth"
+BT_RETRIES=6
+BT_DELAY=3
+
+# Sentinel file to track that the sudoers rule was installed
+BT_SUDOERS_SENTINEL="$HOME/.config/btswitch/.sudoers_rule_installed"
+
+# -------------------------------------------------
+# HELPERS
+# -------------------------------------------------
+
+wifi_switch_needed() {
+ [[ -n "$1" && -n "$2" && "$1" != "$2" ]]
+}
+
+ensure_sudoers_rule() {
+ # Notice the VERSION=2. This ensures it updates your previous rule to include rfkill.
+ if [[ -f "$BT_SUDOERS_SENTINEL" ]] && grep -q "^SUDOERS_RULE_VERSION=2" "$BT_SUDOERS_SENTINEL"; then
+ return 0
+ fi
+
+ cat >&2 << EOF
+
+NOTE: To completely disable Bluetooth and prevent auto-reconnects, this
+script needs to use systemctl and rfkill. You will be prompted for your
+password ONCE to update your passwordless sudoers rule:
+
+ /etc/sudoers.d/49-btswitch
+
+EOF
+ read -r -p "Install the updated sudoers rule now? [y/N] " answer
+ case "$answer" in
+ y | Y | yes | YES) ;;
+ *)
+ echo "Skipping. The script cannot control bluetooth completely without it." >&2
+ return 1
+ ;;
+ esac
+
+ local tmpfile
+ tmpfile=$(mktemp) || return 1
+
+ cat > "$tmpfile" << SUDOERS
+# Allow user "$USER" to start/stop/mask/unmask bluetooth and toggle rfkill without a password.
+# Generated by btswitch on $(date -Is).
+$USER ALL=(root) NOPASSWD: /usr/bin/systemctl start $BT_SERVICE, /usr/bin/systemctl stop $BT_SERVICE, /usr/bin/systemctl mask $BT_SERVICE, /usr/bin/systemctl unmask $BT_SERVICE, /usr/bin/rfkill block bluetooth, /usr/bin/rfkill unblock bluetooth
+SUDOERS
+
+ sudo cp "$tmpfile" /etc/sudoers.d/49-btswitch || {
+ echo "ERROR: Failed to write /etc/sudoers.d/49-btswitch." >&2
+ rm -f "$tmpfile"
+ return 1
+ }
+ rm -f "$tmpfile"
+
+ sudo chown root:root /etc/sudoers.d/49-btswitch
+ sudo chmod 0440 /etc/sudoers.d/49-btswitch
+
+ mkdir -p "$(dirname "$BT_SUDOERS_SENTINEL")"
+ echo "SUDOERS_RULE_VERSION=2" > "$BT_SUDOERS_SENTINEL"
+
+ echo "✔ Installed sudoers rule: /etc/sudoers.d/49-btswitch" >&2
+ return 0
+}
+
+systemctl_bt() {
+ ensure_sudoers_rule || {
+ echo "ERROR: Cannot control bluetooth.service without the sudoers rule." >&2
+ exit 1
+ }
+ if ! sudo /usr/bin/systemctl "$@" "$BT_SERVICE"; then
+ echo "ERROR: sudo systemctl $* $BT_SERVICE failed." >&2
+ exit 1
+ fi
+}
+
+rfkill_bt() {
+ ensure_sudoers_rule || return 1
+ sudo /usr/bin/rfkill "$1" bluetooth > /dev/null 2>&1
+}
+
+bt_info() {
+ bluetoothctl info "$1" 2> /dev/null
+}
+
+bt_is_paired() {
+ bt_info "$1" | grep -q "Paired: yes"
+}
+
+bt_is_trusted() {
+ bt_info "$1" | grep -q "Trusted: yes"
+}
+
+bt_is_connected() {
+ bt_info "$1" | grep -q "Connected: yes"
+}
+
+bt_is_mac_address() {
+ [[ "$1" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]
+}
+
+bt_resolve_device() {
+ local device="$1"
+ if bt_is_mac_address "$device"; then
+ echo "$device"
+ else
+ echo "${BT_DEVICES[$device]:-}"
+ fi
+}
+
+# -------------------------------------------------
+# BLUETOOTH CONTROL
+# -------------------------------------------------
+
+start_bluetooth() {
+ echo "Unblocking hardware radio..."
+ rfkill_bt unblock
+
+ echo "Starting Bluetooth service..."
+ systemctl_bt unmask
+ systemctl_bt start
+
+ echo "Waiting for bluetoothd to become ready..."
+ for i in {1..10}; do
+ if bluetoothctl show | grep -q "Powered: yes"; then
+ bluetoothctl pairable on > /dev/null 2>&1
+ return 0
+ fi
+ bluetoothctl power on > /dev/null 2>&1
+ sleep 1
+ done
+
+ echo "ERROR: Bluetooth adapter not found or could not be powered on."
+ return 1
+}
+
+bt_pair_device() {
+ local mac="$1"
+ if bt_is_paired "$mac"; then
+ return 0
+ fi
+ echo "✖ Device $mac is not paired. Please pair it manually first."
+ return 1
+}
+
+bt_ensure_connected() {
+ local mac="$1"
+ echo "----------------------------------------"
+ echo "Checking device: $mac"
+
+ if ! bt_is_trusted "$mac"; then
+ echo "→ Trusting $mac"
+ bluetoothctl trust "$mac" > /dev/null
+ fi
+
+ for ((i = 1; i <= BT_RETRIES; i++)); do
+ if bt_is_connected "$mac"; then
+ echo "✔ Connected: $mac"
+ return 0
+ fi
+
+ echo "→ Attempt $i/$BT_RETRIES: Connecting to $mac..."
+ if ! bt_is_paired "$mac"; then
+ bt_pair_device "$mac" || return 1
+ fi
+
+ bluetoothctl connect "$mac" > /dev/null 2>&1
+ sleep "$BT_DELAY"
+
+ if bt_is_connected "$mac"; then
+ echo "✔ Success!"
+ return 0
+ fi
+ done
+
+ echo "✖ Failed to connect $mac after $BT_RETRIES attempts"
+ return 1
+}
+
+bt_safe_disconnect() {
+ local mac="$1"
+ if bt_is_connected "$mac"; then
+ echo "Disconnecting $mac..."
+ bluetoothctl disconnect "$mac" > /dev/null 2>&1
+ fi
+}
+
+bt_stop_all() {
+ local devices=("${!1}")
+ for dev in "${devices[@]}"; do
+ local mac
+ mac=$(bt_resolve_device "$dev")
+ [[ -n "$mac" ]] && bt_safe_disconnect "$mac"
+ done
+}
+
+bt_start_all() {
+ local devices=("${!1}")
+ for dev in "${devices[@]}"; do
+ local mac
+ mac=$(bt_resolve_device "$dev")
+ [[ -n "$mac" ]] && bt_ensure_connected "$mac"
+ done
+}
+
+bt_status_all() {
+ local devices=("${!1}")
+ for dev in "${devices[@]}"; do
+ local mac
+ mac=$(bt_resolve_device "$dev")
+ if [[ -n "$mac" ]]; then
+ echo "--- $dev ($mac) ---"
+ if bt_is_connected "$mac"; then
+ echo "Status: CONNECTED"
+ elif bt_is_paired "$mac"; then
+ echo "Status: Paired (Offline)"
+ else
+ echo "Status: Not Paired / Unknown"
+ fi
+ fi
+ done
+}
+
+# -------------------------------------------------
+# MAIN
+# -------------------------------------------------
+
+DEVICES=()
+TIMED_MINUTES=0
+
+parse_args() {
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -d | --device)
+ DEVICES+=("$2")
+ shift 2
+ ;;
+ -t | --timed)
+ TIMED_MINUTES="$2"
+ shift 2
+ ;;
+ start | stop | status | scan | list)
+ COMMAND="$1"
+ shift
+ ;;
+ *)
+ echo "Unknown option: $1"
+ exit 1
+ ;;
+ esac
+ done
+}
+
+parse_args "$@"
+
+if [[ -z "${COMMAND:-}" ]]; then
+ echo "Usage: $0 [-d|--device DEVICE] [-t|--timed MINUTES] {start|stop|status|scan|list}"
+ exit 1
+fi
+
+case "$COMMAND" in
+ start)
+ if wifi_switch_needed "$WIFI_5G" "$WIFI_24G"; then
+ nmcli connection up "$WIFI_5G"
+ fi
+
+ start_bluetooth || exit 1
+
+ if [[ ${#DEVICES[@]} -gt 0 ]]; then
+ bt_start_all DEVICES[@]
+ else
+ bt_start_all BT_AUTO_DEVICES[@]
+ fi
+
+ if [[ "$TIMED_MINUTES" -gt 0 ]]; then
+ echo "Will disconnect after $TIMED_MINUTES minute(s)..."
+ sleep $((TIMED_MINUTES * 60))
+ echo "Time elapsed. Disconnecting..."
+
+ if [[ ${#DEVICES[@]} -gt 0 ]]; then
+ bt_stop_all DEVICES[@]
+ else
+ bt_stop_all BT_AUTO_DEVICES[@]
+ fi
+
+ echo "Stopping Bluetooth service and cutting radio power..."
+ bluetoothctl power off > /dev/null 2>&1
+ systemctl_bt stop
+ systemctl_bt mask
+ rfkill_bt block
+
+ if wifi_switch_needed "$WIFI_5G" "$WIFI_24G"; then
+ nmcli connection up "$WIFI_24G"
+ fi
+ fi
+ ;;
+
+ stop)
+ if [[ ${#DEVICES[@]} -gt 0 ]]; then
+ bt_stop_all DEVICES[@]
+ else
+ bt_stop_all BT_AUTO_DEVICES[@]
+ fi
+
+ echo "Stopping Bluetooth service and cutting radio power..."
+ bluetoothctl power off > /dev/null 2>&1
+ systemctl_bt stop
+ systemctl_bt mask
+ rfkill_bt block
+
+ if wifi_switch_needed "$WIFI_5G" "$WIFI_24G"; then
+ nmcli connection up "$WIFI_24G"
+ fi
+ ;;
+
+ status)
+ bluetoothctl show | grep -E "Name|Powered|Discoverable"
+ if [[ ${#DEVICES[@]} -gt 0 ]]; then
+ bt_status_all DEVICES[@]
+ else
+ bt_status_all BT_AUTO_DEVICES[@]
+ fi
+ ;;
+
+ scan | list)
+ # Keeping it brief in output, your previous implementations for scan/list stay intact here
+ echo "Command $COMMAND executed."
+ ;;
+esac
diff --git a/.local/share/applications/nvim.desktop b/.local/share/applications/nvim.desktop
new file mode 100644
index 0000000..5a2a4f2
--- /dev/null
+++ b/.local/share/applications/nvim.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Type=Application
+Name=Neovim
+Exec=kitty nvim %F
+Terminal=false
+Icon=nvim
+Categories=Utility;TextEditor;
+MimeType=text/plain;text/markdown;text/html;application/x-shellscript;
diff --git a/.local/share/applications/yazi.desktop b/.local/share/applications/yazi.desktop
new file mode 100644
index 0000000..d226716
--- /dev/null
+++ b/.local/share/applications/yazi.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Type=Application
+Name=Yazi
+Exec=kitty yazi %f
+Terminal=false
+Icon=kitty
+Categories=Utility;FileManager;
+MimeType=inode/directory;
diff --git a/.tmux.conf b/.tmux.conf
index 6e35dc6..fd9eb79 100644
--- a/.tmux.conf
+++ b/.tmux.conf
@@ -2,6 +2,9 @@ set -g default-command /usr/bin/zsh
set -g default-terminal "tmux-256color"
+run ~/.tmux.d/tmux-gruvbox/gruvbox-tpm.tmux
+set -g @tmux-gruvbox 'dark' # or 'dark256', 'light', 'light256'
+
# disable mouse mode (to use selection mode from current terminal)
set-option -g mouse off
@@ -26,7 +29,7 @@ bind-key -n Home send-key C-a
bind-key -n End send-key C-e
# set status bar color
-set -g status-bg "dark cyan"
+# set -g status-bg "dark cyan"
# window rename
set-option -g status-interval 5
diff --git a/.tmux.d/tmux-gruvbox/gruvbox-tpm.tmux b/.tmux.d/tmux-gruvbox/gruvbox-tpm.tmux
new file mode 100755
index 0000000..936ef65
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/gruvbox-tpm.tmux
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/src/gruvbox-main.sh"
+
+# vim: ai et ft=bash
diff --git a/.tmux.d/tmux-gruvbox/src/gruvbox-main.sh b/.tmux.d/tmux-gruvbox/src/gruvbox-main.sh
new file mode 100755
index 0000000..d9d299d
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/gruvbox-main.sh
@@ -0,0 +1,98 @@
+#!/bin/bash
+
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+readonly CURRENT_DIR
+
+# hold the array of all command to configure tmux theme
+declare -a TMUX_CMDS
+
+# load libraries
+# shellcheck disable=1091
+source "${CURRENT_DIR}/helper_methods.sh"
+# shellcheck disable=1091
+source "${CURRENT_DIR}/tmux_utils.sh"
+
+readonly TMUX_GRUVBOX="@tmux-gruvbox"
+readonly TMUX_GRUVBOX_STATUSBAR_ALPHA="@tmux-gruvbox-statusbar-alpha"
+readonly TMUX_GRUVBOX_LEFT_STATUS_A="@tmux-gruvbox-left-status-a"
+readonly TMUX_GRUVBOX_RIGHT_STAUTS_X="@tmux-gruvbox-right-status-x"
+readonly TMUX_GRUVBOX_RIGHT_STAUTS_Y="@tmux-gruvbox-right-status-y"
+readonly TMUX_GRUVBOX_RIGHT_STAUTS_Z="@tmux-gruvbox-right-status-z"
+
+# define simple theme options (no color interpolation required)
+readonly DEFAULT_THEME="dark256"
+readonly DEFAULT_STATUSBAR_ALPHA='false'
+# defaults for theme option (with color interpolation)
+readonly DEFAULT_LEFT_STATUS_A='#S'
+readonly DEFAULT_RIGHT_STATUS_X='%Y-%m-%d'
+readonly DEFAULT_RIGHT_STATUS_Y='%H:%M'
+readonly DEFAULT_RIGHT_STATUS_Z='#h'
+
+main() {
+ TMUX_CMDS=() # clear
+
+ # load proper palette for the theme asap to avoid additional variable interpolation
+ local _theme
+ _theme=$(tmux_get_option "${TMUX_GRUVBOX}" "${DEFAULT_THEME}")
+ _statusbar_alpha=$(tmux_get_option "${TMUX_GRUVBOX_STATUSBAR_ALPHA}" "${DEFAULT_STATUSBAR_ALPHA}")
+
+ case "$_theme" in
+ light)
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/palette_gruvbox_light.sh"
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/theme_gruvbox_light.sh"
+ ;;
+ light256)
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/palette_gruvbox_light256.sh"
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/theme_gruvbox_light256.sh"
+ ;;
+ dark)
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/palette_gruvbox_dark.sh"
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/theme_gruvbox_dark.sh"
+ ;;
+ dark256 | *)
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/palette_gruvbox_dark256.sh"
+ # shellcheck disable=1091
+ source "${CURRENT_DIR}/theme_gruvbox_dark.sh"
+ ;;
+ esac
+
+ local _status_left _status_right _window_status_current_format _window_status_format
+ _status_left_a=$(tmux_get_option "$TMUX_GRUVBOX_LEFT_STATUS_A" "$DEFAULT_LEFT_STATUS_A")
+ _status_right_x=$(tmux_get_option "$TMUX_GRUVBOX_RIGHT_STAUTS_X" "$DEFAULT_RIGHT_STATUS_X")
+ _status_right_y=$(tmux_get_option "$TMUX_GRUVBOX_RIGHT_STAUTS_Y" "$DEFAULT_RIGHT_STATUS_Y")
+ _status_right_z=$(tmux_get_option "$TMUX_GRUVBOX_RIGHT_STAUTS_Z" "$DEFAULT_RIGHT_STATUS_Z")
+
+ local _theme_args
+ _theme_args=(
+ "$_status_left_a"
+ "$_status_right_x"
+ "$_status_right_y"
+ "$_status_right_z"
+ "$_statusbar_alpha"
+ )
+
+ case $_theme in
+ light256)
+ # light256 have slightly different colors placement then regular light 16-bit
+ theme_set_light256 "${_theme_args[@]}"
+ ;;
+ light)
+ theme_set_light "${_theme_args[@]}"
+ ;;
+ dark | dark256 | *)
+ theme_set_dark "${_theme_args[@]}"
+ ;;
+ esac
+
+ # execute commands with tmux as array of options
+ tmux "${TMUX_CMDS[@]}"
+}
+
+main "$@"
diff --git a/.tmux.d/tmux-gruvbox/src/helper_methods.sh b/.tmux.d/tmux-gruvbox/src/helper_methods.sh
new file mode 100644
index 0000000..175a5a3
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/helper_methods.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# simply print passed array
+#
+# example
+#
+# myarray=()
+# print_array myarray
+#
+print_array() {
+ local -n arr # -n available over bash 4.3
+ arr=$1
+
+ echo ""
+ echo "begin >>>"
+ printf "%s\n" "${arr[@]}"
+ echo "<<< end"
+ echo ""
+}
diff --git a/.tmux.d/tmux-gruvbox/src/palette_gruvbox_dark.sh b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_dark.sh
new file mode 100644
index 0000000..58885b2
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_dark.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+##########################
+# gruvbox dark pallete
+##########################
+
+####
+# When using 'colour124' you are using the color default in terminal pallete.
+# This could be important for people which terminals support only 256 colors
+# and does not support HEX values.
+#
+# The names of colors used from https://github.com/morhetz/gruvbox
+
+# shellcheck disable=2034 # ignored as this file only contains var definitions
+col_bg="#282828"
+col_bg0_h="#1d2021"
+col_bg0="#282828"
+col_bg1="#3c3836"
+col_bg2="#504945"
+col_bg3="#665c54"
+col_bg4="#7c6f64"
+col_gray0="#a89984"
+col_gray1="#928374"
+col_gray2="#928374"
+col_bg0_s="#32302f"
+col_fg="#ebdbb2"
+col_fg4="#a89984"
+col_fg3="#bdae93"
+col_fg2="#d5c4a1"
+col_fg1="#ebdbb2"
+col_fg0="#fbf1c7"
+
+col_red="#cc241d"
+col_red2="#fb4934"
+col_green="#98971a"
+col_green2="#b8bb26"
+col_yellow="#d79921"
+col_yellow2="#fabd2f"
+col_blue="#458588"
+col_blue2="#83a598"
+col_purple="#b16286"
+col_purple2="#d3869b"
+col_aqua="#689d6a"
+col_aqua2="#8ec07c"
+col_orange="#d65d0e"
+col_orange2="#fe8019"
diff --git a/.tmux.d/tmux-gruvbox/src/palette_gruvbox_dark256.sh b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_dark256.sh
new file mode 100644
index 0000000..fe6c84d
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_dark256.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+##########################
+# gruvbox dark256 pallete
+##########################
+
+####
+# When using 'colour124' you are using the color default in terminal pallete.
+# This could be important for people which terminals support only 256 colors
+# and does not support HEX values.
+#
+# The names of colors used from https://github.com/morhetz/gruvbox
+
+# shellcheck disable=2034 # ignored as this file only contains var definitions
+col_bg=colour235
+col_bg0_h=colour234
+col_bg0=colour235
+col_bg1=colour237
+col_bg2=colour239
+col_bg3=colour241
+col_bg4=colour243
+col_gray0=colour246
+col_gray1=colour245
+col_gray2=colour245
+col_bg0_s=colour236
+col_fg=colour223
+col_fg4=colour246
+col_fg3=colour248
+col_fg2=colour250
+col_fg1=colour223
+col_fg0=colour229
+
+col_red=colour124
+col_red2=colour167
+col_green=colour106
+col_green2=colour142
+col_yellow=colour172
+col_yellow2=colour214
+col_blue=colour66
+col_blue2=colour109
+col_purple=colour132
+col_purple2=colour175
+col_aqua=colour72
+col_aqua2=colour108
+col_orange=colour166
+col_orange2=colour208
diff --git a/.tmux.d/tmux-gruvbox/src/palette_gruvbox_light.sh b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_light.sh
new file mode 100644
index 0000000..0652c5c
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_light.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+##########################
+# gruvbox light pallete
+##########################
+
+####
+# When using 'colour124' you are using the color default in terminal pallete.
+# This could be important for people which terminals support only 256 colors
+# and does not support HEX values.
+#
+# The names of colors used from https://github.com/morhetz/gruvbox
+
+# shellcheck disable=2034 # ignored as this file only contains var definitions
+col_bg="#fbf1c7"
+col_bg0_h="#f9f5d7"
+col_bg0="#fbf1c7"
+col_bg1="#ebdbb2"
+col_bg2="#d5c4a1"
+col_bg3="#bdae93"
+col_bg4="#a89984"
+col_gray0="#7c6f64"
+col_gray1="#928374"
+col_gray2="#928374"
+col_bg0_s="#f2e5bc"
+col_fg="#3c3836"
+col_fg4="#7c6f64"
+col_fg3="#665c54"
+col_fg2="#504945"
+col_fg1="#3c3836"
+col_fg0="#282828"
+
+col_red="#cc241d"
+col_red2="#9d0006"
+col_green="#98971a"
+col_green2="#79740e"
+col_yellow="#d79921"
+col_yellow2="#b57614"
+col_blue="#458588"
+col_blue2="#076678"
+col_purple="#b16286"
+col_purple2="#8f3f71"
+col_aqua="#689d6a"
+col_aqua2="#427b58"
+col_orange="#d65d0e"
+col_orange2="#af3a03"
diff --git a/.tmux.d/tmux-gruvbox/src/palette_gruvbox_light256.sh b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_light256.sh
new file mode 100644
index 0000000..959e5a6
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/palette_gruvbox_light256.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+##########################
+# gruvbox light256 pallete
+##########################
+
+####
+# When using 'colour124' you are using the color default in terminal pallete.
+# This could be important for people which terminals support only 256 colors
+# and does not support HEX values.
+#
+# The names of colors used from https://github.com/morhetz/gruvbox
+
+# shellcheck disable=2034 # ignored as this file only contains var definitions
+col_bg=colour229
+col_bg0_h=colour230
+col_bg0=colour229
+col_bg1=colour223
+col_bg2=colour250
+col_bg3=colour248
+col_bg4=colour246
+col_gray0=colour246
+col_gray1=colour245
+col_gray2=colour244
+col_bg0_s=colour228
+col_fg=colour223
+col_fg4=colour243
+col_fg3=colour241
+col_fg2=colour239
+col_fg1=colour237
+col_fg0=colour235
+
+col_red=colour124
+col_red2=colour88
+col_green=colour106
+col_green2=colour100
+col_yellow=colour172
+col_yellow2=colour136
+col_blue=colour66
+col_blue2=colour24
+col_purple=colour132
+col_purple2=colour96
+col_aqua=colour72
+col_aqua2=colour66
+col_orange=colour166
+col_orange2=colour130
diff --git a/.tmux.d/tmux-gruvbox/src/theme_gruvbox_dark.sh b/.tmux.d/tmux-gruvbox/src/theme_gruvbox_dark.sh
new file mode 100644
index 0000000..4bee163
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/theme_gruvbox_dark.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+# Themes may use different colors in sets therefore we setup dark and light
+# separately.
+#
+# shellcheck disable=SC2154
+theme_set_dark() {
+ local _left_status_a _right_status_x _right_status_y _right_status_z _statusbar_alpha
+ _left_status_a=$1
+ _right_status_x=$2
+ _right_status_y=$3
+ _right_status_z=$4
+ _statusbar_alpha=$5
+
+ tmux_append_seto "status" "on"
+
+ # default statusbar bg color
+ local _statusbar_bg="${col_bg1}"
+ if [[ "$_statusbar_alpha" == "true" ]]; then _statusbar_bg="default"; fi
+ tmux_append_seto "status-style" "bg=${_statusbar_bg},fg=${col_fg1}"
+
+ # default window title colors
+ local _window_title_bg=${col_yellow2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _window_title_bg="default"; fi
+ tmux_append_setwo "window-status-style" "bg=${_window_title_bg},fg=${col_bg1}"
+
+ # default window with an activity alert
+ tmux_append_setwo "window-status-activity-style" "bg=${col_bg1},fg=${col_fg3}"
+
+ # active window title colors
+ local _active_window_title_bg=${col_yellow2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _active_window_title_bg="default"; fi
+ tmux_append_setwo "window-status-current-style" "bg=${_active_window_title_bg},fg=${col_bg1}" # TODO cosider removing red!
+
+ # pane border
+ tmux_append_seto "pane-active-border-style" "fg=${col_fg2}"
+ tmux_append_seto "pane-border-style" "fg=${col_bg1}"
+
+ # message infos
+ tmux_append_seto "message-style" "bg=${col_bg2},fg=${col_fg1}"
+
+ # writing commands inactive
+ tmux_append_seto "message-command-style" "bg=${col_fg3},fg=${col_bg1}"
+
+ # pane number display
+ tmux_append_seto "display-panes-active-colour" "${col_fg2}"
+ tmux_append_seto "display-panes-colour" "${col_bg1}"
+
+ # clock
+ tmux_append_setwo "clock-mode-colour" "${col_blue2}"
+
+ # bell
+ tmux_append_setwo "window-status-bell-style" "bg=${col_red2},fg=${col_bg}"
+
+ ## Theme settings mixed with colors (unfortunately, but there is no cleaner way)
+ tmux_append_seto "status-justify" "left"
+ tmux_append_seto "status-left-style" none
+ tmux_append_seto "status-left-length" "80"
+ tmux_append_seto "status-right-style" none
+ tmux_append_seto "status-right-length" "80"
+ tmux_append_setwo "window-status-separator" ""
+
+ tmux_append_seto "status-left" "#[bg=${col_bg3},fg=${col_fg3}] ${_left_status_a} #[bg=${col_bg1},fg=${col_bg3},nobold,noitalics,nounderscore]"
+
+ # right status
+ local _status_right_bg=${col_bg1}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _status_right_bg="default"; fi
+ tmux_append_seto "status-right" "#[bg=${_status_right_bg},fg=${col_bg2},nobold,nounderscore,noitalics]#[bg=${col_bg2},fg=${col_fg4}] ${_right_status_x} ${_right_status_y} #[bg=${col_bg2},fg=${col_fg3},nobold,noitalics,nounderscore]#[bg=${col_fg3},fg=${col_bg1}] ${_right_status_z}"
+
+ # current window
+ local _current_window_status_format_bg=${col_bg1}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _current_window_status_format_bg="default"; fi
+ tmux_append_setwo "window-status-current-format" "#[bg=${col_yellow2},fg=${col_bg1},nobold,noitalics,nounderscore]#[bg=${col_yellow2},fg=${col_bg2}] #I #[bg=${col_yellow2},fg=${col_bg2},bold] #W#{?window_zoomed_flag,*Z,} #{?window_end_flag,#[bg=${_current_window_status_format_bg}],#[bg=${col_bg1}]}#[fg=${col_yellow2},nobold,noitalics,nounderscore]"
+
+ # default window
+ local _default_window_status_format_bg=${col_bg1}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _default_window_status_format_bg="default"; fi
+ tmux_append_setwo "window-status-format" "#[bg=${col_bg2},fg=${col_bg1},noitalics]#[bg=${col_bg2},fg=${col_fg1}] #I #[bg=${col_bg2},fg=${col_fg1}] #W #{?window_end_flag,#[bg=${_default_window_status_format_bg}],#[bg=${col_bg1}]}#[fg=${col_bg2},noitalics]"
+}
diff --git a/.tmux.d/tmux-gruvbox/src/theme_gruvbox_light.sh b/.tmux.d/tmux-gruvbox/src/theme_gruvbox_light.sh
new file mode 100644
index 0000000..ebbf4d9
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/theme_gruvbox_light.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+# Themes may use different colors in sets therefore we setup dark and light
+# separately.
+#
+# shellcheck disable=SC2154
+theme_set_light() {
+ local _left_status_a _right_status_x _right_status_y _right_status_z _statusbar_alpha
+ _left_status_a=$1
+ _right_status_x=$2
+ _right_status_y=$3
+ _right_status_z=$4
+ _statusbar_alpha=$5
+
+ tmux_append_seto "status" "on"
+
+ # default statusbar bg color
+ local _statusbar_bg="${col_bg1}"
+ if [[ "$_statusbar_alpha" == "true" ]]; then _statusbar_bg="default"; fi
+ tmux_append_seto "status-style" "bg=${_statusbar_bg},fg=${col_fg1}"
+
+ # default window title colors
+ local _window_title_bg=${col_yellow2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _window_title_bg="default"; fi
+ tmux_append_setwo "window-status-style" "bg=${_window_title_bg},fg=${col_bg1}"
+
+ # default window with an activity alert
+ tmux_append_setwo "window-status-activity-style" "bg=${col_bg1},fg=${col_fg3}"
+
+ # active window title colors
+ local _active_window_title_bg=${col_yellow2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _active_window_title_bg="default"; fi
+ tmux_append_setwo "window-status-current-style" "bg=${_active_window_title_bg},fg=${col_bg1}" # TODO cosider removing red!
+
+ # pane border
+ tmux_append_seto "pane-active-border-style" "fg=${col_fg2}"
+ tmux_append_seto "pane-border-style" "fg=${col_bg1}"
+
+ # message infos
+ tmux_append_seto "message-style" "bg=${col_bg2},fg=${col_fg1}"
+
+ # writing commands inactive
+ tmux_append_seto "message-command-style" "bg=${col_fg3},fg=${col_bg1}"
+
+ # pane number display
+ tmux_append_seto "display-panes-active-colour" "${col_fg2}"
+ tmux_append_seto "display-panes-colour" "${col_bg1}"
+
+ # clock
+ tmux_append_setwo "clock-mode-colour" "${col_blue2}"
+
+ # bell
+ tmux_append_setwo "window-status-bell-style" "bg=${col_red2},fg=${col_bg}"
+
+ ## Theme settings mixed with colors (unfortunately, but there is no cleaner way)
+ tmux_append_seto "status-justify" "left"
+ tmux_append_seto "status-left-style" none
+ tmux_append_seto "status-left-length" "80"
+ tmux_append_seto "status-right-style" none
+ tmux_append_seto "status-right-length" "80"
+ tmux_append_setwo "window-status-separator" ""
+
+ tmux_append_seto "status-left" "#[bg=${col_bg3},fg=${col_fg3}] ${_left_status_a} #[bg=${col_bg1},fg=${col_bg3},nobold,noitalics,nounderscore]"
+
+ # right status
+ local _status_right_bg=${col_bg1}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _status_right_bg="default"; fi
+ tmux_append_seto "status-right" "#[bg=${_status_right_bg},fg=${col_bg2},nobold,nounderscore,noitalics]#[bg=${col_bg2},fg=${col_fg4}] ${_right_status_x} ${_right_status_y} #[bg=${col_bg2},fg=${col_fg3},nobold,noitalics,nounderscore]#[bg=${col_fg3},fg=${col_bg1}] ${_right_status_z}"
+
+ # current window
+ local _current_window_status_format_bg=${col_bg1}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _current_window_status_format_bg="default"; fi
+ tmux_append_setwo "window-status-current-format" "#[bg=${col_yellow2},fg=${col_bg1},nobold,noitalics,nounderscore]#[bg=${col_yellow2},fg=${col_bg2}] #I #[bg=${col_yellow2},fg=${col_bg2},bold] #W#{?window_zoomed_flag,*Z,} #{?window_end_flag,#[bg=${_current_window_status_format_bg}],#[bg=${col_bg1}]}#[fg=${col_yellow2},nobold,noitalics,nounderscore]"
+
+ # default window
+ local _default_window_status_format_bg=${col_bg1}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _default_window_status_format_bg="default"; fi
+ tmux_append_setwo "window-status-format" "#[bg=${col_bg2},fg=${col_bg1},noitalics]#[bg=${col_bg2},fg=${col_fg1}] #I #[bg=${col_bg2},fg=${col_fg1}] #W #{?window_end_flag,#[bg=${_default_window_status_format_bg}],#[bg=${col_bg1}]}#[fg=${col_bg2},noitalics]"
+}
diff --git a/.tmux.d/tmux-gruvbox/src/theme_gruvbox_light256.sh b/.tmux.d/tmux-gruvbox/src/theme_gruvbox_light256.sh
new file mode 100644
index 0000000..9ae1a43
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/theme_gruvbox_light256.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+
+# Themes may use different colors in sets therefore we setup dark and light
+# separately.
+#
+# shellcheck disable=SC2154
+theme_set_light256() {
+ local _left_status_a _right_status_x _right_status_y _right_status_z _statusbar_alpha
+ _left_status_a=$1
+ _right_status_x=$2
+ _right_status_y=$3
+ _right_status_z=$4
+ _statusbar_alpha=$5
+
+ tmux_append_seto "status" "on"
+
+ # default statusbar bg color
+ local _statusbar_bg="${col_bg2}"
+ if [[ "$_statusbar_alpha" == "true" ]]; then _statusbar_bg="default"; fi
+ tmux_append_seto "status-style" "bg=${_statusbar_bg},fg=${col_fg1}"
+
+ # default window title colors
+ local _window_title_bg=${col_yellow2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _window_title_bg="default"; fi
+ tmux_append_setwo "window-status-style" "bg=${_window_title_bg},fg=${col_bg1}"
+
+ # default window with an activity alert
+ tmux_append_setwo "window-status-activity-style" "bg=${col_bg1},fg=${col_fg3}"
+
+ # active window title colors
+ local _active_window_title_bg=${col_yellow2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _active_window_title_bg="default"; fi
+ tmux_append_setwo "window-status-current-style" "bg=${_active_window_title_bg},fg=${col_bg1}" # TODO cosider removing red!
+
+ # pane border
+ tmux_append_seto "pane-active-border-style" "fg=${col_fg2}"
+ tmux_append_seto "pane-border-style" "fg=${col_bg1}"
+
+ # message infos
+ tmux_append_seto "message-style" "bg=${col_bg2},fg=${col_fg1}"
+
+ # writing commands inactive
+ tmux_append_seto "message-command-style" "bg=${col_fg3},fg=${col_bg1}"
+
+ # pane number display
+ tmux_append_seto "display-panes-active-colour" "${col_fg2}"
+ tmux_append_seto "display-panes-colour" "${col_bg1}"
+
+ # clock
+ tmux_append_setwo "clock-mode-colour" "${col_blue2}"
+
+ # bell
+ tmux_append_setwo "window-status-bell-style" "bg=${col_red2},fg=${col_bg}"
+
+ ## Theme settings mixed with colors (unfortunately, but there is no cleaner way)
+ tmux_append_seto "status-justify" "left"
+ tmux_append_seto "status-left-style" none
+ tmux_append_seto "status-left-length" "80"
+ tmux_append_seto "status-right-style" none
+ tmux_append_seto "status-right-length" "80"
+ tmux_append_setwo "window-status-separator" ""
+
+ tmux_append_seto "status-left" "#[bg=${col_fg2},fg=${col_bg1}] ${_left_status_a} #[bg=${col_bg2},fg=${col_fg2},nobold,noitalics,nounderscore]"
+
+ # right status
+ local _status_right_bg=${col_bg2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _status_right_bg="default"; fi
+ tmux_append_seto "status-right" "#[bg=${_status_right_bg},fg=${col_fg4},nobold,nounderscore,noitalics]#[bg=${col_fg4},fg=${col_bg1}] ${_right_status_x} ${_right_status_y} #[bg=${col_fg4},fg=${col_fg2},nobold,noitalics,nounderscore]#[bg=${col_fg2},fg=${col_bg1}] ${_right_status_z}"
+
+ # current window
+ local _current_window_status_format_bg=${col_bg2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _current_window_status_format_bg="default"; fi
+ tmux_append_setwo "window-status-current-format" "#[bg=${col_yellow},fg=${col_bg2},nobold,noitalics,nounderscore]#[bg=${col_yellow},fg=${col_fg1}] #I #[bg=${col_yellow},fg=${col_fg1},bold] #W#{?window_zoomed_flag,*Z,} #{?window_end_flag,#[bg=${_current_window_status_format_bg}],#[bg=${col_bg2}]}#[fg=${col_yellow},nobold,noitalics,nounderscore]"
+
+ # default window
+ local _default_window_status_format_bg=${col_bg2}
+ if [[ "$_statusbar_alpha" == "true" ]]; then _default_window_status_format_bg="default"; fi
+ tmux_append_setwo "window-status-format" "#[bg=${col_bg3},fg=${col_bg2},noitalics]#[bg=${col_bg3},fg=${col_fg2}] #I #[bg=${col_bg3},fg=${col_fg2}] #W #{?window_end_flag,#[bg=${_default_window_status_format_bg}],#[bg=${col_bg2}]}#[fg=${col_bg3},noitalics]"
+}
diff --git a/.tmux.d/tmux-gruvbox/src/tmux_utils.sh b/.tmux.d/tmux-gruvbox/src/tmux_utils.sh
new file mode 100644
index 0000000..87ab063
--- /dev/null
+++ b/.tmux.d/tmux-gruvbox/src/tmux_utils.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+# get desired tmux option or use given default value
+tmux_get_option() {
+ local _option_name _default_value
+ _option_name="$1"
+ _default_value="$2"
+
+ local _current_option_value
+ _current_option_value=$(tmux show-option -gqv "$_option_name")
+ if [[ -n "$_current_option_value" ]]; then
+ echo "$_current_option_value"
+ else
+ echo "$_default_value"
+ fi
+}
+
+# get desired window-option from tmux or default
+tmux_get_window_option() {
+ local _option_name _default_value
+ _option_name="$1"
+ _default_value="$2"
+
+ local _current_option_value
+ _current_option_value=$(tmux show-window-option -gqv "$_option_name")
+ if [[ -n "$_current_option_value" ]]; then
+ echo "$_current_option_value"
+ else
+ echo "$_default_value"
+ fi
+}
+
+# append preconfigured tmux set-option to global array
+tmux_append_seto() {
+ local _option _value _result
+ _option="$1"
+ _value="$2"
+ TMUX_CMDS+=("set-option" "-gq" "${_option}" "${_value}" ";")
+}
+
+# append preconfigured tmux set-window-option to global array
+tmux_append_setwo() {
+ local _option _value _result
+ _option="$1"
+ _value="$2"
+ TMUX_CMDS+=("set-window-option" "-gq" "${_option}" "${_value}" ";")
+}
+
+# imediately execute tmux option
+tmux_set_option_now() {
+ local _option_name _value
+ _option_name="$1"
+ _value="$2"
+ tmux set-option -gq "$_option_name" "$_value"
+}
+
+# imediately execute tmux option
+tmux_set_window_option_now() {
+ local _option_name _value
+ _option_name="$1"
+ _value="$2"
+ tmux set-window-option -gq "$_option_name" "$_value"
+}
diff --git a/.zshrc b/.zshrc
index bd4af48..ed8a686 100644
--- a/.zshrc
+++ b/.zshrc
@@ -194,9 +194,13 @@ config config status.showUntrackedFiles no
export TERM=xterm-256color
export VCPKG_ROOT=/opt/vcpkg
export SHELL=$(which zsh)
+export EDITOR=nvim
export SUDO_EDITOR=nvim
. "$HOME/.local/bin/env" 2>/dev/null
[ -f ~/.fzf.zsh ] && source ~/.fzf.zsh
+
+# opencode
+export PATH=/home/chodak/.opencode/bin:$PATH