diff --git a/.config/nvim/lazy-lock.json b/.config/nvim/lazy-lock.json index ec559f8..01bb543 100644 --- a/.config/nvim/lazy-lock.json +++ b/.config/nvim/lazy-lock.json @@ -1,6 +1,6 @@ { "LazyVim": { "branch": "main", "commit": "c10948c50b18fae7f256433afdef09e432410480" }, - "avante.nvim": { "branch": "main", "commit": "dee2402ed0b7c1d3710c0c8eed3d26aa055e118e" }, + "avante.nvim": { "branch": "main", "commit": "d9c33ebe1e7205f416796b5b383d6894abe47495" }, "blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" }, "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "catppuccin": { "branch": "main", "commit": "49a926655a2f5579e9c276470fc300baaa49e524" }, @@ -10,8 +10,9 @@ "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, "flash.nvim": { "branch": "main", "commit": "fcea7ff883235d9024dc41e638f164a450c14ca2" }, "friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" }, - "gitsigns.nvim": { "branch": "main", "commit": "25050e4ed39e628282831d4cbecb1850454ce915" }, + "gitsigns.nvim": { "branch": "main", "commit": "2038c666bd9d8a0b7349a0b6ee00dc83104b9ecf" }, "grug-far.nvim": { "branch": "main", "commit": "c995bbacf8229dc096ec1c3d60f8531059c86c1b" }, + "hybrid-theme": { "branch": "main", "commit": "6ae348938376f345e02eea4e6c27c0874b05f33a" }, "indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" }, "just-runner.nvim": { "branch": "main", "commit": "f29d405aa828900df242600720a2b0e57261489f" }, "lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" }, @@ -19,20 +20,20 @@ "lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" }, "mason-lspconfig.nvim": { "branch": "main", "commit": "21c5b3ebeaa0412e28096bb0701434c51c1fbf76" }, "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" }, - "mini.ai": { "branch": "main", "commit": "4511b3481707c1d021485475d34f2ed2a50bf47b" }, - "mini.files": { "branch": "main", "commit": "02874bc653fbecf2bee2c65441d0ebd09110f011" }, - "mini.icons": { "branch": "main", "commit": "ac38c983aed0a2bd32a65ca3e2348e12e58ca292" }, - "mini.pairs": { "branch": "main", "commit": "30cf2f01c4aaa2033db67376b9924fa2442c05d6" }, + "mini.ai": { "branch": "main", "commit": "d73c36349aa7b0bab5f77ad71701a1d42211a1df" }, + "mini.files": { "branch": "main", "commit": "a5689dae6b732955e33eec225b798d6815063179" }, + "mini.icons": { "branch": "main", "commit": "e56797f90192d81f1fda02e662fc3e8e3d775027" }, + "mini.pairs": { "branch": "main", "commit": "4a014143fcb4e9df26198ccb3ecff3b9e77a048c" }, "noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" }, "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, - "nvim-lint": { "branch": "master", "commit": "1ba49820a3c29ba6ab1b2dd441a63b3822c4e39b" }, - "nvim-lspconfig": { "branch": "master", "commit": "a683e0ddf0cf64c6cd689e18ffb480ade3c162b7" }, + "nvim-lint": { "branch": "master", "commit": "01c9842c089069ab497430159312b2c8868a4590" }, + "nvim-lspconfig": { "branch": "master", "commit": "bfcc0171a43f22afa61d927ffe9fcb6cb85dc99e" }, "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" }, "nvim-treesitter-textobjects": { "branch": "main", "commit": "851e865342e5a4cb1ae23d31caf6e991e1c99f1e" }, "nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" }, "persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" }, "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, - "render-markdown.nvim": { "branch": "main", "commit": "5adf0895310c1904e5abfaad40a2baad7fe44a07" }, + "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" }, "snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" }, "todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" }, "tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" }, diff --git a/.config/nvim/lua/plugins/colorscheme.lua b/.config/nvim/lua/plugins/colorscheme.lua index f81ac04..8394a72 100644 --- a/.config/nvim/lua/plugins/colorscheme.lua +++ b/.config/nvim/lua/plugins/colorscheme.lua @@ -13,39 +13,39 @@ -- } -- Local development: -return { - dir = "/home/chodak/src/git/nvim-hybrid-theme", - name = "hybrid-theme", - lazy = false, - priority = 1000, - config = function() - require("hybrid-theme").setup({ - theme = "dark", - transparent = false, - background_variant = "flat", - italics = { - comments = true, - keywords = false, - functions = false, - strings = false, - variables = false, - }, - }) - require("hybrid-theme").colorscheme() - end, -} - -- return { --- { --- "chodak166/nvim-hybrid-theme", --- name = "hybrid-theme", --- lazy = false, --- priority = 1000, --- config = function() --- require("hybrid-theme").setup({ --- background_variant = "semi_flat", --- }) --- require("hybrid-theme").colorscheme() --- end, --- }, +-- dir = "/home/chodak/src/git/nvim-hybrid-theme", +-- name = "hybrid-theme", +-- lazy = false, +-- priority = 1000, +-- config = function() +-- require("hybrid-theme").setup({ +-- theme = "dark", +-- transparent = false, +-- background_variant = "flat", +-- italics = { +-- comments = true, +-- keywords = false, +-- functions = false, +-- strings = false, +-- variables = false, +-- }, +-- }) +-- require("hybrid-theme").colorscheme() +-- end, -- } + +return { + { + "chodak166/nvim-hybrid-theme", + name = "hybrid-theme", + lazy = false, + priority = 1000, + config = function() + require("hybrid-theme").setup({ + background_variant = "semi_flat", + }) + require("hybrid-theme").colorscheme() + end, + }, +} diff --git a/.config/sway/config b/.config/sway/config new file mode 100644 index 0000000..fd901c1 --- /dev/null +++ b/.config/sway/config @@ -0,0 +1,610 @@ +#---------------------------------------------------------------- +# 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). +################################################################# + +# Fullscreen screenshot → save to ~/Pictures and copy to clipboard +bindsym Print exec grim -t png ~/Pictures/screenshot_$(date +%Y%m%d_%H%M%S).png && grim -t png - | wl-copy -t image/png + +# Region screenshot → select area with slurp +bindsym Shift+Print exec grim -t png -g "$(slurp)" ~/Pictures/screenshot_$(date +%Y%m%d_%H%M%S).png && grim -t png -g "$(slurp)" - | wl-copy -t image/png + +# Active window screenshot (sway-specific: get focused window geometry) +bindsym Control+Print exec grim -t png -g "$(swaymsg -t get_tree | jq -j '.. | select(.type?) | select(.focused) | .rect | "\(.x),\(.y) \(.width)x\(.height)"')" ~/Pictures/screenshot_$(date +%Y%m%d_%H%M%S).png + + +################################################################# +# 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..9bebb94 --- /dev/null +++ b/.config/sway/scripts/output-profile @@ -0,0 +1,44 @@ +#!/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, mirror the laptop display + swaymsg output "$EXTERNAL" enable pos 0 0 + swaymsg output "$LAPTOP" pos 0 0 + ;; + edp-only) + # Disable external output, laptop only + swaymsg output "$EXTERNAL" disable + ;; + 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/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 index 2ff3b9d..4de92dc 100644 --- a/.config/waybar/config.jsonc +++ b/.config/waybar/config.jsonc @@ -31,20 +31,20 @@ }, "custom/network": { - "exec": "/home/chodak/.config/waybar/network.py", + "exec": "/home/chodak/.config/waybar/network.py --icon-size 12pt", "interval": 10, "return-type": "json", "tooltip": true }, "clock#0": { - "format": "{:%H:%M %d/%m/%y}", + "format": "{:%d/%m/%y %H:%M}", "tooltip": false }, "pulseaudio": { - "format": "{icon} {volume:2}%", - "format-bluetooth": "{icon} {volume}%", + "format": "{icon} {volume:2}%", + "format-bluetooth": "{icon} {volume}%", "format-muted": "MUTE", "format-icons": { "headphones": "", @@ -59,7 +59,7 @@ }, "memory": { "interval": 5, - "format": " {}%" + "format": " {}%" }, "custom/top": { "exec": "~/.config/waybar/top.sh", @@ -73,7 +73,7 @@ "warning": 30, "critical": 15 }, - "format": "{icon} {capacity}%", + "format": "{icon} {capacity}%", "format-icons": [ "", "", @@ -83,7 +83,7 @@ ] }, "custom/disk": { - "exec": "/home/chodak/.config/waybar/disk.py", + "exec": "/home/chodak/.config/waybar/disk.py --icon-size 10pt", "interval": 30, "return-type": "json", "tooltip": true, diff --git a/.config/waybar/disk.py b/.config/waybar/disk.py index dab4761..d58347c 100755 --- a/.config/waybar/disk.py +++ b/.config/waybar/disk.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Waybar disk module: shows disk usage across all mounted partitions.""" +import argparse import json import os @@ -17,6 +18,15 @@ 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.""" @@ -87,6 +97,8 @@ def fmt_size(bytes_val): def main(): + args = parse_args() + isize = args.icon_size partitions = get_partitions() if not partitions: @@ -137,11 +149,11 @@ def main(): bar_icon = "⚠" if root_pct >= 90 else "" bar_color = _pct_color(root_pct) bar_text = ( - f"{bar_icon}" + f"{bar_icon}" f" {_span(bar_color, f'{root_pct:.0f}%')}" ) elif tooltip_lines: - bar_text = f" —" + bar_text = f" —" else: bar_text = " —" diff --git a/.config/waybar/network.py b/.config/waybar/network.py index dc67b1a..2d96771 100755 --- a/.config/waybar/network.py +++ b/.config/waybar/network.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 """Waybar network module: shows interface, IP, and up/down transfer rate.""" +import argparse import json import os import re @@ -10,6 +11,7 @@ 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: @@ -62,7 +64,16 @@ def format_rate(bps: float) -> str: 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( @@ -127,7 +138,7 @@ def main() -> None: tooltip_rate = "↓ ?/s ↑ ?/s" text = ( - f"{ICON}" + f"{ICON}" f" {iface} {ip}{rate_str}" ) tooltip = f"{itype}: {iface} — {ip} | {tooltip_rate}" diff --git a/.config/waybar/style.css b/.config/waybar/style.css index 022196d..c1fca7b 100644 --- a/.config/waybar/style.css +++ b/.config/waybar/style.css @@ -30,6 +30,10 @@ window#waybar { padding: 0 6px; } +#pulseaudio.bluetooth { + color: #89b4fa; +} + /* Workspaces: dimmed inactive, bold yellow active */ #workspaces button { padding: 0 6px; diff --git a/.local/bin/btswitch b/.local/bin/btswitch new file mode 100755 index 0000000..f4a11f8 --- /dev/null +++ b/.local/bin/btswitch @@ -0,0 +1,447 @@ +#!/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 + +# Polkit rule that lets the current user start/stop bluetooth.service +# without a password, so this script runs sudo-free. Installed on first +# run via ensure_polkit_rule(). +BT_POLKIT_RULE_FILE="/etc/polkit-1/rules.d/49-btswitch.rules" +# Sentinel file to track that the rule was installed, since the user +# may not have read/execute access to the polkit rules directory. +BT_POLKIT_SENTINEL="$HOME/.config/btswitch/.polkit_rule_installed" + +# ------------------------------------------------- +# HELPERS +# ------------------------------------------------- + +wifi_switch_needed() { + [[ -n "$1" && -n "$2" && "$1" != "$2" ]] +} + +ensure_polkit_rule() { + if [[ -f "$BT_POLKIT_SENTINEL" ]]; then + return 0 + fi + + cat >&2 << EOF + +NOTE: This script needs to start/stop the bluetooth service, which normally +requires root. A one-time polkit rule can be installed so "$USER" can do this +without sudo. You will be prompted for your password once to write: + + $BT_POLKIT_RULE_FILE + +EOF + read -r -p "Install the polkit rule now? [y/N] " answer + case "$answer" in + y | Y | yes | YES) ;; + *) + echo "Skipping. The script cannot control bluetooth.service without it." >&2 + return 1 + ;; + esac + + local tmpfile + tmpfile=$(mktemp) || return 1 + + cat > "$tmpfile" << POLKIT +// Allow user "$USER" to start/stop bluetooth.service without a password. +// Generated by btswitch on $(date -Is). +polkit.addRule(function(action, subject) { + if (action.id === "org.freedesktop.systemd1.manage-units" && + subject.user === "$USER") { + try { + var unit = action.lookup("unit"); + if (unit === "$BT_SERVICE.service") { + return polkit.Result.YES; + } + } catch (e) {} + } +}); +POLKIT + + sudo cp "$tmpfile" "$BT_POLKIT_RULE_FILE" || { + echo "ERROR: Failed to write $BT_POLKIT_RULE_FILE." >&2 + rm -f "$tmpfile" + return 1 + } + rm -f "$tmpfile" + + sudo chown root:polkitd "$BT_POLKIT_RULE_FILE" 2>/dev/null \ + || sudo chown root:root "$BT_POLKIT_RULE_FILE" + sudo chmod 644 "$BT_POLKIT_RULE_FILE" + + mkdir -p "$(dirname "$BT_POLKIT_SENTINEL")" + touch "$BT_POLKIT_SENTINEL" + + echo "✔ Installed polkit rule: $BT_POLKIT_RULE_FILE" >&2 + echo " (polkitd picks it up automatically; no restart needed.)" >&2 + return 0 +} + +systemctl_bt() { + ensure_polkit_rule || { + echo "ERROR: Cannot control bluetooth.service without the polkit rule." >&2 + echo "Run 'sudo systemctl $* $BT_SERVICE' manually, or re-run this script and install the rule." >&2 + exit 1 + } + if ! systemctl "$@" "$BT_SERVICE"; then + echo "ERROR: systemctl $* $BT_SERVICE failed." >&2 + echo "If polkit prompted for a password, the rule file may be missing." >&2 + echo "Remove $BT_POLKIT_SENTINEL and re-run to reinstall it." >&2 + exit 1 + fi +} + +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 +} + +bt_get_all_macs() { + local devices=("${!1}") + for dev in "${devices[@]}"; do + local mac + mac=$(bt_resolve_device "$dev") + [[ -n "$mac" ]] && echo "$mac" + done +} + +# ------------------------------------------------- +# BLUETOOTH CONTROL +# ------------------------------------------------- + +start_bluetooth() { + echo "Starting Bluetooth service..." + 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. Automatic pairing is not supported." + echo " Please pair it manually, then re-run this command:" + echo "" + echo " bluetoothctl" + echo " [bluetoothctl]# pair $mac" + echo " [bluetoothctl]# trust $mac" + echo " [bluetoothctl]# connect $mac" + echo " [bluetoothctl]# quit" + echo "" + echo " (Put the device in pairing mode first, if needed.)" + 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 + else + echo "$mac is already offline." + 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 +} + +bt_scan() { + local scan_duration=30 + declare -A seen_devices + local device_count=0 + + echo "Scanning for Bluetooth devices for $scan_duration seconds..." + echo "Press Ctrl+C to stop early" + echo "----------------------------------------" + + trap 'echo ""; echo "Scan stopped"; exit 0' INT TERM + + local start_time=$(date +%s) + local end_time=$((start_time + scan_duration)) + + while true; do + local current_time=$(date +%s) + if ((current_time >= end_time)); then + break + fi + + while IFS= read -r line; do + if [[ $line =~ ^[[:space:]]*([0-9A-F:]{17})[[:space:]]+(.+)$ ]]; then + local mac="${BASH_REMATCH[1]}" + local name="${BASH_REMATCH[2]}" + + if [[ -z "${seen_devices[$mac]:-}" ]]; then + seen_devices[$mac]="$name" + ((device_count++)) + echo "[$device_count] $mac - $name" + fi + fi + done < <(hcitool scan 2> /dev/null | tail -n +2) + + sleep 1 + done + + echo "----------------------------------------" + echo "Scan complete. Found $device_count unique device(s):" + for mac in "${!seen_devices[@]}"; do + echo " $mac - ${seen_devices[$mac]}" + done + + trap - INT TERM +} + +bt_list() { + echo "=== BT_DEVICES ===" + for name in "${!BT_DEVICES[@]}"; do + echo " $name: ${BT_DEVICES[$name]}" + done + echo "" + echo "=== BT_AUTO_DEVICES ===" + for name in "${BT_AUTO_DEVICES[@]}"; do + echo " $name: ${BT_DEVICES[$name]}" + 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" + echo "Usage: $0 [-d|--device DEVICE] [-t|--timed MINUTES] {start|stop|status|scan|list}" + 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 + +if [[ ${#DEVICES[@]} -gt 0 ]]; then + for dev in "${DEVICES[@]}"; do + mac=$(bt_resolve_device "$dev") + if [[ -z "$mac" ]]; then + echo "ERROR: Unknown device '$dev'. Valid aliases: ${!BT_DEVICES[@]}" + exit 1 + fi + done +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..." + systemctl_bt stop + + 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..." + systemctl_bt stop + + 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) + bt_scan + ;; + + list) + bt_list + ;; +esac diff --git a/.tmux.d/tmux-gruvbox b/.tmux.d/tmux-gruvbox new file mode 160000 index 0000000..aeb30c7 --- /dev/null +++ b/.tmux.d/tmux-gruvbox @@ -0,0 +1 @@ +Subproject commit aeb30c7172a8ed8663409207814cf47d9df10d15