Compare commits

...

38 Commits

Author SHA1 Message Date
3a1899cae1 improve zsh conf 2026-07-01 17:18:21 +02:00
5ad06b3c9e remove oh-my-zsh 2026-06-27 15:06:37 +02:00
e1886aa6b7 update conf 2026-06-24 01:22:25 +02:00
67085827b5 configure inlay typehints in nvim lsp 2026-06-22 15:47:04 +02:00
1c32b40669 add Bluetooth to quickshell bar 2026-06-22 15:46:38 +02:00
9bd023ce2c update conf 2026-06-22 01:55:20 +02:00
ef4b3aa8e0 update conf 2026-06-22 01:52:51 +02:00
6c9f5d4506 update conf 2026-06-22 01:52:51 +02:00
2da8e1a354 fix(nvim): use monokai theme with syntax highlighting / treesitter for diffview.nvim 2026-06-18 12:08:29 +02:00
48b73c40e9 update conf 2026-06-16 17:45:49 +02:00
a7aa1b2237 fix pc screen resolution 2026-05-18 13:06:43 +02:00
37e8efa795 add battery widget 2026-05-15 18:17:20 +02:00
6847a123d5 update quickshell conf 2026-05-12 12:20:34 +02:00
f28788b71a update nb monitors 2026-04-17 15:13:54 +02:00
5aec12cd1f update zhsenv 2026-04-17 14:34:55 +02:00
6efa5d599c update quickshell conf 2026-04-14 18:13:39 +02:00
9248f9b33f change filemanager 2026-04-12 15:32:24 +02:00
252c88b869 update telescope and treesitter for nvim v0.12 2026-04-10 14:08:56 +02:00
63f99ff01a update bar 2026-04-08 02:21:01 +02:00
586e4b6320 update conf 2026-04-06 01:30:38 +02:00
c2b28df404 add quickshell bar 2026-04-06 01:30:28 +02:00
8d95eeb892 add quit all motion 2026-04-05 13:25:20 +02:00
d0d3fc23e8 fix zoxide integration 2026-04-03 12:24:06 +02:00
98235a8c18 zsh conf updates
- Add lazygit alias `lg`
- Configure fzf reverse search
2026-04-01 15:02:32 +02:00
e645ea1641 Update monitors conf on notebook 2026-04-01 14:41:12 +02:00
7824d27555 remove autojmp 2026-04-01 14:38:52 +02:00
4bc368dfd9 Make workspace config device-specific 2026-04-01 14:34:26 +02:00
ef0efe4ba8 Add ignore patterns to telescope live_grep 2026-04-01 14:25:27 +02:00
7ac7ced990 fix ansible conf 2026-03-29 10:56:45 +02:00
f16af76867 update nvim plugins 2026-03-28 05:45:35 +01:00
9fbffb7558 fix neovim LSP issues 2026-03-16 23:44:52 +01:00
545f8ab328 fix nvim config 2026-02-22 17:06:22 +01:00
4093701fca hyprland updates; nvim updates; misc updates 2026-01-06 01:45:44 +01:00
7c918c3c17 update hyprland conf; fix nvim lspconfig 2025-11-21 13:37:39 +01:00
4ae85ca347 update conf 2025-10-17 14:22:01 +02:00
653a75cc18 update hyprland conf 2025-09-16 06:38:45 +02:00
42bae035ee resolve merge-conflict 2025-09-13 17:43:18 +02:00
e0fcb2988f pin mason v1 due to breaking changes with lazy.nvim 2025-07-28 18:05:30 +02:00
70 changed files with 2614 additions and 358 deletions

View File

@ -14,6 +14,12 @@ set incsearch
set hlsearch set hlsearch
set scrolloff=10 set scrolloff=10
let mapleader = " "
let maplocalleader = " "
" Binds " Binds
nnoremap gr :action ShowUsages<CR> nnoremap gr :action ShowUsages<CR>
inoremap <C-e> <Esc>i
nnoremap <C-w>m :action MoveEditorToOppositeTabGroup<CR>
nnoremap <leader>m :action MoveEditorToOppositeTabGroup<CR>

View File

@ -11,6 +11,7 @@ default_roles:
# - ssh # - ssh
- hyprland - hyprland
- ghostty - ghostty
- quickshell
temp: temp:
- docker # Container platform - docker # Container platform
@ -67,4 +68,5 @@ aur_packages:
- pgformatter-git - pgformatter-git
# - jetbrains-toolbox # - jetbrains-toolbox
config_dir: "{{ ansible_facts.env.HOME }}/.config" home_dir: "/home/johannes"
config_dir: "/home/johannes/.config"

View File

@ -1,2 +1,2 @@
[local] [local]
localhost ansible_connection=local ansible_user=johannes localhost ansible_connection=local ansible_user=johannes ansible_python_interpreter=/usr/sbin/python

View File

@ -2,19 +2,20 @@
- name: Setup dotfiles configuration - name: Setup dotfiles configuration
hosts: localhost hosts: localhost
connection: local connection: local
become: false become: true
become_user: johannes
vars: vars:
dotfiles_dir: "{{ ansible_env.HOME }}/dotfiles" dotfiles_dir: "{{ ansible_env.HOME }}/dotfiles"
# roles: # roles:
# - zsh # - zsh
pre_tasks: # pre_tasks:
- name: Create btrfs snapshot before changes # - name: Create btrfs snapshot before changes
shell: | # shell: |
sudo btrfs subvolume snapsho / /.snapshots/before-dotfiles-$(date +%Y%m%d-%H%M%S) # btrfs subvolume snapshot / /.snapshots/before-dotfiles-$(date +%Y%m%d-%H%M%S)
tags: [snapshot] # tags: [snapshot]
become: true # become: true
become_method: sudo # become_method: sudo
tasks: tasks:
- name: Install enabled roles - name: Install enabled roles

View File

@ -1,3 +1,6 @@
# Ctrl+Backspace -> backward-kill-word (send ESC + DEL)
# keybind = ctrl+delete=text:\x1b\x7f
shell-integration = zsh shell-integration = zsh
shell-integration-features = cursor,sudo,title shell-integration-features = cursor,sudo,title

View File

@ -14,8 +14,8 @@ source = ./workspaces.conf
# See https://wiki.hypr.land/Configuring/Keywords/ # See https://wiki.hypr.land/Configuring/Keywords/
$terminal = ghostty $terminal = ghostty
$fileManager = thunar $fileManager = ghostty -e yazi
$menu = wofi --show drun $menu = env XDG_CURRENT_DESKTOP=Hyprland wofi --show drun
################# #################
### AUTOSTART ### ### AUTOSTART ###
@ -23,11 +23,27 @@ $menu = wofi --show drun
# Autostart necessary processes (like notifications daemons, status bars, etc.) # Autostart necessary processes (like notifications daemons, status bars, etc.)
# Or execute your favorite apps at launch like this: # Or execute your favorite apps at launch like this:
exec-once = waybar # exec-once = waybar
exec-once = /usr/lib/hyprpolkitagent/hyprpolkitagent
# exec-once = hyprpanel
exec-once = quickshell
exec-once = dunst exec-once = dunst
exec-once = firefox exec-once = firefox
exec-once = hyprpaper exec-once = hyprpaper
exec-once = spotify # exec-once = gtk-launch deezer-pwa
# hl.window_rule({
# name = "deezer-workspace"
# match = { initialTitle = "Deezer" }
#
# workspace = "9 silent"
# })
# exec-once = ./scripts/close-monokai-popup.sh
# gnome keyring secret service
exec-once = dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP HYPRLAND_INSTANCE_SIGNATURE
# exec-once = systemctl --user stop xdg-desktop-portal-hyprland xdg-desktop-portal-gtk xdg-desktop-portal
# exec-once = systemctl --user start xdg-desktop-portal-hyprland
debug:disable_logs = false
############################# #############################
### ENVIRONMENT VARIABLES ### ### ENVIRONMENT VARIABLES ###
@ -36,8 +52,11 @@ exec-once = spotify
# See https://wiki.hypr.land/Configuring/Environment-variables/ # See https://wiki.hypr.land/Configuring/Environment-variables/
env = XCURSOR_SIZE,24 env = XCURSOR_SIZE,24
env = HYPRCURSOR_SIZE,24 env = HYPRCURSOR_SIZE,24
env = QT_QPA_PLATFORM,waylandl;xcb env = QT_QPA_PLATFORM,wayland
env = QT_QPA_PLATFORMTHEME,qt5ct env = QT_QPA_PLATFORMTHEME,qt5ct
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
env = QT_AUTO_SCREEN_SCALE_FACTOR,1
# env = QT_STYLE_OVERRIDE,kvantum
env = XDG_CURRENT_DESKTOP,Hyprland env = XDG_CURRENT_DESKTOP,Hyprland
env = XDG_SESSION_TYPE,wayland env = XDG_SESSION_TYPE,wayland
@ -45,11 +64,16 @@ env = WLR_NO_HARDWARE_CURSORS,1
env = CLUTTER_BACKEND,wayland env = CLUTTER_BACKEND,wayland
env = SDL_VIDEODRIVER,wayland,x11 env = SDL_VIDEODRIVER,wayland,x11
env = GDK_BACKEND,wayland,x11 env = GDK_BACKEND,wayland,x11
env = SDL_AUDIODRIVER,pipewire
# jetbrains # jetbrains
env = _JAVA_AWT_WM_NONREPARENTING, 1 env = _JAVA_AWT_WM_NONREPARENTING, 1
# env = AWT_TOOLKIT, MToolkit # env = AWT_TOOLKIT, MToolkit
# Force Hyprland to use the RX 9070 XT
# env = AQ_DRM_DEVICES,$HOME/.config/hypr/dgpu
# env = WLR_DRM_DEVICES,$HOME/.config/hypr/dgpu
################### ###################
### PERMISSIONS ### ### PERMISSIONS ###
################### ###################
@ -74,28 +98,25 @@ env = _JAVA_AWT_WM_NONREPARENTING, 1
# https://wiki.hypr.land/Configuring/Variables/#general # https://wiki.hypr.land/Configuring/Variables/#general
general { general {
gaps_in = 5 gaps_in = 5
gaps_out = 20 gaps_out = 10
border_size = 2 border_size = 3
# https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors # https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg col.active_border = rgba(9b1a1aee) rgba(c94040ee) 45deg
col.inactive_border = rgba(595959aa) col.inactive_border = rgba(595959aa)
# Active: cosmic purple -> neon cyan gradient
# col.active_border = rgba(7f5af0ee) rgba(00d1ffee) 45deg
#
# # Inactive: deep space slate
# col.inactive_border = rgba(0c0e14aa)
# Set to true enable resizing windows by clicking and dragging on borders and gaps # Set to true enable resizing windows by clicking and dragging on borders and gaps
resize_on_border = false resize_on_border = false
# Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on # Please see https://wiki.hypr.land/Configuring/Tearing/ before you turn this on
allow_tearing = false allow_tearing = true
layout = dwindle layout = dwindle
} }
# windowrule = immediate, class:^(cs2)$
# --- Decorations / cosmic glass --- # --- Decorations / cosmic glass ---
decoration { decoration {
@ -128,15 +149,19 @@ decoration {
} }
} }
# wofi cursor {
layerrule = blur, wofi # apply compositor blur no_hardware_cursors = true
layerrule = ignorezero, wofi # keep blur even on near-opaque colors }
layerrule = dimaround, wofi # optional: dim background behind wofi
layerrule = ignorealpha 0.90, wofi # target overall opacity for the layer
# waybar # wofi
layerrule = blur, waybar # layerrule = blur, wofi # apply compositor blur
layerrule = ignorealpha 0.15, waybar # layerrule = ignorezero, wofi # keep blur even on near-opaque colors
# layerrule = dimaround, wofi # optional: dim background behind wofi
# layerrule = ignorealpha 0.90, wofi # target overall opacity for the layer
#
# # waybar
# layerrule = blur, waybar
# layerrule = ignorealpha 0.15, waybar
# https://wiki.hypr.land/Configuring/Variables/#animations # https://wiki.hypr.land/Configuring/Variables/#animations
animations { animations {
@ -180,7 +205,7 @@ animations {
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more # See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
dwindle { dwindle {
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below # pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = true # You probably want this preserve_split = true # You probably want this
} }
@ -194,6 +219,7 @@ misc {
force_default_wallpaper = 0 # Set to 0 or 1 to disable the anime mascot wallpapers force_default_wallpaper = 0 # Set to 0 or 1 to disable the anime mascot wallpapers
disable_hyprland_logo = true # If true disables the random hyprland logo / anime girl background. :( disable_hyprland_logo = true # If true disables the random hyprland logo / anime girl background. :(
middle_click_paste = false middle_click_paste = false
vrr = 2
} }
@ -203,18 +229,18 @@ misc {
# https://wiki.hypr.land/Configuring/Variables/#input # https://wiki.hypr.land/Configuring/Variables/#input
input { input {
kb_layout = us kb_layout = us,de
kb_variant = altgr-intl kb_variant = altgr-intl
kb_model = kb_model =
kb_options = # kb_options = grp:caps_toggle
kb_rules = kb_rules =
follow_mouse = 2 follow_mouse = 2
sensitivity = 0 # -1.0 - 1.0, 0 means no modification. sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
scroll_method = on_button_down # scroll_method = on_button_down
scroll_button = 274 # scroll_button = 274
} }
# Example per-device config # Example per-device config
@ -236,11 +262,13 @@ bind = $mainMod, Q, killactive,
bind = $mainMod, N, exec, $fileManager bind = $mainMod, N, exec, $fileManager
bind = $mainMod, V, togglefloating, bind = $mainMod, V, togglefloating,
bind = $mainMod, Space, exec, $menu bind = $mainMod, Space, exec, $menu
bind = $mainMod, G, togglesplit, # dwindle bind = $mainMod, G, layoutmsg, togglesplit # dwindle
bind = $mainMod, F, fullscreen bind = $mainMod, F, fullscreen, 1
bind = $mainMod SHIFT, F, fullscreen, 0
bind = $mainMod, B, exec, firefox bind = $mainMod, B, exec, firefox
bind = $mainMod, M, exec, thunderbird bind = $mainMod, M, exec, thunderbird
bind = $mainMod, L, exec, hyprlock bind = $mainMod, L, exec, hyprlock
bind = $mainMod, P, pseudo
# Move focus with mainMod + arrow keys # Move focus with mainMod + arrow keys
bind = $mainMod, left, movefocus, l bind = $mainMod, left, movefocus, l
@ -252,6 +280,14 @@ bind = $mainMod SHIFT, left, movewindow, l
bind = $mainMod SHIFT, right, movewindow, r bind = $mainMod SHIFT, right, movewindow, r
bind = $mainMod SHIFT, up, movewindow, u bind = $mainMod SHIFT, up, movewindow, u
bind = $mainMod SHIFT, down, movewindow, d bind = $mainMod SHIFT, down, movewindow, d
bind = $mainMod SHIFT, comma, movewindow, mon:+1
bind = $mainMod SHIFT, period, movewindow, mon:-1
# Resize
binde = $mainMod ALT, left, resizeactive, -20 0
binde = $mainMod ALT, right, resizeactive, 20 0
binde = $mainMod ALT, up, resizeactive, 0 -20
binde = $mainMod ALT, down, resizeactive, 0 20
# Example special workspace (scratchpad) # Example special workspace (scratchpad)
bind = $mainMod, S, togglespecialworkspace, magic bind = $mainMod, S, togglespecialworkspace, magic
@ -274,12 +310,12 @@ bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+
bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%- bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%-
# Requires playerctl # Requires playerctl
# Spotify controls # Deezer controls
bind = CTRL ALT, K, exec, playerctl --player=spotify play-pause bind = CTRL ALT, K, exec, ~/.config/hypr/scripts/deezer-controls.sh play-pause
bind = CTRL ALT, Left, exec, playerctl --player=spotify previous bind = CTRL ALT, Left, exec, ~/.config/hypr/scripts/deezer-controls.sh previous
bind = CTRL ALT, Right, exec, playerctl --player=spotify next bind = CTRL ALT, Right, exec, ~/.config/hypr/scripts/deezer-controls.sh next
bind = CTRL ALT, Up, exec, playerctl --player=spotify volume 0.05+ bind = CTRL ALT, Up, exec, ~/.config/hypr/scripts/deezer-controls.sh +5%
bind = CTRL ALT, Down, exec, playerctl --player=spotify volume 0.05- bind = CTRL ALT, Down, exec, ~/.config/hypr/scripts/deezer-controls.sh -5%
bind = ,F7,pass,class:^(discord)$ bind = ,F7,pass,class:^(discord)$
bind = ,F8,pass,class:^(discord)$ bind = ,F8,pass,class:^(discord)$
@ -297,11 +333,61 @@ bind = , Print, exec, hyprshot -zm region
# See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules # See https://wiki.hypr.land/Configuring/Workspace-Rules/ for workspace rules
# Ignore maximize requests from apps. You'll probably like this. # Ignore maximize requests from apps. You'll probably like this.
windowrule = suppressevent maximize, class:.* windowrule {
name = suppress-maximize-events
match:class = .*
suppress_event = maximize
}
xwayland {
enabled = true
use_nearest_neighbor = true
force_zero_scaling = false
create_abstract_socket = false
}
# Fix some dragging issues with XWayland # Fix some dragging issues with XWayland
windowrule = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 windowrule {
name = fix-xwayland-drags
match:class = ^$
match:title = ^$
match:xwayland = true
match:float = true
match:fullscreen = false
match:pin = false
windowrulev2 = tag +jb, class:^jetbrains-.+$,floating:1 no_focus = true
windowrulev2 = stayfocused, tag:jb }
windowrulev2 = noinitialfocus, tag:jb
windowrule {
name = jetbrains-shortcuts
match:class = ^(jetbrains-.*|android-studio)$
no_shortcuts_inhibit = 1
}
windowrule {
name = jetbrains-tile
match:class = ^(jetbrains-.*|android-studio)$
float = 0
}
windowrule {
name = jetbrains-tab-drag
match:class = ^(jetbrains-.*|android-studio)$
match:title = ^(\s*|Delete|Replace All)$
no_initial_focus = 1
stay_focused = 1
float = 1
center = 1
}
windowrule {
name = jetbrains-commit-preview
match:class = ^(jetbrains-.*|android-studio)$
match:title = ^Commit.*$
float = 0
}

View File

@ -1,7 +1,24 @@
preload = /home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png preload = /home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
wallpaper = DP-1,/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png wallpaper {
wallpaper = DP-2,/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png monitor = DP-1
wallpaper = HDMI-A-2,/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
# fitmode = cover
}
wallpaper {
monitor = DP-2
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
# fitmode = cover
}
wallpaper {
monitor = HDMI-A-1
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
# fitmode = cover
}
wallpaper {
monitor = HDMI-A-2
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
# fitmode = cover
}
splash = false splash = false

View File

@ -1,8 +0,0 @@
# Generated by nwg-displays on 2025-09-09 at 19:00:43. Do not edit manually.
monitor=DP-1,2560x1440@165.0,6626x876,1.0
monitor=DP-2,2560x1440@165.0,4066x876,1.0
monitor=HDMI-A-1,0x0@60.0,-1x-1,1.0
monitor=HDMI-A-1,disable
monitor=HDMI-A-2,1920x1080@60.0,9186x396,1.0
monitor=HDMI-A-2,transform,3

View File

@ -0,0 +1,3 @@
# Generated by nwg-displays on 2026-04-10 at 16:05:43. Do not edit manually.
monitor=eDP-1,2880x1800@90.0,0x0,1.4999999999999996

View File

@ -0,0 +1,7 @@
# Generated by nwg-displays on 2026-04-20 at 01:17:12. Do not edit manually.
monitor=DP-1,2560x1440@165.0,6626x876,1.0,vrr,2
monitor=DP-2,2560x1440@165.0,4066x876,1.0
monitor=HDMI-A-2,1920x1080@60.0,9186x396,1.0,transform,3
# monitor=HDMI-A-2,transform,3
monitor=HDMI-A-1,disable

View File

@ -0,0 +1,34 @@
#!/usr/bin/env bash
SOCKET="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"
handle_line() {
local line="$1"
# Split into EVENT and DATA (EVENT>>DATA)
local event="${line%%>>*}"
local data="${line#*>>}"
echo "$data" > "$HOME/close_popup.tmp.txt"
case "$event" in
openwindow)
# Data format:
# WINDOWADDRESS,WORKSPACENAME,WINDOWCLASS,WINDOWTITLE
local addr ws class title
IFS=, read -r addr ws class title <<< "$data"
# echo "$data" > $HOME/close_popup.tmp.txt
if [[ "$class" == "jetbrains-webstorm" && "$title" == "Monokai Pro Trial" ]]; then
# closewindow expects address with 0x prefix
hyprctl dispatch closewindow "address:0x$addr"
# echo "$data" > "$HOME/close_popup.tmp.txt"
fi
;;
esac
}
socat -U - UNIX-CONNECT:$SOCKET | while read -r line; do
handle_line "$line";
done

View File

@ -0,0 +1,95 @@
#!/usr/bin/env bash
action="$1"
if [ -z "$action" ]; then
echo "Usage: $0 [play-pause|next|previous|+5%|-5%|50%]"
exit 1
fi
# Deezer PWA PID
root_pid=$(hyprctl clients -j | jq -r '.[] | select(.initialTitle == "Deezer") | .pid' | head -n1)
if [ -z "$root_pid" ] || [ "$root_pid" = "null" ]; then
echo "[ERROR] Deezer window not found."
exit 1
fi
case "$action" in
play-pause|next|previous)
playerctl --player="chromium.instance${root_pid}" "$action"
exit 0
;;
*)
# Volume: find sink-input whose PID is in the process tree
pids=$(pstree -p "$root_pid" | grep -oP '\(\K[0-9]+')
sink=$(pactl -f json list sink-inputs | jq -r --argjson pids "$(echo "$pids" | jq -R . | jq -s 'map(tonumber)')" \
'.[] | select(.properties["application.process.id"] | tonumber | IN($pids[])) | .index' | head -n1)
if [ -z "$sink" ]; then
echo "[ERROR] No audio stream found for Deezer."
exit 1
fi
pactl set-sink-input-volume "$sink" "$action"
;;
esac
#
# # 2) Gather ALL descendant PIDs
# declare -a pid_array
# pid_array+=("$root_pid")
#
# # Direct children
# children=($(pgrep -P "$root_pid"))
# echo "[INFO] Children: ${children[*]}"
#
# for c in "${children[@]}"; do
# pid_array+=("$c")
# gc=($(pgrep -P "$c"))
# echo "[INFO] Grandchildren of $c: ${gc[*]}"
# for g in "${gc[@]}"; do
# pid_array+=("$g")
# done
# done
#
# echo "[INFO] All collected PIDs: ${pid_array[*]}"
#
# echo "----------------------------------"
# echo "[INFO] Checking sink inputs..."
# echo "----------------------------------"
#
# sink=""
#
# while IFS= read -r block; do
# pid=$(echo "$block" | jq -r '.properties["application.process.id"] // empty')
# index=$(echo "$block" | jq -r '.index')
#
# echo "[SINK] index=$index pid=$pid"
#
# [ -z "$pid" ] && { echo " -> Skipping"; continue; }
#
# for candidate in "${pid_array[@]}"; do
# if [ "$pid" = "$candidate" ]; then
# echo " -> MATCH with PID $candidate"
# sink="$index"
# break 2
# fi
# done
#
# echo " -> No match"
#
# done < <(pactl -f json list sink-inputs | jq -c '.[]')
#
# echo "----------------------------------"
#
# if [ -z "$sink" ]; then
# echo "[ERROR] No audio stream found for Deezer."
# exit 1
# fi
#
# echo "[INFO] Deezer sink index: $sink"
# echo "[INFO] Applying: $change"
#
# pactl set-sink-input-volume "$sink" "$change"
#

View File

@ -0,0 +1,34 @@
$mainMod = SUPER # Sets "Super" key as main modifier
workspace = 1, persistent:true, default:true
workspace = 2, persistent:true
workspace = 3, persistent:true
workspace = 4, persistent:true
workspace = 5, persistent:true
workspace = 6, persistent:true
workspace = 7, persistent:true
workspace = 8, persistent:true
workspace = 9, persistent:true
workspace = 10, persistent:true
bind = $mainMod, 1, exec, hyprctl --batch "dispatch workspace 1;"
bind = $mainMod, 2, exec, hyprctl --batch "dispatch workspace 2;"
bind = $mainMod, 3, exec, hyprctl --batch "dispatch workspace 3;"
bind = $mainMod, 4, exec, hyprctl --batch "dispatch workspace 4;"
bind = $mainMod, 5, exec, hyprctl --batch "dispatch workspace 5;"
bind = $mainMod, 6, exec, hyprctl --batch "dispatch workspace 6;"
bind = $mainMod, 7, exec, hyprctl --batch "dispatch workspace 7;"
bind = $mainMod, 8, exec, hyprctl --batch "dispatch workspace 8;"
bind = $mainMod, 9, exec, hyprctl --batch "dispatch workspace 9;"
bind = $mainMod, 0, exec, hyprctl --batch "dispatch workspace 10;"
bind = $mainMod SHIFT, 1, movetoworkspacesilent, 1
bind = $mainMod SHIFT, 2, movetoworkspacesilent, 2
bind = $mainMod SHIFT, 3, movetoworkspacesilent, 3
bind = $mainMod SHIFT, 4, movetoworkspacesilent, 4
bind = $mainMod SHIFT, 5, movetoworkspacesilent, 5
bind = $mainMod SHIFT, 6, movetoworkspacesilent, 6
bind = $mainMod SHIFT, 7, movetoworkspacesilent, 7
bind = $mainMod SHIFT, 8, movetoworkspacesilent, 8
bind = $mainMod SHIFT, 9, movetoworkspacesilent, 9
bind = $mainMod SHIFT, 0, movetoworkspacesilent, 10

View File

@ -6,9 +6,9 @@ workspace = 3, monitor:DP-1, persistent:true
workspace = 4, monitor:DP-2, persistent:true, default:true workspace = 4, monitor:DP-2, persistent:true, default:true
workspace = 5, monitor:DP-2, persistent:true workspace = 5, monitor:DP-2, persistent:true
workspace = 6, monitor:DP-2, persistent:true workspace = 6, monitor:DP-2, persistent:true
workspace = 7, monitor:HDMI-A-2, persistent:true, default:true workspace = 7, monitor:HDMI-A-2, persistent:true
workspace = 8, monitor:HDMI-A-2, persistent:true workspace = 8, monitor:HDMI-A-2, persistent:true
workspace = 9, monitor:HDMI-A-2, persistent:true workspace = 9, monitor:HDMI-A-2, persistent:true, default:true
workspace = 10, monitor:HDMI-A-1, persistent:true, default:true workspace = 10, monitor:HDMI-A-1, persistent:true, default:true
bind = $mainMod, 1, exec, hyprctl --batch "dispatch focusmonitor DP-1; dispatch workspace 1;" bind = $mainMod, 1, exec, hyprctl --batch "dispatch focusmonitor DP-1; dispatch workspace 1;"

View File

@ -0,0 +1,48 @@
{
"bar.customModules.storage.paths": [
"/"
],
"menus.power.showLabel": false,
"theme.font.size": "1rem",
"theme.font.weight": 500,
"scalingPriority": "gdk",
"menus.power.lowBatteryNotification": false,
"bar.customModules.worldclock.tz": [
"Europe/Vienna"
],
"theme.bar.buttons.modules.power.enableBorder": false,
"menus.dashboard.powermenu.avatar.image": "/home/johannes/Pictures/Wallpapers/wallhaven-852q62.png",
"menus.dashboard.shortcuts.left.shortcut1.tooltip": "Firefox",
"menus.dashboard.shortcuts.left.shortcut1.command": "firefox",
"menus.dashboard.shortcuts.left.shortcut1.icon": "",
"menus.clock.time.military": true,
"menus.clock.weather.location": "Vienna",
"menus.clock.weather.unit": "metric",
"menus.volume.raiseMaximumVolume": true,
"theme.bar.floating": true,
"bar.autoHide": "never",
"theme.bar.buttons.enableBorders": true,
"theme.bar.border.width": "0.1em",
"bar.workspaces.show_icons": false,
"bar.workspaces.show_numbered": false,
"bar.workspaces.workspaceMask": false,
"bar.workspaces.showWsIcons": true,
"bar.workspaces.showApplicationIcons": true,
"bar.workspaces.showAllActive": true,
"bar.workspaces.numbered_active_indicator": "highlight",
"theme.bar.buttons.workspaces.smartHighlight": true,
"theme.bar.buttons.windowtitle.enableBorder": false,
"bar.windowtitle.custom_title": true,
"bar.windowtitle.class_name": true,
"theme.bar.buttons.clock.enableBorder": false,
"bar.clock.format": "%a %b %d %H:%M:%S",
"theme.bar.menus.menu.battery.scaling": 100,
"theme.bar.buttons.battery.visible": false,
"bar.battery.label": false,
"bar.battery.hideLabelWhenFull": true,
"theme.bar.buttons.battery.enableBorder": false,
"theme.osd.enable": true,
"theme.bar.buttons.y_margins": "0.2em",
"theme.bar.dropdownGap": "2.9em",
"theme.bar.outer_spacing": "1.6em"
}

View File

@ -0,0 +1 @@
{}

View File

@ -8,10 +8,8 @@
"margin-right": 10, // gap from the right edge "margin-right": 10, // gap from the right edge
"margin-bottom": 0, "margin-bottom": 0,
/* order: 1-2-3 | window-title | tray + clock */
// "modules-left": [ "custom/group1", "custom/group2", "custom/group3", "custom/group4","custom/group5","custom/group6","custom/group7","custom/group8","custom/group9","custom/group10"],
"modules-left": ["hyprland/workspaces"], "modules-left": ["hyprland/workspaces"],
"modules-center": [ "hyprland/window" ], "modules-center": ["wlr/taskbar"],
"modules-right": [ "tray", "custom/separator", "network", "bluetooth", "custom/separator", "disk", "cpu", "memory", "pulseaudio", "custom/separator", "clock"], "modules-right": [ "tray", "custom/separator", "network", "bluetooth", "custom/separator", "disk", "cpu", "memory", "pulseaudio", "custom/separator", "clock"],
@ -19,7 +17,7 @@
"disable-scroll": true, "disable-scroll": true,
"all-outputs": true, "all-outputs": true,
"warp-on-scroll": false, "warp-on-scroll": false,
"format": "{icon}", "format": "{icon}{windows}",
"format-icons": { "format-icons": {
"1": "1", "1": "1",
"2": "2", "2": "2",
@ -32,6 +30,29 @@
"9": "9", "9": "9",
"10": "0" "10": "0"
}, },
"window-rewrite-default": " 󱃶",
"format-window-separator": "",
"window-rewrite": {
"class<firefox>": " ",
"class<com.mitchellh.ghostty>": " ",
"class<com.mitchellh.ghostty> title<vim.*>": " ",
"class<Spotify>": " ",
"class<QOwnNotes>": " ",
"class<thunderbird>": " ",
"class<thunar>": " ",
"class<jetbrains-webstorm>": " ",
"class<jetbrains-pycharm>": " ",
"class<steam>": " ",
"class<discord>": " "
// "": "",
// "": "",
// "": "",
// "": "",
// "": "",
// "": "",
// "": "",
// "": ""
},
"persistent-workspaces": { "persistent-workspaces": {
"1": [], "1": [],
"2": [], "2": [],
@ -47,7 +68,14 @@
}, },
"hyprland/window": { "format": "{title}", "max-length": 60 }, // "hyprland/window": { "format": "{title}", "max-length": 60 },
"wlr/taskbar": {
"format": "{icon}",
"icon-theme": "Ars-Dark-Icons",
"tooltip-format": "{title}",
"on-click": "activate",
"on-click-middle": "close"
},
"tray": { "icon-size": 16, "spacing": 10 }, "tray": { "icon-size": 16, "spacing": 10 },
"pulseaudio": { "pulseaudio": {

View File

@ -60,7 +60,7 @@ window#waybar {
#tray menu menuitem:hover { background: alpha(@accent1, 0.16); } #tray menu menuitem:hover { background: alpha(@accent1, 0.16); }
#workspaces button { #workspaces button {
padding: 0 8px; padding: 0 2px;
background-color: transparent; background-color: transparent;
color: #6c7086; color: #6c7086;
border-radius: 3px; border-radius: 3px;

View File

@ -1,8 +1,46 @@
--- ---
- name: Symlink hyprland - name: Check if hypr path is a symlink
stat:
path: "{{ config_dir }}/hypr"
register: hypr_stat
- name: Remove existing hypr symlink if present
file: file:
src: "{{ role_path }}/files/hypr" path: "{{ config_dir }}/hypr"
dest: "{{ config_dir }}/hypr" state: absent
when: hypr_stat.stat.exists and hypr_stat.stat.islnk
- name: Create hypr config directory
file:
path: "{{ config_dir }}/hypr"
state: directory
mode: '0755'
- name: Symlink hyprland config files
file:
src: "{{ role_path }}/files/hypr/{{ conf_file }}"
dest: "{{ config_dir }}/hypr/{{ conf_file }}"
state: link
force: true
loop:
- hyprland.conf
- hyprlock.conf
- hyprpaper.conf
- scripts
loop_control:
loop_var: conf_file
- name: Symlink host-specific monitors.conf
file:
src: "{{ role_path }}/files/hypr/monitors_{{ ansible_facts['hostname'] }}.conf"
dest: "{{ config_dir }}/hypr/monitors.conf"
state: link
force: true
- name: Symlink host-specific workspaces.conf
file:
src: "{{ role_path }}/files/hypr/workspaces_{{ ansible_facts['hostname'] }}.conf"
dest: "{{ config_dir }}/hypr/workspaces.conf"
state: link state: link
force: true force: true
@ -27,3 +65,9 @@
state: link state: link
force: true force: true
- name: Symlink hyprpanel
file:
src: "{{ role_path }}/files/hyprpanel"
dest: "{{ config_dir }}/hyprpanel"
state: link
force: true

View File

@ -0,0 +1 @@
vim.opt_local.shiftwidth = 2

View File

@ -1 +1,4 @@
vim.opt_local.shiftwidth = 4 vim.opt_local.shiftwidth = 2
vim.opt_local.expandtab = true
vim.opt_local.softtabstop = 2
vim.opt_local.tabstop = 2

View File

View File

@ -1,41 +1,42 @@
{ {
"LuaSnip": { "branch": "master", "commit": "458560534a73f7f8d7a11a146c801db00b081df0" }, "LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
"alpha-nvim": { "branch": "main", "commit": "2b3cbcdd980cae1e022409289245053f62fb50f6" }, "alpha-nvim": { "branch": "main", "commit": "6c6a89d5b068b5251c8bdf0dd57bb921bcfeeb09" },
"auto-session": { "branch": "main", "commit": "ee320d7a59c1dfe83df7c9d58150d9f194690d53" }, "auto-session": { "branch": "main", "commit": "6cde3874a9283c2db55627acca5f28e4fa402320" },
"bufdelete.nvim": { "branch": "master", "commit": "f6bcea78afb3060b198125256f897040538bcb81" }, "bufdelete.nvim": { "branch": "master", "commit": "f6bcea78afb3060b198125256f897040538bcb81" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" }, "bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" }, "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
"cmp-nvim-lsp": { "branch": "main", "commit": "a8912b88ce488f411177fc8aed358b04dc246d7b" }, "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
"cmp-path": { "branch": "main", "commit": "e52e640b7befd8113b3350f46e8cfcfe98fcf730" }, "cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
"cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" }, "cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" }, "diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
"flutter-tools.nvim": { "branch": "main", "commit": "65b7399804315a1160933b64292d3c5330aa4e9f" }, "flutter-tools.nvim": { "branch": "main", "commit": "7d1acfd139215e02d2784733af69a61aaebe06e8" },
"friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" }, "friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
"lazygit.nvim": { "branch": "main", "commit": "cdd3527e251f96eb0527162b156ad839286fcd97" }, "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
"lspkind.nvim": { "branch": "master", "commit": "d79a1c3299ad0ef94e255d045bed9fa26025dab6" }, "lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
"lualine.nvim": { "branch": "master", "commit": "a94fc68960665e54408fe37dcf573193c4ce82c9" }, "lazygit.nvim": { "branch": "main", "commit": "a04ad0dbc725134edbee3a5eea29290976695357" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "1a31f824b9cd5bc6f342fc29e9a53b60d74af245" }, "lspkind.nvim": { "branch": "master", "commit": "c7274c48137396526b59d86232eabcdc7fed8a32" },
"mason-tool-installer.nvim": { "branch": "main", "commit": "517ef5994ef9d6b738322664d5fdd948f0fdeb46" }, "lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
"mason.nvim": { "branch": "main", "commit": "4da89f3ab04783da990f9bd40aaa36c22e59375b" }, "mason-lspconfig.nvim": { "branch": "main", "commit": "21c5b3ebeaa0412e28096bb0701434c51c1fbf76" },
"monokai-nightasty.nvim": { "branch": "main", "commit": "8182c45dcaf82e7316cfda6d439ee8ad783ae594" }, "mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" },
"neodev.nvim": { "branch": "main", "commit": "46aa467dca16cf3dfe27098042402066d2ae242d" }, "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
"none-ls-extras.nvim": { "branch": "main", "commit": "924fe88a9983c7d90dbb31fc4e3129a583ea0a90" }, "mini.nvim": { "branch": "main", "commit": "1345d191bb3da9c7b0e977f4387c5761f9bff68d" },
"none-ls.nvim": { "branch": "main", "commit": "3ce66bc62eb363f19cceeb1fae2e71ea2bede56d" }, "monokai-nightasty.nvim": { "branch": "main", "commit": "1e9b92006782a1217d0a7a871b871768f1cbf5ed" },
"nvim-autopairs": { "branch": "master", "commit": "23320e75953ac82e559c610bec5a90d9c6dfa743" }, "none-ls-extras.nvim": { "branch": "main", "commit": "27681d797a26f1b4d6119296df42f5204c88a2dc" },
"nvim-cmp": { "branch": "main", "commit": "b5311ab3ed9c846b585c0c15b7559be131ec4be9" }, "none-ls.nvim": { "branch": "main", "commit": "01f8e62ea11603e59ad9ff7afcfa94fd183f76d6" },
"nvim-lsp-file-operations": { "branch": "master", "commit": "9744b738183a5adca0f916527922078a965515ed" }, "nvim-autopairs": { "branch": "master", "commit": "7b9923abad60b903ece7c52940e1321d39eccc79" },
"nvim-lspconfig": { "branch": "master", "commit": "4d3b3bb8815fbe37bcaf3dbdb12a22382bc11ebe" }, "nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
"nvim-surround": { "branch": "main", "commit": "7a7a78a52219a3312c1fcabf880cea07a7956a5f" }, "nvim-lsp-file-operations": { "branch": "master", "commit": "b9c795d3973e8eec22706af14959bc60c579e771" },
"nvim-tree.lua": { "branch": "master", "commit": "a9156c013916d3c8a2905ab2e26e9dd74beb642e" }, "nvim-lspconfig": { "branch": "master", "commit": "bfcc0171a43f22afa61d927ffe9fcb6cb85dc99e" },
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" }, "nvim-surround": { "branch": "main", "commit": "2e93e154de9ff326def6480a4358bfc149d5da2c" },
"nvim-ts-autotag": { "branch": "main", "commit": "a1d526af391f6aebb25a8795cbc05351ed3620b5" }, "nvim-tree.lua": { "branch": "master", "commit": "8e8be709c9a93e1ebefde7964884a8b564bfd3e0" },
"nvim-web-devicons": { "branch": "master", "commit": "4a8369f4c78ef6f6f895f0cec349e48f74330574" }, "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" }, "nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
"telescope-fzf-native.nvim": { "branch": "main", "commit": "1f08ed60cafc8f6168b72b80be2b2ea149813e55" }, "nvim-web-devicons": { "branch": "master", "commit": "dfbfaa967a6f7ec50789bead7ef87e336c1fa63c" },
"telescope.nvim": { "branch": "0.1.x", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" }, "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
"todo-comments.nvim": { "branch": "main", "commit": "304a8d204ee787d2544d8bc23cd38d2f929e7cc5" }, "render-markdown.nvim": { "branch": "main", "commit": "f422cb5c6855f150e2ddcfaf44e7157b98b34f6a" },
"venv-selector.nvim": { "branch": "regexp", "commit": "c677caa1030808a9f90092e522de7cc20c1390dd" }, "telescope-fzf-native.nvim": { "branch": "main", "commit": "b25b749b9db64d375d782094e2b9dce53ad53a40" },
"vim-floaterm": { "branch": "master", "commit": "fd4bdd66eca56c6cc59f2119e4447496d8cde2ea" }, "telescope.nvim": { "branch": "master", "commit": "9377230aa5305d9e9aca4ed8dadf1070fb4aa9fc" },
"which-key.nvim": { "branch": "main", "commit": "370ec46f710e058c9c1646273e6b225acf47cbed" } "vim-floaterm": { "branch": "master", "commit": "bb4ba7952e906408e1f83b215f55ffe57efcade6" },
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
} }

View File

@ -20,6 +20,10 @@ keymap.set('n', 'j', 'gj', opts)
keymap.set('n', '<UP>', 'gk', opts) keymap.set('n', '<UP>', 'gk', opts)
keymap.set('n', '<DOWN>', 'gj', opts) keymap.set('n', '<DOWN>', 'gj', opts)
-- bind tabswitching to Tab and Shift-Tab
keymap.set('n', '<Tab>', ':tabnext<CR>', opts)
keymap.set('n', '<S-Tab>', ':tabprev<CR>', opts)
-- COMMANDS -- COMMANDS
-- print current working directory -- print current working directory

View File

@ -1,5 +1,7 @@
local opt = vim.opt local opt = vim.opt
vim.filetype.add({ extension = { mdx = 'mdx' } })
-- numbers -- numbers
opt.relativenumber = true opt.relativenumber = true
opt.number = true opt.number = true

View File

@ -1,4 +1,4 @@
local lazypath = vim.fn.stdpath('data') .. 'lazy/lazy.nvim' local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
-- bootstrap lazy -- bootstrap lazy
if not (vim.uv or vim.loop).fs_stat(lazypath) then if not (vim.uv or vim.loop).fs_stat(lazypath) then

View File

@ -29,12 +29,37 @@ return {
local opts = { silent = true, noremap = true } local opts = { silent = true, noremap = true }
local function git_branch()
local branch = vim.trim(vim.fn.system('git branch --show-current 2>/dev/null'))
if vim.v.shell_error ~= 0 or branch == '' then return nil end
return branch
end
local function session_save()
local branch = git_branch()
if branch then
vim.cmd('AutoSession save ' .. branch)
else
vim.cmd('AutoSession save')
end
end
local function session_restore()
local branch = git_branch()
if branch then
vim.cmd('AutoSession restore ' .. branch)
else
vim.cmd('AutoSession restore')
end
end
local keymap = vim.keymap local keymap = vim.keymap
keymap.set('n', '<leader>ss', '<cmd>SessionSave<cr>', opts) keymap.set('n', '<leader>ss', session_save, opts)
keymap.set('n', '<leader>sq', '<cmd>SessionSave<cr><cmd>wa<cr><cmd>qa<cr>', opts) keymap.set('n', '<leader>sq', function() session_save(); vim.cmd('wa | qa') end, opts)
keymap.set('n', '<leader>sl', '<cmd>SessionSearch<cr>', opts) keymap.set('n', '<leader>sl', '<cmd>AutoSession search<cr>', opts)
keymap.set('n', '<leader>sr', '<cmd>SessionRestore<cr>', opts) keymap.set('n', '<leader>sr', session_restore, opts)
keymap.set('n', '<leader>sd', '<cmd>Autosession delete<cr>', opts) keymap.set('n', '<leader>sd', '<cmd>AutoSession delete<cr>', opts)
keymap.set('n', '<leader>qa', '<cmd>qa<cr>', opts)
local function named_save() local function named_save()
vim.ui.input({ prompt = 'Enter Session name: ' }, function(input) vim.ui.input({ prompt = 'Enter Session name: ' }, function(input)
@ -46,12 +71,14 @@ return {
local wk = require('which-key') local wk = require('which-key')
wk.add({ wk.add({
{ '<leader>s', group = 'auto-session' }, { '<leader>s', group = 'auto-session' },
{ '<leader>q', group = 'quit' },
{ '<leader>ss', desc = 'SessionSave' }, { '<leader>ss', desc = 'SessionSave' },
{ '<leader>sS', desc = 'NamedSessionSave' }, { '<leader>sS', desc = 'NamedSessionSave' },
{ '<leader>sq', desc = 'Save and Quit' }, { '<leader>sq', desc = 'Save and Quit' },
{ '<leader>sl', desc = 'SessionList (SessionSearch)'}, { '<leader>sl', desc = 'SessionList (SessionSearch)'},
{ '<leader>sr', desc = 'SessionRestore'}, { '<leader>sr', desc = 'SessionRestore'},
{ '<leader>sd', desc = 'SessionDelete'}, { '<leader>sd', desc = 'SessionDelete'},
{ '<leader>qa', desc = 'Quit All'},
}) })
end, end,
} }

View File

@ -39,6 +39,7 @@ return {
['<Enter>'] = cmp.mapping.confirm({ select = true }), ['<Enter>'] = cmp.mapping.confirm({ select = true }),
}), }),
sources = cmp.config.sources({ sources = cmp.config.sources({
{ name = 'lazydev', group_index = 0 },
{ name = 'nvim_lsp' }, { name = 'nvim_lsp' },
{ name = 'luasnip' }, { name = 'luasnip' },
{ name = 'buffer' }, { name = 'buffer' },

View File

@ -1,3 +1,28 @@
return { return {
'sindrets/diffview.nvim' 'sindrets/diffview.nvim',
config = function ()
require('diffview').setup({
enhanced_diff_hl = true
})
-- Fix text color on fully added/deleted lines
vim.api.nvim_set_hl(0, "DiffAdd", { bg = "#2d4a2d", fg = "NONE" })
vim.api.nvim_set_hl(0, "DiffDelete", { bg = "#4a2d2d", fg = "NONE" })
vim.api.nvim_set_hl(0, "DiffChange", { bg = "#2d3a4a", fg = "NONE" })
vim.api.nvim_set_hl(0, "DiffText", { bg = "#1a5a7a", fg = "NONE" })
local wk = require('which-key')
wk.add({
{ '<leader>d', group = 'diffview' },
{ '<leader>do', '<cmd>DiffviewOpen<cr>', desc = 'Open diff view' },
{ '<leader>dh', '<cmd>DiffviewFileHistory<cr>', desc = 'File history (repo)' },
{ '<leader>df', '<cmd>DiffviewFileHistory %<cr>', desc = 'File history (current)' },
{ '<leader>dc', '<cmd>DiffviewClose<cr>', desc = 'Close diff view' },
-- Toggle panels
{ '<leader>dt', '<cmd>DiffviewToggleFiles<cr>', desc = 'Toggle file panel' },
{ '<leader>dr', '<cmd>DiffviewRefresh<cr>', desc = 'Refresh' },
})
end
} }

View File

@ -8,7 +8,7 @@ return {
'LazyGitFilter', 'LazyGitFilter',
'LazyGitFilterCurrentFile', 'LazyGitFilterCurrentFile',
}, },
depedencies = { dependencies = {
'nvim-lua/plenary.nvim', 'nvim-lua/plenary.nvim',
}, },
} }

View File

@ -2,14 +2,21 @@ return {
'neovim/nvim-lspconfig', 'neovim/nvim-lspconfig',
event = { 'BufReadPre', 'BufNewFile' }, event = { 'BufReadPre', 'BufNewFile' },
dependencies = { dependencies = {
'williamboman/mason.nvim', 'mason-org/mason.nvim',
'mason-org/mason-lspconfig.nvim',
'hrsh7th/cmp-nvim-lsp', 'hrsh7th/cmp-nvim-lsp',
{ 'antosha417/nvim-lsp-file-operations', config = true }, { 'antosha417/nvim-lsp-file-operations', config = true },
{ 'folke/neodev.nvim', opts = {} }, {
'folke/lazydev.nvim',
ft = 'lua',
opts = {
library = {
{ path = '${3rd}/luv/library', words = { 'vim%.uv' } },
},
},
},
}, },
config = function() config = function()
local lspconfig = require('lspconfig')
local mason_lspconfig = require('mason-lspconfig')
local cmp_nvim_lsp = require('cmp_nvim_lsp') local cmp_nvim_lsp = require('cmp_nvim_lsp')
local keymap = vim.keymap local keymap = vim.keymap
local wk = require('which-key') local wk = require('which-key')
@ -21,13 +28,17 @@ return {
return { desc = desc, buffer = ev.buf, silent = true } return { desc = desc, buffer = ev.buf, silent = true }
end end
keymap.set('n', 'gr', vim.lsp.buf.rename, opts('Rename')) -- Render inlay hints (type/param/return) for servers that send them
keymap.set('n', 'K', vim.lsp.buf.hover, opts('Show documentation')) if vim.lsp.inlay_hint then
keymap.set('n', 'gR', '<cmd>Telescope lsp_references<cr>', opts('Show LSP references')) vim.lsp.inlay_hint.enable(true, { bufnr = ev.buf })
end
keymap.set('n', 'gd', '<cmd>Telescope lsp_definitions<cr>', opts('Go to definition'))
keymap.set('n', 'gr', '<cmd>Telescope lsp_references<cr>', opts('Show LSP references'))
keymap.set('n', 'gD', vim.lsp.buf.declaration, opts('Go to declaration')) keymap.set('n', 'gD', vim.lsp.buf.declaration, opts('Go to declaration'))
keymap.set('n', 'gd', '<cmd>Telescope lsp_definitions<cr>', opts('Show LSP definitions'))
keymap.set('n', 'gi', '<cmd>Telescope lsp_implementations<cr>', opts('Show LSP implementations')) keymap.set('n', 'gi', '<cmd>Telescope lsp_implementations<cr>', opts('Show LSP implementations'))
keymap.set('n', 'gt', '<cmd>Telescope lsp_type_definitions<cr>', opts('Show LSP type definitions')) keymap.set('n', 'gt', '<cmd>Telescope lsp_type_definitions<cr>', opts('Show LSP type definitions'))
keymap.set('n', 'K', vim.lsp.buf.hover, opts('Show documentation'))
keymap.set('n', 'gE', vim.diagnostic.open_float, opts('Show diagnostics')) keymap.set('n', 'gE', vim.diagnostic.open_float, opts('Show diagnostics'))
keymap.set({ 'n', 'v' }, 'ga', vim.lsp.buf.code_action, opts('Show available code actions')) keymap.set({ 'n', 'v' }, 'ga', vim.lsp.buf.code_action, opts('Show available code actions'))
wk.add({ wk.add({
@ -37,89 +48,87 @@ return {
end, end,
}) })
-- Apply default capabilities to all servers
local capabilities = cmp_nvim_lsp.default_capabilities() local capabilities = cmp_nvim_lsp.default_capabilities()
mason_lspconfig.setup_handlers({ vim.lsp.config('*', { capabilities = capabilities })
-- default handler for installed servers
function(server_name) -- lua_ls: suppress vim global warnings, improve completion
lspconfig[server_name].setup({ vim.lsp.config('lua_ls', {
capabilities = capabilities,
})
end,
-- TODO add lsp configs
['lua_ls'] = function()
lspconfig['lua_ls'].setup({
capabilities = capabilities,
settings = { settings = {
Lua = { Lua = {
diagnostics = { diagnostics = {
globals = { 'vim' }, globals = { 'vim' },
}, },
completion = { completion = {
callSnippet = 'Replace' callSnippet = 'Replace',
},
},
}, },
}
}
}) })
end,
['emmet_ls'] = function() -- ts_ls: TypeScript/JavaScript/JSX/TSX
-- capabilities.textDocument.completion.completionItem.snippetSupport = true vim.lsp.config('ts_ls', {
lspconfig['emmet_ls'].setup({ cmd = { 'typescript-language-server', '--stdio' },
capabilities = capabilities,
filetypes = { filetypes = {
'html',
'css',
'sass',
'scss',
'less',
'javascript', 'javascript',
'javascriptreact', 'javascriptreact',
'typescript', 'typescript',
'typescriptreact', 'typescriptreact',
'svelte', },
'vue',
}
})
end,
['ts_ls'] = function()
lspconfig['ts_ls'].setup({
capabilities = capabilities,
cmd = { "typescript-language-server", "--stdio" },
init_options = { init_options = {
hostInfo = "neovim", hostInfo = 'neovim',
-- preferences = { },
-- includePackagesJsonAutoImports = "on", settings = {
-- } typescript = {
inlayHints = {
includeInlayVariableTypeHints = true,
includeInlayParameterNameHints = 'all',
includeInlayFunctionLikeReturnTypeHints = true,
}
}
} }
}) })
end,
-- ['pyright'] = function() -- eslint: linting for JS/TS/React projects
-- lspconfig['pyright'].setup({ vim.lsp.config('eslint', {
-- -- cmd = { "pyright-langserver", "--stdio" }, settings = {
-- -- filetypes = { "python" }, workingDirectories = { mode = 'auto' },
-- -- root_dir = function(filename) },
-- -- return util.root_pattern(unpack(root_files))(filename) or util.path.dirname(filename)
-- -- end,
-- -- settings = {
-- -- python = {
-- -- analysis = {
-- -- autoSearchPaths = true,
-- -- diagnosticMode = "workspace",
-- -- useLibraryCodeForTypes = true
-- -- }
-- -- }
-- -- }
-- capabilities = capabilities,
-- settings = {
-- python = {
-- analysis = {
-- useLibraryCodeForTypes = true,
-- typeCheckingMode = "basic",
-- -- configPath = vim.fn.expand('./pyrightconfig.json')
-- }
-- }
-- },
-- })
-- end,
}) })
end
-- Explicitly enable all mason-managed servers.
-- This is belt-and-suspenders alongside mason-lspconfig's automatic_enable,
-- ensuring servers start regardless of mason-lspconfig version.
-- mdx_analyzer: needs typescript SDK path to find tsserverlibrary.js
vim.lsp.config('mdx_analyzer', {
init_options = {
typescript = {
tsdk = vim.fn.stdpath('data') .. '/mason/packages/typescript-language-server/node_modules/typescript/lib',
},
},
})
-- hyprls: not managed by mason, enable manually
vim.lsp.config('hyprls', {
cmd = { 'hyprls' },
filetypes = { 'hyprlang' },
root_dir = function(fname)
return vim.fn.getcwd()
end,
})
vim.lsp.enable('hyprls')
vim.lsp.config('qmlls', {
cmd = { 'qmlls6' },
})
vim.lsp.enable('qmlls')
-- vim.lsp.config('postgres_lsp', {
-- cmd = { 'postgres-language-server', 'lsp-proxy' },
-- })
vim.lsp.enable('postgres_lsp')
vim.lsp.enable({ 'lua_ls', 'html', 'cssls', 'bashls', 'ts_ls', 'eslint', 'tailwindcss', 'mdx_analyzer', 'rust_analyzer', 'docker_compose_language_service', 'docker_language_server' })
end,
} }

View File

@ -1,7 +1,10 @@
return { return {
{ 'williamboman/mason.nvim', commit = '4da89f3' }, {
{ 'williamboman/mason-lspconfig.nvim', commit = '1a31f82' }, 'mason-org/mason.nvim',
{ 'WhoIsSethDaniel/mason-tool-installer.nvim' }, dependencies = {
'mason-org/mason-lspconfig.nvim',
'WhoIsSethDaniel/mason-tool-installer.nvim',
},
config = function() config = function()
local mason = require('mason') local mason = require('mason')
local mason_lspconfig = require('mason-lspconfig') local mason_lspconfig = require('mason-lspconfig')
@ -10,14 +13,14 @@ return {
mason.setup({ mason.setup({
ui = { ui = {
icons = { icons = {
package_installed = "", package_installed = '',
package_pending = "", package_pending = '',
package_uninstalled = "", package_uninstalled = '',
}, },
}, },
pip = { pip = {
upgrade_pip = true, upgrade_pip = true,
} },
}) })
mason_lspconfig.setup({ mason_lspconfig.setup({
@ -29,6 +32,9 @@ return {
-- 'emmet_ls', -- 'emmet_ls',
'bashls', 'bashls',
'ts_ls', 'ts_ls',
'eslint',
'tailwindcss',
'rust_analyzer',
}, },
}) })
@ -39,7 +45,8 @@ return {
-- 'black', -- 'black',
-- 'pylint', -- 'pylint',
-- 'eslint_d', -- 'eslint_d',
} },
}) })
end, end,
},
} }

View File

@ -8,7 +8,17 @@ return {
local sources = { local sources = {
null_ls.builtins.formatting.prettier.with({ null_ls.builtins.formatting.prettier.with({
filetypes = { 'javascript', 'typescript', 'typescriptreact', 'css', 'html', 'json', 'markdown', 'yaml' } filetypes = {
'javascript',
'javascriptreact',
'typescript',
'typescriptreact',
'css',
'html',
'json',
'markdown',
'yaml',
},
}), }),
-- null_ls.builtins.formatting.black, -- null_ls.builtins.formatting.black,
-- null_ls.builtins.formatting.djlint, -- null_ls.builtins.formatting.djlint,
@ -19,16 +29,26 @@ return {
null_ls.setup({ null_ls.setup({
sources = sources, sources = sources,
-- on_attach = function(client, bufnr)
-- if client.server_capabilities.documentFormattingProvider then
-- vim.
-- end
-- end,
}) })
local keymap = vim.keymap local keymap = vim.keymap
local fmt_func = function() local fmt_func = function()
vim.lsp.buf.format({ async = true }) local null_ls_active = false
for _, client in ipairs(vim.lsp.get_clients({ bufnr = 0 })) do
if client.name == "null-ls" then
null_ls_active = true
break
end
end
vim.lsp.buf.format({
async = true,
filter = function(client)
if null_ls_active then
return client.name == "null-ls"
end
return true
end,
})
end end
keymap.set('n', '<leader>ii', fmt_func, { noremap = true, silent = true }) keymap.set('n', '<leader>ii', fmt_func, { noremap = true, silent = true })

View File

@ -0,0 +1,8 @@
return {
'MeanderingProgrammer/render-markdown.nvim',
dependencies = {
'nvim-treesitter/nvim-treesitter',
'nvim-mini/mini.nvim',
},
}

View File

@ -1,6 +1,5 @@
return { return {
'nvim-telescope/telescope.nvim', 'nvim-telescope/telescope.nvim', -- 0.1.x dropped; master has nvim 0.12 compat (ft_to_lang removal)
branch = '0.1.x',
dependencies = { dependencies = {
'nvim-lua/plenary.nvim', 'nvim-lua/plenary.nvim',
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" }, { "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
@ -13,9 +12,12 @@ return {
-- local trouble = require('trouble') -- local trouble = require('trouble')
-- local trouble_telescope = require('trouble.sources.telescope') -- local trouble_telescope = require('trouble.sources.telescope')
local ignore_patterns = { "node_modules/" }
telescope.setup({ telescope.setup({
defaults = { defaults = {
path_display = { 'smart' }, path_display = { 'smart' },
file_ignore_patterns = ignore_patterns,
}, },
}) })
@ -26,9 +28,13 @@ return {
local keymap = vim.keymap local keymap = vim.keymap
local builtin = require('telescope.builtin') local builtin = require('telescope.builtin')
keymap.set('n', '<leader>ff', builtin.find_files, opts) keymap.set('n', '<leader>ff', function() builtin.find_files({ hidden = true }) end, opts)
keymap.set('n', '<leader>fh', builtin.oldfiles, opts) keymap.set('n', '<leader>fh', builtin.oldfiles, opts)
keymap.set('n', '<leader>fs', builtin.live_grep, opts) keymap.set('n', '<leader>fs', function ()
builtin.live_grep({
file_ignore_patterns = vim.list_extend(vim.deepcopy(ignore_patterns), { "supabase/migrations/" })
})
end, opts)
keymap.set('n', '<leader>fc', builtin.grep_string, opts) keymap.set('n', '<leader>fc', builtin.grep_string, opts)
local wk = require('which-key') local wk = require('which-key')

View File

@ -1,23 +1,18 @@
return { return {
"nvim-treesitter/nvim-treesitter", "nvim-treesitter/nvim-treesitter",
branch = "main", -- master is frozen; main required for nvim 0.12+
event = { "BufReadPre", "BufNewFile" }, event = { "BufReadPre", "BufNewFile" },
build = ":TSUpdate", build = ":TSUpdate",
dependencies = { dependencies = {
"windwp/nvim-ts-autotag", "windwp/nvim-ts-autotag",
}, },
config = function() config = function()
local treesitter = require('nvim-treesitter.configs') require('nvim-treesitter').setup({
treesitter.setup({
auto_install = true, auto_install = true,
sync_install = false, prefer_git = true, -- tarball downloads fail for some parsers on 0.12
highlight = { highlight = { enable = true },
enable = true,
additional_vim_regex_highlighting = false,
},
indent = { enable = true }, indent = { enable = true },
autotag = { enable = true }, install = {
ensure_installed = {
'json', 'json',
'javascript', 'javascript',
'typescript', 'typescript',
@ -35,17 +30,10 @@ return {
'python', 'python',
'gitignore', 'gitignore',
'c', 'c',
}, 'mdx',
ignore_install = {},
incremental_selection = {
enable = true,
keymaps = {
init_selection = '<C-Space>',
node_incremental = '<C-Space>',
scope_incremental = false,
node_decremental = '<bs>',
},
}, },
}) })
require('nvim-ts-autotag').setup()
end, end,
} }

View File

@ -1,24 +0,0 @@
return {
"linux-cultist/venv-selector.nvim",
dependencies = {
"neovim/nvim-lspconfig",
-- "mfussenegger/nvim-dap", "mfussenegger/nvim-dap-python", --optional
{ "nvim-telescope/telescope.nvim", branch = "0.1.x", dependencies = { "nvim-lua/plenary.nvim" } },
},
lazy = false,
branch = "regexp", -- This is the regexp branch, use this for the new version
config = function()
require("venv-selector").setup({
-- settings = {
-- search = {
-- my_envs = {
-- command = "fd python$ ~/git/cerenim/backend/"
-- }
-- }
-- }
})
end,
keys = {
{ ",v", "<cmd>VenvSelect<cr>" },
},
}

View File

@ -3,6 +3,8 @@
package: package:
name: neovim name: neovim
state: present state: present
become: yes
become_user: root
- name: Symlink neovim - name: Symlink neovim
file: file:

View File

@ -0,0 +1,166 @@
import Quickshell
import QtQuick
import "../components"
PanelWindow {
id: root
property var modelData
screen: modelData
anchors {
top: true
left: true
right: true
}
implicitHeight: 40
color: Theme.bg
readonly property string screenName: modelData.name
property string activePopup: ""
readonly property int bw: Theme.borderWidth // 2
readonly property int pad: Theme.enclosureMargin // 3
// ── Bar bottom border ─────────────────────────────────────────────
Rectangle {
anchors { bottom: parent.bottom; left: parent.left; right: parent.right }
height: root.bw
color: Theme.border
}
// ── Bar content ───────────────────────────────────────────────────
Item {
anchors {
fill: parent
leftMargin: 8
rightMargin: 8
topMargin: root.bw
bottomMargin: root.bw
}
Workspaces {
anchors { left: parent.left; verticalCenter: parent.verticalCenter }
screenName: root.screenName
}
MusicPlayer {
id: musicChip
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
onClicked: root.activePopup = root.activePopup === "controls" ? "" : "controls"
}
Row {
id: rightRow
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
spacing: 8
SysTray {
anchors.verticalCenter: parent.verticalCenter
barWindow: root
}
Battery {
anchors.verticalCenter: parent.verticalCenter
}
NetworkStatus {
anchors.verticalCenter: parent.verticalCenter
}
BluetoothControl {
anchors.verticalCenter: parent.verticalCenter
onClickedLeft: root.activePopup = root.activePopup === "bluetooth" ? "" : "bluetooth"
}
VolumeControl {
anchors.verticalCenter: parent.verticalCenter
onClickedLeft: root.activePopup = root.activePopup === "mixer" ? "" : "mixer"
}
Clock {
id: clockDisp
anchors.verticalCenter: parent.verticalCenter
onClicked: root.activePopup = root.activePopup === "calendar" ? "" : "calendar"
}
}
}
// ── Music controls popup ──────────────────────────────────────────
PopoutWindow {
popupName: "controls"
activePopup: root.activePopup
anchor.window: root
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
anchor.rect.x: Math.round((root.width - implicitWidth) / 2)
implicitWidth: Math.max(musicChip.implicitWidth + 2 * root.pad,
ctrlContent.implicitWidth + 2 * root.bw)
implicitHeight: ctrlContent.implicitHeight + root.bw + Theme.radius
MusicPlayerControls {
id: ctrlContent
anchors { left: parent.left; right: parent.right }
player: musicChip.player
}
}
// ── Calendar popup ────────────────────────────────────────────────
PopoutWindow {
popupName: "calendar"
activePopup: root.activePopup
anchor.window: root
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
readonly property real pw: Math.max(rightRow.width + 2 * root.pad,
calContent.implicitWidth + 2 * root.bw)
anchor.rect.x: root.width - pw
implicitWidth: pw
implicitHeight: calContent.implicitHeight + root.bw + Theme.radius
CalendarContent {
id: calContent
anchors { left: parent.left; right: parent.right }
now: clockDisp.now
}
}
// ── Bluetooth popup ───────────────────────────────────────────────
PopoutWindow {
id: btPopup
popupName: "bluetooth"
activePopup: root.activePopup
anchor.window: root
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
readonly property real pw: Math.max(rightRow.width + 2 * root.pad,
btContent.implicitWidth + 2 * root.bw)
anchor.rect.x: root.width - pw
implicitWidth: pw
implicitHeight: btContent.implicitHeight + root.bw + Theme.radius
BluetoothContent {
id: btContent
anchors { left: parent.left; right: parent.right }
scanning: btPopup._open
}
}
// ── Volume mixer popup ────────────────────────────────────────────
PopoutWindow {
popupName: "mixer"
activePopup: root.activePopup
anchor.window: root
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
readonly property real pw: Math.max(rightRow.width + 2 * root.pad,
mixContent.implicitWidth + 2 * root.bw)
anchor.rect.x: root.width - pw
implicitWidth: pw
implicitHeight: mixContent.implicitHeight + root.bw + Theme.radius
VolumeMixerContent {
id: mixContent
anchors { left: parent.left; right: parent.right }
}
}
}

View File

@ -0,0 +1,84 @@
import Quickshell
import Quickshell.Io
import QtQuick
import "../components"
Item {
id: root
implicitWidth: hasBattery ? row.implicitWidth + 10 : 0
implicitHeight: 24
visible: hasBattery
property bool hasBattery: false
property int percent: 0
property string status: "Unknown" // "Charging", "Discharging", "Full", "Unknown"
// \uf0e7 = bolt (charging), \uf240-\uf244 = FA battery full…empty
readonly property string battIcon: {
if (status === "Charging" || status === "Full") return "\uf0e7"
if (percent <= 10) return "\uf244"
if (percent <= 25) return "\uf243"
if (percent <= 50) return "\uf242"
if (percent <= 75) return "\uf241"
return "\uf240"
}
readonly property bool isLow: percent <= 20 && status === "Discharging"
Timer {
interval: 30000
running: true
repeat: true
triggeredOnStart: true
onTriggered: battProc.running = true
}
Process {
id: battProc
// Find first BAT* supply; print "NONE" if none exists
command: ["sh", "-c",
"BAT=$(ls /sys/class/power_supply/ 2>/dev/null | grep -m1 '^BAT'); " +
"[ -z \"$BAT\" ] && echo NONE && exit 0; " +
"echo \"$(cat /sys/class/power_supply/$BAT/capacity):$(cat /sys/class/power_supply/$BAT/status)\""
]
stdout: SplitParser {
splitMarker: "\n"
onRead: data => {
const line = data.trim()
if (line === "" || line === "NONE") {
root.hasBattery = false
return
}
const sep = line.indexOf(":")
if (sep < 0) return
root.percent = parseInt(line.substring(0, sep)) || 0
root.status = line.substring(sep + 1)
root.hasBattery = true
}
}
onExited: running = false
}
Row {
id: row
anchors.centerIn: parent
spacing: 5
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.battIcon
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 14
color: root.status === "Charging" ? Theme.accent
: root.isLow ? "#cc3333"
: Theme.text
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.percent + "%"
font.pixelSize: 11
color: root.isLow ? "#cc3333" : Theme.textDim
}
}
}

View File

@ -0,0 +1,187 @@
import Quickshell.Bluetooth
import QtQuick
import QtQuick.Layouts
import "../components"
// Bluetooth panel a plain Item so it can live inside an expanding section border.
Item {
id: root
implicitWidth: 300
implicitHeight: btCol.implicitHeight + 24
// Driven by the popup's open state — only scan for nearby devices while visible.
property bool scanning: false
readonly property var adapter: Bluetooth.defaultAdapter
readonly property bool powered: adapter?.enabled ?? false
// Run device discovery only while the popup is open and the adapter is on.
Binding {
target: root.adapter
property: "discovering"
value: root.scanning && root.powered
when: root.adapter !== null
}
// \uf293 = FA bluetooth-b, \uf294 = FA bluetooth
function devIcon(dev) {
return dev.connected ? "\uf293" : "\uf294"
}
ColumnLayout {
id: btCol
anchors { fill: parent; margins: 12 }
spacing: 4
RowLayout {
Layout.fillWidth: true
Layout.bottomMargin: 2
Text {
text: "Bluetooth"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
}
Text {
text: root.powered ? "On" : "Off"
font.pixelSize: 10
color: root.powered ? Theme.accent : Theme.textDim
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: if (root.adapter) root.adapter.enabled = !root.adapter.enabled
}
}
}
Text {
visible: !root.powered
text: "Adapter disabled"
color: Theme.textDim
font.pixelSize: 11
Layout.fillWidth: true
}
// ── Paired devices ────────────────────────────────────────────
Text {
visible: root.powered
text: "Paired"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
Layout.topMargin: 6
Layout.bottomMargin: 2
}
Repeater {
model: Bluetooth.devices
delegate: DeviceRow {
required property var modelData
Layout.fillWidth: true
device: modelData
visible: root.powered && modelData.bonded
}
}
// ── Available (discoverable) devices ──────────────────────────
RowLayout {
visible: root.powered
Layout.fillWidth: true
Layout.topMargin: 6
Layout.bottomMargin: 2
Text {
text: "Available"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
}
Text {
visible: root.adapter?.discovering ?? false
text: "scanning…"
color: Theme.textDim
font.pixelSize: 10
}
}
Repeater {
model: Bluetooth.devices
delegate: DeviceRow {
required property var modelData
Layout.fillWidth: true
device: modelData
// Only named devices — hides the swarm of random-address BLE beacons.
visible: root.powered && !modelData.bonded
&& (modelData.deviceName ?? "").length > 0
}
}
}
component DeviceRow: RowLayout {
id: drow
required property var device
spacing: 8
implicitHeight: 28
// Once a freshly-paired device bonds, trust it (so it can reconnect
// without an agent) and bring up the connection automatically.
Connections {
target: drow.device
function onBondedChanged() {
if (!drow.device.bonded) return
if (!drow.device.trusted) drow.device.trusted = true
if (!drow.device.connected) drow.device.connect()
}
}
Text {
text: root.devIcon(drow.device)
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 13
color: drow.device.connected ? Theme.accent : Theme.textDim
}
Text {
text: drow.device.name || drow.device.address || "?"
font.pixelSize: 11
color: drow.device.connected ? Theme.text : Theme.textDim
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
visible: drow.device.batteryAvailable
text: Math.round(drow.device.battery * 100) + "%"
font.pixelSize: 10
color: Theme.textDim
Layout.preferredWidth: 30
horizontalAlignment: Text.AlignRight
}
Text {
text: drow.device.pairing ? "pairing…"
: drow.device.state === BluetoothDeviceState.Connecting ? "…"
: drow.device.state === BluetoothDeviceState.Disconnecting ? "…"
: !drow.device.bonded ? "Pair"
: drow.device.connected ? "Disconnect"
: "Connect"
font.pixelSize: 10
color: drow.device.connected ? Theme.text : Theme.textDim
Layout.preferredWidth: 70
horizontalAlignment: Text.AlignRight
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
const d = drow.device
if (d.pairing) d.cancelPair()
else if (!d.bonded) d.pair()
else if (d.connected) d.disconnect()
else d.connect()
}
}
}
}
}

View File

@ -0,0 +1,63 @@
import Quickshell.Bluetooth
import QtQuick
import "../components"
Item {
id: root
implicitWidth: chip.implicitWidth + 10
implicitHeight: 24
signal clickedLeft
readonly property var adapter: Bluetooth.defaultAdapter
readonly property bool powered: adapter?.enabled ?? false
readonly property var connectedDevices:
Bluetooth.devices.values.filter(d => d.connected)
// \uf293 = FA bluetooth-b (on), \uf294 = FA bluetooth (off / no connection)
readonly property string btIcon: root.powered ? "\uf293" : "\uf294"
function label() {
const n = root.connectedDevices.length
if (n === 0) return ""
if (n === 1) {
const name = root.connectedDevices[0].name || ""
return name.length > 16 ? name.substring(0, 15) + "…" : name
}
return n + " devices"
}
Row {
id: chip
anchors.centerIn: parent
spacing: 5
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.btIcon
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 14
color: (root.powered && root.connectedDevices.length > 0) ? Theme.text : Theme.textDim
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.label()
font.pixelSize: 11
color: Theme.textDim
visible: text !== ""
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (root.adapter) root.adapter.enabled = !root.adapter.enabled
} else {
root.clickedLeft()
}
}
}
}

View File

@ -0,0 +1,139 @@
import QtQuick
import "../components"
// Calendar grid a plain Item so it can live inside an expanding section border.
Item {
id: root
property var now: new Date()
property int viewYear: now.getFullYear()
property int viewMonth: now.getMonth() // 0-based
readonly property var monthNames: [
"January","February","March","April","May","June",
"July","August","September","October","November","December"
]
readonly property int daysInMonth: new Date(viewYear, viewMonth + 1, 0).getDate()
// First weekday of month: Mon=0 … Sun=6
readonly property int startOffset: (new Date(viewYear, viewMonth, 1).getDay() + 6) % 7
readonly property int totalCells: Math.ceil((startOffset + daysInMonth) / 7) * 7
readonly property int todayYear: now.getFullYear()
readonly property int todayMonth: now.getMonth()
readonly property int todayDay: now.getDate()
// All widths derived from this constant — never from parent.width —
// to avoid Column polish loops when the popup is wider than the grid.
readonly property int cellW: 28
readonly property int cellGap: 2
readonly property int gridW: 7 * cellW + 6 * cellGap // 208
implicitWidth: gridW + 24
implicitHeight: calLayout.implicitHeight + 16
Column {
id: calLayout
// Centre the fixed-width grid inside the (potentially wider) popup.
// No left+right anchors → implicitWidth stays at gridW, no resize loop.
anchors { top: parent.top; topMargin: 8; horizontalCenter: parent.horizontalCenter }
spacing: 6
// Month navigation — all child widths are explicit constants
Row {
spacing: 0
Text {
text: ""
color: Theme.text
font.pixelSize: 16
width: 20
horizontalAlignment: Text.AlignHCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.viewMonth === 0) { root.viewMonth = 11; root.viewYear-- }
else root.viewMonth--
}
}
}
Text {
text: root.monthNames[root.viewMonth] + " " + root.viewYear
color: Theme.text
font.pixelSize: 13
font.bold: true
width: root.gridW - 40 // fixed constant, not parent.width
horizontalAlignment: Text.AlignHCenter
}
Text {
text: ""
color: Theme.text
font.pixelSize: 16
width: 20
horizontalAlignment: Text.AlignHCenter
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.viewMonth === 11) { root.viewMonth = 0; root.viewYear++ }
else root.viewMonth++
}
}
}
}
// Weekday headers
Row {
spacing: root.cellGap
Repeater {
model: ["Mo","Tu","We","Th","Fr","Sa","Su"]
Text {
text: modelData
color: Theme.textDim
font.pixelSize: 10
width: root.cellW
horizontalAlignment: Text.AlignHCenter
}
}
}
// Day grid
Grid {
columns: 7
spacing: root.cellGap
Repeater {
model: root.totalCells
delegate: Item {
width: root.cellW
height: 22
readonly property int dayNum: index - root.startOffset + 1
readonly property bool valid:
index >= root.startOffset &&
index < root.startOffset + root.daysInMonth
readonly property bool isToday:
valid &&
root.viewYear === root.todayYear &&
root.viewMonth === root.todayMonth &&
dayNum === root.todayDay
Rectangle {
anchors.fill: parent
radius: 3
color: isToday ? Theme.accent : "transparent"
visible: isToday
}
Text {
anchors.centerIn: parent
text: parent.valid ? String(parent.dayNum) : ""
color: parent.isToday ? Theme.text : Theme.textDim
font.pixelSize: 11
font.bold: parent.isToday
}
}
}
}
}
}

View File

@ -0,0 +1,47 @@
import QtQuick
import "../components"
Item {
id: root
signal clicked
implicitWidth: timeLabel.implicitWidth + 16
implicitHeight: 24
property var now: new Date()
Timer {
interval: 1000
running: true
repeat: true
onTriggered: root.now = new Date()
}
readonly property string display: {
const d = now
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
const dd = String(d.getDate()).padStart(2, "0")
const mm = String(d.getMonth() + 1).padStart(2, "0")
const yyyy = d.getFullYear()
const h = String(d.getHours()).padStart(2, "0")
const m = String(d.getMinutes()).padStart(2, "0")
const s = String(d.getSeconds()).padStart(2, "0")
return days[d.getDay()] + " " + dd + "." + mm + "." + yyyy + " " + h + ":" + m + ":" + s
}
Text {
id: timeLabel
anchors.centerIn: parent
text: root.display
color: Theme.text
font.pixelSize: 12
font.family: "JetBrainsMono Nerd Font Mono"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked()
}
}

View File

@ -0,0 +1,38 @@
import Quickshell.Services.Mpris
import QtQuick
import "../components"
Item {
id: root
signal clicked
// Pick the first playing player, fall back to first available
readonly property var player: {
const vals = Mpris.players.values
for (const p of vals) {
if (p.isPlaying) return p
}
return vals.length > 0 ? vals[0] : null
}
visible: player !== null
implicitWidth: player ? Math.min(240, titleLabel.implicitWidth + 20) : 0
implicitHeight: 24
Text {
id: titleLabel
anchors.centerIn: parent
width: parent.width - 20
text: root.player?.trackTitle ?? ""
color: Theme.text
font.pixelSize: 12
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
MouseArea {
anchors.fill: parent
onClicked: root.clicked()
}
}

View File

@ -0,0 +1,151 @@
import Quickshell.Services.Mpris
import QtQuick
import QtQuick.Layouts
import "../components"
// Plain Item lives inside the expanding center section border in Bar.qml.
Item {
id: root
required property var player
implicitWidth: 280
implicitHeight: layout.implicitHeight + 28
// Keep the progress binding live while playing, pause while seeking.
FrameAnimation {
running: (root.player?.isPlaying ?? false) && !progressArea.pressed
onTriggered: root.player.positionChanged()
}
ColumnLayout {
id: layout
anchors { fill: parent; margins: 14 }
spacing: 8
// Track info
Text {
text: root.player?.trackTitle ?? ""
color: Theme.text
font.pixelSize: 13
font.bold: true
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
text: root.player?.trackArtist ?? ""
color: Theme.textDim
font.pixelSize: 11
Layout.fillWidth: true
elide: Text.ElideRight
visible: (root.player?.trackArtist ?? "") !== ""
Layout.topMargin: -4
}
// Progress bar
Item {
visible: root.player?.positionSupported && root.player?.lengthSupported
&& (root.player?.length ?? 0) > 0
Layout.fillWidth: true
height: 12
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 3
radius: 2
color: Theme.progressTrack
Rectangle {
width: root.player?.length > 0
? parent.width * (root.player.position / root.player.length)
: 0
height: parent.height
radius: parent.radius
color: Theme.accent
}
}
MouseArea {
id: progressArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
function seek(mx) {
if (!(root.player?.canSeek)) return
const ratio = Math.max(0, Math.min(1, mx / width))
root.player.position = ratio * (root.player?.length ?? 0)
}
onClicked: seek(mouseX)
onPositionChanged: if (pressed) seek(mouseX)
}
}
// Media controls
RowLayout {
Layout.fillWidth: true
spacing: 0
Item { Layout.fillWidth: true }
ControlButton {
icon: "⏮"
iconSize: 18
enabled: root.player?.canGoPrevious ?? false
onActivated: root.player.previous()
}
Item { width: 8 }
ControlButton {
icon: root.player?.isPlaying ? "⏸" : "▶"
iconSize: 22
enabled: true
onActivated: root.player?.togglePlaying()
}
Item { width: 8 }
ControlButton {
icon: "⏭"
iconSize: 18
enabled: root.player?.canGoNext ?? false
onActivated: root.player.next()
}
Item { Layout.fillWidth: true }
}
}
component ControlButton: Rectangle {
id: btn
required property string icon
required property int iconSize
required property bool enabled
signal activated
implicitWidth: label.implicitWidth + 16
implicitHeight: label.implicitHeight + 10
radius: Theme.radius
color: btnArea.containsMouse ? Qt.rgba(1, 1, 1, 0.08) : 'transparent'
Behavior on color { ColorAnimation { duration: 80 } }
Text {
id: label
anchors.centerIn: parent
text: btn.icon
font.pixelSize: btn.iconSize
color: btn.enabled ? Theme.text : Theme.textDisabled
}
MouseArea {
id: btnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: if (btn.enabled) btn.activated()
}
}
}

View File

@ -0,0 +1,86 @@
import Quickshell
import Quickshell.Io
import QtQuick
import "../components"
Item {
id: root
implicitWidth: row.implicitWidth + 10
implicitHeight: 24
property string connType: "none" // "wifi", "ethernet", "none"
property string connName: ""
// accumulate results here, flush to displayed props on exit
property string _pendingType: "none"
property string _pendingName: ""
// \uf1eb = FA wifi, \uf0e8 = FA sitemap (wired proxy), \uf127 = FA chain-broken
readonly property string netIcon:
connType === "wifi" ? "\uf1eb" :
connType === "ethernet" ? "\uf0e8" : "\uf127"
function parseLine(line) {
const idx1 = line.indexOf(":")
if (idx1 < 0) return
const idx2 = line.indexOf(":", idx1 + 1)
if (idx2 < 0) return
const type = line.substring(0, idx1)
const state = line.substring(idx1 + 1, idx2)
const conn = line.substring(idx2 + 1).trim()
if (state === "connected" && (type === "wifi" || type === "ethernet")) {
if (root._pendingType === "none" || type === "wifi") {
root._pendingType = type
root._pendingName = conn
}
}
}
Timer {
interval: 5000
running: true
repeat: true
triggeredOnStart: true
onTriggered: {
root._pendingType = "none"
root._pendingName = ""
netProc.running = true
}
}
Process {
id: netProc
command: ["nmcli", "-t", "-f", "TYPE,STATE,CONNECTION", "dev"]
stdout: SplitParser {
splitMarker: "\n"
onRead: data => root.parseLine(data)
}
onExited: {
root.connType = root._pendingType
root.connName = root._pendingName
running = false
}
}
Row {
id: row
anchors.centerIn: parent
spacing: 5
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.netIcon
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 14
color: root.connType !== "none" ? Theme.text : Theme.textDim
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.connName.length > 16 ? root.connName.substring(0, 15) + "…" : root.connName
font.pixelSize: 11
color: Theme.textDim
visible: root.connName !== ""
}
}
}

View File

@ -0,0 +1,162 @@
import Quickshell
import QtQuick
import QtQuick.Shapes
import "../components"
PopupWindow {
id: self
property string popupName: ""
property string activePopup: ""
visible: false
color: "transparent"
default property alias content: contentSlot.data
readonly property int bw: Theme.borderWidth
readonly property real r: Theme.radius
readonly property bool _open: activePopup === popupName
on_OpenChanged: {
if (_open) {
hideTimer.stop();
self.visible = true;
fade.opacity = 1.0;
} else {
fade.opacity = 0.0;
hideTimer.start();
}
}
Timer {
id: hideTimer
interval: 200
onTriggered: self.visible = false
}
Item {
id: fade
anchors.fill: parent
opacity: 0.0
Behavior on opacity {
NumberAnimation {
duration: 180
easing.type: Easing.InOutCubic
}
}
// Background + border in one Shape so fill matches stroke exactly
Shape {
id: pbs
anchors.fill: parent
ShapePath {
strokeWidth: self.bw
strokeColor: Theme.border
fillColor: Theme.bg
// Start flush at top-left, hidden under bar
startX: 0
startY: self.r
// Left side down to bottom-left corner
PathLine {
x: 0
y: pbs.height - self.r
}
// Bottom-left corner — Counterclockwise for outward curve
PathArc {
x: self.r
y: pbs.height
radiusX: self.r
radiusY: self.r
direction: PathArc.Counterclockwise
}
// Bottom edge
PathLine {
x: pbs.width - self.r
y: pbs.height
}
// Bottom-right corner — Counterclockwise for outward curve
PathArc {
x: pbs.width
y: pbs.height - self.r
radiusX: self.r
radiusY: self.r
direction: PathArc.Counterclockwise
}
// Right side up, flush to top, hidden under bar
PathLine {
x: pbs.width
y: self.r
}
}
}
// // Top-left concave corner
// Shape {
// x: 0
// y: 0
// width: self.r
// height: self.r
// ShapePath {
// fillColor: Theme.bg
// strokeColor: "transparent"
// startX: 0
// startY: 0
// PathLine {
// x: self.r
// y: 0
// }
// PathArc {
// x: 0
// y: self.r
// radiusX: self.r
// radiusY: self.r
// direction: PathArc.Clockwise
// }
// }
// }
//
// // Top-right concave corner
// Shape {
// x: parent.width - self.r
// y: 0
// width: self.r
// height: self.r
// ShapePath {
// fillColor: Theme.bg
// strokeColor: "transparent"
// startX: self.r
// startY: 0
// PathLine {
// x: 0
// y: 0
// }
// PathArc {
// x: self.r
// y: self.r
// radiusX: self.r
// radiusY: self.r
// direction: PathArc.Counterclockwise
// }
// }
// }
Item {
id: contentSlot
anchors {
top: parent.top
topMargin: self.r + self.bw
left: parent.left
leftMargin: self.bw
right: parent.right
rightMargin: self.bw
}
}
}
}

View File

@ -0,0 +1,65 @@
import Quickshell
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import QtQuick
import "../components"
Item {
id: root
required property var barWindow
implicitWidth: trayRow.width
implicitHeight: 24
Row {
id: trayRow
anchors.verticalCenter: parent.verticalCenter
spacing: 3
Repeater {
model: SystemTray.items
delegate: Item {
id: trayDelegate
required property var modelData
width: 24
height: 24
// Hover highlight
Rectangle {
anchors.fill: parent
radius: Theme.radius
color: hoverArea.containsMouse ? Qt.rgba(1,1,1,0.08) : "transparent"
Behavior on color { ColorAnimation { duration: 80 } }
}
TrayIcon {
anchors.centerIn: parent
icon: trayDelegate.modelData.icon
size: 16
}
MouseArea {
id: hoverArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: mouse => {
const item = trayDelegate.modelData
const wantsMenu = mouse.button === Qt.RightButton || item.onlyMenu
if (wantsMenu && item.hasMenu) {
// display() needs the quickshell PanelWindow (not QQuickWindow).
// mapToItem(null) gives scene/window-local coordinates.
const pos = trayDelegate.mapToItem(null, 0, trayDelegate.height)
item.display(root.barWindow, Math.round(pos.x), Math.round(pos.y))
} else if (!item.onlyMenu) {
item.activate()
}
}
}
}
}
}
}

View File

@ -0,0 +1,74 @@
import Quickshell.Services.Pipewire
import QtQuick
import QtQuick.Layouts
import "../components"
Item {
id: root
implicitWidth: chip.implicitWidth + 10
implicitHeight: 24
signal clickedLeft
PwObjectTracker {
objects: Pipewire.nodes.values
}
function safeVolume(node) {
if (!node || !node.ready || !node.audio) return 0
const v = node.audio.volume
return (v !== undefined && !isNaN(v)) ? v : 0
}
function setVolume(node, v) {
if (!node || !node.ready || !node.audio) return
node.audio.volume = Math.max(0, Math.min(1, v))
}
function volIcon(vol, muted) {
if (muted) return "\uf6a9"
if (vol > 0.6) return "\uf028"
if (vol > 0.2) return "\uf027"
return "\uf026"
}
readonly property var defaultSink: Pipewire.defaultAudioSink
readonly property real defaultVolume: safeVolume(defaultSink)
readonly property bool defaultMuted: defaultSink?.audio?.muted ?? false
Row {
id: chip
anchors.centerIn: parent
spacing: 5
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.volIcon(root.defaultVolume, root.defaultMuted)
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 14
color: root.defaultMuted ? Theme.textDim : Theme.text
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.defaultMuted ? "mute" : Math.round(root.defaultVolume * 100) + "%"
font.pixelSize: 11
color: Theme.textDim
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.PointingHandCursor
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (root.defaultSink?.ready && root.defaultSink?.audio)
root.defaultSink.audio.muted = !root.defaultSink.audio.muted
} else {
root.clickedLeft()
}
}
onWheel: wheel => {
if (!root.defaultSink?.ready) return
root.setVolume(root.defaultSink, root.defaultVolume + wheel.angleDelta.y / 120 * 0.05)
}
}
}

View File

@ -0,0 +1,143 @@
import Quickshell.Services.Pipewire
import QtQuick
import QtQuick.Layouts
import "../components"
// Volume mixer a plain Item so it can live inside an expanding section border.
Item {
id: root
implicitWidth: 300
implicitHeight: mixerCol.implicitHeight + 24
PwObjectTracker {
objects: Pipewire.nodes.values
}
function safeVolume(node) {
if (!node || !node.ready || !node.audio) return 0
const v = node.audio.volume
return (v !== undefined && !isNaN(v)) ? v : 0
}
function setVolume(node, v) {
if (!node || !node.ready || !node.audio) return
node.audio.volume = Math.max(0, Math.min(1, v))
}
function volIcon(vol, muted) {
if (muted) return "\uf6a9"
if (vol > 0.6) return "\uf028"
if (vol > 0.2) return "\uf027"
return "\uf026"
}
ColumnLayout {
id: mixerCol
anchors { fill: parent; margins: 12 }
spacing: 4
Text {
text: "Output Devices"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
Layout.bottomMargin: 2
}
Repeater {
model: Pipewire.nodes
delegate: NodeRow {
required property var modelData
Layout.fillWidth: true
node: modelData
visible: modelData.isSink && !modelData.isStream && modelData.ready
}
}
Text {
text: "Applications"
color: Theme.textDim
font.pixelSize: 10
Layout.fillWidth: true
Layout.topMargin: 6
Layout.bottomMargin: 2
}
Repeater {
model: Pipewire.nodes
delegate: NodeRow {
required property var modelData
Layout.fillWidth: true
node: modelData
// Application playback streams (sink inputs) report isStream && isSink.
visible: modelData.isStream && modelData.isSink && modelData.ready
}
}
}
component NodeRow: RowLayout {
id: row
required property var node
spacing: 8
implicitHeight: 28
Text {
text: root.volIcon(root.safeVolume(row.node), row.node.audio?.muted ?? false)
font.family: "JetBrainsMono Nerd Font Mono"
font.pixelSize: 13
color: (row.node.audio?.muted ?? false) ? Theme.textDim : Theme.text
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
if (row.node.ready && row.node.audio)
row.node.audio.muted = !row.node.audio.muted
}
}
}
Text {
text: row.node.description || row.node.nickname || row.node.name || "?"
font.pixelSize: 11
color: Theme.textDim
Layout.preferredWidth: 90
elide: Text.ElideRight
}
Item {
Layout.fillWidth: true
implicitHeight: 16
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 3
radius: 2
color: Theme.progressTrack
Rectangle {
width: parent.width * Math.min(1, root.safeVolume(row.node))
height: parent.height
radius: parent.radius
color: Theme.accent
}
}
MouseArea {
anchors { fill: parent; topMargin: -4; bottomMargin: -4 }
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
function seek(mx) { root.setVolume(row.node, mx / width) }
onClicked: seek(mouseX)
onPositionChanged: if (pressed) seek(mouseX)
}
}
Text {
text: Math.round(root.safeVolume(row.node) * 100) + "%"
font.pixelSize: 10
color: Theme.textDim
Layout.preferredWidth: 30
horizontalAlignment: Text.AlignRight
}
}
}

View File

@ -0,0 +1,70 @@
import Quickshell
import Quickshell.Hyprland
// import Quickshell.Widgets
import QtQuick
import "../components"
Rectangle {
id: root
required property var workspace
required property string screenName
visible: workspace?.lastIpcObject?.monitor === screenName
implicitWidth: Math.max(32, iconsRow.implicitWidth + 14)
implicitHeight: 18
radius: 6
color: workspace?.active ? Theme.accent : 'transparent'
Row {
id: iconsRow
anchors.centerIn: parent
spacing: 3
Repeater {
model: root.workspace?.toplevels
delegate: Item {
id: iconItem
required property var modelData
implicitWidth: 16
implicitHeight: 16
property string appClass: ""
Component.onCompleted: {
var cls = modelData?.lastIpcObject?.["class"] ?? ""
if (cls !== "") {
appClass = cls
} else if (modelData) {
modelData.lastIpcObjectChanged.connect(function() {
var c = iconItem.modelData?.lastIpcObject?.["class"] ?? ""
if (c !== "") iconItem.appClass = c
})
Qt.callLater(Hyprland.refreshToplevels)
}
}
TrayIcon {
anchors.fill: parent
icon: iconItem.appClass
size: 16
}
}
}
// Workspace number shown when no windows are open
Text {
visible: (root.workspace?.toplevels?.values?.length ?? 0) === 0
text: root.workspace?.id ?? ""
color: root.workspace?.active ? Theme.text : Theme.textDim
font.pixelSize: 11
}
}
MouseArea {
anchors.fill: parent
onClicked: Hyprland.dispatch("workspace " + root.workspace.id)
}
}

View File

@ -0,0 +1,22 @@
pragma ComponentBehavior: Bound
import Quickshell.Hyprland
import QtQuick
Row {
id: root
required property string screenName
spacing: 4
Repeater {
id: workspacesRepeater
model: Hyprland.workspaces
property string screenName: root.screenName
delegate: WorkspaceButton {
required property var modelData
workspace: modelData
screenName: workspacesRepeater.screenName
}
}
}

View File

@ -0,0 +1,15 @@
Bar 1.0 Bar.qml
Battery 1.0 Battery.qml
BluetoothControl 1.0 BluetoothControl.qml
BluetoothContent 1.0 BluetoothContent.qml
PopoutWindow 1.0 PopoutWindow.qml
CalendarContent 1.0 CalendarContent.qml
Clock 1.0 Clock.qml
MusicPlayer 1.0 MusicPlayer.qml
MusicPlayerControls 1.0 MusicPlayerControls.qml
NetworkStatus 1.0 NetworkStatus.qml
SysTray 1.0 SysTray.qml
VolumeControl 1.0 VolumeControl.qml
VolumeMixerContent 1.0 VolumeMixerContent.qml
Workspaces 1.0 Workspaces.qml
WorkspaceButton 1.0 WorkspaceButton.qml

View File

@ -0,0 +1,11 @@
import QtQuick
import Quickshell.Widgets
import "."
WrapperRectangle {
radius: Theme.radius
color: 'transparent'
border.color: Theme.border
border.width: Theme.borderWidth
margin: Theme.enclosureMargin
}

View File

@ -0,0 +1,19 @@
pragma Singleton
import QtQuick
QtObject {
// Core palette
readonly property color bg: '#D9000000' // bar background (semi-transparent)
readonly property color bgPopup: '#000000' // popup background (fully opaque)
readonly property color accent: '#9B1A1A'
readonly property color border: '#FFFFFF'
readonly property color text: '#FFFFFF'
readonly property color textDim: '#888888'
readonly property color textDisabled: '#444444'
readonly property color progressTrack: '#333333'
// Shape / sizing
readonly property int radius: 16
readonly property int borderWidth: 2
readonly property int enclosureMargin: 3
}

View File

@ -0,0 +1,103 @@
// import QtQuick
// import Quickshell.Widgets
//
// Item {
// id: root
//
// property string icon: ""
// property int size: 16
//
// implicitWidth: size
// implicitHeight: size
//
// readonly property url resolvedSource: {
// if (!icon) return ""
// if (icon.startsWith("/") || icon.startsWith("file://") || icon.startsWith("image://"))
// return icon
// if (icon.includes("?path=")) {
// const [name, path] = icon.split("?path=")
// const baseName = name.slice(name.lastIndexOf("/") + 1)
// return `file://${path}/${baseName}`
// }
// return `image://icon/${icon}`
// }
//
// readonly property bool isIconTheme: resolvedSource.toString().startsWith("image://icon/")
// readonly property bool hasSource: resolvedSource.toString() !== ""
//
// IconImage {
// anchors.fill: parent
// source: root.resolvedSource
// visible: root.hasSource && root.isIconTheme
// asynchronous: true
// }
//
// Image {
// anchors.fill: parent
// source: root.hasSource && !root.isIconTheme ? root.resolvedSource : ""
// visible: root.hasSource && !root.isIconTheme
// asynchronous: true
// fillMode: Image.PreserveAspectFit
// }
// }
// pragma ComponentBehavior: Bound
import QtQuick
// import Quickshell
import Quickshell.Widgets
Item {
id: root
property string icon: ""
property int size: 16
implicitWidth: size
implicitHeight: size
readonly property url resolvedSource: {
if (!icon) return ""
if (icon.startsWith("/") || icon.startsWith("file://") || icon.startsWith("image://"))
return icon
if (icon.includes("?path=")) {
const [name, path] = icon.split("?path=")
const baseName = name.slice(name.lastIndexOf("/") + 1)
return `file://${path}/${baseName}`
}
// Use Quickshell's icon image provider instead of iconPath()
return `image://icon/${icon}`
}
Loader {
anchors.fill: parent
// asynchronous: true
sourceComponent: root.resolvedSource && root.resolvedSource.toString() !== ""
? root.resolvedSource.toString().startsWith("image://icon/")
? iconComponent
: imageComponent
: null
}
Component {
id: iconComponent
IconImage {
source: root.resolvedSource
asynchronous: true
// mipmap: true
}
}
Component {
id: imageComponent
Image {
source: root.resolvedSource
asynchronous: true
// mipmap: true
fillMode: Image.PreserveAspectFit
}
}
}

View File

@ -0,0 +1,3 @@
singleton Theme 1.0 Theme.qml
Enclosure 1.0 Enclosure.qml
TrayIcon 1.0 TrayIcon.qml

View File

@ -0,0 +1,13 @@
//@ pragma UseQApplication
import Quickshell
import "bar"
ShellRoot {
Variants {
model: Quickshell.screens
Bar { }
// // modelData injected by Variants; Bar.qml binds screen: modelData internally
// }
}
}

View File

@ -0,0 +1,53 @@
---
- name: Check if quickshell path is a symlink
stat:
path: "{{ config_dir }}/quickshell"
register: quickshell_stat
- name: Remove existing quickshell symlink if present
file:
path: "{{ config_dir }}/quickshell"
state: absent
when: quickshell_stat.stat.exists and quickshell_stat.stat.islnk
- name: Create quickshell config directories
file:
path: "{{ item }}"
state: directory
mode: '0755'
loop:
- "{{ config_dir }}/quickshell"
- "{{ config_dir }}/quickshell/bar"
- "{{ config_dir }}/quickshell/components"
- name: Symlink quickshell config files
file:
src: "{{ role_path }}/files/{{ item.src }}"
dest: "{{ config_dir }}/quickshell/{{ item.dest }}"
state: link
force: true
loop:
# Root
- { src: shell.qml, dest: shell.qml }
# Shared components module
- { src: components/Theme.qml, dest: components/Theme.qml }
- { src: components/Enclosure.qml, dest: components/Enclosure.qml }
- { src: components/TrayIcon.qml, dest: components/TrayIcon.qml }
- { src: components/qmldir, dest: components/qmldir }
# Bar module (imported as "bar" in shell.qml)
- { src: bar/Bar.qml, dest: bar/Bar.qml }
- { src: bar/Battery.qml, dest: bar/Battery.qml }
- { src: bar/CalendarContent.qml, dest: bar/CalendarContent.qml }
- { src: bar/Clock.qml, dest: bar/Clock.qml }
- { src: bar/MusicPlayer.qml, dest: bar/MusicPlayer.qml }
- { src: bar/MusicPlayerControls.qml, dest: bar/MusicPlayerControls.qml }
- { src: bar/NetworkStatus.qml, dest: bar/NetworkStatus.qml }
- { src: bar/PopoutWindow.qml, dest: bar/PopoutWindow.qml }
- { src: bar/SysTray.qml, dest: bar/SysTray.qml }
- { src: bar/VolumeControl.qml, dest: bar/VolumeControl.qml }
- { src: bar/VolumeMixerContent.qml, dest: bar/VolumeMixerContent.qml }
- { src: bar/Workspaces.qml, dest: bar/Workspaces.qml }
- { src: bar/WorkspaceButton.qml, dest: bar/WorkspaceButton.qml }
- { src: bar/BluetoothContent.qml, dest: bar/BluetoothContent.qml }
- { src: bar/BluetoothControl.qml, dest: bar/BluetoothControl.qml }
- { src: bar/qmldir, dest: bar/qmldir }

View File

@ -2,14 +2,16 @@
- name: Update package cache (Arch-based systems) - name: Update package cache (Arch-based systems)
pacman: pacman:
update_cache: yes update_cache: yes
when: ansible_os_family == "Archlinux" when: ansible_facts.os_family == "Archlinux"
become: yes become: yes
become_user: root
- name: Install system packages - name: Install system packages
package: package:
name: "{{ system_packages }}" name: "{{ system_packages }}"
state: present state: present
become: yes become: yes
become_user: root
- name: Ensure .config directory exists - name: Ensure .config directory exists
file: file:

View File

@ -0,0 +1,9 @@
zsh_plugins_dir: "{{ home_dir }}/.zsh/plugins"
zsh_plugins:
- name: zsh_autosuggestions
repo: https://github.com/zsh-users/zsh-autosuggestions.git
version: v0.7.1
- name: zsh-syntax-highlighting
repo: https://github.com/zsh-users/zsh-syntax-highlighting.git
version: 0.8.0

20
roles/zsh/files/.zshenv Normal file
View File

@ -0,0 +1,20 @@
export PATH="/home/johannes/.local/bin:/home/johannes/.cargo/bin:$PATH"
# pnpm
export PNPM_HOME="$HOME/.local/share/pnpm"
case ":$PATH:" in
*":$PNPM_HOME:"*) ;;
*) export PATH="$PNPM_HOME:$PATH" ;;
esac
# pnpm end
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
if [[ -n "$MACHINE_TYPE" && -f "$HOME/dotfiles/zsh/hosts/$MACHINE_TYPE.zsh" ]]; then
source "$HOME/dotfiles/zsh/hosts/$MACHINE_TYPE.zsh"
fi
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/gcr/ssh"
export SDL_AUDIODRIVER=pipewire

View File

@ -1,22 +1,43 @@
# If you come from bash you might have to change your $PATH. HISTFILE="$HOME/.zsh_history"
# export PATH=$HOME/bin:$HOME/.local/bin:/usr/local/bin:$PATH HISTSIZE=50000
SAVEHOST=50000
setopt share_history # persist, and share live across open shells
# setopt inc_append_history # history of session appended to HISTFILE after session ends
setopt extended_history # record timestamps
setopt hist_ignore_dups # drop a command identical to the one before it
setopt hist_ignore_space # don't record commands typed with a leading space
setopt hist_verify # let you edit a !-expansion before it runs
# Path to your Oh My Zsh installation. autoload -Uz compinit
export ZSH="$HOME/.oh-my-zsh" compinit
ZSH_THEME="bira" autoload -Uz vcs_info
zstyle ':omz:update' mode reminder # just remind me to update when it's time setopt prompt_subst
COMPLETION_WAITING_DOTS="true"
plugins=( zstyle ':vcs_info:*' enable git
git zstyle ':vcs_info:git:*' formats ' %F{yellow}%b %f'
zsh-syntax-highlighting zstyle ':vcs_info:git:*' actionformats ' %F{yellow}%b|%a %f'
zsh-autosuggestions
fzf-zsh-plugin
)
source $ZSH/oh-my-zsh.sh zstyle ':vcs_info:git*+set-message:*' hooks git-dirty
+vi-git-dirty() {
if [[ -n $(git status --porcelain 2>/dev/null) ]]; then
hook_com[branch]+='%F{red}●%F{yellow}'
fi
}
precmd() { vcs_info }
PROMPT='╭─%B%(!.%F{red}.%F{green})%n@%m%f %F{blue}%~%f%b${vcs_info_msg_0_}
╰─%B%(!.#.$)%b '
RPROMPT='%B%(?..%F{red}%? ↵%f)%b'
expand-or-complete-with-dots() {
print -Pn '%F{red}…%f'
zle expand-or-complete
zle redisplay
}
zle -N expand-or-complete-with-dots
bindkey '^I' expand-or-complete-with-dots
# Preferred editor for local and remote sessions # Preferred editor for local and remote sessions
if [[ -n $SSH_CONNECTION ]]; then if [[ -n $SSH_CONNECTION ]]; then
@ -31,21 +52,14 @@ for zsh_file in "$DOTFILES_ZSH_DIR"/*.zsh; do
[[ -r "$zsh_file" ]] && source "$zsh_file" [[ -r "$zsh_file" ]] && source "$zsh_file"
done done
ZSH_PLUGINS_DIR="$HOME/.zsh/plugins"
source "$ZSH_PLUGINS_DIR/zsh_autosuggestions/zsh-autosuggestions.zsh"
source "$ZSH_PLUGINS_DIR/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
# TODO source all files with .zsh suffix bindkey '^H' backward-kill-word # Ctrl+Backspace - delete word
# source ./aliases.zsh bindkey '^[[3;5~' kill-word # Ctrl+Delete - delete forward word
# source ./dev_env.zsh bindkey '^[[1;5C' forward-word # Ctrl+Right - jump forward a word
# source ./tools.zsh bindkey '^[[1;5D' backward-word # Ctrl+Left - jump backward a word
# TODO eval "$(zoxide init zsh)"
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
if [[ -n "$MACHINE_TYPE" && -f "$HOME/dotfiles/zsh/hosts/$MACHINE_TYPE.zsh" ]]; then
source "$HOME/dotfiles/zsh/hosts/$MACHINE_TYPE.zsh"
fi
alias drun='docker run -it --network=host --device=/dev/kfd --device=/dev/dri --group-add=video --ipc=host --cap-add=SYS_PTRACE --security-opt seccomp=unconfined'

View File

@ -18,3 +18,7 @@ rm() {
print "⚠️ To use real rm, use /bin/rm" print "⚠️ To use real rm, use /bin/rm"
trash-put "$@" trash-put "$@"
} }
alias lg='lazygit'
alias tt='taskwarrior-tui'

View File

@ -1,8 +0,0 @@
# export DJANGO_SETTINGS_MODULE=config.settings.dev
export DATABASE_HOST=127.0.0.1
export DATABASE_NAME=cerenim
export DATABASE_USER=brainhero
export DATABASE_PORT=5432
export DATABASE_DJANGO_SCHEMA=django
export SECRET_KEY=abasdjkasd
export DATABASE_PASSWORD=abcdefghijk

View File

@ -1 +1,2 @@
source /usr/share/autojump/autojump.zsh export FZF_CTRL_R_OPTS="--layout=reverse"
source <(fzf --zsh)

View File

@ -1,24 +1,43 @@
--- ---
- name: Install zsh - name: Install zsh and git
package: package:
name: zsh name:
- zsh
- git
state: present state: present
become: yes
# become_user: root
- name: Install oh-my-zsh - name: Ensure zsh plugin directory exists
shell: |
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
args:
creates: "{{ ansible_env.HOME }}/.oh-my-zsh"
- name: Check for custom zshrc
stat:
path: "{{ role_path }}/files/.zshrc"
register: custom_zshrc
- name: Symlink custom zshrc
file: file:
src: "{{ role_path }}/files/.zshrc" path: "{{ zsh_plugins_dir }}"
dest: "{{ ansible_env.HOME }}/.zshrc" state: directory
mode: "0755"
- name: Clone/checkout zsh plugins at pinned versions
git:
repo: "{{ item.repo }}"
dest: "{{ zsh_plugins_dir }}/{{ item.name }}"
version: "{{ item.version }}"
update: true
loop: "{{ zsh_plugins }}"
# - name: Install oh-my-zsh
# shell: |
# sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
# args:
# creates: "{{ home_dir }}/.oh-my-zsh"
- name: Symlink .zshenv
file:
src: "{{ role_path }}/files/.zshenv"
dest: "{{ home_dir }}/.zshenv"
state: link
force: yes
- name: Symlink .zshrc
file:
src: "{{ role_path }}/files/.zshrc"
dest: "{{ home_dir }}/.zshrc"
state: link state: link
force: yes force: yes
when: custom_zshrc.stat.exists