Compare commits

..
3 Commits
Author SHA1 Message Date
mischbeck f3b2fb1c18 add lmstudio path 2026-08-19 20:48:56 +02:00
mischbeck 67d50fb504 adds overview, removes old stuff 2026-08-19 20:47:29 +02:00
mischbeck 6a9ae98c26 some fixes in hyprland 2026-08-19 19:56:58 +02:00
83 changed files with 4053 additions and 1662 deletions
+7
View File
@@ -17,3 +17,10 @@ alias dotfiles='/usr/bin/git --git-dir="$HOME/.dotfiles/" --work-tree="$HOME"'
alias vi=nvim alias vi=nvim
alias svi='sudo nvim' alias svi='sudo nvim'
. "$HOME/.local/bin/env"
# Added by LM Studio CLI (lms)
export PATH="$PATH:/home/michaelb/.lmstudio/bin"
# End of LM Studio CLI section
-15
View File
@@ -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%
}
-35
View File
@@ -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
+41
View File
@@ -0,0 +1,41 @@
-- config/autostart.lua
-- Replaces exec-once lines. In Lua config use hl.exec_once().
local function once(cmd)
hl.exec_cmd(cmd)
end
hl.on("hyprland.start", function()
hl.exec_cmd("systemctl --user start hyprland-session.target")
-- ── Bar (QuickShell, replaces waybar) ────────────────────────
hl.exec_cmd("quickshell -c own")
hl.exec_cmd("awww-daemon")
-- ── System tray / applets ────────────────────────────────────
hl.exec_cmd("blueman-applet")
once("fcitx5 -d")
once("mako")
once("nm-applet --indicator")
once("/usr/lib/polkit-kde-authentication-agent-1")
-- ── Wallpaper ─────────────────────────────────────────────────
once("waypaper --restore")
-- ── DBus / env plumbing ───────────────────────────────────────
once("systemctl --user import-environment")
once("dbus-update-activation-environment --systemd")
-- ── Idle daemon ───────────────────────────────────────────────
once(idlehandler)
-- ── Other tools ───────────────────────────────────────────────
once("vicinae server")
once("/usr/bin/pypr --debug /tmp/pypr.log")
end)
hl.on("config.reloaded", function()
hl.exec_cmd("killall quickshell ")
hl.exec_cmd("sleep 0.2; quickshell -c own")
end)
hl.on("hyprland.shutdown", function()
os.execute("systemctl --user stop hyprland-session.target && sleep 0.1")
-- uses a blocking exec function and sleeps a bit to give things time to close
-- you might also want to kill troublesome/crashing non-systemd background services here:
-- os.execute("pkill wallpaperthing; systemctl --user stop hyprland-session.target && sleep 0.1")
end)
-19
View File
@@ -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)
+22
View File
@@ -0,0 +1,22 @@
-- config/colors.lua
-- Centralised palette
-- Use these Lua globals throughout every other module.
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"
-33
View File
@@ -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
}
}
-12
View File
@@ -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 -
-21
View File
@@ -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
+22
View File
@@ -0,0 +1,22 @@
-- config/environment.lua
-- Wayland / cursor env vars (mirrors environment.conf)
-- NOTE: In 0.55 Lua config these are set via hl.config({ env = ... })
-- or directly sourced into the Lua environment before hl.exec_once.
hl.config({
env = {
HYPRCURSOR_THEME = "vimix-kanagawa-hyprcursors-lotus",
HYPRCURSOR_SIZE = "34",
XCURSOR_THEME = "vimix-kanagawa-hyprcursors-lotus",
XCURSOR_SIZE = "34",
QT_CURSOR_SIZE = "34",
ELECTRON_OZONE_PLATFORM_HINT = "wayland",
QT_QPA_PLATFORM = "wayland",
SDL_VIDEODRIVER = "wayland",
CLUTTER_BACKEND = "wayland",
XDG_SESSION_TYPE = "wayland",
MOZ_ENABLE_WAYLAND = "1",
NIXOS_OZONE_WL = "1",
WLR_DRM_NO_DIRECT_SCANOUT = "1",
},
})
-12
View File
@@ -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
}
+16
View File
@@ -0,0 +1,16 @@
-- config/input.lua
hl.config({
input = {
kb_layout = "de",
follow_mouse = 2,
float_switch_override_focus = 2,
numlock_by_default = true,
},
})
-- Per-device config for the G502
hl.device({
name = "logitech-gaming-mouse-g502",
sensitivity = 0.9,
})
-175
View File
@@ -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
+123
View File
@@ -0,0 +1,123 @@
-- config/keybinds.lua
-- All keybinds, translated from keybinds.conf to the 0.55 Lua API.
-- Modifier shorthand used: "SUPER" = $mainMod.
local S = "SUPER"
-- ── Applications ─────────────────────────────────────────────
hl.bind(S .. " + RETURN", hl.dsp.exec_cmd(terminal))
hl.bind(S .. " + E", hl.dsp.exec_cmd(filemanager))
hl.bind(S .. " + Q", hl.dsp.window.close())
hl.bind(S .. " + SHIFT + M", hl.dsp.exec_cmd('loginctl terminate-user ""'))
hl.bind(S .. " + V", hl.dsp.window.float({ action = "toggle" }))
hl.bind(S .. " + SPACE", hl.dsp.exec_cmd(applauncher))
hl.bind(S .. " + F", hl.dsp.window.fullscreen())
hl.bind(S .. " + Y", hl.dsp.window.pin())
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", 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"
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd(vol .. " up"), { repeating = true })
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd(vol .. " down"), { repeating = true })
hl.bind("XF86AudioMute", hl.dsp.exec_cmd(vol .. " toggle"), { repeating = true })
-- ── Playback ─────────────────────────────────────────────────
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"))
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"))
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"))
-- ── Brightness ───────────────────────────────────────────────
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl s +5%"), { repeating = true })
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 5%-"), { repeating = true })
-- ── Utilities ────────────────────────────────────────────────
hl.bind(S .. " + SHIFT + P", hl.dsp.exec_cmd("gnome-calculator"))
hl.bind(S .. " + L", hl.dsp.exec_cmd("~/.config/swaylock/lockscript.sh"))
hl.bind(S .. " + P", hl.dsp.exec_cmd("hyprpicker -a"))
hl.bind(S .. " + C", hl.dsp.exec_cmd("vicinae vicinae://extensions/vicinae/clipboard/history"))
-- ── Screenshots ──────────────────────────────────────────────
hl.bind(S .. " + PRINT", hl.dsp.exec_cmd("hyprshot -m window"))
hl.bind("PRINT", hl.dsp.exec_cmd("hyprshot -m output"))
hl.bind("SHIFT + PRINT", hl.dsp.exec_cmd("hyprshot -m region"))
hl.bind(S .. " + S", hl.dsp.exec_cmd('~/.config/hypr/scripts/record-or-screenshot.sh -d "' .. dmenu .. '"'))
-- ── Window focus ─────────────────────────────────────────────
hl.bind(S .. " + left", hl.dsp.focus({ direction = "l" }))
hl.bind(S .. " + right", hl.dsp.focus({ direction = "r" }))
hl.bind(S .. " + up", hl.dsp.focus({ direction = "u" }))
hl.bind(S .. " + down", hl.dsp.focus({ direction = "d" }))
-- ── Window move ──────────────────────────────────────────────
hl.bind(S .. " + SHIFT + left", hl.dsp.window.move({ direction = "l" }))
hl.bind(S .. " + SHIFT + right", hl.dsp.window.move({ direction = "r" }))
hl.bind(S .. " + SHIFT + up", hl.dsp.window.move({ direction = "u" }))
hl.bind(S .. " + SHIFT + down", hl.dsp.window.move({ direction = "d" }))
-- Mouse drag / resize
hl.bind(S .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
hl.bind(S .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
-- ── Workspace Move ───────────────────────────────────────────
hl.bind(S .. " + SHIFT + TAB", hl.dsp.workspace.move({ monitor = "+1" }))
-- ── Resize submap ────────────────────────────────────────────
hl.bind(S .. " + SHIFT + R", hl.dsp.submap("resize"))
hl.define_submap("resize", function()
-- Set repeating binds for resizing the active window.
hl.bind("right", hl.dsp.window.resize({ x = 10, y = 0, relative = true }), { repeating = true })
hl.bind("left", hl.dsp.window.resize({ x = -10, y = 0, relative = true }), { repeating = true })
hl.bind("up", hl.dsp.window.resize({ x = 0, y = 10, relative = true }), { repeating = true })
hl.bind("down", hl.dsp.window.resize({ x = 0, y = -10, relative = true }), { repeating = true })
-- Use `reset` to go back to the global submap
hl.bind("escape", hl.dsp.submap("reset"))
end)
-- ── Workspace switching ───────────────────────────────────────
for i = 1, 10 do
local key = tostring(i % 10)
local ws = tostring(i)
hl.bind(S .. " + " .. key, hl.dsp.focus({ workspace = ws }))
hl.bind(S .. " + CTRL + " .. key, hl.dsp.window.move({ workspace = ws, follow = true }))
end
hl.bind(S .. " + PERIOD", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(S .. " + COMMA", hl.dsp.focus({ workspace = "e-1" }))
hl.bind(S .. " + ALT_L + right", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(S .. " + ALT_L + left", hl.dsp.focus({ workspace = "e-1" }))
hl.bind(S .. " + CTRL + right", hl.dsp.window.move({ workspace = "+1", follow = true }))
hl.bind(S .. " + CTRL + left", hl.dsp.window.move({ workspace = "-1", follow = true }))
hl.bind(S .. " + slash", hl.dsp.focus({ workspace = "previous" }))
hl.bind(S .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }), { mouse = true })
hl.bind(S .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }), { mouse = true })
-- Special / scratchpad
hl.bind(S .. " + minus", hl.dsp.window.move({ workspace = "special" }))
hl.bind(S .. " + equal", hl.dsp.workspace.toggle_special())
hl.bind(S .. " + F1", hl.dsp.workspace.toggle_special({ name = "scratchpad" }))
hl.bind(S .. " + SHIFT + ALT_L + F1", hl.dsp.window.move({ workspace = "special:scratchpad", follow = false }))
-- Quickshell overview
-- hl.bind(S .. " + TAB", hl.dsp.exec_cmd("qs ipc -c overview call overview toggle"))
-- ── Monitors ─────────────────────────────────────────────────
-- hl.bind(S .. " + F8", function() monitors.solo() end)
-- hl.bind(S .. " + F9", function() monitors.dual() end)
-- hl.bind(S .. " + F10", function() monitors.triple() end)
-- QuickShell
hl.bind(S .. " + W", hl.dsp.exec_cmd("echo 1 > /tmp/qs-wallpaper-ipc"))
-21
View File
@@ -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
+41
View File
@@ -0,0 +1,41 @@
-- config/monitors.lua
--
-- Monitor layout switching via shell script (wlr-randr).
-- hl.monitor({ disabled=false }) cannot re-enable monitors at runtime,
-- so all switching is delegated to monitor-toggle.sh.
--[[ local layouts = {}
local script = os.getenv("HOME") .. "/.config/hypr/scripts/monitor-toggle.sh"
function layouts.solo() hl.exec_cmd(script .. " laptop") end
function layouts.dual() hl.exec_cmd(script .. " dual") end
function layouts.triple() hl.exec_cmd(script .. " triple") end
-- Apply default at startup
layouts.dual()
-- Expose for keybinds.lua
return layouts
]]
hl.monitor({
output = "eDP-2",
mode = "1920x1080@144",
position = "0x0",
scale = 1,
})
hl.monitor({
output = "HDMI-A-1",
mode = "1920x1080@60",
position = "auto-left",
scale = 1,
})
-- hl.monitor({
-- output = "DP-2",
-- mode = "1920x1080@60",
-- position = "auto-right",
-- scale = 1.2,
-- transform = 1
--})
-12
View File
@@ -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
}
}
+1
View File
@@ -0,0 +1 @@
hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
-89
View File
@@ -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
}
+112
View File
@@ -0,0 +1,112 @@
-- config/variables.lua
-- Mirrors: variables.conf + decorations.conf + animations.conf
-- ── Application shortcuts ─────────────────────────────────────
terminal = "kitty"
filemanager = "nemo"
applauncher = "vicinae toggle"
dmenu = "vicinae dmenu --placeholder"
idlehandler = "hypridle" -- switched to hypridle; adjust if needed
-- ── General ───────────────────────────────────────────────────
hl.config({
general = {
gaps_in = 2,
gaps_out = 1,
border_size = 1,
["col.active_border"] = color4,
["col.inactive_border"] = color2,
layout = "dwindle",
resize_on_border = true,
extend_border_grab_area = 10,
snap = { enabled = true },
},
group = {
["col.border_active"] = color9,
["col.border_inactive"] = color2,
["col.border_locked_active"] = color3,
["col.border_locked_inactive"] = color0,
groupbar = {
font_size = 12,
height = 16,
indicator_height = 16,
rounding = 3,
font_family = "Roboto Sans",
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,
},
},
-- ── Decorations ───────────────────────────────────────────
decoration = {
active_opacity = 1.0,
inactive_opacity = 0.7,
rounding = 5,
dim_inactive = true,
dim_strength = 0.1,
blur = {
size = 30,
passes = 2,
xray = true,
noise = 0.01,
},
shadow = {
enabled = true,
range = 10,
},
},
-- ── Misc ──────────────────────────────────────────────────
misc = {
font_family = "Roboto Sans",
splash_font_family = "Roboto Sans",
disable_hyprland_logo = true,
["col.splash"] = color2,
background_color = background,
enable_swallow = false,
swallow_regex = "^(cachy-browser|firefox|nautilus|nemo|thunar|btrfs-assistant.)$",
focus_on_activate = true,
vrr = 0,
session_lock_xray = true,
middle_click_paste = false,
},
render = {
direct_scanout = false,
},
dwindle = {
special_scale_factor = 0.8,
preserve_split = true,
},
master = {
new_status = "master",
special_scale_factor = 0.8,
},
cursor = {
no_hardware_cursors = true,
},
binds = {
allow_workspace_cycles = true,
workspace_back_and_forth = true,
workspace_center_on = 1,
movefocus_cycles_fullscreen = true,
window_direction_monitor_fallback = true,
},
})
hl.curve("smooth", { type = "bezier", points = { { 0, 0.05 }, { 0.44, 0.99 } } })
hl.animation({ leaf = "workspaces", enabled = true, speed = 3, bezier = "smooth", style = "slidefade" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 3, bezier = "smooth", style = "slidefade" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "default", style = "slidefade" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 3, bezier = "default", style = "slidefade" })
hl.animation({ leaf = "border", enabled = true, speed = 5, bezier = "default" })
-315
View File
@@ -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
}
-77
View File
@@ -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)$
+125
View File
@@ -0,0 +1,125 @@
-- config/windowrules.lua
-- Translated from config_windowrules.conf to hl.window_rule() / hl.layer_rule().
-- ── Float rules ───────────────────────────────────────────────
local float_classes = {
"org.pulseaudio.pavucontrol",
"blueman-manager",
"zenity",
"pomodorolm",
"CachyOSHello",
"Rofi",
"Picture in picture",
"Save File",
"Open File",
"Steam - Self Updater",
"^Extension:.*",
}
for _, cls in ipairs(float_classes) do
hl.window_rule({ match = { class = cls }, float = true })
end
hl.window_rule({ match = { class = "LibreWolf", title = "Picture-in-Picture" }, float = true })
hl.window_rule({
match = { class = "^(xdg-desktop-portal-gtk|xdg-desktop-portal-kde|xdg-desktop-portal-hyprland).*" },
float = true,
})
hl.window_rule({
match = {
class = "^(polkit-gnome-authentication-agent-1|hyprpolkitagent|org.kde.polkit-kde-authentication-agent-1).*",
},
float = true,
})
hl.window_rule({ match = { class = "Zotero", title = "Progress" }, float = true })
hl.window_rule({ match = { class = "Zotero", title = "Zotero Settings" }, float = true })
hl.window_rule({ match = { class = "Zotero", title = "Citation Dialog" }, float = true, center = true, no_anim = true })
hl.window_rule({ match = { class = "waypaper" }, float = true, center = true })
hl.window_rule({ match = { title = "calcure" }, float = true, center = true })
hl.window_rule({ match = { class = "org.gnome.Calculator" }, float = true, center = true })
hl.window_rule({
match = { class = "org.gnome.Calendar", title = "Calendar" },
float = true,
center = true,
animation = "popin",
opacity = "0.8",
})
hl.window_rule({ match = { class = "steam", title = "Freundesliste" }, float = true, size = { 400, 600 } })
-- ── PiP ──────────────────────────────────────────────────────
hl.window_rule({
match = { title = "Picture-in-Picture" },
float = true,
size = { 960, 540 },
move = { "(monitor_w*0.25)", "0" },
})
-- ── Media / misc float + position ────────────────────────────
hl.window_rule({
match = { title = "^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp|nwg-look|nwg-displays)$" },
float = true,
})
hl.window_rule({
match = { title = "^(imv|mpv|danmufloat|termfloat|nemo|ncmpcpp)$" },
move = { "(monitor_w*0.25)", "0" },
size = { 960, 540 },
})
hl.window_rule({ match = { title = "danmufloat" }, pin = true })
hl.window_rule({ match = { title = "^(danmufloat|termfloat)$" }, rounding = 5 })
-- ── Opacity ───────────────────────────────────────────────────
hl.window_rule({ match = { class = "^(thunar|nemo|dolphin)$" }, opacity = "0.92" })
hl.window_rule({ match = { class = "^(discord|armcord|webcord)$" }, opacity = "0.96" })
hl.window_rule({ match = { title = "^(QQ|Telegram)$" }, opacity = "0.95" })
hl.window_rule({ match = { title = "NetEase Cloud Music Gtk4" }, opacity = "0.95" })
hl.window_rule({ match = { class = "kitty" }, opacity = "1.0" })
hl.window_rule({ match = { class = "^(libreoffice.*)$" }, opacity = "1.0 override 1.0 override 1.0" })
hl.window_rule({ match = { title = "^(.+nvim$)" }, opacity = "0.9" })
hl.window_rule({ match = { class = "^code$" }, opacity = "0.93" })
-- ── Blur / dim exceptions ─────────────────────────────────────
hl.window_rule({ match = { class = "org.mozilla.firefox" }, no_blur = true })
hl.window_rule({ match = { class = "zen" }, no_dim = true })
-- ── Animations ───────────────────────────────────────────────
hl.window_rule({ match = { class = "^(kitty|Alacritty)$" }, animation = "slide right" })
-- ── Floating decoration overrides ────────────────────────────
hl.window_rule({
match = { float = true, workspace = "w[fv1-10]" },
border_size = 2,
border_color = color4,
rounding = 8,
})
hl.window_rule({
match = { float = false, workspace = "f[1-10]" },
border_size = 3,
rounding = 4,
})
-- ── Pomodorolm size ──────────────────────────────────────────
hl.window_rule({ match = { class = "pomodorolm" }, size = { 260, 340 } })
-- ── Layer rules ───────────────────────────────────────────────
hl.layer_rule({ match = { namespace = "logout_dialog" }, animation = "slide top" })
-- QuickShell bar (replaces waybar layer rule)
hl.layer_rule({ match = { namespace = "quickshell" }, animation = "slide down" })
hl.layer_rule({ match = { namespace = "wallpaper" }, animation = "fade 50%" })
-- vicinae
hl.layer_rule({
match = { namespace = "vicinae" },
blur = true,
animation = "popin",
dim_around = true,
ignore_alpha = 1,
})
-- screenshare fix
hl.window_rule({
match = { class = "^(xwaylandvideobridge)$" },
opacity = "0.0 override 0.0",
no_anim = true,
no_initial_focus = true,
no_focus = true,
no_blur = true,
max_size = { 1, 1 },
})
-14
View File
@@ -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
+23
View File
@@ -0,0 +1,23 @@
-- config/windowrules.lua
--hl.workspace_rule({ workspace = "r[0-6]", monitor = "eDP-2" })
--hl.workspace_rule({ workspace = "r[7-8]", monitor = "HDMI-A-1" })
--hl.workspace_rule({ workspace = "9", layout = "scrolling", monitor = "DP-2" })
for i = 7, 9 do
hl.workspace_rule({ workspace = tostring(i), monitor = "eDP-2", default = true })
end
for i = 0, 6 do
hl.workspace_rule({ workspace = tostring(i), monitor = "HDMI-A-1", default = true })
end
-- Workspace 9: Scolling layout
hl.workspace_rule({
workspace = "9",
monitor = "DP-2",
layout = "scrolling",
default = true,
layout_opts = {
direction = "down",
column_width = 0.7,
},
})
-29
View File
@@ -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.
# }
-24
View File
@@ -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
-6
View File
@@ -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
-4
View File
@@ -1,4 +0,0 @@
[pyprland]
plugins = [
"shift_monitors",
]
-33
View File
@@ -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
+27 -218
View File
@@ -2,256 +2,65 @@
set -euo pipefail set -euo pipefail
PRIMARY="eDP-2" PRIMARY="eDP-2"
RIGHT_EXTERNAL_NAME="DP-2" DP_OUT="DP-2"
MIDDLE_EXTERNAL_NAME="HDMI-A-1" HDMI_OUT="HDMI-A-1"
# Exact enable lines (from your config) # eDP-2 at scale 1.5 → logical width 1280px → externals start at x=1280
EXT1='desc:Samsung Electric Company S24F350 H4ZR302705, highres@highrr, auto-right, 1' # DP-2 at 1920px wide → HDMI starts at x=1280+1920=3200
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
notify() { notify() {
if command -v notify-send >/dev/null 2>&1; then notify-send -a "monitor-toggle" "$1" "${2:-}" 2>/dev/null || true
notify-send -a "Hyprland" "$1" "${2:-}"
else
[ -n "${2:-}" ] && printf '%s: %s\n' "$1" "$2" >&2 || printf '%s\n' "$1" >&2
fi
} }
hypr() { active_monitors() {
local out hyprctl -j monitors | jq -r '.[] | select(.disabled != true) | .name'
if ! out="$(hyprctl "$@" 2>&1)"; then
notify "hyprctl failed" "$out"
exit 1
fi
printf '%s' "$out"
} }
hypr_batch() { has_external() {
local out active_monitors | grep -qvx "$PRIMARY"
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 - <<PY 2>/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 dont 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
} }
profile_laptop() { profile_laptop() {
# Minimal: disable only currently enabled externals wlr-randr --output "$DP_OUT" --off 2>/dev/null || true
local batch="" wlr-randr --output "$HDMI_OUT" --off 2>/dev/null || true
batch+="$(batch_disable_enabled_externals)" notify "Laptop only"
apply_profile "Laptop-only" "$batch"
} }
profile_dual() { profile_dual() {
# Disable enabled externals, then enable preferred main external rule wlr-randr --output "$PRIMARY" --on --mode 1920x1080@144 --pos 0,0 --scale 1
local batch="" wlr-randr --output "$HDMI_OUT" --on --mode 1920x1080@60 --pos 1920,0 --scale 1
batch+="$(batch_disable_enabled_externals)" wlr-randr --output "$DP_OUT" --off 2>/dev/null || true
batch+="$(enable_rule "$DUAL_MAIN")" notify "Dual"
apply_profile "Dual" "$batch"
} }
profile_triple() { profile_triple() {
# Disable enabled externals, then enable both rules wlr-randr --output "$PRIMARY" --on --mode 1920x1080@144 --pos 0,0 --scale 1
# Order: EXT2 first so it tends to appear “middle” with auto-right wlr-randr --output "$HDMI_OUT" --on --mode 1920x1080@60 --pos 1920,0 --scale 1
local batch="" wlr-randr --output "$DP_OUT" --on --mode 1920x1080@60 --pos 3840,0 --scale 1 --transform 90
batch+="$(batch_disable_enabled_externals)" notify "Triple"
batch+="$(enable_rule "$EXT2")"
batch+="$(enable_rule "$EXT1")"
apply_profile "Triple" "$batch"
dock_settle
enforce_triple_order
} }
toggle_externals() { toggle() {
if external_enabled; then if has_external; then
profile_laptop profile_laptop
else else
if [ -n "$EXT1" ] && [ -n "$EXT2" ]; then
profile_triple profile_triple
else
profile_dual
fi fi
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() { status() {
echo "Enabled monitors:" echo "Active monitors:"
enabled_monitor_names | sed 's/^/ - /' active_monitors | sed 's/^/ - /'
echo
echo "Focused: $(focused_monitor_name || true)"
} }
case "${1:-}" in case "${1:-}" in
laptop) profile_laptop ;; laptop) profile_laptop ;;
dual) profile_dual ;; dual) profile_dual ;;
triple) profile_triple ;; triple) profile_triple ;;
toggle-externals) toggle_externals ;; toggle) toggle ;;
dpms-toggle-focused) dpms_toggle_focused ;;
kick-externals) dock_settle; kick_externals; notify "Kicked externals (DPMS)" "" ;;
status) status ;; status) status ;;
*) *)
cat <<EOF echo "Usage: $(basename "$0") laptop|dual|triple|toggle|status"
Usage: exit 1
$(basename "$0") laptop # eDP-2 only (disable enabled externals)
$(basename "$0") dual # eDP-2 + preferred external
$(basename "$0") triple # eDP-2 + both externals
$(basename "$0") toggle-externals # laptop-only <-> (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
;; ;;
esac esac
+1 -1
View File
@@ -1,4 +1,4 @@
#!/bin/env bash #!/usr/bin/env bash
# CLI options # CLI options
DMENU_CMD="" DMENU_CMD=""
@@ -31,7 +31,7 @@ else
elif [[ $line =~ ^[[:space:]]*description:[[:space:]]*(.*)$ ]]; then elif [[ $line =~ ^[[:space:]]*description:[[:space:]]*(.*)$ ]]; then
DESC="${BASH_REMATCH[1]}" DESC="${BASH_REMATCH[1]}"
elif [[ $line =~ ^[[:space:]]*transform:[[:space:]]*([0-9]+)$ ]]; then elif [[ $line =~ ^[[:space:]]*transform:[[:space:]]*([0-9]+)$ ]]; then
TFORM="${BASHREMATCH[1]}" TFORM="${BASH_REMATCH[1]}"
elif [[ $line =~ ^[[:space:]]*focused:[[:space:]]*yes$ ]]; then elif [[ $line =~ ^[[:space:]]*focused:[[:space:]]*yes$ ]]; then
FOC=1 FOC=1
elif [[ -z $line ]]; then elif [[ -z $line ]]; then
View File
+1 -1
View File
@@ -1,5 +1,5 @@
include colors.conf include colors.conf
background_opacity .3 background_opacity .5
background_blur 16 background_blur 16
-60
View File
@@ -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 {}
}
}
-23
View File
@@ -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()
}
}
}
-39
View File
@@ -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)
}
}
-38
View File
@@ -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
}
@@ -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
}
}
@@ -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)
}
}
}
}
}
}
}
@@ -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
}
}
@@ -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"))
}
}
}
}
}
@@ -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<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1]
readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1]
readonly property list<real> 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<real> 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<real> 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<real> 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
}
}
+194
View File
@@ -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;
}
}
@@ -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);
}
}
@@ -0,0 +1 @@
singleton ColorUtils 1.0 ColorUtils.qml
+7
View File
@@ -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
@@ -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
}
@@ -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"
}
@@ -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
}
}
@@ -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
}
}
}
@@ -0,0 +1,4 @@
StyledText 1.0 StyledText.qml
StyledRectangularShadow 1.0 StyledRectangularShadow.qml
StyledToolTip 1.0 StyledToolTip.qml
StyledToolTipContent 1.0 StyledToolTipContent.qml
@@ -25,7 +25,7 @@ Pill {
text: root.icon + " " + root.capacity + "%" text: root.icon + " " + root.capacity + "%"
font.family: Theme.fontSans font.family: Theme.fontSans
font.pixelSize: Theme.fontSize font.pixelSize: Theme.fontSize
color: root.critical ? Theme.color1 : Theme.foreground color: root.critical ? Theme.color1 : Theme.pillText
} }
Process { Process {
@@ -30,7 +30,7 @@ Pill {
return "ᛒ off" return "ᛒ off"
} }
font { family: Theme.fontSans; pixelSize: Theme.fontSize } font { family: Theme.fontSans; pixelSize: Theme.fontSize }
color: Theme.foreground color: Theme.pillText
} }
// Poll bluetoothctl show + info every 5 s // Poll bluetoothctl show + info every 5 s
@@ -9,13 +9,15 @@ Rectangle {
property var bars: Array(12).fill(0) property var bars: Array(12).fill(0)
property bool silence: bars.every(v => v === 0) property bool silence: bars.every(v => v === 0)
property bool allBlank: bars.every(v => v < 28.5)
readonly property var blocks: [" ","▁","▂","▃","▄","▅","▆","▇","█"] readonly property var blocks: [" ","▁","▂","▃","▄","▅","▆","▇","█"]
visible: !allBlank
implicitWidth: cavaRow.implicitWidth + Theme.pillPadH * 2 implicitWidth: cavaRow.implicitWidth + Theme.pillPadH * 2
implicitHeight: Theme.barHeight implicitHeight: Theme.barHeight
radius: Theme.radius radius: Theme.radius
color: cavaHover.containsMouse ? Theme.pillHover : Theme.pill color: cavaHover.containsMouse ? Theme.pill : Theme.pill
Behavior on color { ColorAnimation { duration: 150 } } Behavior on color { ColorAnimation { duration: 150 } }
RowLayout { RowLayout {
@@ -32,7 +34,7 @@ Rectangle {
: root.blocks[Math.min(Math.floor(root.bars[index] / 28.5), 8)] : root.blocks[Math.min(Math.floor(root.bars[index] / 28.5), 8)]
font.family: Theme.fontMono font.family: Theme.fontMono
font.pixelSize: Theme.fontSize + 1 font.pixelSize: Theme.fontSize + 1
color: Theme.foreground color: Theme.pillText
} }
} }
} }
@@ -14,7 +14,7 @@ Pill {
text: " " + Qt.formatDateTime(clock.now, "HH:mm") + text: " " + Qt.formatDateTime(clock.now, "HH:mm") +
" " + Qt.formatDateTime(clock.now, "d MMM") " " + Qt.formatDateTime(clock.now, "d MMM")
font { family: Theme.fontSans; pixelSize: Theme.fontSize } font { family: Theme.fontSans; pixelSize: Theme.fontSize }
color: Theme.foreground color: Theme.pillText
} }
// Update every 10 s (no need for per-second ticks) // Update every 10 s (no need for per-second ticks)
@@ -19,7 +19,7 @@ Pill {
text: root.freqGhz.toFixed(1) + "GHz | " + root.usagePct + "%" text: root.freqGhz.toFixed(1) + "GHz | " + root.usagePct + "%"
font.family: Theme.fontSans font.family: Theme.fontSans
font.pixelSize: Theme.fontSize font.pixelSize: Theme.fontSize
color: Theme.foreground color: Theme.pillText
} }
// /proc/stat - first line is total CPU // /proc/stat - first line is total CPU
@@ -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
}
}
@@ -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() }
}
}
}
@@ -21,7 +21,7 @@ Pill {
var p = activePlayer var p = activePlayer
var info = "" var info = ""
if (p.trackArtists && p.trackTitle) if (p.trackArtists && p.trackTitle)
info = p.trackArtists.join(", ") + " - " + p.trackTitle info = Array.from(p.trackArtists).join(", ") + " - " + p.trackTitle
else if (p.trackTitle) else if (p.trackTitle)
info = p.trackTitle info = p.trackTitle
if (info.length > 45) info = info.substring(0, 45) + "..." if (info.length > 45) info = info.substring(0, 45) + "..."
@@ -35,7 +35,7 @@ Pill {
Text { Text {
text: root.trackText text: root.trackText
font { family: Theme.fontSans; pixelSize: Theme.fontSize } font { family: Theme.fontSans; pixelSize: Theme.fontSize }
color: Theme.foreground color: Theme.pillText
elide: Text.ElideRight elide: Text.ElideRight
maximumLineCount: 1 maximumLineCount: 1
} }
@@ -51,4 +51,8 @@ Pill {
if (w.angleDelta.y > 0) root.activePlayer.next() if (w.angleDelta.y > 0) root.activePlayer.next()
else root.activePlayer.previous() else root.activePlayer.previous()
} }
onHoveredChanged: {
if (hovered) GlobalStates.popups.open("media", mapToItem(null, width / 2, 0).x)
}
} }
@@ -16,7 +16,7 @@ Pill {
text: " " + root.usedGb.toFixed(2) + " / " + root.totalGb.toFixed(0) + " GB" text: " " + root.usedGb.toFixed(2) + " / " + root.totalGb.toFixed(0) + " GB"
font.family: Theme.fontSans font.family: Theme.fontSans
font.pixelSize: Theme.fontSize font.pixelSize: Theme.fontSize
color: Theme.foreground color: Theme.pillText
} }
Process { Process {
@@ -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
}
}
@@ -27,7 +27,7 @@ Pill {
text: (root.icons[root.profile] ?? "⚡") text: (root.icons[root.profile] ?? "⚡")
font.family: Theme.fontSans font.family: Theme.fontSans
font.pixelSize: Theme.fontSize font.pixelSize: Theme.fontSize
color: Theme.foreground color: Theme.pillText
} }
// Read current profile periodically // Read current profile periodically
@@ -2,10 +2,12 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
import Quickshell
import ".." import ".."
Rectangle { Rectangle {
id: root id: root
required property var parentWindow
color: "transparent" color: "transparent"
radius: Theme.radius radius: Theme.radius
@@ -21,12 +23,13 @@ Rectangle {
model: SystemTray.items model: SystemTray.items
Rectangle { Rectangle {
id: trayRect
required property SystemTrayItem modelData required property SystemTrayItem modelData
width: 22; height: 22 width: Theme.barHeight-Theme.barPadding*2; height: Theme.barHeight-Theme.barPadding*2
radius: Theme.radius radius: Theme.radius
color: trayHover.containsMouse color: trayHover.containsMouse
? Theme.pillHover ? Theme.pillHover
: Qt.rgba(0.976, 0.945, 0.851, 0.15) : Theme.pill
Behavior on color { ColorAnimation { duration: 150 } } Behavior on color { ColorAnimation { duration: 150 } }
@@ -37,6 +40,18 @@ Rectangle {
smooth: true 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 { MouseArea {
id: trayHover id: trayHover
anchors.fill: parent anchors.fill: parent
@@ -45,8 +60,8 @@ Rectangle {
onClicked: (m) => { onClicked: (m) => {
if (m.button === Qt.LeftButton) if (m.button === Qt.LeftButton)
modelData.activate() modelData.activate()
else else if (modelData.hasMenu)
modelData.contextMenu(mapToGlobal(mouseX, mouseY)) menuAnchor.open()
} }
} }
@@ -18,7 +18,7 @@ Pill {
text: root.icon + " " + root.tempC + "°C" text: root.icon + " " + root.tempC + "°C"
font.family: Theme.fontSans font.family: Theme.fontSans
font.pixelSize: Theme.fontSize 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 // Read first available CPU package sensor - works regardless of hwmon number
@@ -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
}
}
}
}
}
}
@@ -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
}
}
@@ -10,7 +10,7 @@ Pill {
Text { Text {
text: weatherText text: weatherText
font { family: Theme.fontSans; pixelSize: Theme.fontSize } font { family: Theme.fontSans; pixelSize: Theme.fontSize }
color: Theme.foreground color: Theme.pillText
} }
// Fetch via curl // Fetch via curl
@@ -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
}
}
@@ -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") + "\" })"
)
}
}
}
}
}
@@ -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
```
@@ -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;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -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();
}
}
@@ -0,0 +1,3 @@
Overview 1.0 Overview.qml
OverviewWidget 1.0 OverviewWidget.qml
OverviewWindow 1.0 OverviewWindow.qml
-14
View File
@@ -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
}
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
show-failed-attempts show-failed-attempts
clock clock
color=00000000 color=55555500
indicator indicator
indicator-radius=100 indicator-radius=100
indicator-thickness=1 indicator-thickness=1
@@ -29,5 +29,5 @@ grace-no-mouse
grace-no-touch grace-no-touch
datestr=%a, %B %e datestr=%a, %B %e
timestr=%I:%M %p timestr=%I:%M %p
fade-in=0.2 fade-in=0.5
ignore-empty-password ignore-empty-password
+22 -19
View File
@@ -1,19 +1,22 @@
$wallpaper = {{wallpaper}} -- config/colors.lua
$background = rgb({{background | strip}}) -- Centralised palette
$foreground = rgb({{foreground | strip}}) -- Use these Lua globals throughout every other module.
$color0 = rgb({{color0 | strip}}) background = "rgb({{background | strip}})"
$color1 = rgb({{color1 | strip}}) foreground = "rgb({{foreground | strip}})"
$color2 = rgb({{color2 | strip}}) color0 = "rgb({{color0 | strip}})"
$color3 = rgb({{color3 | strip}}) color1 = "rgb({{color1 | strip}})"
$color4 = rgb({{color4 | strip}}) color2 = "rgb({{color2 | strip}})"
$color5 = rgb({{color5 | strip}}) color3 = "rgb({{color3 | strip}})"
$color6 = rgb({{color6 | strip}}) color4 = "rgb({{color4 | strip}})"
$color7 = rgb({{color7 | strip}}) color5 = "rgb({{color5 | strip}})"
$color8 = rgb({{color8 | strip}}) color6 = "rgb({{color6 | strip}})"
$color9 = rgb({{color9 | strip}}) color7 = "rgb({{color7 | strip}})"
$color10 = rgb({{color10 | strip}}) color8 = "rgb({{color8 | strip}})"
$color11 = rgb({{color11 | strip}}) color9 = "rgb({{color9 | strip}})"
$color12 = rgb({{color12 | strip}}) color10 = "rgb({{color10 | strip}})"
$color13 = rgb({{color13 | strip}}) color11 = "rgb({{color11 | strip}})"
$color14 = rgb({{color14 | strip}}) color12 = "rgb({{color12 | strip}})"
$color15 = rgb({{color15 | strip}}) color13 = "rgb({{color13 | strip}})"
color14 = "rgb({{color14 | strip}})"
color15 = "rgb({{color15 | strip}})"
wallpaper = "{{wallpaper}}"
+19 -19
View File
@@ -1,25 +1,25 @@
# uses https://codeberg.org/explosion-mental/wallust-templates/src/branch/master/kitty.conf # uses https://codeberg.org/explosion-mental/wallust-templates/src/branch/master/kitty.conf
background {{ background }} background {{ background }}
foreground {{ foreground }} foreground {{ foreground | lighten(0.1) }}
color0 {{ color0 }} color0 {{ color0 }}
color1 {{ color1 }} color1 {{ color1 | saturate(0.3) | lighten(0.1) }}
color2 {{ color2 }} color2 {{ color2 | saturate(0.3) | lighten(0.1) }}
color3 {{ color3 }} color3 {{ color3 | saturate(0.3) | lighten(0.1) }}
color4 {{ color4 }} color4 {{ color4 | saturate(0.3) | lighten(0.1) }}
color5 {{ color5 }} color5 {{ color5 | saturate(0.3) | lighten(0.1) }}
color6 {{ color6 }} color6 {{ color6 | saturate(0.3) | lighten(0.1) }}
color7 {{ color7 }} color7 {{ color7 | lighten(0.1) }}
color8 {{ color8 }} color8 {{ color8 | lighten(0.1) }}
color9 {{ color9 }} color9 {{ color9 | saturate(0.4) | lighten(0.15) }}
color10 {{ color10 }} color10 {{ color10 | saturate(0.4) | lighten(0.15) }}
color11 {{ color11 }} color11 {{ color11 | saturate(0.4) | lighten(0.15) }}
color12 {{ color12 }} color12 {{ color12 | saturate(0.4) | lighten(0.15) }}
color13 {{ color13 }} color13 {{ color13 | saturate(0.4) | lighten(0.15) }}
color14 {{ color14 }} color14 {{ color14 | saturate(0.4) | lighten(0.15) }}
color15 {{ color15 }} color15 {{ color15 | lighten(0.1) }}
mark1_foreground {{ color6 | saturate(0.2) }} mark1_foreground {{ color6 | saturate(0.4) }}
mark2_foreground {{ color7 | saturate(0.2) }} mark2_foreground {{ color7 | saturate(0.3) }}
mark3_foreground {{ color6 | saturate(0.2) }} mark3_foreground {{ color6 | saturate(0.4) }}
+16 -16
View File
@@ -1,16 +1,16 @@
{{ background }} {{ background | darken(0.1) }}
{{ color1 }} {{ color1 | saturate(0.4) | lighten(0.15) }}
{{ color2 }} {{ color2 | saturate(0.4) | lighten(0.15) }}
{{ color3 }} {{ color3 | saturate(0.4) | lighten(0.15) }}
{{ color4 }} {{ color4 | saturate(0.4) | lighten(0.15) }}
{{ color5 }} {{ color5 | saturate(0.4) | lighten(0.15) }}
{{ color6 }} {{ color6 | saturate(0.4) | lighten(0.15) }}
{{ color7 }} {{ color7 | lighten(0.15) }}
{{ color8 }} {{ color8 | lighten(0.1) }}
{{ color9 }} {{ color9 | saturate(0.5) | lighten(0.2) }}
{{ color10 }} {{ color10 | saturate(0.5) | lighten(0.2) }}
{{ color11 }} {{ color11 | saturate(0.5) | lighten(0.2) }}
{{ color12 }} {{ color12 | saturate(0.5) | lighten(0.2) }}
{{ color13 }} {{ color13 | saturate(0.5) | lighten(0.2) }}
{{ color14 }} {{ color14 | saturate(0.5) | lighten(0.2) }}
{{ color15 }} {{ color15 | lighten(0.15) }}
+14 -5
View File
@@ -15,7 +15,7 @@ backend = "fastresize"
# What color space to use to produce and select the most prominent colors: # What color space to use to produce and select the most prominent colors:
# lab - labmixed - lch - lchmixed # 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: # Use the most prominent colors in a way that makes sense, a scheme color palette:
# dark - dark16 - darkcomp - darkcomp16 # dark - dark16 - darkcomp - darkcomp16
@@ -23,7 +23,7 @@ color_space = "lab"
# harddark - harddark16 - harddarkcomp - harddarkcomp16 # harddark - harddark16 - harddarkcomp - harddarkcomp16
# softdark - softdark16 - softdarkcomp - softdarkcomp16 # softdark - softdark16 - softdarkcomp - softdarkcomp16
# softlight - softlight16 - softlightcomp - softlightcomp16 # softlight - softlight16 - softlightcomp - softlightcomp16
palette = "dark" palette = "harddarkcomp"
# Ensures a "readable contrast" (OPTIONAL, disabled by default) # Ensures a "readable contrast" (OPTIONAL, disabled by default)
# Should only be enabled when you notice an unreadable contrast frequently happening # 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 value for templating, by default 100 (no other use whatsoever)
#alpha = 50 #alpha = 50
[templates] [templates]
#waybar = { template = 'waybar.css', target = '~/.config/waybar/colors-wallust.css' } #waybar = { template = 'waybar.css', target = '~/.config/waybar/colors-wallust.css' }
vicinae = { src = 'vicinae.toml', dst = '~/.local/share/vicinae/themes/wallust.toml'} vicinae = { src = 'vicinae.toml', dst = '~/.local/share/vicinae/themes/wallust.toml'}
# alacritty = { template = 'alacritty.toml', target = '~/.config/alacritty/colors.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' } vscode = { template = 'vscode', target = '~/.cache/wal/colors' }
kitty = { template = 'kitty.conf', target = '~/.config/kitty/colors.conf'} kitty = { template = 'kitty.conf', target = '~/.config/kitty/colors.conf', max_depth = 1 }
tofi = { template = 'tofi', target = '~/.config/tofi/config'} 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!"'
+25 -8
View File
@@ -2,9 +2,9 @@
language = en language = en
folder = /usr/share/wallpapers folder = /usr/share/wallpapers
monitors = All 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 show_path_in_tooltip = True
backend = swaybg backend = awww
fill = fill fill = fill
sort = name sort = name
color = #ffffff color = #ffffff
@@ -12,16 +12,33 @@ subfolders = True
all_subfolders = False all_subfolders = False
show_hidden = False show_hidden = False
show_gifs_only = 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 number_of_columns = 3
swww_transition_type = wipe awww_transition_type = wipe
swww_transition_step = 90 awww_transition_step = 90
swww_transition_angle = 0 awww_transition_angle = 30
swww_transition_duration = 2 awww_transition_duration = 1
swww_transition_fps = 60 awww_transition_fps = 60
mpvpaper_sound = False mpvpaper_sound = False
mpvpaper_options = mpvpaper_options =
use_xdg_state = False use_xdg_state = False
zen_mode = True zen_mode = True
stylesheet = /home/michaelb/.config/waypaper/style.css 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