diff --git a/.config/hypr/config/animations.conf b/.config/hypr/config/animations.conf deleted file mode 100644 index 588d966..0000000 --- a/.config/hypr/config/animations.conf +++ /dev/null @@ -1,15 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Animations Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# https://wiki.hyprland.org/0.45.0/Configuring/Animations/ - -animations { - enabled = yes - bezier = overshot, 0.13, 0.99, 0.29, 1.1 - animation = windowsIn, 1, 4, overshot, popin - animation = windowsOut, 1, 5, default, popin 80% - animation = border, 1, 5, default - animation = workspacesIn, 1, 6, overshot, slide - animation = workspacesOut, 1, 6, overshot, slidefade 80% -} diff --git a/.config/hypr/config/autostart.conf b/.config/hypr/config/autostart.conf deleted file mode 100644 index 63d7bba..0000000 --- a/.config/hypr/config/autostart.conf +++ /dev/null @@ -1,35 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Autostart Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/defaults.conf - -# Autostart wiki https://wiki.hyprland.org/0.45.0/Configuring/Keywords/#executing # -exec-once = qs -exec-once = blueman-applet & -exec-once = fcitx5 -d & -exec-once = mako & -exec-once = nm-applet --indicator & -exec-once = bash -c "mkfifo /tmp/$HYPRLAND_INSTANCE_SIGNATURE.wob && tail -f /tmp/$HYPRLAND_INSTANCE_SIGNATURE.wob | wob -c ~/.config/hypr/wob.ini & disown" & -exec-once = /usr/lib/polkit-kde-authentication-agent-1 & - -exec-once = waypaper --restore - -# ## Slow app launch fix -exec-once = systemctl --user import-environment & -exec-once = hash dbus-update-activation-environment 2>/dev/null & -exec-once = dbus-update-activation-environment --systemd & - -# ## Idle configuration -exec-once = $idlehandler - -# Clipboard fix -# https://github.com/hyprwm/Hyprland/issues/2319#issuecomment-2409983376 -#exec-once = wl-paste -t text -w xclip -selection clipboard & -#exec-once = wl-paste --watch cliphist store & - -# vicinae -exec-once = vicinae server - -# pyprland -exec-once = /usr/bin/pypr --debug /tmp/pypr.log diff --git a/.config/hypr/config/colors.conf b/.config/hypr/config/colors.conf deleted file mode 100644 index c53ee41..0000000 --- a/.config/hypr/config/colors.conf +++ /dev/null @@ -1,19 +0,0 @@ -$wallpaper = /usr/share/wallpapers/downloaded/pexels-8kspain-21564213.jpg -$background = rgb(252425) -$foreground = rgb(F9F1D9) -$color0 = rgb(505051) -$color1 = rgb(9C604E) -$color2 = rgb(807A52) -$color3 = rgb(908BAB) -$color4 = rgb(B7815F) -$color5 = rgb(B9BECA) -$color6 = rgb(EED793) -$color7 = rgb(EEE3C1) -$color8 = rgb(A79F87) -$color9 = rgb(9C604E) -$color10 = rgb(807A52) -$color11 = rgb(908BAB) -$color12 = rgb(B7815F) -$color13 = rgb(B9BECA) -$color14 = rgb(EED793) -$color15 = rgb(EEE3C1) \ No newline at end of file diff --git a/.config/hypr/config/colors.lua b/.config/hypr/config/colors.lua index a76fd48..9d87794 100644 --- a/.config/hypr/config/colors.lua +++ b/.config/hypr/config/colors.lua @@ -1,22 +1,22 @@ -- config/colors.lua -- Centralised palette -- Use these Lua globals throughout every other module. -background = "rgb(090A0D)" -foreground = "rgb(B1B2B5)" -color0 = "rgb(484338)" -color1 = "rgb(5C564B)" -color2 = "rgb(54708A)" -color3 = "rgb(8B7C56)" -color4 = "rgb(537FBA)" -color5 = "rgb(E3D096)" -color6 = "rgb(6886F3)" -color7 = "rgb(86898D)" -color8 = "rgb(5E6063)" -color9 = "rgb(5C564B)" -color10 = "rgb(54708A)" -color11 = "rgb(8B7C56)" -color12 = "rgb(537FBA)" -color13 = "rgb(E3D096)" -color14 = "rgb(6886F3)" -color15 = "rgb(86898D)" -wallpaper = "/usr/share/wallpapers/downloaded/pexels-philippedonn-1114690.jpg" +background = "rgb(080701)" +foreground = "rgb(AEADA7)" +color0 = "rgb(463E37)" +color1 = "rgb(3B3E4D)" +color2 = "rgb(5D8071)" +color3 = "rgb(464E89)" +color4 = "rgb(1982B3)" +color5 = "rgb(609D75)" +color6 = "rgb(73BAFF)" +color7 = "rgb(848279)" +color8 = "rgb(5C5B54)" +color9 = "rgb(3B3E4D)" +color10 = "rgb(5D8071)" +color11 = "rgb(464E89)" +color12 = "rgb(1982B3)" +color13 = "rgb(609D75)" +color14 = "rgb(73BAFF)" +color15 = "rgb(848279)" +wallpaper = "/usr/share/wallpapers/downloaded/pexels-jplenio-1102908.jpg" diff --git a/.config/hypr/config/decorations.conf b/.config/hypr/config/decorations.conf deleted file mode 100644 index f102f59..0000000 --- a/.config/hypr/config/decorations.conf +++ /dev/null @@ -1,33 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Decorations Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/colors.conf - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#decoration - -decoration { - active_opacity = 1 - inactive_opacity = 0.7 - rounding = 1 - dim_inactive = true - dim_strength = 0.1 - - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#blur - blur { - size = 30 - passes = 2 # more passes = more resource intensive. - xray = true - noise = 0.01 - } - - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#shadow - shadow { - enabled = true - range = 10 - } - - -} - - diff --git a/.config/hypr/config/defaults.conf b/.config/hypr/config/defaults.conf deleted file mode 100644 index 5c6fd2c..0000000 --- a/.config/hypr/config/defaults.conf +++ /dev/null @@ -1,12 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Defaults Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -$filemanager = nemo -#$applauncher = tofi-drun --drun-launch=true -#$applauncher = rofi -show combi -modi window,run,combi -combi-modi window,run -$dmenu = vicinae dmenu --placeholder -$applauncher = vicinae toggle -$terminal = kitty -$idlehandler = swayidle -w timeout 300 'swaylock -f -c 000000' before-sleep 'swaylock -f -c 000000' -$capturing = grim -g "$(slurp)" - | swappy -f - diff --git a/.config/hypr/config/environment.conf b/.config/hypr/config/environment.conf deleted file mode 100644 index e5dec8f..0000000 --- a/.config/hypr/config/environment.conf +++ /dev/null @@ -1,21 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Enviroment Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -envd = HYPRCURSOR_THEME,vimix-kanagawa-hyprcursors-lotus -envd = HYPRCURSOR_SIZE,34 -env = XCURSOR_THEME,vimix-kanagawa-hyprcursors-lotus -envd = XCURSOR_SIZE,34 -envd = QT_CURSOR_SIZE,34 - -# Force electron apps to use wayland -env = ELECTRON_OZONE_PLATFORM_HINT,wayland -env = QT_QPA_PLATFORM, wayland -env = SDL_VIDEODRIVER, wayland -env = CLUTTER_BACKEND, wayland -env = XDG_SESSION_TYPE, wayland -env = MOZ_ENABLE_WAYLAND, 1 -env = NIXOS_OZONE_WL, 1 - -# fixes full screen flickering -env = WLR_DRM_NO_DIRECT_SCANOUT,1 diff --git a/.config/hypr/config/input.conf b/.config/hypr/config/input.conf deleted file mode 100644 index 475bed6..0000000 --- a/.config/hypr/config/input.conf +++ /dev/null @@ -1,12 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Input Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Input wiki https://wiki.hyprland.org/0.45.0/Configuring/Variables/#input - -input { - kb_layout = de - follow_mouse = 2 # 0|1|2|3 - float_switch_override_focus = 2 - numlock_by_default = true -} diff --git a/.config/hypr/config/keybinds.conf b/.config/hypr/config/keybinds.conf deleted file mode 100644 index dc59347..0000000 --- a/.config/hypr/config/keybinds.conf +++ /dev/null @@ -1,175 +0,0 @@ -# 0. Includes -source = ~/.config/hypr/config/defaults.conf - -# 1. Applications -# https://wiki.hyprland.org/Configuring/Binds/ -bindd = $mainMod, RETURN, Opens your preferred terminal emulator ($terminal), exec, $terminal -bindd = $mainMod, E, Opens your preferred filemanager ($filemanager), exec, $filemanager -#bindd = $mainMod, A, Screen capture selection, exec, $capturing -bindd = $mainMod, Q, Closes (not kill) current window, killactive, -bindd = $mainMod SHIFT, M, Exits Hyprland by terminating the user sessions, exec, loginctl terminate-user "" -bindd = $mainMod, V, Switches current window between floating and tiling mode, togglefloating, -bindd = $mainMod, SPACE, Runs your application launcher, exec, $applauncher -bindd = $mainMod, F, Toggles current window fullscreen mode, fullscreen -bindd = $mainMod, Y, Pin current window (shows on all workspaces),pin -bindd = $mainMod, J, Toggles curren window split mode, togglesplit, # dwindle - -# ======= Grouping Windows ======= -bindd = $mainMod, K, Toggles current window group mode (ungroup all related), togglegroup, -bindd = $mainMod, Tab, Switches to the next window in the group, changegroupactive, f - -# ======= Volume Control ======= - -bindel = , XF86AudioRaiseVolume, exec, ~/.config/hypr/scripts/volume-helper.sh up -bindel = , XF86AudioLowerVolume, exec, ~/.config/hypr/scripts/volume-helper.sh down -bindel = , XF86AudioMute, exec, ~/.config/hypr/scripts/volume-helper.sh toggle - -# ======= Playback Control ======= - -bindd = , XF86AudioPlay, Toggles play/pause, exec, playerctl play-pause -bindd = , XF86AudioNext, Next track, exec, playerctl next -bindd = , XF86AudioPrev, Previous track, exec, playerctl previous - -# ======= Screen Brightness ======= - -bindel = , XF86MonBrightnessUp, exec, brightnessctl s +5% #Increases brightness 5% -bindel = , XF86MonBrightnessDown, exec, brightnessctl s 5%- #Decreases brightness 5% -bindd = $mainMod SHIFT, P, Runs the calculator application, exec, gnome-calculator -bindd = $mainMod, L, Lock the screen, exec, ~/.config/swaylock/lockscript.sh -bindd = $mainMod, O, Reload/restarts Waybar, exec, killall -SIGUSR2 waybar - -# ======= Window Actions ======= - -## Move window with mainMod + LMB/RMB and dragging -bindd = $mainMod, mouse:272, Move the window towards a direction, movewindow -## Move window towards a direction -bindd = $mainMod SHIFT, left, Move active window to the left, movewindow, l -bindd = $mainMod SHIFT, right, Move active window to the right, movewindow, r -bindd = $mainMod SHIFT, up, Move active window upwards, movewindow, u -bindd = $mainMod SHIFT, down, Move active window downwards, movewindow, d -## Move focus with mainMod + arrow keys -bindd = $mainMod, left, Move focus to the left, movefocus, l -bindd = $mainMod, right, Move focus to the right, movefocus, r -bindd = $mainMod, up, Move focus upwards, movefocus, u -bindd = $mainMod, down, Move focus downwards, movefocus, d -## Resizing windows -# Activate keyboard window resize mode -# https://wiki.hyprland.org/Configuring/Binds/#submaps -bindd = $mainMod, R, Activates window resizing mode, submap, resize -submap = resize -bindd = , right, Resize to the right (resizing mode), resizeactive, 15 0 -bindd = , left, Resize to the left (resizing mode), resizeactive, -15 0 -bindd = , up, Resize upwards (resizing mode), resizeactive, 0 -15 -bindd = , down, Resize downwards (resizing mode), resizeactive, 0 15 -bindd = , l, Resize to the right (resizing mode), resizeactive, 15 0 -bindd = , h, Resize to the left (resizing mode), resizeactive, -15 0 -bindd = , k, Resize upwards (resizing mode), resizeactive, 0 -15 -bindd = , j, Resize downwards (resizing mode), resizeactive, 0 15 -bindd = , escape, Ends window resizing mode, submap, reset -submap = reset -# Quick resize window with keyboard -# !!! added $mainMod here because CTRL + SHIFT is used for word selection in various text editors -bindd = $mainMod CTRL SHIFT, right, Resize to the right, resizeactive, 25 0 -bindd = $mainMod CTRL SHIFT, left, Resize to the left, resizeactive, -25 0 -bindd = $mainMod CTRL SHIFT, up, Resize upwards, resizeactive, 0 -25 -bindd = $mainMod CTRL SHIFT, down, Resize downwards, resizeactive, 0 25 -bindd = $mainMod CTRL SHIFT, l, Resize to the right, resizeactive, 15 0 -bindd = $mainMod CTRL SHIFT, h, Resize to the left, resizeactive, -15 0 -bindd = $mainMod CTRL SHIFT, k, Resize upwards, resizeactive, 0 -15 -bindd = $mainMod CTRL SHIFT, j, Resize downwards, resizeactive, 0 15 -# Resize window with mainMod + LMB/RMB and dragging -bindm = $mainMod, mouse:273, resizewindow #Resize the window towards a direction -bindm = $mainMod, mouse:272, movewindow #Drag window -## Resizing Windows End # -## Move active window to a workspace with $mainMod + CTRL + [0-9] -bindd = $mainMod CTRL, 1, Move window and switch to workspace 1, movetoworkspace, 1 -bindd = $mainMod CTRL, 2, Move window and switch to workspace 2, movetoworkspace, 2 -bindd = $mainMod CTRL, 3, Move window and switch to workspace 3, movetoworkspace, 3 -bindd = $mainMod CTRL, 4, Move window and switch to workspace 4, movetoworkspace, 4 -bindd = $mainMod CTRL, 5, Move window and switch to workspace 5, movetoworkspace, 5 -bindd = $mainMod CTRL, 6, Move window and switch to workspace 6, movetoworkspace, 6 -bindd = $mainMod CTRL, 7, Move window and switch to workspace 7, movetoworkspace, 7 -bindd = $mainMod CTRL, 8, Move window and switch to workspace 8, movetoworkspace, 8 -bindd = $mainMod CTRL, 9, Move window and switch to workspace 9, movetoworkspace, 9 -bindd = $mainMod CTRL, 0, Move window and switch to workspace 10, movetoworkspace, 10 -bindd = $mainMod CTRL, left, Move window and switch to the next workspace, movetoworkspace, -1 -bindd = $mainMod CTRL, right, Move window and switch to the previous workspace, movetoworkspace, +1 -## Same as above, but doesn't switch to the workspace -bindd = $mainMod SHIFT, 1, Move window silently to workspace 1, movetoworkspacesilent, 1 -bindd = $mainMod SHIFT, 2, Move window silently to workspace 2, movetoworkspacesilent, 2 -bindd = $mainMod SHIFT, 3, Move window silently to workspace 3, movetoworkspacesilent, 3 -bindd = $mainMod SHIFT, 4, Move window silently to workspace 4, movetoworkspacesilent, 4 -bindd = $mainMod SHIFT, 5, Move window silently to workspace 5, movetoworkspacesilent, 5 -bindd = $mainMod SHIFT, 6, Move window silently to workspace 6, movetoworkspacesilent, 6 -bindd = $mainMod SHIFT, 7, Move window silently to workspace 7, movetoworkspacesilent, 7 -bindd = $mainMod SHIFT, 8, Move window silently to workspace 8, movetoworkspacesilent, 8 -bindd = $mainMod SHIFT, 9, Move window silently to workspace 9, movetoworkspacesilent, 9 -bindd = $mainMod SHIFT, 0, Move window silently to workspace 10, movetoworkspacesilent, 10 -# Window actions End # -# ======= Workspace Actions ======= - -# Switch workspaces with mainMod + [0-9] -bindd = $mainMod, 1, Switch to workspace 1, workspace, 1 -bindd = $mainMod, 2, Switch to workspace 2, workspace, 2 -bindd = $mainMod, 3, Switch to workspace 3, workspace, 3 -bindd = $mainMod, 4, Switch to workspace 4, workspace, 4 -bindd = $mainMod, 5, Switch to workspace 5, workspace, 5 -bindd = $mainMod, 6, Switch to workspace 6, workspace, 6 -bindd = $mainMod, 7, Switch to workspace 7, workspace, 7 -bindd = $mainMod, 8, Switch to workspace 8, workspace, 8 -bindd = $mainMod, 9, Switch to workspace 9, workspace, 9 -bindd = $mainMod, 0, Switch to workspace 10, workspace, 10 -# Scroll through existing workspaces with mainMod + , or . -bindd = $mainMod, PERIOD, Scroll through workspaces incrementally, workspace, e+1 -bindd = $mainMod, COMMA, Scroll through workspaces decrementally, workspace, e-1 -# With $mainMod + Alt + Left / Right -bindd = $mainMod ALT, left, Switch to the previous workspace, workspace, e-1 -bindd = $mainMod ALT, right, Switch to the next workspace, workspace, e+1 -# With $mainMod + scroll -bindd = $mainMod, mouse_down, Scroll through workspaces incrementally, workspace, e+1 -bindd = $mainMod, mouse_up, Scroll through workspaces decrementally, workspace, e-1 -bindd = $mainMod, slash, Switch to the previous workspace, workspace, previous -# Special workspaces (scratchpads) -bindd = $mainMod, minus, Move active window to Special workspace, movetoworkspace,special -bindd = $mainMod, equal, Toggles the Special workspace, togglespecialworkspace, special -bindd = $mainMod, F1, Call special workspace scratchpad, togglespecialworkspace, scratchpad -bindd = $mainMod ALT SHIFT, F1, Move active window to s~/.config/hypr/scripts/pecial workspace scratchpad, movetoworkspacesilent, special:scratchpad - -# ======= Screenshot ======= -# Screenshot a window -bind = $mainMod, PRINT, exec, hyprshot -m window -# Screenshot a monitor -bind = , PRINT, exec, hyprshot -m output -# Screenshot a region -bind = $shiftMod, PRINT, exec, hyprshot -m region -# Screenrec -bind = $mainMod, S, exec, ~/.config/hypr/scripts/record-or-screenshot.sh -d "$dmenu" - -# ======= Color Picker ======= -bindd = $mainMod, P, Launch hyprpicker to pick a color, exec, hyprpicker -a - -# ======= Additional Settings ======= -#bind = $mainMod, c, exec, cliphist list | tofi --prompt-text="clip:" | cliphist decode | wl-copy -bind = $mainMod, c, exec, vicinae vicinae://extensions/vicinae/clipboard/history - -# https://wiki.hyprland.org/Configuring/Binds -binds { - allow_workspace_cycles = 1 - workspace_back_and_forth = 1 - workspace_center_on = 1 - movefocus_cycles_fullscreen = true - window_direction_monitor_fallback = true -} - -# Toggle Waybar -bind = $mainMod, W, exec, killall -SIGUSR1 waybar -bind = $mainMod SHIFT, W, exec, killall -SIGUSR2 waybar # restart - -# ======= Monitors ======= -# Toggle Monitor Flip -bind = $mainMod, F7, exec, ~/.config/hypr/scripts/rotate_current_screen.sh - -bind = $mainMod, F8, exec, ~/.config/hypr/scripts/monitor-toggle.sh toggle-externals -# bind = $mainMod, F8, exec, ~/.config/hypr/scripts/monitor-toggle.sh laptop -bind = $mainMod, F9, exec, ~/.config/hypr/scripts/monitor-toggle.sh dual -bind = $mainMod, F10, exec, ~/.config/hypr/scripts/monitor-toggle.sh triple \ No newline at end of file diff --git a/.config/hypr/config/keybinds.lua b/.config/hypr/config/keybinds.lua index ca180e5..6fc7c30 100644 --- a/.config/hypr/config/keybinds.lua +++ b/.config/hypr/config/keybinds.lua @@ -17,7 +17,16 @@ hl.bind(S .. " + J", hl.dsp.layout("togglesplit")) -- dwindle only -- ── Groups ─────────────────────────────────────────────────── hl.bind(S .. " + K", hl.dsp.group.toggle()) -hl.bind(S .. " + Tab", hl.dsp.group.next( window )) +-- hl.bind(S .. " + Tab", hl.dsp.group.next( window )) + +hl.bind(S .. " + Tab", function() + local w = hl.get_active_window() + if w and w.group then + hl.dispatch(hl.dsp.group.next()) + else + hl.dispatch(hl.dsp.exec_cmd("qs ipc -c own call overview toggle")) + end +end) -- ── Volume ─────────────────────────────────────────────────── local vol = "~/.config/hypr/scripts/volume-helper.sh" diff --git a/.config/hypr/config/monitor.conf b/.config/hypr/config/monitor.conf deleted file mode 100644 index ee68e57..0000000 --- a/.config/hypr/config/monitor.conf +++ /dev/null @@ -1,21 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Monitor Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Monitor wiki https://wiki.hyprland.org/0.45.0/Configuring/Monitors/ - -monitor = eDP-2, highres@highrr, 0x0, 1, vrr, 1 - -#monitor = HDMI-A-1, highres@highrr, auto-right, 1, transform, 1 -#monitor = desc:Samsung Electric Company S24F350 H4ZR302705, highres@highrr, auto-right, 1 -monitor = desc:Samsung Electric Company S24F350 H4ZK111233, highres@highrr, 1920x0, 1#, transform, 1 - -# monitor = HDMI-A-1, highres@highrr, auto-right, 1, vrr, 0 - -# If you need to scale things like steam etc, please uncomment these lines. -# Adjust GDK_SCALE accordingly to your liking. -#xwayland { -# force_zero_scaling = true # Unscale XWayland -#} - -#env = GDK_SCALE, 1.25 # GDK Scaling Factor diff --git a/.config/hypr/config/plugins.conf b/.config/hypr/config/plugins.conf deleted file mode 100644 index 9db57b6..0000000 --- a/.config/hypr/config/plugins.conf +++ /dev/null @@ -1,12 +0,0 @@ -# https://github.com/sandwichfarm/hyprexpo-plus -# currently not working -plugin { - hyprexpo { - columns = 3 - gap_size = 5 - workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1 - keynav_enable = 1 - keynav_wrap_h = 1 # wrap horizontally at row edges - keynav_wrap_v = 1 # wrap vertically at column edges - } -} diff --git a/.config/hypr/config/variables.conf b/.config/hypr/config/variables.conf deleted file mode 100644 index 893a327..0000000 --- a/.config/hypr/config/variables.conf +++ /dev/null @@ -1,89 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Variables Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -source = ~/.config/hypr/config/colors.conf - -# https://wiki.hypr.land/Configuring/Variables/#general -general { - gaps_in = 2 - gaps_out = 1 - border_size = 1 - col.active_border = $color4 - col.inactive_border = $color2 - layout = dwindle # master|dwindle - resize_on_border = true - extend_border_grab_area = 10 - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#snap - snap { - enabled = true - } -} - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#group -group { - col.border_active = $color6 - col.border_inactive = $color2 - col.border_locked_active = $color3 - col.border_locked_inactive = $color0 - - # https://wiki.hyprland.org/0.45.0/Configuring/Variables/#groupbar - groupbar { - font_size = 13 - height = 1 - indicator_height = 16 - rounding = 2 - font_family = "Roboto Sanss" - text_color = $foreground - text_offset = -8 - font_weight_active = ultraheavy - font_weight_inactive = semibold - col.active = $color6 - col.inactive = $color2 - col.locked_active = $color3 - col.locked_inactive = $color0 - } -} - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#misc -misc { - font_family = "Roboto Sans" - splash_font_family = "Roboto Sans" - disable_hyprland_logo = true - col.splash = $color2 - background_color = $background - enable_swallow = true - swallow_regex = ^(cachy-browser|firefox|nautilus|nemo|thunar|btrfs-assistant.)$ - focus_on_activate = true - vrr = 0 - # Hypridle cmatrix fix: - session_lock_xray = true - middle_click_paste = false -} - -# https://wiki.hyprland.org/0.45.0/Configuring/Variables/#render -render { - direct_scanout = true -} - -# See https://wiki.hyprland.org/0.45.0/Configuring/Dwindle-Layout/ for more -dwindle { - special_scale_factor = 0.8 - pseudotile = true # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below - preserve_split = true -} - -# See https://wiki.hyprland.org/0.45.0/Configuring/Master-Layout/ for more -master { - new_status = master - special_scale_factor = 0.8 -} - -cursor { - no_hardware_cursors = true -} - -device { - name = logitech-gaming-mouse-g502 - sensitivity=0.9 - } diff --git a/.config/hypr/config/variables.lua b/.config/hypr/config/variables.lua index 3c34d27..a2d212d 100644 --- a/.config/hypr/config/variables.lua +++ b/.config/hypr/config/variables.lua @@ -23,21 +23,21 @@ hl.config({ }, group = { - ["col.border_active"] = color6, + ["col.border_active"] = color9, ["col.border_inactive"] = color2, ["col.border_locked_active"] = color3, ["col.border_locked_inactive"] = color0, groupbar = { - font_size = 13, - height = 1, + font_size = 12, + height = 16, indicator_height = 16, - rounding = 2, + rounding = 3, font_family = "Roboto Sans", - text_color = foreground, - text_offset = -8, - font_weight_active = "ultraheavy", - font_weight_inactive = "semibold", - ["col.active"] = color6, + text_color = background, + text_offset = -16, + font_weight_active = "bold", + font_weight_inactive = "normal", + ["col.active"] = color4, ["col.inactive"] = color2, ["col.locked_active"] = color3, ["col.locked_inactive"] = color0, diff --git a/.config/hypr/config/windowrules.conf b/.config/hypr/config/windowrules.conf deleted file mode 100644 index 2324e46..0000000 --- a/.config/hypr/config/windowrules.conf +++ /dev/null @@ -1,315 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Windowrules Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Windows Rules https://wiki.hyprland.org/0.45.0/Configuring/Window-Rules/ # - -# Float Necessary Windows -windowrule { - name = windowrule-1 - float = on - match:title = Rofi -} - -windowrule { - name = windowrule-2 - float = on - match:class = ^(org.pulseaudio.pavucontrol) -} - -windowrule { - name = windowrule-3 - float = on - match:class = ^()$ - match:title = ^(Picture in picture)$ -} - -windowrule { - name = windowrule-4 - float = on - match:class = ^()$ - match:title = ^(Save File)$ -} - -windowrule { - name = windowrule-5 - float = on - match:class = ^()$ - match:title = ^(Open File)$ -} - -windowrule { - name = windowrule-6 - float = on - match:class = ^(LibreWolf)$ - match:title = ^(Picture-in-Picture)$ -} - -windowrule { - name = windowrule-7 - float = on - match:class = ^(blueman-manager)$ -} - -windowrule { - name = windowrule-8 - float = on - match:class = ^(xdg-desktop-portal-gtk|xdg-desktop-portal-kde|xdg-desktop-portal-hyprland)(.*)$ -} - -windowrule { - name = windowrule-9 - float = on - size = 260 340 - match:class = ^(pomodorolm)$ -} - -windowrule { - name = windowrule-10 - float = on - match:title = ^(Extension:.*)$ -} - -windowrule { - name = windowrule-11 - float = on - match:class = ^(polkit-gnome-authentication-agent-1|hyprpolkitagent|org.org.kde.polkit-kde-authentication-agent-1)(.*)$ -} - -windowrule { - name = windowrule-12 - float = on - match:class = ^(CachyOSHello)$ -} - -windowrule { - name = windowrule-13 - float = on - match:class = ^(zenity)$ -} - -windowrule { - name = windowrule-14 - float = on - match:class = ^()$ - match:title = ^(Steam - Self Updater)$ -} - -windowrule { - name = windowrule-15 - float = on - match:class = ^(Zotero)$ - match:title = ^(Progress)$ -} - -# Increase the opacity -windowrule { - name = windowrule-16 - opacity = 0.92 - match:class = ^(thunar|nemo|dolphin)$ -} - -windowrule { - name = windowrule-17 - opacity = 0.96 - match:class = ^(discord|armcord|webcord)$ -} - -windowrule { - name = windowrule-18 - opacity = 0.95 - match:title = ^(QQ|Telegram)$ -} - -windowrule { - name = windowrule-19 - opacity = 0.95 - match:title = ^(NetEase Cloud Music Gtk4)$ -} - -windowrule { - name = windowrule-20 - opacity = 1 - match:class = ^(kitty)$ -} - -# General window rules -windowrule { - name = windowrule-21 - float = on - size = 960 540 - move = ((monitor_w*0.25)-) - match:title = ^(Picture-in-Picture)$ -} - -windowrule { - name = windowrule-22 - float = on - match:title = ^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp|nwg-look|nwg-displays)$ -} - -windowrule { - name = windowrule-23 - move = ((monitor_w*0.25)-) - size = 960 540 - match:title = ^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$ -} - -windowrule { - name = windowrule-24 - pin = on - match:title = ^(danmufloat)$ -} - -windowrule { - name = windowrule-25 - rounding = 5 - match:title = ^(danmufloat|termfloat)$ -} - -windowrule { - name = windowrule-26 - animation = slide right - match:class = ^(kitty|Alacritty)$ -} - -windowrule { - name = windowrule-27 - no_blur = on - match:class = ^(org.mozilla.firefox)$ -} - -windowrule { - name = windowrule-28 - no_dim = on - match:class = ^(zen)$ -} - -windowrule { - name = windowrule-29 - float = on - match:title = ^(Zotero Settings)$ -} - -# Decorations related to floating windows on workspaces 1 to 10 -windowrule { - name = windowrule-30 - border_size = 2 - border_color = $color4 - rounding = 8 - match:float = 1 - match:workspace = w[fv1-10] -} - -# Decorations related to tiling windows on workspaces 1 to 10 -windowrule { - name = windowrule-31 - border_size = 3 - rounding = 4 - match:float = 0 - match:workspace = f[1-10] -} - -windowrule { - name = windowrule-32 - opacity = 0.9 - match:title = ^(.+nvim$) -} - -# Windows Rules End # - -# Workspaces Rules https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/ # -# workspace = 1, default:true, monitor:$priMon -# workspace = 6, default:true, monitor:$secMon -# Workspace selectors https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/#workspace-selectors -# workspace = r[1-5], monitor:$priMon -# workspace = r[6-10], monitor:$secMon -# workspace = special:scratchpad, on-created-empty:$applauncher -# no_gaps_when_only deprecated instead workspaces rules with selectors can do the same -# Smart gaps from 0.45.0 https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/#smart-gaps -#workspace = w[tv1-10], gapsout:5, gapsin:3 -#workspace = f[1], gapsout:5, gapsin:3 -# Workspaces Rules End # - -# Layers Rules # -layerrule { - name = layerrule-1 - animation = slide top - match:namespace = logout_dialog -} - -# layerrule = animation popin 50%, waybar -layerrule { - name = layerrule-2 - animation = slide down - match:namespace = waybar -} - -layerrule { - name = layerrule-3 - animation = fade 50% - match:namespace = wallpaper -} - -# vicinae -layerrule { - name = vicinae-blur - blur = on - animation = popin - dim_around = on - ignore_alpha = 1 - match:namespace = vicinae -} - -# Layers Rules End # - -# Zotero Libreoffice -windowrule { - name = windowrule-33 - float = on - center = on - no_anim = on - match:class = ^(Zotero)$ - match:title = ^(Citation Dialog)$ -} - -windowrule { - name = libreoffice-opaque - match:class = ^(libreoffice.*)$ - - # active, inactive, fullscreen (all forced to 1.0) - opacity = 1.0 override 1.0 override 1.0 override -} - - -# gnome calendar -windowrule { - name = windowrule-cal - float = on - center = on - match:class = ^(org.gnome.Calendar)$ - match:title = ^(Calendar)$ - animation = popin - opacity = 0.8 -} - -windowrule { - name = waypaper-fix - match:class = ^(waypaper)$ - float = on - center = on -} - -windowrule { - name = calcure - match:title = ^(calcure)$ - float = on - center = on -} - -windowrule { - name = gnome-calculator-fix - match:class = ^(org.gnome.Calculator)$ - float = on - center = on -} diff --git a/.config/hypr/config/windowrules.conf_bak b/.config/hypr/config/windowrules.conf_bak deleted file mode 100644 index c81679c..0000000 --- a/.config/hypr/config/windowrules.conf_bak +++ /dev/null @@ -1,77 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Windowrules Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -# Windows Rules https://wiki.hyprland.org/0.45.0/Configuring/Window-Rules/ # - -# Float Necessary Windows -windowrule = float, title:Rofi -windowrule = float, class:^(org.pulseaudio.pavucontrol) -windowrule = float, class:^()$,title:^(Picture in picture)$ -windowrule = float, class:^()$,title:^(Save File)$ -windowrule = float, class:^()$,title:^(Save File)$ -windowrule = float, class:^()$,title:^(Open File)$ -windowrule = float, class:^(LibreWolf)$,title:^(Picture-in-Picture)$ -windowrule = float, class:^(blueman-manager)$ -windowrule = float, class:^(xdg-desktop-portal-gtk|xdg-desktop-portal-kde|xdg-desktop-portal-hyprland)(.*)$ -windowrule = float, class:^(pomodorolm)$ -windowrule = float, title:^(Extension:.*)$ -windowrule = float, class:^(polkit-gnome-authentication-agent-1|hyprpolkitagent|org.org.kde.polkit-kde-authentication-agent-1)(.*)$ -windowrule = float, class:^(CachyOSHello)$ -windowrule = float, class:^(zenity)$ -windowrule = float, class:^()$,title:^(Steam - Self Updater)$ -windowrule = float, class:^(Zotero)$,title:^(Progress)$ -# Increase the opacity -windowrule = opacity 0.92, class:^(thunar|nemo|dolphin)$ -windowrule = opacity 0.96, class:^(discord|armcord|webcord)$ -windowrule = opacity 0.95, title:^(QQ|Telegram)$ -windowrule = opacity 0.95, title:^(NetEase Cloud Music Gtk4)$ -windowrule = opacity 1, class:^(kitty)$ -# General window rules -windowrule = size 260 340, class:^(pomodorolm)$ -windowrule = float, title:^(Picture-in-Picture)$ -windowrule = size 960 540, title:^(Picture-in-Picture)$ -windowrule = move 25%-, title:^(Picture-in-Picture)$ -windowrule = float, title:^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp|nwg-look|nwg-displays)$ -windowrule = move 25%-, title:^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$ -windowrule = size 960 540, title:^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$ -windowrule = pin, title:^(danmufloat)$ -windowrule = rounding 5, title:^(danmufloat|termfloat)$ -windowrule = animation slide right, class:^(kitty|Alacritty)$ -windowrule = noblur, class:^(org.mozilla.firefox)$ -windowrule = nodim, class:^(zen)$ -windowrule = float, title:^(Zotero Settings)$ -# Decorations related to floating windows on workspaces 1 to 10 -windowrule = bordersize 2, floating:1, onworkspace:w[fv1-10] -windowrule = bordercolor $color4, floating:1, onworkspace:w[fv1-10] -windowrule = rounding 8, floating:1, onworkspace:w[fv1-10] -# Decorations related to tiling windows on workspaces 1 to 10 -windowrule = bordersize 3, floating:0, onworkspace:f[1-10] -windowrule = rounding 4, floating:0, onworkspace:f[1-10] -windowrule = opacity 0.9, title:^(.+nvim$) -# Windows Rules End # - -# Workspaces Rules https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/ # -# workspace = 1, default:true, monitor:$priMon -# workspace = 6, default:true, monitor:$secMon -# Workspace selectors https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/#workspace-selectors -# workspace = r[1-5], monitor:$priMon -# workspace = r[6-10], monitor:$secMon -# workspace = special:scratchpad, on-created-empty:$applauncher -# no_gaps_when_only deprecated instead workspaces rules with selectors can do the same -# Smart gaps from 0.45.0 https://wiki.hyprland.org/0.45.0/Configuring/Workspace-Rules/#smart-gaps -#workspace = w[tv1-10], gapsout:5, gapsin:3 -#workspace = f[1], gapsout:5, gapsin:3 -# Workspaces Rules End # - -# Layers Rules # -layerrule = animation slide top, logout_dialog -# layerrule = animation popin 50%, waybar -layerrule = animation slide down, waybar -layerrule = animation fade 50%, wallpaper -# Layers Rules End # - -# Zotero Libreoffice -windowrule = float,class:^(Zotero)$,title:^(Citation Dialog)$ -windowrule = center,class:^(Zotero)$,title:^(Citation Dialog)$ -windowrule = noanim,class:^(Zotero)$,title:^(Citation Dialog)$ diff --git a/.config/hypr/config/workspaces.conf b/.config/hypr/config/workspaces.conf deleted file mode 100644 index eef968d..0000000 --- a/.config/hypr/config/workspaces.conf +++ /dev/null @@ -1,14 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Monitor Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -workspace = 1, monitor:eDP-2 -workspace = 2, monitor:eDP-2 -workspace = 3, monitor:eDP-2 -workspace = 4, monitor:eDP-2 -workspace = 5, monitor:eDP-2 -workspace = 6, monitor:eDP-2 -workspace = 7, monitor:eDP-2 - -workspace = 8, monitor:DP-2 -workspace = 9, monitor:HDMI-A-1 diff --git a/.config/hypr/hypridle.conf b/.config/hypr/hypridle.conf deleted file mode 100644 index 37e8ef1..0000000 --- a/.config/hypr/hypridle.conf +++ /dev/null @@ -1,29 +0,0 @@ -general { - lock_cmd = pidof swaylock || swaylock # avoid starting multiple hyprlock instances. - before_sleep_cmd = brightnessctl -r # lock before suspend. - after_sleep_cmd = hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display. -} - -listener { - timeout = 7200 # 2.5min. - on-timeout = brightnessctl -s set 10 # set monitor backlight to minimum, avoid 0 on OLED monitor. - on-resume = brightnessctl -r # monitor backlight restore. -} - -# turn off keyboard backlight, comment out this section if you dont have a keyboard backlight. -listener { - timeout = 7200 # 2.5min. - on-timeout = brightnessctl -sd asus::kbd_backlight set 0 # turn off keyboard backlight. - on-resume = brightnessctl -rd asus::kbd_backlight # turn on keyboard backlight. -} - -listener { - timeout = 7200 # 5min - on-timeout = ~/.config/swaylock/lockscript.sh # lock screen when timeout has passed -} - -# listener { -# timeout = 900 # 15min -# on-timeout = hyprctl dispatch dpms off 5.5 # screen off when timeout has passed -# on-resume = hyprctl dispatch dpms on && brightnessctl -r # screen on when activity is detected after timeout has fired. -# } diff --git a/.config/hypr/hyprland.conf b/.config/hypr/hyprland.conf deleted file mode 100644 index 86a84c7..0000000 --- a/.config/hypr/hyprland.conf +++ /dev/null @@ -1,24 +0,0 @@ -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ CachyOS Hyprland Configuration ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ - -$mainMod = SUPER - -# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -# ┃ Source Files ┃ -# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -source = ~/.config/hypr/config/animations.conf -source = ~/.config/hypr/config/autostart.conf -source = ~/.config/hypr/config/decorations.conf -source = ~/.config/hypr/config/environment.conf -source = ~/.config/hypr/config/input.conf -source = ~/.config/hypr/config/keybinds.conf -source = ~/.config/hypr/config/monitor.conf -source = ~/.config/hypr/config/variables.conf -source = ~/.config/hypr/config/windowrules.conf -source = ~/.config/hypr/config/workspaces.conf -source = ~/.config/hypr/config/plugins.conf - -# Modifying these configs can be done by creating a user defined config in the home directory, e.g. -## ~/.config/hypr/config/user-config.conf -# source ~/.config/hypr/config/user-config.conf \ No newline at end of file diff --git a/.config/hypr/monitors.conf b/.config/hypr/monitors.conf deleted file mode 100644 index b3d791b..0000000 --- a/.config/hypr/monitors.conf +++ /dev/null @@ -1,6 +0,0 @@ -# Generated by nwg-displays on 2026-02-04 at 14:45:07. Do not edit manually. - -monitor=desc:BOE NE156FHM-NX6,1920x1080@144.0,0x0,1.0 -monitor=desc:Samsung Electric Company S24F350 H4ZK111233,1920x1080@60.0,1920x0,1.0 -monitor=desc:Samsung Electric Company S24F350 H4ZK111233,transform,1 -monitor=desc:Samsung Electric Company S24F350 H4ZR302705,1920x1080@60.0,3000x0,1.0 diff --git a/.config/hypr/pyprland.toml b/.config/hypr/pyprland.toml deleted file mode 100644 index 3d94636..0000000 --- a/.config/hypr/pyprland.toml +++ /dev/null @@ -1,4 +0,0 @@ -[pyprland] -plugins = [ - "shift_monitors", -] diff --git a/.config/hypr/scripts/launch-waybar.sh b/.config/hypr/scripts/launch-waybar.sh deleted file mode 100755 index 7d125a5..0000000 --- a/.config/hypr/scripts/launch-waybar.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# start waybar if not started -if ! pgrep -x "waybar" > /dev/null; then - waybar & -fi - -# current checksums -current_checksum_config=$(md5sum ~/.config/waybar/config) -current_checksum_style=$(md5sum ~/.config/waybar/style.css) -current_checksum_colors=$(md5sum ~/.config/waybar/colors-wallust.css) - -# loop forever -while true; do - # new checksums - new_checksum_config=$(md5sum ~/.config/waybar/config) - new_checksum_style=$(md5sum ~/.config/waybar/style.css) - new_checksum_colors==$(md5sum ~/.config/waybar/colors-wallust.css) - - # if checksums are different - if [ "$current_checksum_config" != "$new_checksum_config" ] || [ "$current_checksum_style" != "$new_checksum_style" ] || [ "$current_checksum_colors" != "$new_checksum_colors" ]; then - # kill waybar - killall waybar - - # start waybar - waybar & - - # update checksums - current_checksum_config=$new_checksum_config - current_checksum_style=$new_checksum_style - current_checksum_colors=$new_checksum_colors - fi -done \ No newline at end of file diff --git a/.config/hypr/scripts/monitor-toggle.sh b/.config/hypr/scripts/monitor-toggle.sh index aa13645..0185d9b 100755 --- a/.config/hypr/scripts/monitor-toggle.sh +++ b/.config/hypr/scripts/monitor-toggle.sh @@ -2,256 +2,65 @@ set -euo pipefail PRIMARY="eDP-2" -RIGHT_EXTERNAL_NAME="DP-2" -MIDDLE_EXTERNAL_NAME="HDMI-A-1" +DP_OUT="DP-2" +HDMI_OUT="HDMI-A-1" -# Exact enable lines (from your config) -EXT1='desc:Samsung Electric Company S24F350 H4ZR302705, highres@highrr, auto-right, 1' -EXT2='desc:Samsung Electric Company S24F350 H4ZK111233, highres@highrr, auto-right, 1, transform, 1' - -# In dual mode, which one do we prefer? -DUAL_MAIN="$EXT1" - -# Optional: restart Waybar after layout changes (0=off, 1=on) -RESTART_WAYBAR=1 - -# Dock/MST settle timing (tune if needed) -SETTLE_SECS=1.0 -DPMS_KICK_RETRIES=2 -DPMS_KICK_SLEEP=0.35 +# eDP-2 at scale 1.5 → logical width 1280px → externals start at x=1280 +# DP-2 at 1920px wide → HDMI starts at x=1280+1920=3200 notify() { - if command -v notify-send >/dev/null 2>&1; then - notify-send -a "Hyprland" "$1" "${2:-}" - else - [ -n "${2:-}" ] && printf '%s: %s\n' "$1" "$2" >&2 || printf '%s\n' "$1" >&2 - fi + notify-send -a "monitor-toggle" "$1" "${2:-}" 2>/dev/null || true } -hypr() { - local out - if ! out="$(hyprctl "$@" 2>&1)"; then - notify "hyprctl failed" "$out" - exit 1 - fi - printf '%s' "$out" +active_monitors() { + hyprctl -j monitors | jq -r '.[] | select(.disabled != true) | .name' } -hypr_batch() { - local out - if ! out="$(hyprctl --batch "$1" 2>&1)"; then - notify "hyprctl --batch failed" "$out" - exit 1 - fi - printf '%s' "$out" -} - -restart_waybar() { - [ "${RESTART_WAYBAR:-0}" -eq 1 ] || return 0 - if pgrep -x waybar >/dev/null 2>&1; then - pkill -SIGUSR2 waybar >/dev/null 2>&1 || true - fi -} - -jqok() { command -v jq >/dev/null 2>&1; } -monjson() { hyprctl -j monitors 2>/dev/null || true; } - -sleep_s() { python - </dev/null || sleep 1 -import time -time.sleep(float("$1")) -PY -} - -# --- monitor discovery helpers --- - -enabled_monitor_names() { - if jqok; then - monjson | jq -r '.[] | select(.disabled != true) | .name' - else - # Fallback is weaker; jq is strongly recommended - hyprctl monitors | awk ' - /^Monitor /{name=$2} - /disabled: false/{print name} - ' - fi -} - -focused_monitor_name() { - if jqok; then - monjson | jq -r '.[] | select(.focused==true) | .name // empty' - else - hyprctl monitors | awk ' - /^Monitor /{name=$2} - /focused: yes/{print name; exit} - ' - fi -} - -external_enabled() { - local n - while IFS= read -r n; do - [ "$n" != "$PRIMARY" ] && return 0 - done < <(enabled_monitor_names) - return 1 -} - -# We don’t try to parse desc lines; we just treat them as “enable rules” -enable_rule() { - local rule="$1" - [ -n "$rule" ] && printf 'keyword monitor %s; ' "$rule" -} - -disable_by_name() { - local name="$1" - [ -n "$name" ] && printf 'keyword monitor %s, disable; ' "$name" -} - -# Disable all *currently enabled* externals by name (minimal churn) -batch_disable_enabled_externals() { - local batch="" n="" - while IFS= read -r n; do - [ "$n" = "$PRIMARY" ] && continue - batch+="$(disable_by_name "$n")" - done < <(enabled_monitor_names) - printf '%s' "$batch" -} - -# Dock settle wait: give MST/alt-mode time to re-enumerate before we apply rules / kick DPMS -dock_settle() { - sleep_s "$SETTLE_SECS" -} - -# DPMS kick for all enabled externals (works well for dock hotplug weirdness) -kick_externals() { - local names=() n="" - if jqok; then - mapfile -t names < <(monjson | jq -r --arg P "$PRIMARY" ' - .[] | select(.name != $P and (.disabled != true)) | .name - ') - else - mapfile -t names < <(enabled_monitor_names | awk -v P="$PRIMARY" '$0!=P') - fi - - [ "${#names[@]}" -eq 0 ] && return 0 - - for _ in $(seq 1 "$DPMS_KICK_RETRIES"); do - for n in "${names[@]}"; do hypr dispatch dpms off "$n" >/dev/null 2>&1 || true; done - sleep_s "$DPMS_KICK_SLEEP" - for n in "${names[@]}"; do hypr dispatch dpms on "$n" >/dev/null 2>&1 || true; done - sleep_s "$DPMS_KICK_SLEEP" - done -} - -enforce_triple_order() { - # Keep HDMI between the laptop panel and DP-2, with DP-2 always on the right. - enabled_monitor_names | grep -qx "$MIDDLE_EXTERNAL_NAME" || return 0 - enabled_monitor_names | grep -qx "$RIGHT_EXTERNAL_NAME" || return 0 - - local py=0 px=0 pspan=1920 mspan=1920 mx rx - if jqok; then - read -r px py pspan < <(monjson | jq -r --arg N "$PRIMARY" ' - .[] | select(.name == $N) | - (.transform // 0) as $t | - "\(.x // 0) \(.y // 0) \(if (($t % 2) == 1) then (.height // 1080) else (.width // 1920) end)" - ') - read -r mspan < <(monjson | jq -r --arg N "$MIDDLE_EXTERNAL_NAME" ' - .[] | select(.name == $N) | - (.transform // 0) as $t | - "\(if (($t % 2) == 1) then (.height // 1080) else (.width // 1920) end)" - ') - fi - - mx=$((px + pspan)) - rx=$((mx + mspan)) - - hypr keyword monitor "$MIDDLE_EXTERNAL_NAME, highres@highrr, ${mx}x${py}, 1, transform, 1" >/dev/null 2>&1 || true - hypr keyword monitor "$RIGHT_EXTERNAL_NAME, highres@highrr, ${rx}x${py}, 1" >/dev/null 2>&1 || true -} - -apply_profile() { - local label="$1" - local batch="$2" - dock_settle - [ -n "$batch" ] && hypr_batch "$batch" >/dev/null - dock_settle - kick_externals - notify "Profile: $label" "" - restart_waybar +has_external() { + active_monitors | grep -qvx "$PRIMARY" } profile_laptop() { - # Minimal: disable only currently enabled externals - local batch="" - batch+="$(batch_disable_enabled_externals)" - apply_profile "Laptop-only" "$batch" + wlr-randr --output "$DP_OUT" --off 2>/dev/null || true + wlr-randr --output "$HDMI_OUT" --off 2>/dev/null || true + notify "Laptop only" } profile_dual() { - # Disable enabled externals, then enable preferred main external rule - local batch="" - batch+="$(batch_disable_enabled_externals)" - batch+="$(enable_rule "$DUAL_MAIN")" - apply_profile "Dual" "$batch" + wlr-randr --output "$PRIMARY" --on --mode 1920x1080@144 --pos 0,0 --scale 1 + wlr-randr --output "$HDMI_OUT" --on --mode 1920x1080@60 --pos 1920,0 --scale 1 + wlr-randr --output "$DP_OUT" --off 2>/dev/null || true + notify "Dual" } profile_triple() { - # Disable enabled externals, then enable both rules - # Order: EXT2 first so it tends to appear “middle” with auto-right - local batch="" - batch+="$(batch_disable_enabled_externals)" - batch+="$(enable_rule "$EXT2")" - batch+="$(enable_rule "$EXT1")" - apply_profile "Triple" "$batch" - dock_settle - enforce_triple_order + wlr-randr --output "$PRIMARY" --on --mode 1920x1080@144 --pos 0,0 --scale 1 + wlr-randr --output "$HDMI_OUT" --on --mode 1920x1080@60 --pos 1920,0 --scale 1 + wlr-randr --output "$DP_OUT" --on --mode 1920x1080@60 --pos 3840,0 --scale 1 --transform 90 + notify "Triple" } -toggle_externals() { - if external_enabled; then +toggle() { + if has_external; then profile_laptop else - if [ -n "$EXT1" ] && [ -n "$EXT2" ]; then - profile_triple - else - profile_dual - fi + profile_triple fi } -dpms_toggle_focused() { - local name - name="$(focused_monitor_name)" - [ -z "$name" ] && { notify "No focused monitor" ""; exit 2; } - hypr dispatch dpms toggle "$name" >/dev/null - notify "DPMS toggle" "$name" -} - status() { - echo "Enabled monitors:" - enabled_monitor_names | sed 's/^/ - /' - echo - echo "Focused: $(focused_monitor_name || true)" + echo "Active monitors:" + active_monitors | sed 's/^/ - /' } case "${1:-}" in - laptop) profile_laptop ;; - dual) profile_dual ;; - triple) profile_triple ;; - toggle-externals) toggle_externals ;; - dpms-toggle-focused) dpms_toggle_focused ;; - kick-externals) dock_settle; kick_externals; notify "Kicked externals (DPMS)" "" ;; - status) status ;; + laptop) profile_laptop ;; + dual) profile_dual ;; + triple) profile_triple ;; + toggle) toggle ;; + status) status ;; *) - cat < (dual/triple) - $(basename "$0") dpms-toggle-focused # blank/unblank focused output (layout unchanged) - $(basename "$0") kick-externals # DPMS off/on for enabled externals - $(basename "$0") status # print enabled + focused -EOF - exit 2 + echo "Usage: $(basename "$0") laptop|dual|triple|toggle|status" + exit 1 ;; esac diff --git a/.config/hypr/scripts/record-or-screenshot.sh b/.config/hypr/scripts/record-or-screenshot.sh index af5a268..c0c4ae8 100755 --- a/.config/hypr/scripts/record-or-screenshot.sh +++ b/.config/hypr/scripts/record-or-screenshot.sh @@ -1,4 +1,4 @@ -#!/bin/env bash +#!/usr/bin/env bash # CLI options DMENU_CMD="" diff --git a/.config/hypr/scripts/rotate_current_screen.sh b/.config/hypr/scripts/rotate_current_screen.sh index 8e5dbda..e9adf6f 100755 --- a/.config/hypr/scripts/rotate_current_screen.sh +++ b/.config/hypr/scripts/rotate_current_screen.sh @@ -31,7 +31,7 @@ else elif [[ $line =~ ^[[:space:]]*description:[[:space:]]*(.*)$ ]]; then DESC="${BASH_REMATCH[1]}" elif [[ $line =~ ^[[:space:]]*transform:[[:space:]]*([0-9]+)$ ]]; then - TFORM="${BASHREMATCH[1]}" + TFORM="${BASH_REMATCH[1]}" elif [[ $line =~ ^[[:space:]]*focused:[[:space:]]*yes$ ]]; then FOC=1 elif [[ -z $line ]]; then diff --git a/.config/hypr/workspaces.conf b/.config/hypr/workspaces.conf deleted file mode 100644 index e69de29..0000000 diff --git a/.config/kitty/kitty.conf b/.config/kitty/kitty.conf index 001f4d9..49bafbc 100644 --- a/.config/kitty/kitty.conf +++ b/.config/kitty/kitty.conf @@ -1,5 +1,5 @@ include colors.conf -background_opacity .3 +background_opacity .5 background_blur 16 diff --git a/.config/quickshell/Bar.qml b/.config/quickshell/Bar.qml deleted file mode 100644 index 9efc733..0000000 --- a/.config/quickshell/Bar.qml +++ /dev/null @@ -1,60 +0,0 @@ -// Bar.qml - top panel -// Neighbouring types (Pill, ClockWidget, Theme, etc.) are auto-imported by QuickShell. -import Quickshell -import Quickshell.Wayland -import QtQuick -import QtQuick.Layouts -import "./modules" - -PanelWindow { - id: root - - WlrLayershell.namespace: "quickshell-bar" - WlrLayershell.layer: WlrLayer.Top - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - anchors { - top: true - left: true - right: true - } - - margins { - left: 2 - right: 2 - bottom: 1 - top: 3 - } - - implicitHeight: Theme.barHeight - exclusiveZone: Theme.barHeight - color: "transparent" - - RowLayout { - anchors.fill: parent - anchors.leftMargin: 4 - anchors.rightMargin: 4 - spacing: Theme.spacing - - // ─── LEFT ────────────────────────────────────────── - ClockWidget {} - WeatherWidget {} - SysTrayWidget {} - WorkspacesWidget { screen: root.screen } - MediaWidget {} - WindowTitleWidget { screen: root.screen } - - Item { Layout.fillWidth: true } - - // ─── RIGHT ───────────────────────────────────────── - CavaWidget {} - AudioWidget {} - MemoryWidget {} - CpuWidget {} - TemperatureWidget {} - BatteryWidget {} - BluetoothWidget {} - PowerProfilesWidget {} - PowerMenuWidget {} - } -} diff --git a/.config/quickshell/Exec.qml b/.config/quickshell/Exec.qml deleted file mode 100644 index 7f8b270..0000000 --- a/.config/quickshell/Exec.qml +++ /dev/null @@ -1,23 +0,0 @@ -// Exec.qml - fire-and-forget process launcher -// Usage from any file: Exec.run(["kitty", "-e", "btop"]) -pragma Singleton -import Quickshell -import Quickshell.Io -import QtQuick - -Singleton { - id: root - - function run(cmd) { - const proc = procPool.createObject(root, { command: cmd }); - proc.running = true; - } - - Component { - id: procPool - Process { - running: false - onExited: destroy() - } - } -} diff --git a/.config/quickshell/Pill.qml b/.config/quickshell/Pill.qml deleted file mode 100644 index 4432cf3..0000000 --- a/.config/quickshell/Pill.qml +++ /dev/null @@ -1,39 +0,0 @@ -// Pill.qml - styled module container -import QtQuick -import QtQuick.Layouts - -Rectangle { - id: root - - default property alias content: inner.data - - property bool hovered: mouseArea.containsMouse - signal clicked(var mouse) - signal scrolled(var wheel) - - property real leftPadding: Theme.pillPadH - property real rightPadding: Theme.pillPadH - implicitWidth: inner.implicitWidth + leftPadding + rightPadding - implicitHeight: Theme.barHeight - radius: Theme.radius - color: hovered ? Theme.pillHover : Theme.pill - - Behavior on color { ColorAnimation { duration: 150 } } - - RowLayout { - id: inner - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: root.leftPadding - spacing: 4 - } - - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton - onClicked: (m) => root.clicked(m) - onWheel: (w) => root.scrolled(w) - } -} diff --git a/.config/quickshell/Theme.qml b/.config/quickshell/Theme.qml deleted file mode 100644 index faebb46..0000000 --- a/.config/quickshell/Theme.qml +++ /dev/null @@ -1,38 +0,0 @@ -// Theme.qml - global palette & dimensions -// QuickShell auto-discovers this; access from any file as `Theme.colorN` etc. -pragma Singleton -import Quickshell -import QtQuick - -Singleton { - // ── Wallust palette ────────────────────────────────────── - readonly property color background: "#252425" - readonly property color foreground: "#F9F1D9" - readonly property color color0: "#505051" - readonly property color color1: "#9C604E" - readonly property color color2: "#807A52" - readonly property color color3: "#908BAB" - readonly property color color4: "#B7815F" - readonly property color color5: "#B9BECA" - readonly property color color6: "#EED793" - readonly property color color7: "#EEE3C1" - readonly property color color8: "#A79F87" - - // ── Derived / semantic ──────────────────────────────────── - readonly property color pill: Qt.rgba(0.976, 0.945, 0.851, 0.15) - readonly property color pillHover: color2 - readonly property color wsActive: color3 - readonly property color wsUrgent: color1 - - // ── Typography ──────────────────────────────────────────── - readonly property string fontSans: "Fira Sans Condensed" - readonly property string fontMono: "FiraCode Nerd Font" - readonly property int fontSize: 12 - - // ── Bar geometry ───────────────────────────────────────── - readonly property int barHeight: 28 - readonly property int barPadding: 2 - readonly property int radius: 5 - readonly property int pillPadH: 10 - readonly property int spacing: 4 -} diff --git a/.config/quickshell/modules/AudioWidget.qml b/.config/quickshell/modules/AudioWidget.qml deleted file mode 100644 index 1c50431..0000000 --- a/.config/quickshell/modules/AudioWidget.qml +++ /dev/null @@ -1,48 +0,0 @@ -// modules/AudioWidget.qml - wireplumber volume, matches waybar style. -// Icon set mirrors waybar wireplumber format-icons (NerdFont). -import QtQuick -import QtQuick.Layouts -import Quickshell.Services.Pipewire -import ".." - -Pill { - id: root - - property var node: Pipewire.defaultAudioSink - property bool muted: node?.audio.muted ?? false - property real vol: node?.audio.volume ?? 0 - - property string icon: { - if (muted || vol === 0) return " " - if (vol < 0.34) return " " - if (vol < 0.67) return " " - return " " - } - - onClicked: (m) => { - if (m.button === Qt.LeftButton) Exec.run(["pavucontrol"]) - if (m.button === Qt.RightButton && node) - node.audio.muted = !node.audio.muted - } - - onScrolled: (w) => { - if (!node) return - var delta = w.angleDelta.y > 0 ? 0.04 : -0.04 - node.audio.volume = Math.max(0, Math.min(1.5, node.audio.volume + delta)) - } - - // Cava feeds into this on the left → right border only - leftPadding: 0 - rightPadding: Theme.pillPadH - - Text { - text: root.icon - font { family: Theme.fontMono; pixelSize: Theme.fontSize; } - color: "#fab387" // peach accent matching waybar foreground color for icon - } - Text { - text: root.muted ? "muted" : Math.round(root.vol * 100) + "%" - font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground - } -} diff --git a/.config/quickshell/modules/PowerMenuWidget.qml b/.config/quickshell/modules/PowerMenuWidget.qml deleted file mode 100644 index a7eb7f4..0000000 --- a/.config/quickshell/modules/PowerMenuWidget.qml +++ /dev/null @@ -1,83 +0,0 @@ -// modules/PowerMenuWidget.qml - ⏻ button with inline popup menu. -// Matches waybar custom/power with menu-actions. -import QtQuick -import QtQuick.Layouts -import Quickshell.Io -import ".." - -Pill { - id: root - onClicked: (m) => { - if (m.button === Qt.LeftButton) menu.visible = !menu.visible - } - - Text { - text: "⏻ " - font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground - } - - // Inline drop-up popup - appears above the bar - Rectangle { - id: menu - visible: false - z: 100 - - width: 130 - height: menuCol.implicitHeight + 16 - radius: 5 - color: Qt.rgba(0.086, 0.075, 0.125, 0.85) - border.color: Qt.rgba(1, 1, 1, 0.06) - border.width: 1 - - // Anchor above the pill - parent: root.parent // reparent to bar so z-ordering works - x: root.x + root.width - width - y: root.y - height - 4 - - ColumnLayout { - id: menuCol - anchors { fill: parent; margins: 8 } - spacing: 2 - - Repeater { - model: [ - { label: "Suspend", cmd: ["systemctl", "suspend"] }, - { label: "Hibernate", cmd: ["systemctl", "hibernate"] }, - { label: "Logout", cmd: ["hyprctl", "dispatch", "exit"] }, - { label: "Reboot", cmd: ["reboot"] }, - { label: "Shutdown", cmd: ["shutdown", "now"] }, - ] - - delegate: Rectangle { - required property var modelData - Layout.fillWidth: true - height: 26 - radius: 5 - color: itemHover.containsMouse - ? Theme.pillHover - : "transparent" - Behavior on color { ColorAnimation { duration: 100 } } - - Text { - anchors { left: parent.left; verticalCenter: parent.verticalCenter; leftMargin: 8 } - text: modelData.label - font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground - } - - MouseArea { - id: itemHover - anchors.fill: parent - hoverEnabled: true - onClicked: { - menu.visible = false - Exec.run(modelData.cmd) - } - } - } - } - } - - } -} diff --git a/.config/quickshell/modules/WindowTitleWidget.qml b/.config/quickshell/modules/WindowTitleWidget.qml deleted file mode 100644 index 15818de..0000000 --- a/.config/quickshell/modules/WindowTitleWidget.qml +++ /dev/null @@ -1,28 +0,0 @@ -// modules/WindowTitleWidget.qml - hyprland/window equivalent -import QtQuick -import Quickshell.Hyprland -import ".." - -Pill { - id: root - required property var screen - - property string title: { - var ws = Hyprland.focusedWorkspace - if (!ws) return "" - var win = ws.lastWindow - if (!win) return "" - var t = win.title ?? "" - return t.length > 60 ? t.substring(0, 60) + "..." : t - } - - visible: title !== "" - - Text { - text: root.title - font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground - elide: Text.ElideRight - maximumLineCount: 1 - } -} diff --git a/.config/quickshell/modules/WorkspacesWidget.qml b/.config/quickshell/modules/WorkspacesWidget.qml deleted file mode 100644 index 29eb72c..0000000 --- a/.config/quickshell/modules/WorkspacesWidget.qml +++ /dev/null @@ -1,70 +0,0 @@ -// modules/WorkspacesWidget.qml -// Kanji workspace labels, per-monitor, matches waybar hyprland/workspaces. -import QtQuick -import QtQuick.Layouts -import Quickshell.Hyprland -import ".." - -Rectangle { - id: root - required property var screen - - color: "transparent" - implicitWidth: wsRow.implicitWidth - implicitHeight: Theme.barHeight - - // Filter workspaces that belong to this screen's monitor - property string monitorName: { - for (var i = 0; i < Hyprland.monitors.values.length; i++) { - var m = Hyprland.monitors.values[i] - if (m.name === screen.name) return m.name - } - return "" - } - - RowLayout { - id: wsRow - anchors.verticalCenter: parent.verticalCenter - spacing: Theme.spacing - - Repeater { - model: { - // sort visible workspaces for this monitor - var all = Hyprland.workspaces.values - return all.filter(ws => ws.monitor && ws.monitor.name === root.monitorName) - .sort((a, b) => a.id - b.id) - } - - delegate: Rectangle { - required property var modelData - property bool isActive: modelData.id === (Hyprland.focusedWorkspace?.id ?? -1) - - width: 32 - height: Theme.barHeight - 4 - radius: Theme.radius - Layout.alignment: Qt.AlignVCenter - color: isActive - ? Theme.wsActive - : (wsBtn.containsMouse ? Theme.pillHover : Theme.pill) - - Behavior on color { ColorAnimation { duration: 200 } } - - Text { - anchors.centerIn: parent - text: Math.min(modelData.id - 1, 10 - 1) - font { family: Theme.fontSans; pixelSize: 11 } - color: Theme.foreground - } - - MouseArea { - id: wsBtn - anchors.fill: parent - hoverEnabled: true - onClicked: Hyprland.dispatch("workspace " + modelData.id) - onWheel: (w) => Hyprland.dispatch( - "workspace " + (w.angleDelta.y > 0 ? "e+1" : "e-1")) - } - } - } - } -} diff --git a/.config/quickshell/own/common/Appearance.qml b/.config/quickshell/own/common/Appearance.qml new file mode 100644 index 0000000..e3f168a --- /dev/null +++ b/.config/quickshell/own/common/Appearance.qml @@ -0,0 +1,360 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io +import "functions" +import "." as Common +import ".." + +Singleton { + id: root + property string colorSource: Common.Config.options.appearance.colorSource + property string caelestiaAccentProfile: Common.Config.options.appearance.caelestia.accentProfile + property string lastCaelestiaPayload: "" + property QtObject m3colors: { + if (colorSource === "matugen" && matugenLoader.item) + return matugenLoader.item; + if (colorSource === "caelestia" && caelestiaPaletteLoaded) + return caelestiaColors; + return defaultColors; + } + property QtObject animation + property QtObject animationCurves + property QtObject colors + property QtObject rounding + property QtObject font + property QtObject sizes + property bool caelestiaPaletteLoaded: false + + Loader { + id: matugenLoader + active: root.colorSource === "matugen" + source: "Appearance.colors.qml" + } + + // Retuned to pull from this shell's own wallust palette (Theme/Colors) instead of the + // upstream stock M3 purple palette, so the overview matches the bar's look. See + // ../modules/overview/CREDITS.md. + property QtObject defaultColors: QtObject { + property bool darkmode: true + property color m3primary: Theme.wsActive + property color m3onPrimary: Colors.background + property color m3primaryContainer: ColorUtils.mix(Theme.wsActive, Colors.background, 0.35) + property color m3onPrimaryContainer: Colors.foreground + property color m3secondary: Theme.wsActive + property color m3onSecondary: Colors.background + property color m3secondaryContainer: ColorUtils.mix(Theme.wsActive, Colors.background, 0.55) + property color m3onSecondaryContainer: Colors.foreground + property color m3background: Colors.background + property color m3onBackground: Colors.foreground + property color m3surface: Colors.background + property color m3surfaceContainerLow: ColorUtils.mix(Colors.foreground, Colors.background, 0.06) + property color m3surfaceContainer: ColorUtils.mix(Colors.foreground, Colors.background, 0.10) + property color m3surfaceContainerHigh: ColorUtils.mix(Colors.foreground, Colors.background, 0.15) + property color m3surfaceContainerHighest: ColorUtils.mix(Colors.foreground, Colors.background, 0.20) + property color m3onSurface: Colors.foreground + property color m3surfaceVariant: ColorUtils.mix(Colors.foreground, Colors.background, 0.25) + property color m3onSurfaceVariant: Colors.foreground + property color m3inverseSurface: Colors.foreground + property color m3inverseOnSurface: Colors.background + property color m3outline: Colors.color8 + property color m3outlineVariant: ColorUtils.mix(Colors.foreground, Colors.background, 0.30) + property color m3shadow: "#000000" + } + + property QtObject caelestiaColors: QtObject { + property bool darkmode: defaultColors.darkmode + property color m3primary: defaultColors.m3primary + property color m3onPrimary: defaultColors.m3onPrimary + property color m3primaryContainer: defaultColors.m3primaryContainer + property color m3onPrimaryContainer: defaultColors.m3onPrimaryContainer + property color m3secondary: defaultColors.m3secondary + property color m3onSecondary: defaultColors.m3onSecondary + property color m3secondaryContainer: defaultColors.m3secondaryContainer + property color m3onSecondaryContainer: defaultColors.m3onSecondaryContainer + property color m3background: defaultColors.m3background + property color m3onBackground: defaultColors.m3onBackground + property color m3surface: defaultColors.m3surface + property color m3surfaceContainerLow: defaultColors.m3surfaceContainerLow + property color m3surfaceContainer: defaultColors.m3surfaceContainer + property color m3surfaceContainerHigh: defaultColors.m3surfaceContainerHigh + property color m3surfaceContainerHighest: defaultColors.m3surfaceContainerHighest + property color m3onSurface: defaultColors.m3onSurface + property color m3surfaceVariant: defaultColors.m3surfaceVariant + property color m3onSurfaceVariant: defaultColors.m3onSurfaceVariant + property color m3inverseSurface: defaultColors.m3inverseSurface + property color m3inverseOnSurface: defaultColors.m3inverseOnSurface + property color m3outline: defaultColors.m3outline + property color m3outlineVariant: defaultColors.m3outlineVariant + property color m3shadow: defaultColors.m3shadow + } + + function loadCaelestiaPalette() { + getCaelestiaScheme.running = true; + } + + function relativeLuminance(color) { + const c = Qt.color(color); + function channel(v) { + return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + } + return (0.2126 * channel(c.r)) + (0.7152 * channel(c.g)) + (0.0722 * channel(c.b)); + } + + function bestOnColor(backgroundColor) { + return relativeLuminance(backgroundColor) > 0.5 ? "#121212" : "#f5f5f5"; + } + + function firstColor(palette, keys, fallback) { + for (const key of keys) { + if (palette[key]) + return palette[key]; + } + return fallback; + } + + function applyCaelestiaPalette(palette, mode) { + if (caelestiaAccentProfile === "vibrant") { + const primary = firstColor(palette, ["blue", "klink", "term12", "primary"], defaultColors.m3primary); + const secondary = firstColor(palette, ["mauve", "lavender", "term13", "secondary"], defaultColors.m3secondary); + const tertiary = firstColor(palette, ["pink", "rosewater", "term11", "tertiary"], defaultColors.m3secondaryContainer); + const primaryContainer = firstColor(palette, ["sapphire", "klinkSelection", "primaryContainer"], defaultColors.m3primaryContainer); + const secondaryContainer = firstColor(palette, ["surface2", "secondaryContainer"], defaultColors.m3secondaryContainer); + + caelestiaColors.m3primary = primary; + caelestiaColors.m3onPrimary = bestOnColor(primary); + caelestiaColors.m3primaryContainer = primaryContainer; + caelestiaColors.m3onPrimaryContainer = bestOnColor(primaryContainer); + caelestiaColors.m3secondary = secondary; + caelestiaColors.m3onSecondary = bestOnColor(secondary); + caelestiaColors.m3secondaryContainer = secondaryContainer; + caelestiaColors.m3onSecondaryContainer = bestOnColor(secondaryContainer); + // Preserve a stronger accent presence across UI mixes. + caelestiaColors.m3surfaceVariant = firstColor(palette, ["surface1", "surfaceVariant"], defaultColors.m3surfaceVariant); + caelestiaColors.m3outline = firstColor(palette, ["overlay2", "outline"], defaultColors.m3outline); + caelestiaColors.m3outlineVariant = firstColor(palette, ["overlay0", "outlineVariant"], defaultColors.m3outlineVariant); + if (tertiary) + caelestiaColors.m3secondaryContainer = ColorUtils.mix(secondaryContainer, tertiary, 0.7); + } else { + const map = { + "primary": "m3primary", + "onPrimary": "m3onPrimary", + "primaryContainer": "m3primaryContainer", + "onPrimaryContainer": "m3onPrimaryContainer", + "secondary": "m3secondary", + "onSecondary": "m3onSecondary", + "secondaryContainer": "m3secondaryContainer", + "onSecondaryContainer": "m3onSecondaryContainer", + "surfaceVariant": "m3surfaceVariant", + "outline": "m3outline", + "outlineVariant": "m3outlineVariant" + }; + for (const key in map) { + if (palette[key]) + caelestiaColors[map[key]] = palette[key]; + } + } + + // Keep foundational tones from Material keys for readability. + const baseMap = { + "background": "m3background", + "onBackground": "m3onBackground", + "surface": "m3surface", + "surfaceContainerLow": "m3surfaceContainerLow", + "surfaceContainer": "m3surfaceContainer", + "surfaceContainerHigh": "m3surfaceContainerHigh", + "surfaceContainerHighest": "m3surfaceContainerHighest", + "onSurface": "m3onSurface", + "inverseSurface": "m3inverseSurface", + "inverseOnSurface": "m3inverseOnSurface", + "shadow": "m3shadow" + }; + for (const key in baseMap) { + if (palette[key]) + caelestiaColors[baseMap[key]] = palette[key]; + } + + if (palette["onSurfaceVariant"]) + caelestiaColors.m3onSurfaceVariant = palette["onSurfaceVariant"]; + + if (mode === "light") + caelestiaColors.darkmode = false; + else if (mode === "dark") + caelestiaColors.darkmode = true; + } + + Process { + id: getCaelestiaScheme + command: ["sh", "-lc", "caelestia scheme get 2>/dev/null || true"] + stdout: StdioCollector { + id: caelestiaCollector + onStreamFinished: { + const text = caelestiaCollector.text; + if (!text || !text.trim()) { + root.caelestiaPaletteLoaded = false; + return; + } + + const ansiPattern = /\x1b\[[0-9;]*m/g; + const lines = text.split("\n"); + let mode = ""; + const palette = ({}); + + for (const rawLine of lines) { + const line = rawLine.replace(ansiPattern, "").trim(); + + if (line.startsWith("Mode:")) { + mode = line.split(":")[1]?.trim()?.toLowerCase() ?? ""; + continue; + } + + const match = line.match(/^([A-Za-z0-9_]+):\s*.*?([0-9a-fA-F]{6})$/); + if (!match) + continue; + + palette[match[1]] = `#${match[2]}`; + } + + const normalized = JSON.stringify({ mode: mode, palette: palette, profile: caelestiaAccentProfile }); + if (normalized === root.lastCaelestiaPayload) + return; + + root.lastCaelestiaPayload = normalized; + applyCaelestiaPalette(palette, mode); + root.caelestiaPaletteLoaded = Object.keys(palette).length > 0; + } + } + } + + Timer { + id: caelestiaRefreshTimer + interval: Math.max(500, Common.Config.options.appearance.caelestia.refreshInterval) + running: root.colorSource === "caelestia" && Common.Config.options.appearance.caelestia.autoRefresh + repeat: true + triggeredOnStart: false + onTriggered: root.loadCaelestiaPalette() + } + + onColorSourceChanged: { + if (colorSource === "caelestia") + loadCaelestiaPalette(); + } + + onCaelestiaAccentProfileChanged: { + if (colorSource === "caelestia") { + root.lastCaelestiaPayload = ""; + loadCaelestiaPalette(); + } + } + + Component.onCompleted: { + if (colorSource === "caelestia") + loadCaelestiaPalette(); + } + + colors: QtObject { + property color colSubtext: m3colors.m3outline + property color colLayer0: m3colors.m3background + property color colOnLayer0: m3colors.m3onBackground + property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4) + property color colLayer1: m3colors.m3surfaceContainerLow + property color colOnLayer1: m3colors.m3onSurfaceVariant + property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45) + property color colLayer1Hover: ColorUtils.mix(colLayer1, colOnLayer1, 0.92) + property color colLayer1Active: ColorUtils.mix(colLayer1, colOnLayer1, 0.85) + property color colLayer2: m3colors.m3surfaceContainer + property color colOnLayer2: m3colors.m3onSurface + property color colLayer2Hover: ColorUtils.mix(colLayer2, colOnLayer2, 0.90) + property color colLayer2Active: ColorUtils.mix(colLayer2, colOnLayer2, 0.80) + property color colPrimary: m3colors.m3primary + property color colOnPrimary: m3colors.m3onPrimary + property color colSecondary: m3colors.m3secondary + property color colSecondaryContainer: m3colors.m3secondaryContainer + property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer + property color colTooltip: m3colors.m3inverseSurface + property color colOnTooltip: m3colors.m3inverseOnSurface + property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7) + property color colOutline: m3colors.m3outline + } + + rounding: QtObject { + property int unsharpen: Common.Config.options.appearance.rounding.unsharpen + property int verysmall: Common.Config.options.appearance.rounding.verysmall + property int small: Common.Config.options.appearance.rounding.small + property int normal: Common.Config.options.appearance.rounding.normal + property int large: Common.Config.options.appearance.rounding.large + property int full: Common.Config.options.appearance.rounding.full + property int screenRounding: Common.Config.options.appearance.rounding.screenRounding + property int windowRounding: Common.Config.options.appearance.rounding.windowRounding + } + + font: QtObject { + property QtObject family: QtObject { + property string main: Common.Config.options.appearance.font.family.main + property string title: Common.Config.options.appearance.font.family.title + property string expressive: Common.Config.options.appearance.font.family.expressive + } + property QtObject pixelSize: QtObject { + property int smaller: Common.Config.options.appearance.font.pixelSize.smaller + property int small: Common.Config.options.appearance.font.pixelSize.small + property int normal: Common.Config.options.appearance.font.pixelSize.normal + property int larger: Common.Config.options.appearance.font.pixelSize.larger + property int huge: Common.Config.options.appearance.font.pixelSize.huge + } + } + + animationCurves: QtObject { + readonly property list expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] + readonly property list expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] + readonly property list emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1] + readonly property real expressiveDefaultSpatialDuration: Common.Config.options.appearance.animation.duration.elementMove + readonly property real expressiveEffectsDuration: Common.Config.options.appearance.animation.duration.elementMoveFast + } + + animation: QtObject { + property QtObject elementMove: QtObject { + property int duration: animationCurves.expressiveDefaultSpatialDuration + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.expressiveDefaultSpatial + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMove.duration + easing.type: root.animation.elementMove.type + easing.bezierCurve: root.animation.elementMove.bezierCurve + } + } + } + + property QtObject elementMoveEnter: QtObject { + property int duration: Common.Config.options.appearance.animation.duration.elementMoveEnter + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.emphasizedDecel + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMoveEnter.duration + easing.type: root.animation.elementMoveEnter.type + easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve + } + } + } + + property QtObject elementMoveFast: QtObject { + property int duration: animationCurves.expressiveEffectsDuration + property int type: Easing.BezierSpline + property list bezierCurve: animationCurves.expressiveEffects + property Component numberAnimation: Component { + NumberAnimation { + duration: root.animation.elementMoveFast.duration + easing.type: root.animation.elementMoveFast.type + easing.bezierCurve: root.animation.elementMoveFast.bezierCurve + } + } + } + } + + sizes: QtObject { + property real elevationMargin: Common.Config.options.appearance.sizes.elevationMargin + } +} diff --git a/.config/quickshell/own/common/Config.qml b/.config/quickshell/own/common/Config.qml new file mode 100644 index 0000000..621b39d --- /dev/null +++ b/.config/quickshell/own/common/Config.qml @@ -0,0 +1,194 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Io + +Singleton { + id: root + + property var userOptions: ({}) + + function read(path, fallback) { + const parts = path.split("."); + let current = userOptions; + + for (const part of parts) { + if (current === null || current === undefined || typeof current !== "object" || !(part in current)) { + return fallback; + } + current = current[part]; + } + + return current === undefined || current === null ? fallback : current; + } + + function readInt(path, fallback) { + const value = read(path, fallback); + const parsed = Number(value); + return Number.isInteger(parsed) ? parsed : fallback; + } + + function readReal(path, fallback) { + const value = read(path, fallback); + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : fallback; + } + + function readBool(path, fallback) { + const value = read(path, fallback); + return typeof value === "boolean" ? value : fallback; + } + + function readString(path, fallback) { + const value = read(path, fallback); + if (typeof value !== "string") + return fallback; + + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : fallback; + } + + property QtObject options: QtObject { + property QtObject appearance: QtObject { + property string colorSource: root.readString( + "appearance.colorSource", + root.readBool("appearance.useMatugenColors", false) ? "matugen" : "default" + ) + property bool useMatugenColors: colorSource === "matugen" + property QtObject caelestia: QtObject { + property bool autoRefresh: root.readBool("appearance.caelestia.autoRefresh", true) + property int refreshInterval: root.readInt("appearance.caelestia.refreshInterval", 2000) + property string accentProfile: root.readString("appearance.caelestia.accentProfile", "vibrant") + } + + property QtObject rounding: QtObject { + property int unsharpen: root.readInt("appearance.rounding.unsharpen", 2) + property int verysmall: root.readInt("appearance.rounding.verysmall", 8) + property int small: root.readInt("appearance.rounding.small", 12) + property int normal: root.readInt("appearance.rounding.normal", 17) + property int large: root.readInt("appearance.rounding.large", 23) + property int full: root.readInt("appearance.rounding.full", 9999) + property int screenRounding: root.readInt("appearance.rounding.screenRounding", large) + property int windowRounding: root.readInt("appearance.rounding.windowRounding", 18) + } + + property QtObject font: QtObject { + property QtObject family: QtObject { + property string main: root.readString("appearance.font.family.main", "sans-serif") + property string title: root.readString("appearance.font.family.title", "sans-serif") + property string expressive: root.readString("appearance.font.family.expressive", "sans-serif") + } + + property QtObject pixelSize: QtObject { + property int smaller: root.readInt("appearance.font.pixelSize.smaller", 12) + property int small: root.readInt("appearance.font.pixelSize.small", 15) + property int normal: root.readInt("appearance.font.pixelSize.normal", 16) + property int larger: root.readInt("appearance.font.pixelSize.larger", 19) + property int huge: root.readInt("appearance.font.pixelSize.huge", 22) + } + } + + property QtObject animation: QtObject { + property QtObject duration: QtObject { + property int elementMove: root.readInt("appearance.animation.duration.elementMove", 500) + property int elementMoveEnter: root.readInt("appearance.animation.duration.elementMoveEnter", 400) + property int elementMoveFast: root.readInt("appearance.animation.duration.elementMoveFast", 200) + } + } + + property QtObject sizes: QtObject { + property real elevationMargin: root.readReal("appearance.sizes.elevationMargin", 10) + } + } + + property QtObject overview: QtObject { + property int rows: root.readInt("overview.rows", 2) + property int columns: root.readInt("overview.columns", 5) + property real scale: root.readReal("overview.scale", 0.16) + property bool enable: root.readBool("overview.enable", true) + property bool hideEmptyRows: root.readBool("overview.hideEmptyRows", true) + property bool closeOnFocusLoss: root.readBool("overview.closeOnFocusLoss", true) + property bool useWorkspaceMap: root.readBool("overview.useWorkspaceMap", false) + property var workspaceMap: root.read("overview.workspaceMap", []) + property bool orderRightLeft: root.readBool("overview.orderRightLeft", false) + property bool orderBottomUp: root.readBool("overview.orderBottomUp", false) + property bool previewsEnabled: root.readBool("overview.previewsEnabled", true) + property string previewMode: root.readString("overview.previewMode", "live") + property bool includeInactiveMonitorPreviews: root.readBool("overview.includeInactiveMonitorPreviews", true) + property int previewRecaptureDelayMs: root.readInt("overview.previewRecaptureDelayMs", 60) + property bool showSpecialWorkspaces: root.readBool("overview.showSpecialWorkspaces", true) + property var specialWorkspaces: root.read("overview.specialWorkspaces", []) + property int specialWorkspaceColumns: root.readInt("overview.specialWorkspaceColumns", columns) + property string emptyWorkspaceWallpaper: root.readString("overview.emptyWorkspaceWallpaper", "") + property string specialEmptyWorkspaceWallpaper: root.readString("overview.specialEmptyWorkspaceWallpaper", "") + property real workspaceSpacing: root.readReal("overview.workspaceSpacing", 5) + property real backgroundPadding: root.readReal("overview.backgroundPadding", 10) + property real workspaceNumberBaseSize: root.readReal("overview.workspaceNumberBaseSize", 250) + property QtObject effects: QtObject { + property bool enableBackdrop: root.readBool("overview.effects.enableBackdrop", false) + property real backdropOpacity: root.readReal("overview.effects.backdropOpacity", 0.28) + property real panelOpacity: root.readReal("overview.effects.panelOpacity", 0.92) + property real workspaceOpacity: root.readReal("overview.effects.workspaceOpacity", 0.86) + property real emptyWorkspaceWallpaperOverlayOpacity: root.readReal("overview.effects.emptyWorkspaceWallpaperOverlayOpacity", 0.18) + property real windowOverlayOpacity: root.readReal("overview.effects.windowOverlayOpacity", 0.22) + property bool enableBlur: root.readBool("overview.effects.enableBlur", false) + property bool glassMode: root.readBool("overview.effects.glassMode", false) + property real glassTintStrength: root.readReal("overview.effects.glassTintStrength", 0.35) + property real glassBorderOpacity: root.readReal("overview.effects.glassBorderOpacity", 0.72) + property real glassShineOpacity: root.readReal("overview.effects.glassShineOpacity", 0.14) + } + } + + property QtObject position: QtObject { + property int topMargin: root.readInt("position.topMargin", 100) + } + + property QtObject windowPreview: QtObject { + property bool showIcons: root.readBool("windowPreview.showIcons", true) + property real iconToWindowRatio: root.readReal("windowPreview.iconToWindowRatio", 0.25) + property real iconToWindowRatioCompact: root.readReal("windowPreview.iconToWindowRatioCompact", 0.45) + property real xwaylandIndicatorToIconRatio: root.readReal("windowPreview.xwaylandIndicatorToIconRatio", 0.35) + property real inactiveMonitorOpacity: root.readReal("windowPreview.inactiveMonitorOpacity", 0.4) + property bool cropToFill: root.readBool("windowPreview.cropToFill", false) + } + + property QtObject hacks: QtObject { + property int arbitraryRaceConditionDelay: root.readInt("hacks.arbitraryRaceConditionDelay", 150) + property int hyprlandEventDebounceMs: root.readInt("hacks.hyprlandEventDebounceMs", 40) + } + } + + Process { + id: loadUserConfig + command: [ + "sh", + "-lc", + "cfg=\"${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/overview/config.json\"; [ -r \"$cfg\" ] && cat \"$cfg\"" + ] + stdout: StdioCollector { + id: configCollector + onStreamFinished: { + const payload = configCollector.text.trim(); + if (!payload) + return; + + try { + const parsed = JSON.parse(payload); + if (typeof parsed === "object" && parsed !== null) { + root.userOptions = parsed; + } else { + console.warn("overview: config.json must contain a JSON object; ignoring file"); + } + } catch (error) { + console.warn("overview: failed to parse user config.json; using defaults", error); + } + } + } + } + + Component.onCompleted: { + loadUserConfig.running = true; + } +} diff --git a/.config/quickshell/own/common/functions/ColorUtils.qml b/.config/quickshell/own/common/functions/ColorUtils.qml new file mode 100644 index 0000000..6162df1 --- /dev/null +++ b/.config/quickshell/own/common/functions/ColorUtils.qml @@ -0,0 +1,68 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function colorWithHueOf(color1, color2) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + var hue = c2.hsvHue; + var sat = c1.hsvSaturation; + var val = c1.hsvValue; + var alpha = c1.a; + return Qt.hsva(hue, sat, val, alpha); + } + + function colorWithSaturationOf(color1, color2) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + var hue = c1.hsvHue; + var sat = c2.hsvSaturation; + var val = c1.hsvValue; + var alpha = c1.a; + return Qt.hsva(hue, sat, val, alpha); + } + + function colorWithLightness(color, lightness) { + var c = Qt.color(color); + return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a); + } + + function colorWithLightnessOf(color1, color2) { + var c2 = Qt.color(color2); + return colorWithLightness(color1, c2.hslLightness); + } + + function adaptToAccent(color1, color2) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + var hue = c2.hslHue; + var sat = c2.hslSaturation; + var light = c1.hslLightness; + var alpha = c1.a; + return Qt.hsla(hue, sat, light, alpha); + } + + function mix(color1, color2, percentage = 0.5) { + var c1 = Qt.color(color1); + var c2 = Qt.color(color2); + return Qt.rgba( + percentage * c1.r + (1 - percentage) * c2.r, + percentage * c1.g + (1 - percentage) * c2.g, + percentage * c1.b + (1 - percentage) * c2.b, + percentage * c1.a + (1 - percentage) * c2.a + ); + } + + function transparentize(color, percentage = 1) { + var c = Qt.color(color); + return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage)); + } + + function applyAlpha(color, alpha) { + var c = Qt.color(color); + var a = Math.max(0, Math.min(1, alpha)); + return Qt.rgba(c.r, c.g, c.b, a); + } +} diff --git a/.config/quickshell/own/common/functions/qmldir b/.config/quickshell/own/common/functions/qmldir new file mode 100644 index 0000000..4c648e7 --- /dev/null +++ b/.config/quickshell/own/common/functions/qmldir @@ -0,0 +1 @@ +singleton ColorUtils 1.0 ColorUtils.qml diff --git a/.config/quickshell/own/common/qmldir b/.config/quickshell/own/common/qmldir new file mode 100644 index 0000000..a848518 --- /dev/null +++ b/.config/quickshell/own/common/qmldir @@ -0,0 +1,7 @@ +singleton Appearance 1.0 Appearance.qml +singleton Config 1.0 Config.qml +singleton ColorUtils 1.0 functions/ColorUtils.qml +StyledText 1.0 widgets/StyledText.qml +StyledRectangularShadow 1.0 widgets/StyledRectangularShadow.qml +StyledToolTip 1.0 widgets/StyledToolTip.qml +StyledToolTipContent 1.0 widgets/StyledToolTipContent.qml diff --git a/.config/quickshell/own/common/widgets/StyledRectangularShadow.qml b/.config/quickshell/own/common/widgets/StyledRectangularShadow.qml new file mode 100644 index 0000000..ccdff1a --- /dev/null +++ b/.config/quickshell/own/common/widgets/StyledRectangularShadow.qml @@ -0,0 +1,14 @@ +import QtQuick +import QtQuick.Effects +import ".." + +RectangularShadow { + required property var target + anchors.fill: target + radius: 20 + blur: 0.9 * Appearance.sizes.elevationMargin + offset: Qt.vector2d(0.0, 1.0) + spread: 1 + color: Appearance.colors.colShadow + cached: true +} diff --git a/.config/quickshell/own/common/widgets/StyledText.qml b/.config/quickshell/own/common/widgets/StyledText.qml new file mode 100644 index 0000000..abfcefa --- /dev/null +++ b/.config/quickshell/own/common/widgets/StyledText.qml @@ -0,0 +1,16 @@ +import QtQuick +import ".." + +Text { + id: root + property bool animateChange: false + + renderType: Text.NativeRendering + verticalAlignment: Text.AlignVCenter + font { + hintingPreference: Font.PreferFullHinting + family: Appearance?.font.family.main ?? "sans-serif" + pixelSize: Appearance?.font.pixelSize.small ?? 15 + } + color: Appearance?.m3colors.m3onBackground ?? "white" +} diff --git a/.config/quickshell/own/common/widgets/StyledToolTip.qml b/.config/quickshell/own/common/widgets/StyledToolTip.qml new file mode 100644 index 0000000..4d4678c --- /dev/null +++ b/.config/quickshell/own/common/widgets/StyledToolTip.qml @@ -0,0 +1,23 @@ +import QtQuick +import QtQuick.Controls +import "." + +ToolTip { + id: root + property bool extraVisibleCondition: true + property bool alternativeVisibleCondition: false + readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition + verticalPadding: 5 + horizontalPadding: 10 + background: null + + visible: internalVisibleCondition + + contentItem: StyledToolTipContent { + id: contentItem + text: root.text + shown: root.internalVisibleCondition + horizontalPadding: root.horizontalPadding + verticalPadding: root.verticalPadding + } +} diff --git a/.config/quickshell/own/common/widgets/StyledToolTipContent.qml b/.config/quickshell/own/common/widgets/StyledToolTipContent.qml new file mode 100644 index 0000000..b8c29c1 --- /dev/null +++ b/.config/quickshell/own/common/widgets/StyledToolTipContent.qml @@ -0,0 +1,49 @@ +import QtQuick +import "." +import "../" + +Item { + id: root + required property string text + property bool shown: false + property real horizontalPadding: 10 + property real verticalPadding: 5 + implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding + implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding + + property bool isVisible: backgroundRectangle.implicitHeight > 0 + + Rectangle { + id: backgroundRectangle + anchors { + bottom: root.bottom + horizontalCenter: root.horizontalCenter + } + color: Appearance?.colors.colTooltip ?? "#3C4043" + radius: Appearance?.rounding.verysmall ?? 7 + opacity: shown ? 1 : 0 + implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0 + implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0 + clip: true + + Behavior on implicitWidth { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on implicitHeight { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on opacity { + animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this) + } + + StyledText { + id: tooltipTextObject + anchors.centerIn: parent + text: root.text + font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14 + font.hintingPreference: Font.PreferNoHinting + color: Appearance?.colors.colOnTooltip ?? "#FFFFFF" + wrapMode: Text.Wrap + } + } +} diff --git a/.config/quickshell/own/common/widgets/qmldir b/.config/quickshell/own/common/widgets/qmldir new file mode 100644 index 0000000..0efe136 --- /dev/null +++ b/.config/quickshell/own/common/widgets/qmldir @@ -0,0 +1,4 @@ +StyledText 1.0 StyledText.qml +StyledRectangularShadow 1.0 StyledRectangularShadow.qml +StyledToolTip 1.0 StyledToolTip.qml +StyledToolTipContent 1.0 StyledToolTipContent.qml diff --git a/.config/quickshell/modules/BatteryWidget.qml b/.config/quickshell/own/modules/BatteryWidget.qml similarity index 95% rename from .config/quickshell/modules/BatteryWidget.qml rename to .config/quickshell/own/modules/BatteryWidget.qml index df9695e..37395ac 100644 --- a/.config/quickshell/modules/BatteryWidget.qml +++ b/.config/quickshell/own/modules/BatteryWidget.qml @@ -25,7 +25,7 @@ Pill { text: root.icon + " " + root.capacity + "%" font.family: Theme.fontSans font.pixelSize: Theme.fontSize - color: root.critical ? Theme.color1 : Theme.foreground + color: root.critical ? Theme.color1 : Theme.pillText } Process { diff --git a/.config/quickshell/modules/BluetoothWidget.qml b/.config/quickshell/own/modules/BluetoothWidget.qml similarity index 98% rename from .config/quickshell/modules/BluetoothWidget.qml rename to .config/quickshell/own/modules/BluetoothWidget.qml index e980844..d24f1cf 100644 --- a/.config/quickshell/modules/BluetoothWidget.qml +++ b/.config/quickshell/own/modules/BluetoothWidget.qml @@ -30,7 +30,7 @@ Pill { return "ᛒ off" } font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground + color: Theme.pillText } // Poll bluetoothctl show + info every 5 s diff --git a/.config/quickshell/modules/CavaWidget.qml b/.config/quickshell/own/modules/CavaWidget.qml similarity index 93% rename from .config/quickshell/modules/CavaWidget.qml rename to .config/quickshell/own/modules/CavaWidget.qml index eb70f47..7610370 100644 --- a/.config/quickshell/modules/CavaWidget.qml +++ b/.config/quickshell/own/modules/CavaWidget.qml @@ -9,13 +9,15 @@ Rectangle { property var bars: Array(12).fill(0) property bool silence: bars.every(v => v === 0) + property bool allBlank: bars.every(v => v < 28.5) readonly property var blocks: [" ","▁","▂","▃","▄","▅","▆","▇","█"] + visible: !allBlank implicitWidth: cavaRow.implicitWidth + Theme.pillPadH * 2 implicitHeight: Theme.barHeight radius: Theme.radius - color: cavaHover.containsMouse ? Theme.pillHover : Theme.pill + color: cavaHover.containsMouse ? Theme.pill : Theme.pill Behavior on color { ColorAnimation { duration: 150 } } RowLayout { @@ -32,7 +34,7 @@ Rectangle { : root.blocks[Math.min(Math.floor(root.bars[index] / 28.5), 8)] font.family: Theme.fontMono font.pixelSize: Theme.fontSize + 1 - color: Theme.foreground + color: Theme.pillText } } } diff --git a/.config/quickshell/modules/ClockWidget.qml b/.config/quickshell/own/modules/ClockWidget.qml similarity index 96% rename from .config/quickshell/modules/ClockWidget.qml rename to .config/quickshell/own/modules/ClockWidget.qml index 8f6f227..b8a33ba 100644 --- a/.config/quickshell/modules/ClockWidget.qml +++ b/.config/quickshell/own/modules/ClockWidget.qml @@ -14,7 +14,7 @@ Pill { text: " " + Qt.formatDateTime(clock.now, "HH:mm") + " " + Qt.formatDateTime(clock.now, "d MMM") font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground + color: Theme.pillText } // Update every 10 s (no need for per-second ticks) diff --git a/.config/quickshell/modules/CpuWidget.qml b/.config/quickshell/own/modules/CpuWidget.qml similarity index 97% rename from .config/quickshell/modules/CpuWidget.qml rename to .config/quickshell/own/modules/CpuWidget.qml index d96c714..3111e81 100644 --- a/.config/quickshell/modules/CpuWidget.qml +++ b/.config/quickshell/own/modules/CpuWidget.qml @@ -19,7 +19,7 @@ Pill { text: root.freqGhz.toFixed(1) + "GHz | " + root.usagePct + "%" font.family: Theme.fontSans font.pixelSize: Theme.fontSize - color: Theme.foreground + color: Theme.pillText } // /proc/stat - first line is total CPU diff --git a/.config/quickshell/own/modules/MediaCavaWidget.qml b/.config/quickshell/own/modules/MediaCavaWidget.qml new file mode 100644 index 0000000..af7b285 --- /dev/null +++ b/.config/quickshell/own/modules/MediaCavaWidget.qml @@ -0,0 +1,248 @@ +// MediaCavaWidget.qml - cava visualiser + media info + volume in one pill +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Services.Mpris +import ".." + +Rectangle { + id: root + + // ── Cava ────────────────────────────────────────────────── + property var bars: Array(12).fill(0) + property bool silence: bars.every(v => v === 0) + property bool allBlank: bars.every(v => v < 28.5) + property bool silentMode: false + readonly property var blocks: [" ","▁","▂","▃","▄","▅","▆","▇","█"] + + // ── Audio (wpctl - avoids PipeWire binding issues) ──────── + property real vol: 0.0 + property bool muted: false + property string volIcon: { + if (muted || vol === 0) return " " + if (vol < 0.34) return " " + if (vol < 0.67) return " " + return " " + } + + // ── Media ───────────────────────────────────────────────── + property MprisPlayer activePlayer: { + var players = Mpris.players.values + for (var i = 0; i < players.length; i++) + if (players[i].identity.toLowerCase() === "spotify") return players[i] + return players.length > 0 ? players[0] : null + } + + property string trackText: { + if (!activePlayer) return "" + var p = activePlayer + var info = "" + if (p.trackArtists && p.trackTitle) + info = Array.from(p.trackArtists).join(", ") + " - " + p.trackTitle + else if (p.trackTitle) + info = p.trackTitle + if (info.length > 45) info = info.substring(0, 45) + "..." + if (p.playbackState !== MprisPlaybackState.Playing && info) + info = " " + info + return info + " " + } + + // ── Silence delay ───────────────────────────────────────── + onAllBlankChanged: { + if (allBlank) silenceTimer.start() + else { silenceTimer.stop(); silentMode = false } + } + + Timer { + id: silenceTimer + interval: Theme.cavaDissapearTime + repeat: false + onTriggered: root.silentMode = true + } + + // ── Hover → media popup ─────────────────────────────────── + property bool hovered: mouseArea.containsMouse + onHoveredChanged: { + if (hovered) GlobalStates.popups.open("media", mapToItem(null, width / 2, 0).x) + } + + // ── Appearance ──────────────────────────────────────────── + implicitHeight: Theme.barHeight + implicitWidth: contentRow.implicitWidth + Theme.pillPadH * 2 + radius: Theme.radius + clip: true + color: root.hovered ? Theme.pillHover : Theme.pill + Behavior on color { ColorAnimation { duration: 150 } } + + // Volume fill - slightly more opaque than background, fills left→right by volume % + Rectangle { + anchors { left: parent.left; top: parent.top; bottom: parent.bottom } + width: parent.width * Math.min(root.vol, 1.0) + radius: Theme.radius + color: root.hovered ? Theme.setColorAlpha(Theme.pill, 0.3) : Theme.setColorAlpha(Theme.pill, 0.9) + Behavior on width { NumberAnimation { duration: 150; easing.type: Easing.OutCubic } } + } + + // ── Content ─────────────────────────────────────────────── + RowLayout { + id: contentRow + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: Theme.pillPadH + spacing: 4 + + // Cava bars -d slides right and collapses after silence delay + Item { + id: cavaContainer + clip: true + implicitHeight: Theme.barHeight + implicitWidth: root.silentMode ? 0 : cavaRow.implicitWidth + Behavior on implicitWidth { + NumberAnimation { duration: 350; easing.type: Easing.InOutCubic } + } + + FontMetrics { + id: barMetrics + font.family: Theme.fontMono + font.pixelSize: Theme.fontSize + 1 + } + + Row { + id: cavaRow + anchors.verticalCenter: parent.verticalCenter + spacing: 1 + x: root.silentMode ? 16 : 0 + Behavior on x { + NumberAnimation { duration: 350; easing.type: Easing.InOutCubic } + } + + Repeater { + model: root.bars.length + Text { + required property int index + width: barMetrics.advanceWidth("█") + horizontalAlignment: Text.AlignHCenter + text: root.silence + ? " " + : root.blocks[Math.min(Math.floor(root.bars[index] / 28.5), 8)] + font { family: Theme.fontMono; pixelSize: Theme.fontSize + 1 } + color: Theme.pillText + } + } + } + } + + // Track info + Text { + visible: root.trackText !== "" + text: root.trackText + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.pillText + elide: Text.ElideRight + maximumLineCount: 1 + } + + // Volume icon + Text { + text: root.volIcon + font { family: Theme.fontMono; pixelSize: Theme.fontSize } + color: "#fab387" + } + + // Volume percentage + Text { + text: root.muted ? "muted" : Math.round(root.vol * 100) + "%" + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.pillText + } + } + + MouseArea { + id: mouseArea + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton + + onClicked: (m) => { + if (m.button === Qt.RightButton) { + Exec.run(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "toggle"]) + root.muted = !root.muted + } + } + + onWheel: (w) => { + var step = w.angleDelta.y > 0 ? "4%+" : "4%-" + Exec.run(["wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@", step]) + // Optimistic update - poll syncs it within 1s + root.vol = Math.max(0, Math.min(1.5, root.vol + (w.angleDelta.y > 0 ? 0.04 : -0.04))) + } + } + + // ── Volume poll (wpctl) ─────────────────────────────────── + Process { + id: volProc + running: false + command: ["wpctl", "get-volume", "@DEFAULT_AUDIO_SINK@"] + stdout: SplitParser { + onRead: (line) => { + // "Volume: 0.40" or "Volume: 0.40 [MUTED]" + var m = /Volume:\s+([\d.]+)(.*)/.exec(line) + if (m) { + root.vol = parseFloat(m[1]) + root.muted = m[2].includes("MUTED") + } + } + } + onExited: volProc.running = false + } + + Timer { + interval: 1000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: volProc.running = true + } + + // ── Cava process ────────────────────────────────────────── + Component.onCompleted: writeCfg.running = true + + Process { + id: writeCfg + running: false + command: ["bash", "-c", + "mkdir -p /tmp/qs-cava && cat > /tmp/qs-cava/cava.ini <<'CFG'\n" + + "[general]\n" + + "framerate = 30\n" + + "bars = 12\n" + + "[input]\n" + + "method = pipewire\n" + + "source = auto\n" + + "[smoothing]\n" + + "noise_reduction = 77\n" + + "monstercat = 1\n" + + "[output]\n" + + "method = raw\n" + + "raw_target = /dev/stdout\n" + + "data_format = ascii\n" + + "ascii_max_range = 255\n" + + "bar_delimiter = 59\n" + + "CFG\n" + ] + onExited: cavaProc.running = true + } + + Process { + id: cavaProc + running: false + command: ["cava", "-p", "/tmp/qs-cava/cava.ini"] + stdout: SplitParser { + onRead: (line) => { + const parts = line.trim().replace(/;$/, "").split(";") + if (parts.length >= root.bars.length) + root.bars = parts.slice(0, root.bars.length).map(v => parseInt(v) || 0) + } + } + onExited: cavaProc.running = true + } +} diff --git a/.config/quickshell/own/modules/MediaPopup.qml b/.config/quickshell/own/modules/MediaPopup.qml new file mode 100644 index 0000000..967ecb3 --- /dev/null +++ b/.config/quickshell/own/modules/MediaPopup.qml @@ -0,0 +1,207 @@ +// MediaCavaWidget.qml - cava visualiser + media info + volume in one pill +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import Quickshell.Services.Mpris +import ".." + +Popup { + id: root + popupName: "media" + + property MprisPlayer player: { + var players = Mpris.players.values + for (var i = 0; i < players.length; i++) + if (players[i].identity.toLowerCase() === "spotify") return players[i] + return players.length > 0 ? players[0] : null + } + + property bool isPlaying: player?.playbackState === MprisPlaybackState.Playing ?? false + property real displayPosition: 0 + + Timer { + interval: 1000 + running: root.visible && root.player !== null + repeat: true + triggeredOnStart: true + onTriggered: root.displayPosition = root.player?.position ?? 0 + } + + function formatTime(secs) { + secs = Math.max(0, Math.floor(secs)) + return Math.floor(secs / 60) + ":" + String(secs % 60).padStart(2, "0") + } + + component CtrlBtn: Text { + property string icon: "" + property color baseColor: Theme.pillText + property color hoverColor: Theme.pillHover + signal activate() + text: icon + font { family: Theme.fontMono; pixelSize: 20 } + color: ma.containsMouse ? hoverColor : baseColor + Behavior on color { ColorAnimation { duration: 100 } } + MouseArea { + id: ma + anchors.fill: parent + hoverEnabled: true + onClicked: parent.activate() + } + } + + ColumnLayout { + width: 200 + spacing: 8 + + // Album art - hidden when no art is available + Rectangle { + visible: artImage.status === Image.Ready + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 180 + Layout.preferredHeight: artImage.status === Image.Ready ? 180 : 0 + Layout.topMargin: artImage.status === Image.Ready ? 10 : 0 + radius: Theme.radius + clip: true + color: Theme.pill + + Image { + id: artImage + anchors.fill: parent + source: root.player?.trackArtUrl ?? "" + fillMode: Image.PreserveAspectCrop + smooth: true + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (root.player) + Exec.run(["hyprctl", "dispatch", "hl.dsp.focus({ window = 'class:" + root.player.identity + "' })"]) + GlobalStates.popups.close() + } + } + } + + // Track title with marquee scroll + Item { + id: titleClip + Layout.fillWidth: true + Layout.leftMargin: 15 + Layout.rightMargin: 15 + Layout.topMargin: 4 + implicitHeight: titleText.implicitHeight + clip: true + + Text { + id: titleText + text: root.player?.trackTitle ?? "Nothing playing" + font { family: Theme.fontSans; pixelSize: Theme.fontSize + 1; bold: true } + color: titleMa.containsMouse && root.player ? Qt.lighter(Theme.pillText, 1.3) : Theme.pillText + Behavior on color { ColorAnimation { duration: 100 } } + + MouseArea { + id: titleMa + anchors.fill: parent + hoverEnabled: true + cursorShape: root.player ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: if (root.player) { + Exec.run(["hyprctl", "dispatch", "hl.dsp.focus({ window = 'class:" + root.player.identity + "' })"]) + GlobalStates.popups.close() + } + } + + onTextChanged: { + marquee.stop() + x = 0 + Qt.callLater(() => { + if (implicitWidth > titleClip.width) marquee.start() + }) + } + } + + Component.onCompleted: Qt.callLater(() => { + if (titleText.implicitWidth > width) marquee.start() + }) + + SequentialAnimation { + id: marquee + loops: Animation.Infinite + + PauseAnimation { duration: 1500 } + NumberAnimation { + target: titleText; property: "x" + from: 0 + to: -(titleText.implicitWidth - titleClip.width + 10) + duration: Math.max(1, titleText.implicitWidth - titleClip.width) * 18 + easing.type: Easing.Linear + } + PauseAnimation { duration: 800 } + NumberAnimation { + target: titleText; property: "x" + to: 0; duration: 400; easing.type: Easing.OutCubic + } + } + } + + // Artist + Text { + Layout.fillWidth: true + Layout.leftMargin: 15 + Layout.rightMargin: 15 + horizontalAlignment: Text.AlignHCenter + text: root.player?.trackArtists ? Array.from(root.player.trackArtists).join(", ") : "" + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.pillText + elide: Text.ElideRight + } + + // Progress bar + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 15 + Layout.rightMargin: 15 + height: 3 + radius: 2 + color: Theme.pill + + Rectangle { + width: root.player && root.player.length > 0 + ? Math.min(1, root.displayPosition / root.player.length) * parent.width + : 0 + height: parent.height + radius: parent.radius + color: Theme.color6 + } + } + + // Time labels + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: 15 + Layout.rightMargin: 15 + Text { + text: root.formatTime(root.displayPosition) + font { family: Theme.fontMono; pixelSize: Theme.fontSize - 1 } + color: Theme.pillText + } + Item { Layout.fillWidth: true } + Text { + text: root.formatTime(root.player?.length ?? 0) + font { family: Theme.fontMono; pixelSize: Theme.fontSize - 1 } + color: Theme.pillText + } + } + + // Controls + RowLayout { + Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 10 + spacing: 20 + CtrlBtn { text: ""; onActivate: root.player?.previous() } + CtrlBtn { text: root.isPlaying ? "" : ""; onActivate: root.player?.togglePlaying() } + CtrlBtn { text: ""; onActivate: root.player?.stop() } + CtrlBtn { text: ""; onActivate: root.player?.next() } + } + } +} diff --git a/.config/quickshell/modules/MediaWidget.qml b/.config/quickshell/own/modules/MediaWidget.qml similarity index 87% rename from .config/quickshell/modules/MediaWidget.qml rename to .config/quickshell/own/modules/MediaWidget.qml index 41d5f42..4a3ddf9 100644 --- a/.config/quickshell/modules/MediaWidget.qml +++ b/.config/quickshell/own/modules/MediaWidget.qml @@ -21,7 +21,7 @@ Pill { var p = activePlayer var info = "" if (p.trackArtists && p.trackTitle) - info = p.trackArtists.join(", ") + " - " + p.trackTitle + info = Array.from(p.trackArtists).join(", ") + " - " + p.trackTitle else if (p.trackTitle) info = p.trackTitle if (info.length > 45) info = info.substring(0, 45) + "..." @@ -35,7 +35,7 @@ Pill { Text { text: root.trackText font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground + color: Theme.pillText elide: Text.ElideRight maximumLineCount: 1 } @@ -51,4 +51,8 @@ Pill { if (w.angleDelta.y > 0) root.activePlayer.next() else root.activePlayer.previous() } + + onHoveredChanged: { + if (hovered) GlobalStates.popups.open("media", mapToItem(null, width / 2, 0).x) + } } diff --git a/.config/quickshell/modules/MemoryWidget.qml b/.config/quickshell/own/modules/MemoryWidget.qml similarity index 96% rename from .config/quickshell/modules/MemoryWidget.qml rename to .config/quickshell/own/modules/MemoryWidget.qml index 560d945..224fd95 100644 --- a/.config/quickshell/modules/MemoryWidget.qml +++ b/.config/quickshell/own/modules/MemoryWidget.qml @@ -16,7 +16,7 @@ Pill { text: " " + root.usedGb.toFixed(2) + " / " + root.totalGb.toFixed(0) + " GB" font.family: Theme.fontSans font.pixelSize: Theme.fontSize - color: Theme.foreground + color: Theme.pillText } Process { diff --git a/.config/quickshell/own/modules/PowerMenuWidget.qml b/.config/quickshell/own/modules/PowerMenuWidget.qml new file mode 100644 index 0000000..225fdb8 --- /dev/null +++ b/.config/quickshell/own/modules/PowerMenuWidget.qml @@ -0,0 +1,20 @@ +// modules/PowerMenuWidget.qml - ⏻ button with inline popup menu. +import QtQuick +import QtQuick.Layouts +import Quickshell.Io +import ".." + +Pill { + id: root + onHoveredChanged: { + if (hovered) GlobalStates.popups.toggle("powerMenu", mapToItem(null, width / 2, 0).x) + } + onClicked: { + GlobalStates.popups.toggle("powerMenu", mapToItem(null, width / 2, 0).x) + } + Text { + text: " " + font { family: Theme.fontMono; pixelSize: Theme.fontSize } + color: Theme.pillText + } +} diff --git a/.config/quickshell/modules/PowerProfilesWidget.qml b/.config/quickshell/own/modules/PowerProfilesWidget.qml similarity index 97% rename from .config/quickshell/modules/PowerProfilesWidget.qml rename to .config/quickshell/own/modules/PowerProfilesWidget.qml index 1264606..5fb7b1d 100644 --- a/.config/quickshell/modules/PowerProfilesWidget.qml +++ b/.config/quickshell/own/modules/PowerProfilesWidget.qml @@ -27,7 +27,7 @@ Pill { text: (root.icons[root.profile] ?? "⚡") font.family: Theme.fontSans font.pixelSize: Theme.fontSize - color: Theme.foreground + color: Theme.pillText } // Read current profile periodically diff --git a/.config/quickshell/modules/SysTrayWidget.qml b/.config/quickshell/own/modules/SysTrayWidget.qml similarity index 69% rename from .config/quickshell/modules/SysTrayWidget.qml rename to .config/quickshell/own/modules/SysTrayWidget.qml index 083f218..8f90837 100644 --- a/.config/quickshell/modules/SysTrayWidget.qml +++ b/.config/quickshell/own/modules/SysTrayWidget.qml @@ -2,10 +2,12 @@ import QtQuick import QtQuick.Layouts import Quickshell.Services.SystemTray +import Quickshell import ".." Rectangle { id: root + required property var parentWindow color: "transparent" radius: Theme.radius @@ -21,12 +23,13 @@ Rectangle { model: SystemTray.items Rectangle { + id: trayRect required property SystemTrayItem modelData - width: 22; height: 22 + width: Theme.barHeight-Theme.barPadding*2; height: Theme.barHeight-Theme.barPadding*2 radius: Theme.radius color: trayHover.containsMouse ? Theme.pillHover - : Qt.rgba(0.976, 0.945, 0.851, 0.15) + : Theme.pill Behavior on color { ColorAnimation { duration: 150 } } @@ -37,6 +40,18 @@ Rectangle { smooth: true } + QsMenuAnchor { + id: menuAnchor + menu: modelData.menu + anchor.window: root.parentWindow + anchor.rect: Qt.rect( + trayRect.mapToItem(null, 0, 0).x, + trayRect.mapToItem(null, 0, 0).y, + trayRect.width, + trayRect.height + ) + } + MouseArea { id: trayHover anchors.fill: parent @@ -45,8 +60,8 @@ Rectangle { onClicked: (m) => { if (m.button === Qt.LeftButton) modelData.activate() - else - modelData.contextMenu(mapToGlobal(mouseX, mouseY)) + else if (modelData.hasMenu) + menuAnchor.open() } } diff --git a/.config/quickshell/modules/TemperatureWidget.qml b/.config/quickshell/own/modules/TemperatureWidget.qml similarity index 94% rename from .config/quickshell/modules/TemperatureWidget.qml rename to .config/quickshell/own/modules/TemperatureWidget.qml index 9df2ce0..9bbd0f4 100644 --- a/.config/quickshell/modules/TemperatureWidget.qml +++ b/.config/quickshell/own/modules/TemperatureWidget.qml @@ -18,7 +18,7 @@ Pill { text: root.icon + " " + root.tempC + "°C" font.family: Theme.fontSans font.pixelSize: Theme.fontSize - color: root.critical ? Theme.color1 : Theme.foreground + color: root.critical ? Theme.color1 : Theme.pillText } // Read first available CPU package sensor - works regardless of hwmon number diff --git a/.config/quickshell/own/modules/WallpaperPopup.qml b/.config/quickshell/own/modules/WallpaperPopup.qml new file mode 100644 index 0000000..17e239d --- /dev/null +++ b/.config/quickshell/own/modules/WallpaperPopup.qml @@ -0,0 +1,197 @@ +// WallpaperPopup.qml - A popup for browsing and setting wallpapers from /usr/share/wallpapers +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import ".." + +PanelWindow { + id: root + required property var screen + + property bool logicalOpen: GlobalStates.popups.active === "wallpaper" + property bool _keepVisible: false + visible: logicalOpen || _keepVisible + + onLogicalOpenChanged: { + if (logicalOpen) { + closeTimer.stop() + _keepVisible = false + if (wallpaperModel.count === 0) scanProc.running = true + openY.start() + openOpacity.start() + focusTimer.start() + } else { + _keepVisible = true + closeY.start() + closeOpacity.start() + closeTimer.restart() + } + } + + Timer { id: closeTimer; interval: 280; onTriggered: root._keepVisible = false } + Timer { id: focusTimer; interval: 50; onTriggered: grid.forceActiveFocus() } + + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.namespace: "quickshell-popup-wallpaper" + WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + anchors { top: true; left: true; right: true; bottom: true } + color: Qt.rgba(0, 0, 0, 0) + + MouseArea { anchors.fill: parent; onClicked: GlobalStates.popups.close() } + + NumberAnimation { id: openY; target: box; property: "y"; to: 1; duration: 250; easing.type: Easing.OutCubic } + NumberAnimation { id: closeY; target: box; property: "y"; to: -340; duration: 200; easing.type: Easing.InCubic } + NumberAnimation { id: openOpacity; target: box; property: "opacity"; to: Theme.popupOpacity; duration: 200 } + NumberAnimation { id: closeOpacity; target: box; property: "opacity"; to: 0.0; duration: 200 } + + // ── Named-pipe IPC ──────────────────────────────────────── + // Trigger from Hyprland keybind: echo 1 > /tmp/qs-wallpaper-ipc + // Trigger from another QML popup: Exec.run(["bash", "-c", "echo 1 > /tmp/qs-wallpaper-ipc"]) + Process { + id: ipcReader + running: true + command: ["bash", "-c", + "rm -f /tmp/qs-wallpaper-ipc && " + + "mkfifo /tmp/qs-wallpaper-ipc && " + + "while true; do cat /tmp/qs-wallpaper-ipc; done"] + stdout: SplitParser { + onRead: () => GlobalStates.popups.toggle("wallpaper", 0) + } + onExited: running = true + } + + // ── Wallpaper scanner ───────────────────────────────────── + ListModel { id: wallpaperModel } + + Process { + id: scanProc + running: false + command: ["bash", "-c", + "find /usr/share/wallpapers -type f " + + "\\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \\) " + + "| sort"] + stdout: SplitParser { + onRead: (line) => { + var p = line.trim() + if (p) wallpaperModel.append({ path: p }) + } + } + } + + Process { id: setProc; running: false } + + // ── Popup box ───────────────────────────────────────────── + Rectangle { + id: box + + // anchorX = 0 → centered; anchorX > 0 → centered under widget + x: { + var ax = GlobalStates.popups.anchorX + var cx = ax > 0 ? ax - width / 2 : parent.width / 2 - width / 2 + return Math.max(4, Math.min(parent.width - width - 4, cx)) + } + y: -340 + opacity: 0.0 + + width: 324 + height: 500 + + color: Theme.popupBackground + radius: Theme.radius * 2 + clip: true + border.color: Theme.popupBorderColor + border.width: Theme.popupBorderWidth + + MouseArea { anchors.fill: parent } + + Text { + anchors.centerIn: parent + visible: wallpaperModel.count === 0 + text: "Scanning..." + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.pillText + opacity: 0.5 + } + + GridView { + id: grid + anchors { fill: parent; margins: 8 } + clip: true + cellWidth: Math.floor(width / 2) + cellHeight: 104 + focus: true + keyNavigationEnabled: true + currentIndex: 0 + + Keys.onReturnPressed: { + var item = wallpaperModel.get(grid.currentIndex) + if (item) { + setProc.command = ["waypaper", "--wallpaper", item.path] + setProc.running = true + GlobalStates.popups.close() + } + } + Keys.onEscapePressed: GlobalStates.popups.close() + + model: wallpaperModel + + highlight: Rectangle { + z: 2 + radius: Theme.radius + 1 + color: "transparent" + border.color: Theme.accent ?? "#cba6f7" + border.width: 2 + } + highlightFollowsCurrentItem: true + highlightMoveDuration: 80 + + delegate: Item { + required property string path + required property int index + + width: grid.cellWidth - 4 + height: 100 + + Rectangle { + anchors { fill: parent; margins: 2 } + radius: Theme.radius + color: "black" + clip: true + + Image { + anchors.fill: parent + source: "file://" + path + fillMode: Image.PreserveAspectCrop + sourceSize: Qt.size(152, 96) + smooth: true + asynchronous: true + opacity: status === Image.Ready ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 150 } } + } + + Rectangle { + anchors.fill: parent + radius: Theme.radius + color: hover.containsMouse ? Qt.rgba(1, 1, 1, 0.2) : "transparent" + Behavior on color { ColorAnimation { duration: 100 } } + } + + MouseArea { + id: hover + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + grid.currentIndex = index + setProc.command = ["waypaper", "--wallpaper", path] + setProc.running = true + GlobalStates.popups.close() + } + onEntered: grid.currentIndex = index + } + } + } + } + } +} diff --git a/.config/quickshell/own/modules/WallpaperWidget.qml b/.config/quickshell/own/modules/WallpaperWidget.qml new file mode 100644 index 0000000..d92a557 --- /dev/null +++ b/.config/quickshell/own/modules/WallpaperWidget.qml @@ -0,0 +1,14 @@ +// WallpaperWidget.qml - pill that opens the wallpaper changer popup +import QtQuick +import ".." + +Pill { + id: root + onClicked: GlobalStates.popups.toggle("wallpaper", mapToItem(null, width / 2, 0).x) + + Text { + text: " " + font { family: Theme.fontMono; pixelSize: Theme.fontSize } + color: Theme.pillText + } +} diff --git a/.config/quickshell/modules/WeatherWidget.qml b/.config/quickshell/own/modules/WeatherWidget.qml similarity index 96% rename from .config/quickshell/modules/WeatherWidget.qml rename to .config/quickshell/own/modules/WeatherWidget.qml index 433569a..2d62ff9 100644 --- a/.config/quickshell/modules/WeatherWidget.qml +++ b/.config/quickshell/own/modules/WeatherWidget.qml @@ -10,7 +10,7 @@ Pill { Text { text: weatherText font { family: Theme.fontSans; pixelSize: Theme.fontSize } - color: Theme.foreground + color: Theme.pillText } // ── Fetch via curl ──────────────────────────────────────── diff --git a/.config/quickshell/own/modules/WindowTitleWidget.qml b/.config/quickshell/own/modules/WindowTitleWidget.qml new file mode 100644 index 0000000..75ae22b --- /dev/null +++ b/.config/quickshell/own/modules/WindowTitleWidget.qml @@ -0,0 +1,30 @@ +// modules/WindowTitleWidget.qml - hyprland/window equivalent +import QtQuick +import Quickshell.Hyprland +import ".." + +Pill { + id: root + required property var screen + + clip: true + + Behavior on implicitWidth { + NumberAnimation { duration: 250; easing.type: Easing.InOutCubic } + } + + readonly property string displayTitle: { + let rawTitle = Hyprland.activeToplevel ? Hyprland.activeToplevel.title : ""; + if (rawTitle.length > 45) { + return rawTitle.substring(0, Theme.titleLength) + "..."; + } + return rawTitle; + } + Text { + text: root.displayTitle + font { family: Theme.fontSans; pixelSize: Theme.fontSize } + color: Theme.pillText + elide: Text.ElideRight + maximumLineCount: 1 + } +} diff --git a/.config/quickshell/own/modules/WorkspacesWidget.qml b/.config/quickshell/own/modules/WorkspacesWidget.qml new file mode 100644 index 0000000..c9671f4 --- /dev/null +++ b/.config/quickshell/own/modules/WorkspacesWidget.qml @@ -0,0 +1,107 @@ +// WorkspacesWidget.qml - shows workspaces and allows switching between them +import QtQuick +import QtQuick.Layouts +import Quickshell.Hyprland +import ".." + +Rectangle { + id: root + required property var screen + + property var sortedWorkspaces: Array.from(Hyprland.workspaces.values).sort((a, b) => a.id - b.id) + property int activeIndex: { + var fid = Hyprland.focusedWorkspace?.id ?? -1 + for (var i = 0; i < sortedWorkspaces.length; i++) + if (sortedWorkspaces[i].id === fid) return i + return -1 + } + + // Get the active workspace for this monitor + property var monitorActiveWorkspace: { + var activeWs = Hyprland.focusedWorkspace + // Check if focused workspace is on this monitor + if (activeWs && activeWs.monitor?.id === root.screen.id) { + return activeWs + } + // Otherwise find the first workspace on this monitor + for (var i = 0; i < sortedWorkspaces.length; i++) { + if (sortedWorkspaces[i].monitor?.id === root.screen.id) { + return sortedWorkspaces[i] + } + } + return null + } + + function isWorkspaceOnThisMonitor(workspace) { + // Find the monitor in Hyprland.monitors that matches this screen's name + for (const monitor of Hyprland.monitors.values) { + if (monitor.name === root.screen.name && monitor.id === workspace.monitor.id) { + return true + } + } + return false + } + + readonly property int btnWidth: 32 + + color: Theme.pill + radius: Theme.radius + clip: true + implicitWidth: wsRow.implicitWidth + implicitHeight: Theme.barHeight + Behavior on implicitWidth { + NumberAnimation { duration: Theme.workspaceSlideTime; easing.type: Easing.InOutCubic } + } + + // Sliding active indicator - single Rectangle that travels between buttons + Rectangle { + visible: root.activeIndex >= 0 + x: root.activeIndex * root.btnWidth + width: root.btnWidth + height: parent.height + color: Theme.wsActive + + Behavior on x { + NumberAnimation { duration: Theme.workspaceSlideTime; easing.type: Easing.InOutCubic } + } + } + + RowLayout { + id: wsRow + anchors.fill: parent + spacing: 0 + + Repeater { + model: root.sortedWorkspaces + + delegate: Rectangle { + required property var modelData + + Layout.fillHeight: true + implicitWidth: root.btnWidth + color: wsBtn.containsMouse ? Theme.pillHover : "transparent" + // Reduce opacity for workspaces not on this monitor + opacity: root.isWorkspaceOnThisMonitor(modelData) ? 1.0 : 0.4 + Behavior on opacity { NumberAnimation { duration: 150 } } + Behavior on color { ColorAnimation { duration: 150 } } + + Text { + anchors.centerIn: parent + text: modelData.id + font { family: Theme.fontSans; pixelSize: Theme.fontSizeSecondary } + color: Theme.pillText + } + + MouseArea { + id: wsBtn + anchors.fill: parent + hoverEnabled: true + onClicked: Hyprland.dispatch("hl.dsp.focus({ workspace = '" + modelData.id + "' })") + onWheel: (w) => Hyprland.dispatch( + "hl.dsp.focus({ workspace = \"" + (w.angleDelta.y > 0 ? "e+1" : "e-1") + "\" })" + ) + } + } + } + } +} diff --git a/.config/quickshell/own/modules/overview/CREDITS.md b/.config/quickshell/own/modules/overview/CREDITS.md new file mode 100644 index 0000000..8db0181 --- /dev/null +++ b/.config/quickshell/own/modules/overview/CREDITS.md @@ -0,0 +1,53 @@ +# Overview module - credits + +The files in this directory (`Overview.qml`, `OverviewWidget.qml`, `OverviewWindow.qml`) and +their support singletons (`../../common/Appearance.qml`, `../../common/Config.qml`, +`../../common/functions/ColorUtils.qml`, `../../common/widgets/Styled*.qml`, +`../../services/HyprlandData.qml`) are copied from +[Shanu-Kumawat/quickshell-overview](https://github.com/Shanu-Kumawat/quickshell-overview), +which was itself extracted from the overview feature in +[end-4/dots-hyprland](https://github.com/end-4/dots-hyprland) ("illogical-impulse") by +[end-4](https://github.com/end-4). + +Released under the GPL by the upstream project. + +### Local deviations from upstream + +- `Overview.qml` / `OverviewWidget.qml` / `OverviewWindow.qml`: added one `import "../.."` line + each, so `GlobalStates.overviewOpen` (merged into `own/GlobalStates.qml`, see below) resolves + from this subdirectory. No other logic changed. +- `../../common/Appearance.qml`: `defaultColors` was retuned to pull from this shell's own + `Theme`/`Colors` wallust singletons instead of the stock M3 purple palette, so the overview + matches the bar's colors (needed one added `import ".."`). This is the module's own documented + way to customize its palette (see upstream README, "Theme & Colors"). +- `../../common/Config.qml`: untouched. Transparency/rounding are instead tuned via + `~/.config/quickshell/overview/config.json` (the module's own documented user-override file), + kept separate from the upstream defaults it reads. + +## Why a copy instead of a symlink/submodule + +The module was merged into this shell's own process (`own/shell.qml`) so it runs in the same +`quickshell -c own` instance as the bar, rather than as a second `qs -c overview` process. + +An untouched clone of the upstream repo is kept side by side at +`~/.config/quickshell/overview` - use it as the reference copy when checking for upstream +updates (`git -C ~/.config/quickshell/overview pull`) and re-sync the files above by hand if +anything changes there. + +## Configuration + +Runtime options (grid size, scale, effects, etc.) are unaffected by the merge - `Config.qml` +still reads user overrides from `$XDG_CONFIG_HOME/quickshell/overview/config.json` regardless +of where the QML itself lives. See `~/.config/quickshell/overview/config.example.json` and +`~/.config/quickshell/overview/README.md` (in the untouched upstream clone) for the full list +of options. + +## Toggling it + +The merged module still registers the same IPC target, just under the `own` shell instance: + +```bash +qs ipc -c own call overview toggle +qs ipc -c own call overview open +qs ipc -c own call overview close +``` diff --git a/.config/quickshell/own/modules/overview/Overview.qml b/.config/quickshell/own/modules/overview/Overview.qml new file mode 100644 index 0000000..ad9d60f --- /dev/null +++ b/.config/quickshell/own/modules/overview/Overview.qml @@ -0,0 +1,253 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import Quickshell.Hyprland +import "../../common" +import "../../services" +import "../.." +import "." + +Scope { + id: overviewScope + Variants { + id: overviewVariants + model: Quickshell.screens + PanelWindow { + id: root + required property var modelData + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id) + property bool blurEnabled: Config.options.overview.effects.enableBlur + property bool backdropEnabled: Config.options.overview.effects.enableBackdrop + property real backdropOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.backdropOpacity)) + property bool closeOnFocusLoss: Config.options.overview.closeOnFocusLoss ?? true + screen: modelData + visible: GlobalStates.overviewOpen + + WlrLayershell.namespace: blurEnabled ? "quickshell:overview-blur" : "quickshell:overview" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + color: "transparent" + + anchors { + top: true + bottom: true + left: true + right: true + } + + HyprlandFocusGrab { + id: grab + windows: [root] + property bool canBeActive: root.monitorIsFocused + active: false + onCleared: () => { + // Only the monitor that owns the grab may close the overview + if (root.closeOnFocusLoss && !active && canBeActive) + GlobalStates.overviewOpen = false; + } + } + + Connections { + target: GlobalStates + function onOverviewOpenChanged() { + if (GlobalStates.overviewOpen) { + delayedGrabTimer.start(); + } + } + } + + // Re-evaluate grab ownership when focused monitor changes + Connections { + target: Hyprland + function onFocusedMonitorChanged() { + if (!GlobalStates.overviewOpen) + return; + // Transfer the grab to the newly focused monitor + if (root.monitorIsFocused && !grab.active) { + grab.active = true; + } else if (!root.monitorIsFocused && grab.active) { + grab.active = false; + } + } + } + + Timer { + id: delayedGrabTimer + interval: Config.options.hacks.arbitraryRaceConditionDelay + repeat: false + onTriggered: { + if (!grab.canBeActive) + return; + grab.active = GlobalStates.overviewOpen; + } + } + + // Keep the layershell surface full-screen so backdrop/blur are not constrained by content size. + implicitWidth: screen.width + implicitHeight: screen.height + + Item { + id: keyHandler + anchors.fill: parent + visible: GlobalStates.overviewOpen + focus: GlobalStates.overviewOpen + z: 0 + + Rectangle { + id: backdropLayer + anchors.fill: parent + visible: root.backdropEnabled + color: "#000000" + opacity: root.backdropOpacity + z: 0 + } + + MouseArea { + id: outsideClickCatcher + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + enabled: root.closeOnFocusLoss && GlobalStates.overviewOpen + z: 0 + onPressed: mouse => { + GlobalStates.overviewOpen = false; + mouse.accepted = true; + } + } + + Keys.onPressed: event => { + // close: Escape or Enter + if (event.key === Qt.Key_Escape || event.key === Qt.Key_Return) { + GlobalStates.overviewOpen = false; + event.accepted = true; + return; + } + + // Helper: compute current group bounds + const workspacesPerGroup = Config.options.overview.rows * Config.options.overview.columns; + const currentId = Hyprland.focusedMonitor?.activeWorkspace?.id ?? 1; + const useWorkspaceMap = Config.options.overview.useWorkspaceMap; + const workspaceMap = Config.options.overview.workspaceMap ?? []; + const focusedMonitorId = Hyprland.focusedMonitor?.id ?? root.monitor?.id ?? 0; + const workspaceOffset = useWorkspaceMap ? Number(workspaceMap[focusedMonitorId] ?? 0) : 0; + const currentGroup = Math.floor((currentId - workspaceOffset - 1) / workspacesPerGroup); + const minWorkspaceId = currentGroup * workspacesPerGroup + 1 + workspaceOffset; + const maxWorkspaceId = minWorkspaceId + workspacesPerGroup - 1; + + const rows = Config.options.overview.rows; + const columns = Config.options.overview.columns; + const reverseColumns = Config.options.overview.orderRightLeft; + const reverseRows = Config.options.overview.orderBottomUp; + + const clampedIndex = Math.max(0, Math.min(workspacesPerGroup - 1, currentId - minWorkspaceId)); + const currentNormalRow = Math.floor(clampedIndex / columns); + const currentNormalColumn = clampedIndex % columns; + + function toVisualRow(normalRow) { + return reverseRows ? (rows - normalRow - 1) : normalRow; + } + + function toVisualColumn(normalColumn) { + return reverseColumns ? (columns - normalColumn - 1) : normalColumn; + } + + function toNormalRow(visualRow) { + return reverseRows ? (rows - visualRow - 1) : visualRow; + } + + function toNormalColumn(visualColumn) { + return reverseColumns ? (columns - visualColumn - 1) : visualColumn; + } + + let targetVisualRow = toVisualRow(currentNormalRow); + let targetVisualColumn = toVisualColumn(currentNormalColumn); + + let targetId = null; + + // Arrow keys and vim-style hjkl + if (event.key === Qt.Key_Left || event.key === Qt.Key_H) { + targetVisualColumn = (targetVisualColumn - 1 + columns) % columns; + } else if (event.key === Qt.Key_Right || event.key === Qt.Key_L) { + targetVisualColumn = (targetVisualColumn + 1) % columns; + } else if (event.key === Qt.Key_Up || event.key === Qt.Key_K) { + targetVisualRow = (targetVisualRow - 1 + rows) % rows; + } else if (event.key === Qt.Key_Down || event.key === Qt.Key_J) { + targetVisualRow = (targetVisualRow + 1) % rows; + } + + // Number keys: jump to workspace within the current group + // 1-9 map to positions 1-9, 0 maps to position 10 + else if (event.key >= Qt.Key_1 && event.key <= Qt.Key_9) { + const position = event.key - Qt.Key_0; // 1-9 + if (position <= workspacesPerGroup) { + targetId = minWorkspaceId + position - 1; + } + } else if (event.key === Qt.Key_0) { + // 0 = 10th workspace in the group (if group has 10+ workspaces) + if (workspacesPerGroup >= 10) { + targetId = minWorkspaceId + 9; // 10th position = offset 9 + } + } + + if (targetId === null && ( + event.key === Qt.Key_Left || event.key === Qt.Key_H || + event.key === Qt.Key_Right || event.key === Qt.Key_L || + event.key === Qt.Key_Up || event.key === Qt.Key_K || + event.key === Qt.Key_Down || event.key === Qt.Key_J + )) { + const targetNormalRow = toNormalRow(targetVisualRow); + const targetNormalColumn = toNormalColumn(targetVisualColumn); + targetId = minWorkspaceId + targetNormalRow * columns + targetNormalColumn; + } + + if (targetId !== null) { + const clampedTarget = Math.max(minWorkspaceId, Math.min(maxWorkspaceId, targetId)); + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.focus({workspace = '${clampedTarget}'})`); + } else { + Hyprland.dispatch("workspace " + clampedTarget); + } + event.accepted = true; + } + } + } + + ColumnLayout { + id: columnLayout + visible: GlobalStates.overviewOpen + z: 1 + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: Config.options.position.topMargin + } + + Loader { + id: overviewLoader + active: Config?.options.overview.enable ?? true + sourceComponent: OverviewWidget { + panelWindow: root + visible: true + } + } + } + } + } + + IpcHandler { + target: "overview" + + function toggle() { + GlobalStates.overviewOpen = !GlobalStates.overviewOpen; + } + function close() { + GlobalStates.overviewOpen = false; + } + function open() { + GlobalStates.overviewOpen = true; + } + } +} diff --git a/.config/quickshell/own/modules/overview/OverviewWidget.qml b/.config/quickshell/own/modules/overview/OverviewWidget.qml new file mode 100644 index 0000000..e332b6b --- /dev/null +++ b/.config/quickshell/own/modules/overview/OverviewWidget.qml @@ -0,0 +1,1215 @@ +import QtQuick +import QtQuick.Effects +import QtQuick.Layouts +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland +import "../../common" +import "../../common/functions" +import "../../common/widgets" +import "../../services" +import "../.." +import "." + +Item { + id: root + required property var panelWindow + readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen) + readonly property var toplevels: ToplevelManager.toplevels + readonly property int effectiveActiveWorkspaceId: Math.max(1, Math.min(100, monitor?.activeWorkspace?.id ?? 1)) + readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns + readonly property bool useWorkspaceMap: Config.options.overview.useWorkspaceMap + readonly property var workspaceMap: Config.options.overview.workspaceMap + readonly property int workspaceOffset: useWorkspaceMap ? Number(workspaceMap[root.monitor?.id] ?? 0) : 0 + readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - workspaceOffset - 1) / workspacesShown) + property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name) + property var windows: HyprlandData.windowList + property var windowByAddress: HyprlandData.windowByAddress + property var windowAddresses: HyprlandData.addresses + property var workspaceIds: HyprlandData.workspaceIds + property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id) + property real scale: Config.options.overview.scale + property color activeBorderColor: Appearance.colors.colSecondary + + property real workspaceImplicitWidth: Math.round((monitorData?.transform % 2 === 1) ? + ((monitor.height / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale) : + ((monitor.width / monitor.scale - (monitorData?.reserved?.[0] ?? 0) - (monitorData?.reserved?.[2] ?? 0)) * root.scale)) + property real workspaceImplicitHeight: Math.round((monitorData?.transform % 2 === 1) ? + ((monitor.width / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale) : + ((monitor.height / monitor.scale - (monitorData?.reserved?.[1] ?? 0) - (monitorData?.reserved?.[3] ?? 0)) * root.scale)) + + property real workspaceNumberMargin: 80 + property real workspaceNumberSize: Config.options.overview.workspaceNumberBaseSize * monitor.scale + property int workspaceZ: 0 + property int windowZ: 1 + property int windowDraggingZ: 99999 + property real workspaceSpacing: Config.options.overview.workspaceSpacing + property string emptyWorkspaceWallpaperPath: Config.options.overview.emptyWorkspaceWallpaper + property string specialEmptyWorkspaceWallpaperPath: Config.options.overview.specialEmptyWorkspaceWallpaper + property bool showSpecialWorkspaces: Config.options.overview.showSpecialWorkspaces + property var configuredSpecialWorkspaces: Config.options.overview.specialWorkspaces ?? [] + property int specialWorkspaceColumns: Math.max(1, Config.options.overview.specialWorkspaceColumns) + property real panelOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.panelOpacity)) + property real workspaceOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.workspaceOpacity)) + property real emptyWorkspaceWallpaperOverlayOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.emptyWorkspaceWallpaperOverlayOpacity)) + property bool glassMode: Config.options.overview.effects.glassMode + property real glassTintStrength: Math.max(0, Math.min(1, Config.options.overview.effects.glassTintStrength)) + property real glassBorderOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassBorderOpacity)) + property real glassShineOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassShineOpacity)) + property real effectivePanelOpacity: glassMode ? Math.min(panelOpacity, 0.72) : panelOpacity + property real effectiveWorkspaceOpacity: glassMode ? Math.min(workspaceOpacity, 0.62) : workspaceOpacity + + property int draggingFromWorkspace: -1 + property int draggingTargetWorkspace: -1 + property string draggingTargetSpecialWorkspace: "" + property int previewRecaptureToken: 0 + property var allWorkspaces: HyprlandData.allWorkspaces + property bool previewsEnabled: Config.options.overview.previewsEnabled + property string previewModeRaw: Config.options.overview.previewMode + property string previewMode: { + const mode = `${previewModeRaw ?? "live"}`.trim().toLowerCase(); + return (mode === "event" || mode === "snapshot") ? "event" : "live"; + } + property bool useEventPreviewRefresh: previewsEnabled && previewMode === "event" + + readonly property var monitorSpecialWorkspaceNames: { + const names = []; + for (const ws of (allWorkspaces ?? [])) { + const name = `${ws?.name ?? ""}`; + if (!name.startsWith("special:")) + continue; + if (`${ws?.monitor ?? ""}` !== `${root.monitor?.name ?? ""}`) + continue; + names.push(name.slice(8)); + } + return names; + } + + readonly property var specialWorkspaceNamesFromWindows: { + const names = []; + for (const addr in windowByAddress) { + const win = windowByAddress[addr]; + if ((win?.monitor ?? -1) !== (root.monitor?.id ?? -1)) + continue; + const wsName = `${win?.workspace?.name ?? ""}`; + if (!wsName.startsWith("special:")) + continue; + names.push(wsName.slice(8)); + } + return names; + } + + readonly property var visibleSpecialWorkspaces: { + if (!showSpecialWorkspaces) + return []; + + const out = []; + const pushUnique = (value) => { + const cleaned = `${value ?? ""}`.trim(); + if (cleaned.length === 0 || out.includes(cleaned)) + return; + out.push(cleaned); + }; + + for (const configured of configuredSpecialWorkspaces ?? []) + pushUnique(configured); + for (const name of monitorSpecialWorkspaceNames) + pushUnique(name); + for (const name of specialWorkspaceNamesFromWindows) + pushUnique(name); + + return out; + } + + readonly property bool hasSpecialWorkspaceSection: visibleSpecialWorkspaces.length > 0 + readonly property bool hasEmptyWorkspaceWallpaper: `${emptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0 + readonly property bool hasSpecialEmptyWorkspaceWallpaper: `${specialEmptyWorkspaceWallpaperPath ?? ""}`.trim().length > 0 + readonly property string createSpecialWorkspaceTarget: "__create_special_workspace__" + readonly property real specialWorkspaceTileHeight: root.workspaceImplicitHeight + readonly property real specialStripGap: workspaceSpacing * 1.8 + readonly property real specialStripPadding: Math.max(8, 12 * root.scale) + readonly property real specialStripTitleHeight: Math.max(14, Appearance.font.pixelSize.small * root.scale) + readonly property real specialStripTitleGap: Math.max(6, 8 * root.scale) + readonly property int totalSpecialTiles: visibleSpecialWorkspaces.length + 1 + readonly property real specialSectionWidth: workspaceColumnLayout.implicitWidth + readonly property real specialGridInnerWidth: Math.max(0, root.specialSectionWidth - root.specialStripPadding * 2) + readonly property int effectiveSpecialColumns: Math.max(1, Math.min(root.specialWorkspaceColumns, root.totalSpecialTiles)) + readonly property int specialWorkspaceRows: Math.ceil(root.totalSpecialTiles / root.effectiveSpecialColumns) + readonly property real specialWorkspaceAspectCap: { + let maxAspect = Math.max(1, root.workspaceImplicitWidth / Math.max(1, root.workspaceImplicitHeight)); + for (const name of visibleSpecialWorkspaces) { + const geometry = root.specialWorkspaceGeometry(name, root.monitor?.id); + const width = geometry?.width; + const height = geometry?.height; + if (!Number.isFinite(width) || !Number.isFinite(height) || height <= 0) + continue; + maxAspect = Math.max(maxAspect, width / height); + } + return maxAspect; + } + readonly property real specialWorkspaceTileWidth: { + const gaps = Math.max(0, root.effectiveSpecialColumns - 1); + const rawWidth = (root.specialGridInnerWidth - gaps * workspaceSpacing) / root.effectiveSpecialColumns; + const aspectWidth = root.specialWorkspaceTileHeight * root.specialWorkspaceAspectCap; + const cappedWidth = Math.min(rawWidth, aspectWidth); + return Math.max(80 * root.scale, cappedWidth); + } + readonly property real specialGridUsedWidth: root.effectiveSpecialColumns * root.specialWorkspaceTileWidth + Math.max(0, root.effectiveSpecialColumns - 1) * workspaceSpacing + readonly property real specialGridOffsetX: root.specialStripPadding + Math.max(0, (root.specialGridInnerWidth - root.specialGridUsedWidth) / 2) + readonly property real specialStripTop: workspaceColumnLayout.implicitHeight + workspaceSpacing + root.specialStripGap + readonly property real specialStripTilesTop: root.specialStripTop + root.specialStripPadding + root.specialStripTitleHeight + root.specialStripTitleGap + readonly property real specialGridHeight: root.specialWorkspaceRows * root.specialWorkspaceTileHeight + Math.max(0, root.specialWorkspaceRows - 1) * workspaceSpacing + readonly property real specialStripHeight: root.specialStripPadding * 2 + root.specialStripTitleHeight + root.specialStripTitleGap + root.specialGridHeight + + function getWorkspaceRow(workspaceId) { + if (!Number.isFinite(workspaceId)) + return 0; + const adjusted = workspaceId - workspaceOffset; + const normalRow = Math.floor((adjusted - 1) / Config.options.overview.columns) % Config.options.overview.rows; + return Config.options.overview.orderBottomUp ? (Config.options.overview.rows - normalRow - 1) : normalRow; + } + + function getWorkspaceColumn(workspaceId) { + if (!Number.isFinite(workspaceId)) + return 0; + const adjusted = workspaceId - workspaceOffset; + const normalCol = (adjusted - 1) % Config.options.overview.columns; + return Config.options.overview.orderRightLeft ? (Config.options.overview.columns - normalCol - 1) : normalCol; + } + + function getWorkspaceInCell(rowIndex, colIndex) { + const mappedRow = Config.options.overview.orderBottomUp ? (Config.options.overview.rows - rowIndex - 1) : rowIndex; + const mappedCol = Config.options.overview.orderRightLeft ? (Config.options.overview.columns - colIndex - 1) : colIndex; + return (workspaceGroup * workspacesShown) + (mappedRow * Config.options.overview.columns) + mappedCol + 1 + workspaceOffset; + } + + function getVisibleRowPosition(rowIndex) { + if (!Number.isFinite(rowIndex) || rowIndex < 0) + return 0; + if (!Config.options.overview.hideEmptyRows || !(rowsWithContent instanceof Set)) + return rowIndex; + + let visibleRow = 0; + for (let i = 0; i < rowIndex; i += 1) { + if (rowsWithContent.has(i)) + visibleRow += 1; + } + return visibleRow; + } + + function stepWorkspace(delta) { + if (!Number.isFinite(delta) || delta === 0) + return; + + const currentId = monitor?.activeWorkspace?.id ?? effectiveActiveWorkspaceId; + const minWorkspaceId = workspaceOffset + 1; + let maxWorkspaceId = minWorkspaceId + workspacesShown - 1; + for (const workspaceId of (workspaceIds ?? [])) { + if (Number.isFinite(workspaceId) && workspaceId >= minWorkspaceId) { + maxWorkspaceId = Math.max(maxWorkspaceId, workspaceId); + } + } + maxWorkspaceId = Math.max(maxWorkspaceId, currentId); + + let targetId = currentId + delta; + if (targetId < minWorkspaceId) { + targetId = maxWorkspaceId; + } else if (targetId > maxWorkspaceId) { + targetId = minWorkspaceId; + } + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.focus({workspace = '${targetId}'})`); + } else { + Hyprland.dispatch(`workspace ${targetId}`); + } + } + + function isSpecialWorkspace(windowData) { + const wsName = `${windowData?.workspace?.name ?? ""}`; + return wsName.startsWith("special:"); + } + + function specialWorkspaceName(windowData) { + const wsName = `${windowData?.workspace?.name ?? ""}`; + return wsName.startsWith("special:") ? wsName.slice(8) : ""; + } + + function specialWorkspaceIndex(name) { + return visibleSpecialWorkspaces.indexOf(`${name ?? ""}`); + } + + function specialWorkspaceLabel(name) { + const raw = `${name ?? ""}`.trim(); + if (raw.length === 0) + return "Special"; + return raw.replace(/[-_]+/g, " "); + } + + function nextSpecialWorkspaceName() { + const taken = new Set(); + for (const name of visibleSpecialWorkspaces) + taken.add(`${name ?? ""}`.trim().toLowerCase()); + + const base = "stash"; + if (!taken.has(base)) + return base; + + let index = 2; + while (taken.has(`${base}-${index}`)) + index += 1; + + return `${base}-${index}`; + } + + function wallpaperSource(path) { + const trimmed = `${path ?? ""}`.trim(); + if (trimmed.length === 0) + return ""; + if (trimmed.startsWith("file:/") || trimmed.startsWith("qrc:/") || trimmed.startsWith("image://") || trimmed.startsWith("http://") || trimmed.startsWith("https://")) + return trimmed; + if (trimmed.startsWith("/")) + return `file://${trimmed}`; + return trimmed; + } + + function workspaceHasWindows(workspaceId) { + if (!Number.isFinite(workspaceId)) + return false; + + for (const addr in windowByAddress) { + const win = windowByAddress[addr]; + if (root.isSpecialWorkspace(win)) + continue; + if ((win?.workspace?.id ?? -1) === workspaceId) + return true; + } + return false; + } + + function specialWindowZ(win) { + const pinned = win?.pinned ? 200000 : 0; + const floating = win?.floating ? 100000 : 0; + const focus = 10000 - (win?.focusHistoryID ?? 9999); + return pinned + floating + focus; + } + + function specialWorkspaceGeometry(name, monitorId) { + const trimmedName = `${name ?? ""}`.trim(); + const currentMonitorId = monitorId ?? -1; + let minX = null; + let minY = null; + let maxX = null; + let maxY = null; + + for (const addr in windowByAddress) { + const win = windowByAddress[addr]; + if ((win?.monitor ?? -1) !== currentMonitorId) + continue; + if (root.specialWorkspaceName(win) !== trimmedName) + continue; + + const atX = win?.at?.[0]; + const atY = win?.at?.[1]; + const width = win?.size?.[0]; + const height = win?.size?.[1]; + if (!Number.isFinite(atX) || !Number.isFinite(atY)) + continue; + if (!Number.isFinite(width) || !Number.isFinite(height)) + continue; + + minX = minX === null ? atX : Math.min(minX, atX); + minY = minY === null ? atY : Math.min(minY, atY); + maxX = maxX === null ? (atX + width) : Math.max(maxX, atX + width); + maxY = maxY === null ? (atY + height) : Math.max(maxY, atY + height); + } + + return { + x: minX, + y: minY, + width: (minX !== null && maxX !== null) ? Math.max(1, maxX - minX) : null, + height: (minY !== null && maxY !== null) ? Math.max(1, maxY - minY) : null + }; + } + + // Calculate which rows have windows or current workspace + property var rowsWithContent: { + if (!Config.options.overview.hideEmptyRows) return null; + + let rows = new Set(); + const firstWorkspace = root.workspaceGroup * root.workspacesShown + 1 + workspaceOffset; + const lastWorkspace = (root.workspaceGroup + 1) * root.workspacesShown + workspaceOffset; + + // Add row containing current workspace + const currentWorkspace = effectiveActiveWorkspaceId; + if (currentWorkspace >= firstWorkspace && currentWorkspace <= lastWorkspace) { + rows.add(getWorkspaceRow(currentWorkspace)); + } + + // Add rows with windows + for (let addr in windowByAddress) { + const win = windowByAddress[addr]; + const wsId = win?.workspace?.id; + if (wsId >= firstWorkspace && wsId <= lastWorkspace) { + const rowIndex = getWorkspaceRow(wsId); + rows.add(rowIndex); + } + } + + return rows; + } + + implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + + property Component windowComponent: OverviewWindow {} + property list windowWidgets: [] + + Connections { + target: Hyprland + function onRawEvent(event) { + if (!GlobalStates.overviewOpen || !root.useEventPreviewRefresh) + return; + + const eventName = `${event?.name ?? event?.event ?? event?.type ?? ""}`; + if (eventName === "closewindow" || eventName === "openwindow" || eventName === "movewindow") { + root.previewRecaptureToken += 1; + } + } + } + + StyledRectangularShadow { + target: overviewBackground + } + Rectangle { // Background + id: overviewBackground + property real padding: Config.options.overview.backgroundPadding + anchors.fill: parent + anchors.margins: Appearance.sizes.elevationMargin + + implicitWidth: contentLayout.implicitWidth + padding * 2 + implicitHeight: contentLayout.implicitHeight + padding * 2 + radius: Appearance.rounding.screenRounding * root.scale + (Appearance.rounding.screenRounding !== 0 ? padding : 0) + clip: true + color: ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colLayer1, 0.78 - root.glassTintStrength * 0.35) + : Appearance.colors.colLayer0, + root.effectivePanelOpacity + ) + border.width: 1 + border.color: ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(Appearance.colors.colLayer0Border, Appearance.m3colors.m3outline, 0.52) + : Appearance.colors.colLayer0Border, + root.glassMode ? root.glassBorderOpacity : root.effectivePanelOpacity + ) + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton + onPressed: mouse => mouse.accepted = true + } + + Rectangle { + visible: root.glassMode + anchors.fill: parent + radius: parent.radius + color: "transparent" + gradient: Gradient { + GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.35) } + GradientStop { position: 0.42; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) } + GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.22) } + } + } + + Rectangle { + visible: root.glassMode + anchors.fill: parent + anchors.margins: 1 + radius: Math.max(parent.radius - 1, 0) + color: "transparent" + border.width: 1 + border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassBorderOpacity * 0.20) + } + + ColumnLayout { // Workspaces + id: contentLayout + + z: root.workspaceZ + anchors.centerIn: parent + spacing: workspaceSpacing + ColumnLayout { + id: workspaceColumnLayout + spacing: workspaceSpacing + + Repeater { + model: Config.options.overview.rows + delegate: RowLayout { + id: row + property int rowIndex: index + spacing: workspaceSpacing + visible: !Config.options.overview.hideEmptyRows || + (root.rowsWithContent && root.rowsWithContent.has(rowIndex)) + height: visible ? implicitHeight : 0 + + Repeater { // Workspace repeater + model: Config.options.overview.columns + Rectangle { // Workspace + id: workspace + property int colIndex: index + property int workspaceValue: root.getWorkspaceInCell(rowIndex, colIndex) + property bool showWallpaper: root.hasEmptyWorkspaceWallpaper + property color defaultWorkspaceColor: Appearance.colors.colLayer1 + property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1) + property color hoveredBorderColor: Appearance.colors.colLayer2Hover + property bool hoveredWhileDragging: false + + implicitWidth: root.workspaceImplicitWidth + implicitHeight: root.workspaceImplicitHeight + color: showWallpaper ? "transparent" : ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor, Appearance.colors.colLayer0, 0.46) + : (hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor), + root.effectiveWorkspaceOpacity + ) + radius: Appearance.rounding.screenRounding * root.scale + border.width: 2 + border.color: hoveredWhileDragging + ? ColorUtils.applyAlpha(hoveredBorderColor, root.glassMode ? root.glassBorderOpacity : 1) + : "transparent" + + Image { + id: workspaceWallpaper + visible: workspace.showWallpaper + anchors.fill: parent + source: root.wallpaperSource(root.emptyWorkspaceWallpaperPath) + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + smooth: true + mipmap: true + layer.enabled: workspace.showWallpaper + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: workspaceWallpaperMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + } + } + + Item { + id: workspaceWallpaperMask + anchors.fill: parent + visible: false + layer.enabled: true + layer.smooth: true + Rectangle { + anchors.fill: parent + radius: workspace.radius + } + } + + Rectangle { + visible: workspace.showWallpaper + anchors.fill: parent + radius: parent.radius + color: ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(workspace.hoveredWhileDragging ? workspace.hoveredWorkspaceColor : workspace.defaultWorkspaceColor, Appearance.colors.colLayer0, 0.40) + : (workspace.hoveredWhileDragging ? workspace.hoveredWorkspaceColor : workspace.defaultWorkspaceColor), + workspace.hoveredWhileDragging + ? Math.min(0.28, root.emptyWorkspaceWallpaperOverlayOpacity + 0.08) + : root.emptyWorkspaceWallpaperOverlayOpacity + ) + } + + Rectangle { + visible: root.glassMode + anchors.fill: parent + radius: parent.radius + color: "transparent" + gradient: Gradient { + GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.22) } + GradientStop { position: 0.46; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) } + GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.14) } + } + } + + Rectangle { + visible: root.glassMode + anchors.fill: parent + anchors.margins: 1 + radius: Math.max(parent.radius - 1, 0) + color: "transparent" + border.width: 1 + border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassBorderOpacity * 0.16) + } + + StyledText { + anchors.centerIn: parent + visible: !workspace.showWallpaper + text: workspaceValue + font { + pixelSize: root.workspaceNumberSize * root.scale + weight: Font.DemiBold + family: Appearance.font.family.expressive + } + color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8) + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + MouseArea { + id: workspaceArea + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: { + if (root.draggingTargetWorkspace === -1) { + GlobalStates.overviewOpen = false + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.focus({workspace = '${workspaceValue}'})`); + } else { + Hyprland.dispatch(`workspace ${workspaceValue}`) + } + } + } + } + + DropArea { + anchors.fill: parent + onEntered: { + root.draggingTargetWorkspace = workspaceValue + root.draggingTargetSpecialWorkspace = "" + if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return; + hoveredWhileDragging = true + } + onExited: { + hoveredWhileDragging = false + if (root.draggingTargetWorkspace == workspaceValue) root.draggingTargetWorkspace = -1 + } + } + + } + } + } + } + } + + Item { + visible: root.showSpecialWorkspaces + implicitWidth: 1 + implicitHeight: root.specialStripGap + } + + Item { + id: specialWorkspaceSection + visible: root.showSpecialWorkspaces + implicitWidth: root.specialSectionWidth + implicitHeight: root.specialStripHeight + + Rectangle { + anchors.fill: parent + radius: Appearance.rounding.normal * root.scale + color: ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colLayer1, 0.70) + : Appearance.colors.colLayer1, + root.glassMode ? Math.min(0.74, root.effectivePanelOpacity) : root.effectiveWorkspaceOpacity + ) + border.width: 1 + border.color: ColorUtils.applyAlpha(Appearance.colors.colLayer2Border, root.glassMode ? root.glassBorderOpacity : 0.65) + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + height: Math.max(18 * root.scale, root.specialStripPadding + root.specialStripTitleHeight * 0.8) + radius: parent.radius + color: ColorUtils.applyAlpha(Appearance.colors.colSecondary, root.glassMode ? 0.12 : 0.08) + } + + StyledText { + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: root.specialStripPadding + anchors.topMargin: root.specialStripPadding + text: "Special Workspaces" + font.family: Appearance.font.family.title + font.pixelSize: root.specialStripTitleHeight + font.weight: Font.DemiBold + color: ColorUtils.applyAlpha(Appearance.colors.colOnLayer1, 0.84) + } + + Grid { + id: specialWorkspaceGrid + x: root.specialGridOffsetX + y: root.specialStripPadding + root.specialStripTitleHeight + root.specialStripTitleGap + width: root.specialGridUsedWidth + columns: root.effectiveSpecialColumns + rowSpacing: workspaceSpacing + columnSpacing: workspaceSpacing + + Repeater { + model: root.visibleSpecialWorkspaces + delegate: Rectangle { + id: specialWorkspaceTile + required property string modelData + property string specialName: modelData + property var specialGeometry: root.specialWorkspaceGeometry(specialName, root.monitor?.id) + property color baseColor: ColorUtils.mix(Appearance.colors.colLayer1, Appearance.colors.colLayer0, 0.52) + property bool hasRenderableGeometry: Number.isFinite(specialGeometry?.width) + && Number.isFinite(specialGeometry?.height) + && specialGeometry.width > 0 + && specialGeometry.height > 0 + property bool showWallpaper: root.hasSpecialEmptyWorkspaceWallpaper + property real geometryWidth: hasRenderableGeometry ? specialGeometry.width : Math.max(1, root.workspaceImplicitWidth / root.scale) + property real geometryHeight: hasRenderableGeometry ? specialGeometry.height : Math.max(1, root.workspaceImplicitHeight / root.scale) + property real fitScale: hasRenderableGeometry ? Math.min(width / geometryWidth, height / geometryHeight) : root.scale + property real contentWidth: hasRenderableGeometry ? (geometryWidth * fitScale) : width + property real contentHeight: hasRenderableGeometry ? (geometryHeight * fitScale) : height + property real contentOffsetX: Math.max(0, (width - contentWidth) / 2) + property real contentOffsetY: Math.max(0, (height - contentHeight) / 2) + implicitWidth: root.specialWorkspaceTileWidth + implicitHeight: root.specialWorkspaceTileHeight + radius: Appearance.rounding.screenRounding * root.scale + clip: true + color: showWallpaper ? "transparent" : ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(baseColor, Appearance.colors.colLayer0, 0.44) + : baseColor, + root.effectiveWorkspaceOpacity + ) + border.width: 1 + border.color: ColorUtils.applyAlpha(Appearance.colors.colLayer2Border, root.glassMode ? root.glassBorderOpacity : 0.75) + + Image { + visible: specialWorkspaceTile.showWallpaper + anchors.fill: parent + source: root.wallpaperSource(root.specialEmptyWorkspaceWallpaperPath) + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + smooth: true + mipmap: true + } + + Rectangle { + visible: specialWorkspaceTile.showWallpaper + anchors.fill: parent + radius: parent.radius + color: ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(specialWorkspaceTile.baseColor, Appearance.colors.colLayer0, 0.40) + : specialWorkspaceTile.baseColor, + root.emptyWorkspaceWallpaperOverlayOpacity + ) + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: { + if (root.draggingTargetWorkspace === -1 && !root.draggingTargetSpecialWorkspace) { + GlobalStates.overviewOpen = false; + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.workspace.toggle_special('${specialWorkspaceTile.specialName}')`); + } else { + Hyprland.dispatch(`togglespecialworkspace ${specialWorkspaceTile.specialName}`); + } + } + } + } + + DropArea { + anchors.fill: parent + onEntered: { + root.draggingTargetWorkspace = -1; + root.draggingTargetSpecialWorkspace = specialWorkspaceTile.specialName; + } + onExited: { + if (root.draggingTargetSpecialWorkspace === specialWorkspaceTile.specialName) + root.draggingTargetSpecialWorkspace = ""; + } + } + + Item { + id: specialWorkspaceContent + x: specialWorkspaceTile.contentOffsetX + y: specialWorkspaceTile.contentOffsetY + width: specialWorkspaceTile.contentWidth + height: specialWorkspaceTile.contentHeight + clip: true + + Repeater { + model: ScriptModel { + values: { + if (!specialWorkspaceTile.hasRenderableGeometry) + return []; + return ToplevelManager.toplevels.values.filter((toplevel) => { + const address = `0x${toplevel.HyprlandToplevel.address}`; + const win = windowByAddress[address]; + if ((win?.monitor ?? -1) !== (root.monitor?.id ?? -1)) + return false; + return root.specialWorkspaceName(win) === specialWorkspaceTile.specialName; + }).sort((a, b) => { + const addrA = `0x${a.HyprlandToplevel.address}`; + const addrB = `0x${b.HyprlandToplevel.address}`; + return addrA.localeCompare(addrB); + }); + } + } + delegate: OverviewWindow { + id: specialWindow + required property var modelData + required property int index + property var address: `0x${modelData.HyprlandToplevel.address}` + property int monitorId: windowData?.monitor + property var monitor: HyprlandData.monitors.find(m => m.id === monitorId) + property Item homeParent: specialWorkspaceContent + windowData: windowByAddress[address] + toplevel: modelData + monitorData: monitor + widgetMonitorData: root.monitorData + scale: root.scale + availableWorkspaceWidth: specialWorkspaceContent.width + availableWorkspaceHeight: specialWorkspaceContent.height + positionBaseX: Number.isFinite(specialWorkspaceTile.specialGeometry?.x) ? specialWorkspaceTile.specialGeometry.x : ((monitor?.x ?? 0) + (monitor?.reserved?.[0] ?? 0)) + positionBaseY: Number.isFinite(specialWorkspaceTile.specialGeometry?.y) ? specialWorkspaceTile.specialGeometry.y : ((monitor?.y ?? 0) + (monitor?.reserved?.[1] ?? 0)) + geometryScaleX: specialWorkspaceTile.fitScale / root.scale + geometryScaleY: specialWorkspaceTile.fitScale / root.scale + xOffset: 0 + yOffset: 0 + widgetMonitorId: root.monitor.id + recaptureToken: root.previewRecaptureToken + restrictToWorkspace: false + animateSize: false + z: root.specialWindowZ(windowData) + + function moveToDragLayer() { + const mapped = specialWindow.mapToItem(specialWindowDragLayer, 0, 0); + specialWindow.suspendPositionAnimation = true; + specialWindow.parent = specialWindowDragLayer; + specialWindow.x = mapped.x; + specialWindow.y = mapped.y; + specialWindow.z = root.windowDraggingZ + 1; + Qt.callLater(() => specialWindow.suspendPositionAnimation = false); + } + + function returnToHomeParent() { + specialWindow.suspendPositionAnimation = true; + specialWindow.parent = homeParent; + specialWindow.z = root.specialWindowZ(windowData); + Qt.callLater(() => specialWindow.suspendPositionAnimation = false); + } + + MouseArea { + id: specialDragArea + anchors.fill: parent + hoverEnabled: true + onEntered: hovered = true + onExited: hovered = false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + drag.target: parent + onPressed: (mouse) => { + root.draggingFromWorkspace = -1 + root.draggingTargetSpecialWorkspace = "" + specialWindow.pressed = true + specialWindow.dragInProgress = true + specialWindow.Drag.source = specialWindow + specialWindow.Drag.hotSpot.x = mouse.x + specialWindow.Drag.hotSpot.y = mouse.y + specialWindow.moveToDragLayer() + specialWindow.Drag.active = true + } + onReleased: { + const targetWorkspace = root.draggingTargetWorkspace + const targetSpecialWorkspace = root.draggingTargetSpecialWorkspace + specialWindow.pressed = false + specialWindow.Drag.active = false + specialWindow.dragInProgress = false + root.draggingFromWorkspace = -1 + root.draggingTargetWorkspace = -1 + root.draggingTargetSpecialWorkspace = "" + if (targetSpecialWorkspace === root.createSpecialWorkspaceTarget) { + const createdName = root.nextSpecialWorkspaceName() + //INFO: From special TO NEW special + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${createdName}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`); + } else { + Hyprland.dispatch(`movetoworkspacesilent special:${createdName}, address:${specialWindow.windowData?.address}`) + } + specialWindow.returnToHomeParent() + specialWindow.x = specialWindow.initX + specialWindow.y = specialWindow.initY + } + else if (targetSpecialWorkspace && targetSpecialWorkspace !== specialWorkspaceTile.specialName) { + // Idk + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${targetSpecialWorkspace}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`); + } else { + Hyprland.dispatch(`movetoworkspacesilent special:${targetSpecialWorkspace}, address:${specialWindow.windowData?.address}`) + } + specialWindow.returnToHomeParent() + specialWindow.x = specialWindow.initX + specialWindow.y = specialWindow.initY + } + else if (targetWorkspace !== -1) { + //INFO: From Special TO Normal workspace + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.move({workspace = '${targetWorkspace}', follow = false, window = 'address:${specialWindow.windowData?.address}'})`); + } else { + Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${specialWindow.windowData?.address}`) + } + specialWindow.returnToHomeParent() + specialWindow.x = specialWindow.initX + specialWindow.y = specialWindow.initY + } + else { + specialWindow.returnToHomeParent() + specialWindow.x = specialWindow.initX + specialWindow.y = specialWindow.initY + } + } + onClicked: (event) => { + if (!windowData) + return; + if (event.button === Qt.LeftButton) { + GlobalStates.overviewOpen = false; + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.focus({ window = 'address:${windowData.address}' })`); + } else { + Hyprland.dispatch(`focuswindow address:${windowData.address}`); + } + event.accepted = true; + } else if (event.button === Qt.MiddleButton) { + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.close('address:${windowData.address}')`); + } else { + Hyprland.dispatch(`closewindow address:${windowData.address}`); + } + event.accepted = true; + } + } + + StyledToolTip { + extraVisibleCondition: false + alternativeVisibleCondition: specialDragArea.containsMouse && !specialWindow.Drag.active + text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}` + } + } + } + } + } + } + } + Rectangle { + id: createSpecialWorkspaceTile + property bool showWallpaper: root.hasSpecialEmptyWorkspaceWallpaper + implicitWidth: root.specialWorkspaceTileWidth + implicitHeight: root.specialWorkspaceTileHeight + radius: Appearance.rounding.screenRounding * root.scale + color: showWallpaper ? "transparent" : ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer1, 0.58) + : ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer1, 0.55), + root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget ? 0.90 : root.effectiveWorkspaceOpacity + ) + border.width: 1 + border.color: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget + ? ColorUtils.applyAlpha(root.activeBorderColor, 0.96) + : ColorUtils.applyAlpha(Appearance.colors.colSecondary, 0.46) + + Image { + visible: createSpecialWorkspaceTile.showWallpaper + anchors.fill: parent + source: root.wallpaperSource(root.specialEmptyWorkspaceWallpaperPath) + fillMode: Image.PreserveAspectCrop + asynchronous: true + cache: true + smooth: true + mipmap: true + } + + Rectangle { + visible: createSpecialWorkspaceTile.showWallpaper + anchors.fill: parent + radius: parent.radius + color: ColorUtils.applyAlpha( + root.glassMode + ? ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer0, 0.40) + : ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer1, 0.55), + root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget + ? Math.min(0.28, root.emptyWorkspaceWallpaperOverlayOpacity + 0.08) + : root.emptyWorkspaceWallpaperOverlayOpacity + ) + } + + Rectangle { + anchors.fill: parent + anchors.margins: 1 + radius: Math.max(parent.radius - 1, 0) + color: "transparent" + border.width: 1 + border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassMode ? 0.12 : 0.08) + } + + Column { + anchors.centerIn: parent + spacing: 0 + + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + visible: !createSpecialWorkspaceTile.showWallpaper + text: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget ? "Release" : "+" + font.family: Appearance.font.family.expressive + font.pixelSize: root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget + ? Appearance.font.pixelSize.larger * root.scale + : Appearance.font.pixelSize.huge * 1.25 * root.scale + font.weight: Font.DemiBold + color: ColorUtils.applyAlpha(Appearance.colors.colOnLayer1, 0.92) + horizontalAlignment: Text.AlignHCenter + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.LeftButton + onClicked: { + const createdName = root.nextSpecialWorkspaceName(); + GlobalStates.overviewOpen = false; + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.workspace.toggle_special('${createdName}')`); + } else { + Hyprland.dispatch(`togglespecialworkspace ${createdName}`); + } + } + } + + DropArea { + anchors.fill: parent + onEntered: { + root.draggingTargetWorkspace = -1; + root.draggingTargetSpecialWorkspace = root.createSpecialWorkspaceTarget; + } + onExited: { + if (root.draggingTargetSpecialWorkspace === root.createSpecialWorkspaceTarget) + root.draggingTargetSpecialWorkspace = ""; + } + } + } + } + } + } + } + + Item { // Windows & focused workspace indicator + id: windowSpace + anchors.centerIn: parent + implicitWidth: contentLayout.implicitWidth + implicitHeight: contentLayout.implicitHeight + + WheelHandler { + target: null + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + onWheel: event => { + const deltaY = event.angleDelta.y; + if (!deltaY) + return; + root.stepWorkspace(deltaY > 0 ? -1 : 1); + event.accepted = true; + } + } + + Repeater { // Window repeater + model: ScriptModel { + values: { + return ToplevelManager.toplevels.values.filter((toplevel) => { + const address = `0x${toplevel.HyprlandToplevel.address}` + var win = windowByAddress[address] + if (root.isSpecialWorkspace(win)) + return false; + const minWorkspace = root.workspaceGroup * root.workspacesShown + 1 + workspaceOffset; + const maxWorkspace = (root.workspaceGroup + 1) * root.workspacesShown + workspaceOffset; + const inWorkspaceGroup = (minWorkspace <= win?.workspace?.id && win?.workspace?.id <= maxWorkspace) + return inWorkspaceGroup; + }).sort((a, b) => { + // Proper stacking order based on Hyprland's window properties + const addrA = `0x${a.HyprlandToplevel.address}` + const addrB = `0x${b.HyprlandToplevel.address}` + const winA = windowByAddress[addrA] + const winB = windowByAddress[addrB] + + // 1. Pinned windows are always on top + if (winA?.pinned !== winB?.pinned) { + return winA?.pinned ? 1 : -1 + } + + // 2. Floating windows above tiled windows + if (winA?.floating !== winB?.floating) { + return winA?.floating ? 1 : -1 + } + + // 3. Within same category, sort by focus history + // Lower focusHistoryID = more recently focused = higher in stack + return (winB?.focusHistoryID ?? 0) - (winA?.focusHistoryID ?? 0) + }) + } + } + delegate: OverviewWindow { + id: window + required property var modelData + required property int index + property int monitorId: windowData?.monitor + property var monitor: HyprlandData.monitors.find(m => m.id === monitorId) + property var address: `0x${modelData.HyprlandToplevel.address}` + windowData: windowByAddress[address] + toplevel: modelData + monitorData: monitor + widgetMonitorData: root.monitorData + scale: root.scale + availableWorkspaceWidth: root.workspaceImplicitWidth + availableWorkspaceHeight: root.workspaceImplicitHeight + widgetMonitorId: root.monitor.id + recaptureToken: root.previewRecaptureToken + + property bool atInitPosition: (initX == x && initY == y) + + property int workspaceColIndex: root.getWorkspaceColumn(windowData?.workspace.id) + property int workspaceRowIndex: root.getWorkspaceRow(windowData?.workspace.id) + property int visibleWorkspaceRowIndex: root.getVisibleRowPosition(workspaceRowIndex) + xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex + yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * visibleWorkspaceRowIndex + + Timer { + id: updateWindowPosition + interval: Config.options.hacks.arbitraryRaceConditionDelay + repeat: false + running: false + onTriggered: { + window.x = Math.round(Math.max((windowData?.at[0] - (monitor?.x ?? 0) - (monitorData?.reserved?.[0] ?? 0)) * root.scale * window.widthRatio, 0) + xOffset) + window.y = Math.round(Math.max((windowData?.at[1] - (monitor?.y ?? 0) - (monitorData?.reserved?.[1] ?? 0)) * root.scale * window.heightRatio, 0) + yOffset) + } + } + + z: atInitPosition ? (root.windowZ + index) : root.windowDraggingZ + Drag.hotSpot.x: targetWindowWidth / 2 + Drag.hotSpot.y: targetWindowHeight / 2 + MouseArea { + id: dragArea + anchors.fill: parent + hoverEnabled: true + onEntered: hovered = true + onExited: hovered = false + acceptedButtons: Qt.LeftButton | Qt.MiddleButton + drag.target: parent + onPressed: (mouse) => { + root.draggingFromWorkspace = windowData?.workspace.id + root.draggingTargetSpecialWorkspace = "" + window.pressed = true + window.Drag.active = true + window.Drag.source = window + window.Drag.hotSpot.x = mouse.x + window.Drag.hotSpot.y = mouse.y + } + onReleased: { + const targetWorkspace = root.draggingTargetWorkspace + const targetSpecialWorkspace = root.draggingTargetSpecialWorkspace + window.pressed = false + window.Drag.active = false + root.draggingFromWorkspace = -1 + root.draggingTargetWorkspace = -1 + root.draggingTargetSpecialWorkspace = "" + if (targetSpecialWorkspace === root.createSpecialWorkspaceTarget) { + const createdName = root.nextSpecialWorkspaceName() + //INFO: From normal TO special + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${createdName}', follow = false, window = 'address:${window.windowData?.address}'})`); + } else { + Hyprland.dispatch(`movetoworkspacesilent special:${createdName}, address:${window.windowData?.address}`) + } + updateWindowPosition.restart() + } + else if (targetSpecialWorkspace && targetSpecialWorkspace !== root.specialWorkspaceName(windowData)) { + // Idk + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.move({workspace = 'special:${targetSpecialWorkspace}', follow = false, window = 'address:${window.windowData?.address}'})`); + } else { + Hyprland.dispatch(`movetoworkspacesilent special:${targetSpecialWorkspace}, address:${window.windowData?.address}`) + } + updateWindowPosition.restart() + } + else if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) { + //INFO: From normal TO normal + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.move({workspace = '${targetWorkspace}', follow = false, window = 'address:${window.windowData?.address}'})`); + } else { + Hyprland.dispatch(`movetoworkspacesilent ${targetWorkspace}, address:${window.windowData?.address}`) + } + updateWindowPosition.restart() + } + else { + window.x = window.initX + window.y = window.initY + } + } + onClicked: (event) => { + if (!windowData) return; + + if (event.button === Qt.LeftButton) { + GlobalStates.overviewOpen = false + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.focus({ window = 'address:${windowData.address}' })`); + } else { + Hyprland.dispatch(`focuswindow address:${windowData.address}`) + } + event.accepted = true + } else if (event.button === Qt.MiddleButton) { + if (Hyprland.usingLua) { + Hyprland.dispatch(`hl.dsp.window.close('address:${windowData.address}')`); + } else { + Hyprland.dispatch(`closewindow address:${windowData.address}`) + } + event.accepted = true + } + } + + StyledToolTip { + extraVisibleCondition: false + alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active + text: `${windowData?.title ?? "Unknown"}\n[${windowData?.class ?? "unknown"}] ${windowData?.xwayland ? "[XWayland] " : ""}` + } + } + } + } + + Rectangle { // Focused workspace indicator + id: focusedWorkspaceIndicator + property int activeWorkspaceRowIndex: root.getWorkspaceRow(root.effectiveActiveWorkspaceId) + property int visibleActiveWorkspaceRowIndex: root.getVisibleRowPosition(activeWorkspaceRowIndex) + property int activeWorkspaceColIndex: root.getWorkspaceColumn(root.effectiveActiveWorkspaceId) + x: (root.workspaceImplicitWidth + workspaceSpacing) * activeWorkspaceColIndex + y: (root.workspaceImplicitHeight + workspaceSpacing) * visibleActiveWorkspaceRowIndex + z: root.windowDraggingZ - 1 + width: root.workspaceImplicitWidth + height: root.workspaceImplicitHeight + color: "transparent" + radius: Appearance.rounding.screenRounding * root.scale + border.width: 2 + border.color: root.activeBorderColor + Behavior on x { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + Behavior on y { + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) + } + } + } + + Item { + id: specialWindowDragLayer + anchors.fill: parent + z: root.windowDraggingZ + 1 + } + } +} diff --git a/.config/quickshell/own/modules/overview/OverviewWindow.qml b/.config/quickshell/own/modules/overview/OverviewWindow.qml new file mode 100644 index 0000000..f12904f --- /dev/null +++ b/.config/quickshell/own/modules/overview/OverviewWindow.qml @@ -0,0 +1,265 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Effects +import Quickshell +import Quickshell.Wayland +import "../../common" +import "../../common/functions" +import "../../services" +import "../.." + +Item { // Window + id: root + property var toplevel + property var windowData + property var monitorData + property var widgetMonitorData + property var scale + property var availableWorkspaceWidth + property var availableWorkspaceHeight + property real positionBaseX: (monitorData?.x ?? 0) + (monitorData?.reserved?.[0] ?? 0) + property real positionBaseY: (monitorData?.y ?? 0) + (monitorData?.reserved?.[1] ?? 0) + property int recaptureToken: 0 + property bool restrictToWorkspace: true + property real widthRatio: { + if (!widgetMonitorData || !monitorData) + return 1; + + const widgetWidth = (widgetMonitorData.transform % 2 === 1) ? (widgetMonitorData.height ?? 1) : (widgetMonitorData.width ?? 1); + const sourceWidth = (monitorData.transform % 2 === 1) ? (monitorData.height ?? 1) : (monitorData.width ?? 1); + const sourceScale = monitorData.scale ?? 1; + const widgetScale = widgetMonitorData.scale ?? 1; + return (widgetWidth * sourceScale) / (sourceWidth * widgetScale); + } + property real heightRatio: { + if (!widgetMonitorData || !monitorData) + return 1; + + const widgetHeight = (widgetMonitorData.transform % 2 === 1) ? (widgetMonitorData.width ?? 1) : (widgetMonitorData.height ?? 1); + const sourceHeight = (monitorData.transform % 2 === 1) ? (monitorData.width ?? 1) : (monitorData.height ?? 1); + const sourceScale = monitorData.scale ?? 1; + const widgetScale = widgetMonitorData.scale ?? 1; + return (widgetHeight * sourceScale) / (sourceHeight * widgetScale); + } + property real initX: Math.max(((windowData?.at[0] ?? 0) - positionBaseX) * root.scale * geometryScaleX, 0) + xOffset + property real initY: Math.max(((windowData?.at[1] ?? 0) - positionBaseY) * root.scale * geometryScaleY, 0) + yOffset + property real xOffset: 0 + property real yOffset: 0 + property int widgetMonitorId: 0 + property real geometryScaleX: widthRatio + property real geometryScaleY: heightRatio + + property var targetWindowWidth: (windowData?.size[0] ?? 100) * scale * geometryScaleX + property var targetWindowHeight: (windowData?.size[1] ?? 100) * scale * geometryScaleY + property bool hovered: false + property bool pressed: false + + property bool showIcons: Config.options.windowPreview.showIcons + property var iconToWindowRatio: Config.options.windowPreview.iconToWindowRatio + property var xwaylandIndicatorToIconRatio: Config.options.windowPreview.xwaylandIndicatorToIconRatio + property var iconToWindowRatioCompact: Config.options.windowPreview.iconToWindowRatioCompact + property bool cropToFill: Config.options.windowPreview.cropToFill + property bool previewsEnabled: Config.options.overview.previewsEnabled + property bool includeInactiveMonitorPreviews: Config.options.overview.includeInactiveMonitorPreviews + property int previewRecaptureDelayMs: Config.options.overview.previewRecaptureDelayMs + property real windowOverlayOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.windowOverlayOpacity)) + property bool glassMode: Config.options.overview.effects.glassMode + property real glassShineOpacity: Math.max(0, Math.min(1, Config.options.overview.effects.glassShineOpacity)) + property real effectiveWindowOverlayOpacity: glassMode ? Math.min(windowOverlayOpacity, 0.10) : windowOverlayOpacity + property string previewModeRaw: Config.options.overview.previewMode + property string previewMode: { + const mode = `${previewModeRaw ?? "live"}`.trim().toLowerCase(); + return (mode === "event" || mode === "snapshot") ? "event" : "live"; + } + property bool livePreviewEnabled: previewsEnabled && previewMode === "live" + property bool shouldCapturePreview: { + if (!GlobalStates.overviewOpen || !previewsEnabled || !previewCaptureEnabled) + return false; + if (includeInactiveMonitorPreviews) + return true; + return (windowData?.monitor ?? -1) === widgetMonitorId; + } + property var entry: { + DesktopEntries.applications.values; // re-run when the entry index updates + return DesktopEntries.heuristicLookup(windowData?.class); + } + property string iconName: { + const raw = `${entry?.icon ?? ""}`.trim(); + const withoutProviderPrefix = raw.replace(/^image:\/\/icon\//, ""); + const withoutQuery = withoutProviderPrefix.split("?")[0].trim(); + return withoutQuery.length > 0 ? withoutQuery : "application-x-executable"; + } + property var iconPath: Quickshell.iconPath(iconName, "image-missing") + property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth + + property bool indicateXWayland: windowData?.xwayland ?? false + property bool previewCaptureEnabled: true + property bool initialized: false + property bool dragInProgress: false + property bool suspendPositionAnimation: false + property bool animateSize: true + + x: initX + y: initY + width: Math.min(targetWindowWidth, availableWorkspaceWidth) + height: Math.min(targetWindowHeight, availableWorkspaceHeight) + opacity: (windowData?.monitor ?? -1) == widgetMonitorId ? 1 : Config.options.windowPreview.inactiveMonitorOpacity + visible: { + const thisWsId = windowData?.workspace?.id; + const isFullscreen = (windowData?.fullscreen ?? 0) > 0; + if (isFullscreen || thisWsId === undefined) return true; + return !HyprlandData.windowList.some(w => w.workspace?.id === thisWsId && (w.fullscreen ?? 0) > 0); + } + + clip: true + Component.onCompleted: Qt.callLater(() => root.initialized = true) + + Behavior on x { + enabled: root.initialized && !root.dragInProgress && !root.suspendPositionAnimation + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on y { + enabled: root.initialized && !root.dragInProgress && !root.suspendPositionAnimation + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on width { + enabled: root.initialized && root.animateSize && !root.dragInProgress && !root.suspendPositionAnimation + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + Behavior on height { + enabled: root.initialized && root.animateSize && !root.dragInProgress && !root.suspendPositionAnimation + animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this) + } + + // Opaque background for windows on the active monitor. + // The simplest solution for making those windows fully opaque and not interacting with actual + // windows behind the overview, e.g., applying blur to them. + Rectangle { + visible: (root.windowData?.monitor ?? -1) === root.widgetMonitorId + anchors.fill: parent + radius: Appearance.rounding.windowRounding * root.scale + color: root.glassMode + ? ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer0, 0.38) + : Appearance.colors.colLayer2 + } + + ScreencopyView { + id: windowPreview + readonly property real srcAspect: { + const w = root.windowData?.size?.[0] ?? 0; + const h = root.windowData?.size?.[1] ?? 0; + return (w > 0 && h > 0) ? (w / h) : 1; + } + anchors.centerIn: parent + width: root.cropToFill + ? Math.max(parent.width, parent.height * srcAspect) + : Math.min(parent.width, parent.height * srcAspect) + height: root.cropToFill + ? Math.max(parent.height, parent.width / srcAspect) + : Math.min(parent.height, parent.width / srcAspect) + captureSource: shouldCapturePreview ? root.toplevel : null + live: livePreviewEnabled + layer.enabled: true + layer.smooth: true + layer.effect: MultiEffect { + maskEnabled: true + maskSource: previewMask + maskThresholdMin: 0.5 + maskSpreadAtMin: 1.0 + } + } + + Rectangle { + anchors.fill: parent + radius: Appearance.rounding.windowRounding * root.scale + color: pressed ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Active, Math.min(1, root.effectiveWindowOverlayOpacity + 0.30)) : + hovered ? ColorUtils.applyAlpha(Appearance.colors.colLayer2Hover, Math.min(1, root.effectiveWindowOverlayOpacity + 0.20)) : + ColorUtils.applyAlpha( + root.glassMode ? ColorUtils.mix(Appearance.colors.colLayer2, Appearance.colors.colLayer0, 0.38) : Appearance.colors.colLayer2, + root.effectiveWindowOverlayOpacity + ) + border.color: root.glassMode + ? ColorUtils.applyAlpha(Appearance.m3colors.m3outline, 0.62) + : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.7) + border.width: 1 + + Rectangle { + visible: root.glassMode + anchors.fill: parent + radius: parent.radius + color: "transparent" + gradient: Gradient { + GradientStop { position: 0.0; color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.24) } + GradientStop { position: 0.5; color: ColorUtils.applyAlpha("#FFFFFF", 0.0) } + GradientStop { position: 1.0; color: ColorUtils.applyAlpha("#000000", root.glassShineOpacity * 0.14) } + } + } + + Rectangle { + visible: root.glassMode + anchors.fill: parent + anchors.margins: 1 + radius: Math.max(parent.radius - 1, 0) + color: "transparent" + border.width: 1 + border.color: ColorUtils.applyAlpha("#FFFFFF", root.glassShineOpacity * 0.32) + } + + ColumnLayout { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: parent.right + spacing: Appearance.font.pixelSize.smaller * 0.5 + + Image { + id: windowIcon + visible: root.showIcons + property var iconSize: { + const renderedSize = Math.min(root.width, root.height); + return renderedSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / (root.monitorData?.scale ?? 1); + } + Layout.alignment: Qt.AlignHCenter + source: root.iconPath + width: iconSize + height: iconSize + sourceSize: Qt.size(Math.max(1, Math.round(iconSize)), Math.max(1, Math.round(iconSize))) + } + } + } + + Item { + id: previewMask + width: windowPreview.width + height: windowPreview.height + anchors.centerIn: parent + visible: false + layer.enabled: true + layer.smooth: true + Rectangle { + anchors.centerIn: parent + width: root.width + height: root.height + radius: Appearance.rounding.windowRounding * root.scale + } + } + + function refreshCapture() { + if (!GlobalStates.overviewOpen || livePreviewEnabled || !previewsEnabled) + return; + + root.previewCaptureEnabled = false; + previewResetTimer.restart(); + } + + Timer { + id: previewResetTimer + interval: Math.max(1, previewRecaptureDelayMs) + repeat: false + onTriggered: root.previewCaptureEnabled = true + } + + onRecaptureTokenChanged: { + if (recaptureToken > 0) + root.refreshCapture(); + } +} diff --git a/.config/quickshell/own/modules/overview/qmldir b/.config/quickshell/own/modules/overview/qmldir new file mode 100644 index 0000000..9b15b45 --- /dev/null +++ b/.config/quickshell/own/modules/overview/qmldir @@ -0,0 +1,3 @@ +Overview 1.0 Overview.qml +OverviewWidget 1.0 OverviewWidget.qml +OverviewWindow 1.0 OverviewWindow.qml diff --git a/.config/quickshell/shell.qml b/.config/quickshell/shell.qml deleted file mode 100644 index 5d37a3c..0000000 --- a/.config/quickshell/shell.qml +++ /dev/null @@ -1,14 +0,0 @@ -// shell.qml - entry point -// QuickShell scans this directory and auto-imports neighbours (Bar, Theme, Exec, ...). -// Do NOT create a qmldir file - QuickShell synthesises one automatically. -import Quickshell - -ShellRoot { - Variants { - model: Quickshell.screens - Bar { - required property var modelData - screen: modelData - } - } -} diff --git a/.config/swaylock/config b/.config/swaylock/config index 9f2e177..f3f5bad 100644 --- a/.config/swaylock/config +++ b/.config/swaylock/config @@ -1,6 +1,6 @@ show-failed-attempts clock -color=00000000 +color=55555500 indicator indicator-radius=100 indicator-thickness=1 @@ -29,5 +29,5 @@ grace-no-mouse grace-no-touch datestr=%a, %B %e timestr=%I:%M %p -fade-in=0.2 +fade-in=0.5 ignore-empty-password diff --git a/.config/wallust/templates/hyprland.conf b/.config/wallust/templates/hyprland.conf index 0ab36d6..d8d770d 100644 --- a/.config/wallust/templates/hyprland.conf +++ b/.config/wallust/templates/hyprland.conf @@ -1,19 +1,22 @@ -$wallpaper = {{wallpaper}} -$background = rgb({{background | strip}}) -$foreground = rgb({{foreground | strip}}) -$color0 = rgb({{color0 | strip}}) -$color1 = rgb({{color1 | strip}}) -$color2 = rgb({{color2 | strip}}) -$color3 = rgb({{color3 | strip}}) -$color4 = rgb({{color4 | strip}}) -$color5 = rgb({{color5 | strip}}) -$color6 = rgb({{color6 | strip}}) -$color7 = rgb({{color7 | strip}}) -$color8 = rgb({{color8 | strip}}) -$color9 = rgb({{color9 | strip}}) -$color10 = rgb({{color10 | strip}}) -$color11 = rgb({{color11 | strip}}) -$color12 = rgb({{color12 | strip}}) -$color13 = rgb({{color13 | strip}}) -$color14 = rgb({{color14 | strip}}) -$color15 = rgb({{color15 | strip}}) \ No newline at end of file +-- config/colors.lua +-- Centralised palette +-- Use these Lua globals throughout every other module. +background = "rgb({{background | strip}})" +foreground = "rgb({{foreground | strip}})" +color0 = "rgb({{color0 | strip}})" +color1 = "rgb({{color1 | strip}})" +color2 = "rgb({{color2 | strip}})" +color3 = "rgb({{color3 | strip}})" +color4 = "rgb({{color4 | strip}})" +color5 = "rgb({{color5 | strip}})" +color6 = "rgb({{color6 | strip}})" +color7 = "rgb({{color7 | strip}})" +color8 = "rgb({{color8 | strip}})" +color9 = "rgb({{color9 | strip}})" +color10 = "rgb({{color10 | strip}})" +color11 = "rgb({{color11 | strip}})" +color12 = "rgb({{color12 | strip}})" +color13 = "rgb({{color13 | strip}})" +color14 = "rgb({{color14 | strip}})" +color15 = "rgb({{color15 | strip}})" +wallpaper = "{{wallpaper}}" diff --git a/.config/wallust/templates/kitty.conf b/.config/wallust/templates/kitty.conf index 8946f8b..c1ad860 100644 --- a/.config/wallust/templates/kitty.conf +++ b/.config/wallust/templates/kitty.conf @@ -1,25 +1,25 @@ # uses https://codeberg.org/explosion-mental/wallust-templates/src/branch/master/kitty.conf background {{ background }} -foreground {{ foreground }} +foreground {{ foreground | lighten(0.1) }} -color0 {{ color0 }} -color1 {{ color1 }} -color2 {{ color2 }} -color3 {{ color3 }} -color4 {{ color4 }} -color5 {{ color5 }} -color6 {{ color6 }} -color7 {{ color7 }} -color8 {{ color8 }} -color9 {{ color9 }} -color10 {{ color10 }} -color11 {{ color11 }} -color12 {{ color12 }} -color13 {{ color13 }} -color14 {{ color14 }} -color15 {{ color15 }} +color0 {{ color0 }} +color1 {{ color1 | saturate(0.3) | lighten(0.1) }} +color2 {{ color2 | saturate(0.3) | lighten(0.1) }} +color3 {{ color3 | saturate(0.3) | lighten(0.1) }} +color4 {{ color4 | saturate(0.3) | lighten(0.1) }} +color5 {{ color5 | saturate(0.3) | lighten(0.1) }} +color6 {{ color6 | saturate(0.3) | lighten(0.1) }} +color7 {{ color7 | lighten(0.1) }} +color8 {{ color8 | lighten(0.1) }} +color9 {{ color9 | saturate(0.4) | lighten(0.15) }} +color10 {{ color10 | saturate(0.4) | lighten(0.15) }} +color11 {{ color11 | saturate(0.4) | lighten(0.15) }} +color12 {{ color12 | saturate(0.4) | lighten(0.15) }} +color13 {{ color13 | saturate(0.4) | lighten(0.15) }} +color14 {{ color14 | saturate(0.4) | lighten(0.15) }} +color15 {{ color15 | lighten(0.1) }} -mark1_foreground {{ color6 | saturate(0.2) }} -mark2_foreground {{ color7 | saturate(0.2) }} -mark3_foreground {{ color6 | saturate(0.2) }} +mark1_foreground {{ color6 | saturate(0.4) }} +mark2_foreground {{ color7 | saturate(0.3) }} +mark3_foreground {{ color6 | saturate(0.4) }} diff --git a/.config/wallust/templates/vscode b/.config/wallust/templates/vscode index eaa91d7..a82c17b 100644 --- a/.config/wallust/templates/vscode +++ b/.config/wallust/templates/vscode @@ -1,16 +1,16 @@ -{{ background }} -{{ color1 }} -{{ color2 }} -{{ color3 }} -{{ color4 }} -{{ color5 }} -{{ color6 }} -{{ color7 }} -{{ color8 }} -{{ color9 }} -{{ color10 }} -{{ color11 }} -{{ color12 }} -{{ color13 }} -{{ color14 }} -{{ color15 }} +{{ background | darken(0.1) }} +{{ color1 | saturate(0.4) | lighten(0.15) }} +{{ color2 | saturate(0.4) | lighten(0.15) }} +{{ color3 | saturate(0.4) | lighten(0.15) }} +{{ color4 | saturate(0.4) | lighten(0.15) }} +{{ color5 | saturate(0.4) | lighten(0.15) }} +{{ color6 | saturate(0.4) | lighten(0.15) }} +{{ color7 | lighten(0.15) }} +{{ color8 | lighten(0.1) }} +{{ color9 | saturate(0.5) | lighten(0.2) }} +{{ color10 | saturate(0.5) | lighten(0.2) }} +{{ color11 | saturate(0.5) | lighten(0.2) }} +{{ color12 | saturate(0.5) | lighten(0.2) }} +{{ color13 | saturate(0.5) | lighten(0.2) }} +{{ color14 | saturate(0.5) | lighten(0.2) }} +{{ color15 | lighten(0.15) }} diff --git a/.config/wallust/wallust.toml b/.config/wallust/wallust.toml index 41e1eb2..5a14f6c 100644 --- a/.config/wallust/wallust.toml +++ b/.config/wallust/wallust.toml @@ -15,7 +15,7 @@ backend = "fastresize" # What color space to use to produce and select the most prominent colors: # lab - labmixed - lch - lchmixed -color_space = "lab" +color_space = "lch" # Use the most prominent colors in a way that makes sense, a scheme color palette: # dark - dark16 - darkcomp - darkcomp16 @@ -23,7 +23,7 @@ color_space = "lab" # harddark - harddark16 - harddarkcomp - harddarkcomp16 # softdark - softdark16 - softdarkcomp - softdarkcomp16 # softlight - softlight16 - softlightcomp - softlightcomp16 -palette = "dark" +palette = "harddarkcomp" # Ensures a "readable contrast" (OPTIONAL, disabled by default) # Should only be enabled when you notice an unreadable contrast frequently happening @@ -38,12 +38,21 @@ check_contrast = true # Alpha value for templating, by default 100 (no other use whatsoever) #alpha = 50 - [templates] #waybar = { template = 'waybar.css', target = '~/.config/waybar/colors-wallust.css' } vicinae = { src = 'vicinae.toml', dst = '~/.local/share/vicinae/themes/wallust.toml'} # alacritty = { template = 'alacritty.toml', target = '~/.config/alacritty/colors.toml' } -hyprland = { template = 'hyprland.conf', target = '~/.config/hypr/config/colors.conf' } +hyprland = { template = 'hyprland.conf', target = '~/.config/hypr/config/colors.lua' } vscode = { template = 'vscode', target = '~/.cache/wal/colors' } -kitty = { template = 'kitty.conf', target = '~/.config/kitty/colors.conf'} -tofi = { template = 'tofi', target = '~/.config/tofi/config' } +kitty = { template = 'kitty.conf', target = '~/.config/kitty/colors.conf', max_depth = 1 } +tofi = { template = 'tofi', target = '~/.config/tofi/config'} +quickshell = { template = 'quickshell.qml', target = '~/.config/quickshell/own/Colors.qml'} + +[[entry]] +# create ~/cache/wal folder first +template="vscode" +target="~/.cache/wal/colors" + +[hooks] +notif = 'notify-send "New Scheme Generated!"' + diff --git a/.config/waypaper/config.ini b/.config/waypaper/config.ini index 43f62c3..2c707a9 100644 --- a/.config/waypaper/config.ini +++ b/.config/waypaper/config.ini @@ -2,9 +2,9 @@ language = en folder = /usr/share/wallpapers monitors = All -wallpaper = /usr/share/wallpapers/downloaded/pexels-blooddrainer-1655901.jpg +wallpaper = /usr/share/wallpapers/downloaded/pexels-jplenio-1102908.jpg show_path_in_tooltip = True -backend = swaybg +backend = awww fill = fill sort = name color = #ffffff @@ -12,16 +12,33 @@ subfolders = True all_subfolders = False show_hidden = False show_gifs_only = False -post_command = wallust run '$wallpaper' -s +post_command = wallust run '$wallpaper' -s & sleep 0.5 & killall -SIGUSR2 waybar number_of_columns = 3 -swww_transition_type = wipe -swww_transition_step = 90 -swww_transition_angle = 0 -swww_transition_duration = 2 -swww_transition_fps = 60 +awww_transition_type = wipe +awww_transition_step = 90 +awww_transition_angle = 30 +awww_transition_duration = 1 +awww_transition_fps = 60 mpvpaper_sound = False mpvpaper_options = use_xdg_state = False zen_mode = True stylesheet = /home/michaelb/.config/waypaper/style.css +keybindings = ~/.config/waypaper/keybindings.ini +wallpaperengine_folder = ~/.steam/root/steamapps/workshop/content/431960 +linux_wallpaperengine_clamp = none +linux_wallpaperengine_volume = 15 +linux_wallpaperengine_silent = False +linux_wallpaperengine_noautomute = False +linux_wallpaperengine_no_audio_processing = False +linux_wallpaperengine_fps = 30 +linux_wallpaperengine_disable_particles = True +linux_wallpaperengine_disable_mouse = False +linux_wallpaperengine_disable_parallax = False +linux_wallpaperengine_no_fullscreen_pause = False +swww_transition_type = any +swww_transition_step = 63 +swww_transition_angle = 0 +swww_transition_duration = 2 +swww_transition_fps = 60