33 Commits

Author SHA1 Message Date
98f9a77b23 feature: float bitwarden extension window 2026-08-26 21:05:03 +01:00
bc242ae956 updated to add quickshell 2026-08-26 20:21:08 +01:00
4237b2ed9e Merge pull request 'migration: lua' (#2) from features/hypr_lua_config into features/adding_quickshell
Reviewed-on: #2

pulling into changes
2026-08-26 20:17:28 +01:00
7b0cf9934b migration: lua 2026-08-26 20:14:43 +01:00
9df5731ee1 changed volume location and widget colours 2026-08-25 09:35:22 +01:00
3806935316 implimented multiple VPN connectors 2026-08-25 09:34:55 +01:00
516f8eed82 removed tray 2026-08-25 09:34:19 +01:00
369179c057 added vpn exclusitivity to allow functions to coexist 2026-08-25 09:34:04 +01:00
322804577f added vpn exclusitivity to allow functions to coexist 2026-08-25 09:33:45 +01:00
0eee14858b basic tailscale widget 2026-08-25 07:25:38 +01:00
461f4c3fff added tailscale widget 2026-08-25 07:25:27 +01:00
2f9dfda410 added initial bluetooth widget 2026-08-24 15:12:27 +01:00
e35ceb1450 added bluetooth widget 2026-08-24 15:10:55 +01:00
3d0a18866f implimented network widget 2026-08-24 15:07:18 +01:00
6c415d6fbb added starting funtionality 2026-08-24 15:06:58 +01:00
c4b4a3bd26 added quickshell config 2026-08-24 10:03:59 +01:00
866ee0bb9b Merge pull request 'features/updating_fonts' (#1) from features/updating_fonts into main
Reviewed-on: #1
2026-08-21 09:22:44 +01:00
e77c08a0a2 changed spacings for battery 2026-08-21 09:20:17 +01:00
491509e864 corrected padding so all items are verticallt alligned 2026-08-21 08:38:49 +01:00
c4604f8e0f changed spacing for date 2026-08-21 08:34:48 +01:00
30d5f4c926 adjusted spacing 2026-08-21 08:32:42 +01:00
90d7042995 changed text for alt & updated spacing 2026-08-21 08:28:16 +01:00
b6714cf20b changed spacing for bitwarden launcher 2026-08-21 08:22:28 +01:00
8877835ae0 fixed spacing for bluetooth and wireplumber 2026-08-21 08:20:40 +01:00
32dd10c83d returned wifi icons 2026-08-21 07:46:02 +01:00
f3df5e4f3d changed ethernet and wifi icons 2026-08-21 07:43:56 +01:00
74953850fb updating fonts 2026-08-21 07:21:51 +01:00
04e50bad3f reverted back to config working on laptop 2026-08-21 06:44:17 +01:00
a218026568 updated config files to lua 2026-08-21 05:48:50 +01:00
7c557bffc8 changed domains to ip addresses 2026-08-21 05:48:29 +01:00
2eb122960d docker & pnpm context added
Signed-off-by: thomas <thomas.williams29899@gmail.com>
2026-08-21 04:31:20 +01:00
8c0347ee35 revert c4f220d95a
revert docker & pnpm context added

Signed-off-by: thomas <thomas.williams29899@gmail.com>
2026-08-21 04:30:06 +01:00
2935fc7cfe Update README.md 2026-08-20 07:37:58 +01:00
51 changed files with 5729 additions and 42 deletions

View File

@@ -26,6 +26,26 @@ My dotfiles, a constantly evolving set of configurations which I arguably spend
too much time tweaking, but they make the command line feel like home, so here too much time tweaking, but they make the command line feel like home, so here
we are. we are.
## 🖥️ Installation
In order to apply these dotfiles, you must have the SSH Key stored in your password manager,
then connect the SSH-Agent to your shell. Once that is done, you will need the application
**Stow** in order to keep it synced.
After this, you will need to git clone the repo in the Projects directory
```console
mkdir Projects
git clone ssh://git@gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles.git
```
Once that is complete, run this command:
```console
stow -d ~/Projects/dotfiles/ -t ~/ dotfiles
```
This will sync wallpapers/config files to where they are usually located
## 📦 Programs ## 📦 Programs
List of programs and tools I use. List of programs and tools I use.
@@ -39,7 +59,7 @@ List of programs and tools I use.
|📁 File Manager | [thunar](https://wiki.archlinux.org/title/Thunar) | |📁 File Manager | [thunar](https://wiki.archlinux.org/title/Thunar) |
|📝 Editor | [vs code](https://github.com/microsoft/vscode) / [neovim (changing to soon)](https://github.com/neovim/neovim) | |📝 Editor | [vs code](https://github.com/microsoft/vscode) / [neovim (changing to soon)](https://github.com/neovim/neovim) |
|🌐 Browser | [zen](https://github.com/zen-browser/desktop) / [firefox](https://github.com/topics/firefox-browser) / [startpage](https://blog-bucket-personal.s3.eu-west-2.amazonaws.com/Screenshot_02-May_08-44-23_6614.png) | |🌐 Browser | [zen](https://github.com/zen-browser/desktop) / [firefox](https://github.com/topics/firefox-browser) / [startpage](https://blog-bucket-personal.s3.eu-west-2.amazonaws.com/Screenshot_02-May_08-44-23_6614.png) |
|📊 Bar | [waybar](https://github.com/Alexays/Waybar) | |📊 Bar | [waybar](https://github.com/Alexays/Waybar) / [quickshell](https://quickshell.org/) |
|🚀 Launcher | [rofi](https://github.com/davatorium/rofi) | |🚀 Launcher | [rofi](https://github.com/davatorium/rofi) |
|🔒 Lockscreen | [hyprlock](https://github.com/hyprwm/hyprlock) | |🔒 Lockscreen | [hyprlock](https://github.com/hyprwm/hyprlock) |
|🚪 Login Menu | [sddm](https://github.com/sddm/sddm) | |🚪 Login Menu | [sddm](https://github.com/sddm/sddm) |

View File

@@ -0,0 +1,34 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Ported from UserConfigs/UserAnimations.conf (the active preset)
--
-- The 14 alternative presets from animations/*.conf were also ported, flat, as
-- anim-*.lua (see the plan) — none of them are wired in. They now live in ../presets/.
-- To switch styles, replace the body of this file with `require("presets.anim-<preset>")`,
-- e.g. require("presets.anim-ml4w-classic").
hl.config({
animations = {
enabled = true,
},
})
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("smoothOut", { type = "bezier", points = { { 0.5, 0 }, { 0.99, 0.99 } } })
hl.curve("smoothIn", { type = "bezier", points = { { 0.5, -0.5 }, { 0.68, 1.5 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 5, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "smoothOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 100, bezier = "liner", style = "loop" }) -- used by rainbow borders and rotating colors; speed clamped from 180 -> Lua hl.animation caps speed at 100
hl.animation({ leaf = "fade", enabled = true, speed = 3, bezier = "smoothOut" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "overshot" })
-- animations for -git or version >0.42.0
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 5, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })

View File

@@ -0,0 +1,26 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- HyprFlux static color palette
-- Ported from colours/hyprflux-colors.conf
local M = {}
M.background = "rgb(010102)"
M.foreground = "rgb(FDF8FE)"
M.color0 = "rgb(313131)"
M.color1 = "rgb(09050C)"
M.color2 = "rgb(221647)"
M.color3 = "rgb(2C1A40)"
M.color4 = "rgb(5E3887)"
M.color5 = "rgb(7344A6)"
M.color6 = "rgb(BAB0BD)"
M.color7 = "rgb(F3ECF5)"
M.color8 = "rgb(AAA5AC)"
M.color9 = "rgb(0B0711)"
M.color10 = "rgb(2D1D5F)"
M.color11 = "rgb(3B2355)"
M.color12 = "rgb(7D4AB4)"
M.color13 = "rgb(9A5BDD)"
M.color14 = "rgb(F8EAFC)"
M.color15 = "rgb(F3ECF5)"
return M

View File

@@ -0,0 +1,65 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Decoration Settings
-- Ported from UserConfigs/UserDecorations.conf
--
-- Hyprland Wiki Links
-- Animation - https://wiki.hyprland.org/Configuring/Animations/
-- Decoration - https://wiki.hyprland.org/Configuring/Variables/#decoration
local colors = require("appearance.colors")
hl.config({
general = {
border_size = 2,
gaps_in = 2,
gaps_out = 4,
col = {
active_border = colors.color12,
inactive_border = colors.color10,
},
},
decoration = {
rounding = 10,
active_opacity = 1.0,
inactive_opacity = 0.9,
fullscreen_opacity = 1.0,
dim_inactive = true,
dim_strength = 0.1,
dim_special = 0.8,
shadow = {
enabled = false,
range = 3,
render_power = 1,
color = colors.color12,
color_inactive = colors.color10,
},
blur = {
enabled = true,
size = 6,
passes = 2,
ignore_opacity = true,
new_optimizations = true,
special = true,
popups = true,
},
},
group = {
col = {
border_active = colors.color15,
},
groupbar = {
col = {
active = colors.color0,
},
},
},
})

View File

@@ -0,0 +1,87 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Environment variables. See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Environment-variables/
-- Ported from UserConfigs/ENVariables.conf
-- Toolkit Backend Variables
hl.env("GDK_BACKEND", "wayland,x11,*")
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
hl.env("CLUTTER_BACKEND", "wayland")
-- Run SDL2 applications on Wayland.
-- Remove or set to x11 if games that provide older versions of SDL cause compatibility issues
-- hl.env("SDL_VIDEODRIVER", "wayland")
-- xdg Specifications
hl.env("XDG_CURRENT_DESKTOP", "Hyprland")
hl.env("XDG_SESSION_DESKTOP", "Hyprland")
hl.env("XDG_SESSION_TYPE", "wayland")
-- QT Variables
hl.env("QT_AUTO_SCREEN_SCALE_FACTOR", "1")
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
hl.env("QT_QPA_PLATFORMTHEME", "qt5ct")
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct")
-- hyprland-qt-support
hl.env("QT_QUICK_CONTROLS_STYLE", "org.hyprland.style")
-- xwayland apps scale fix (useful if you use monitor scaling).
-- Set same value if you use scaling in monitors.lua
-- 1 is 100% 1.5 is 150%
-- see https://wiki.hyprland.org/Configuring/XWayland/
hl.env("GDK_SCALE", "1")
hl.env("QT_SCALE_FACTOR", "1")
-- Bibata-Modern-Ice-Cursor
-- NOTE! You must have the hyprcursor version to activate this.
-- https://wiki.hyprland.org/Hypr-Ecosystem/hyprcursor/
hl.env("HYPRCURSOR_THEME", "Bibata-Modern-Classic")
hl.env("HYPRCURSOR_SIZE", "24")
-- firefox
hl.env("MOZ_ENABLE_WAYLAND", "1")
-- electron >28 apps (may help) ##
-- https://www.electronjs.org/docs/latest/api/environment-variables
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") -- auto selects Wayland if possible, X11 otherwise
-- NVIDIA
-- This is from the Hyprland wiki. Uncomment below if an nvidia GPU is detected.
-- See hyprland wiki https://wiki.hyprland.org/Nvidia/#environment-variables
-- hl.env("LIBVA_DRIVER_NAME", "nvidia")
-- hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
-- hl.env("NVD_BACKEND", "direct")
-- hl.env("GSK_RENDERER", "ngl")
-- additional ENV's for nvidia. Caution, activate with care
-- hl.env("GBM_BACKEND", "nvidia-drm")
-- hl.env("__GL_GSYNC_ALLOWED", "1") -- adaptive Vsync
-- hl.env("__NV_PRIME_RENDER_OFFLOAD", "1")
-- hl.env("__VK_LAYER_NV_optimus", "NVIDIA_only")
-- hl.env("WLR_DRM_NO_ATOMIC", "1")
-- FOR VM and POSSIBLY NVIDIA
-- LIBGL_ALWAYS_SOFTWARE software mesa rendering
-- hl.env("LIBGL_ALWAYS_SOFTWARE", "1") -- Warning. May cause hyprland to crash
-- hl.env("WLR_RENDERER_ALLOW_SOFTWARE", "1")
-- nvidia firefox (for hardware acceleration on FF)?
-- check this post https://github.com/elFarto/nvidia-vaapi-driver#configuration
-- hl.env("MOZ_DISABLE_RDD_SANDBOX", "1")
-- hl.env("EGL_PLATFORM", "wayland")
---- Aquamarine Environment Variables ---- ( Hyprland > 0.45 )
-- https://wiki.hyprland.org/Configuring/Environment-variables/#aquamarine-environment-variables
-- hl.env("AQ_TRACE", "1") -- Enables more verbose logging.
-- hl.env("AQ_DRM_DEVICES", "/dev/dri/card1:/dev/dri/card0") -- explicit list of DRM devices (GPUs) to use.
-- hl.env("AQ_MGPU_NO_EXPLICIT", "1") -- Disables explicit syncing on mgpu buffers
-- hl.env("AQ_NO_MODIFIERS", "1") -- Disables modifiers for DRM buffers
---- Hyprland Environment Variables ----
-- https://wiki.hyprland.org/Configuring/Environment-variables/#hyprland-environment-variables
-- hl.env("HYPRLAND_TRACE", "1") -- Enables more verbose logging.
-- hl.env("HYPRLAND_NO_RT", "1") -- Disables realtime priority setting by Hyprland.
-- hl.env("HYPRLAND_NO_SD_NOTIFY", "1") -- If systemd, disables the 'sd_notify' calls.
-- hl.env("HYPRLAND_NO_SD_VARS", "1") -- Disables management of variables in systemd and dbus activation environments.

View File

@@ -0,0 +1,29 @@
-- Generated by HyprMod
-- Ported from hyprland-gui.conf
--
-- HyprMod isn't currently installed on this machine, so this is a straight syntax port of
-- whatever it last wrote. If you reinstall HyprMod, prefer letting its own "Migrate to Lua…"
-- wizard take over managing this file instead of hand-editing it going forward.
hl.config({
general = {
allow_tearing = true,
snap = {
enabled = false,
},
},
misc = {
vrr = 1,
},
})
-- Monitors
hl.monitor({ output = "DP-2", mode = "1920x1080@165.00Hz", position = "0x0", scale = 1, bitdepth = 10, cm = "wide" })
-- Keybinds
-- (unbind = SUPER SHIFT, Print / unbind = SUPER, up from the source .conf have no Lua equivalent needed —
-- we just don't define a conflicting bind elsewhere; SUPER + up -> movefocus u below is otherwise identical
-- to the one already in keybinds.lua, kept here too for parity with the original file)
hl.bind("SUPER + Print", hl.dsp.exec_cmd("hyprshot -o " .. os.getenv("HOME") .. "/Pictures/Screenshots-Linux -m region"))
hl.bind("SUPER + up", hl.dsp.focus({ direction = "u" }))
hl.bind("SUPER + SHIFT + Print", hl.dsp.exec_cmd("hyprshot -o " .. os.getenv("HOME") .. "/Pictures/Screenshots-Linux -m window"))

View File

@@ -0,0 +1,120 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- User Settings
-- Ported from UserConfigs/UserSettings.conf
--
-- refer to Hyprland wiki for more info https://wiki.hyprland.org/Configuring/Variables/
-- NOTE: some settings are in animations.lua / decorations.lua
hl.config({
dwindle = {
preserve_split = true,
-- smart_split = true,
special_scale_factor = 1,
},
master = {
new_status = "master",
new_on_top = true,
mfact = 0.5,
},
general = {
resize_on_border = true,
layout = "dwindle",
},
input = {
kb_layout = "gb",
kb_variant = "",
kb_model = "",
kb_options = "",
kb_rules = "",
repeat_rate = 50,
repeat_delay = 300,
sensitivity = 0.6, -- mouse sensitivity
-- accel_profile = "", -- flat or adaptive or blank or EMPTY means libinput's default mode
numlock_by_default = true,
left_handed = false,
follow_mouse = 1,
float_switch_override_focus = false,
touchpad = {
disable_while_typing = true,
natural_scroll = true,
clickfinger_behavior = false,
middle_button_emulation = false,
tap_to_click = true,
drag_lock = false,
},
-- below for devices with touchdevice ie. touchscreen
touchdevice = {
enabled = true,
},
-- below is for tablet, see link above for proper variables
tablet = {
transform = 0,
left_handed = false,
},
},
-- "gestures" here only holds the tuning knobs. workspace_swipe/workspace_swipe_fingers
-- were removed from this category upstream in favor of hl.gesture() (see below) — and
-- were already commented out in the source .conf, so the swipe gesture itself stays
-- disabled here too, ported as-is.
gestures = {
workspace_swipe_distance = 500,
workspace_swipe_invert = true,
workspace_swipe_min_speed_to_force = 30,
workspace_swipe_cancel_ratio = 0.5,
workspace_swipe_create_new = true,
workspace_swipe_forever = true,
-- workspace_swipe_use_r = true, -- uncomment if wanted a forever create a new workspace with swipe right
},
misc = {
disable_hyprland_logo = true,
disable_splash_rendering = true,
vrr = 2,
mouse_move_enables_dpms = true,
enable_swallow = false,
swallow_regex = "^(kitty)$",
focus_on_activate = false,
initial_workspace_tracking = 0,
middle_click_paste = false,
enable_anr_dialog = true, -- Application not Responding (ANR)
anr_missed_pings = 15, -- ANR Threshold default 1 is too low
},
-- opengl = {
-- nvidia_anti_flicker = true,
-- },
binds = {
workspace_back_and_forth = true,
allow_workspace_cycles = true,
pass_mouse_when_bound = false,
},
-- Could help when scaling and not pixelating
xwayland = {
enabled = true,
force_zero_scaling = true,
},
render = {
direct_scanout = false,
},
cursor = {
sync_gsettings_theme = true,
no_hardware_cursors = 2, -- change to 1 if you want to disable
enable_hyprcursor = true,
warp_on_change_workspace = 2,
no_warps = true,
},
})
-- Uncomment to actually enable the touchpad workspace-swipe gesture (see the note above):
-- hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })

View File

@@ -0,0 +1,52 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Commands and Apps to be executed at launch
-- Ported from UserConfigs/Startup_Apps.conf
-- See https://wiki.hypr.land/Configuring/Basics/Autostart/
local scriptsDir = os.getenv("HOME") .. "/.config/hypr/scripts"
hl.on("hyprland.start", function()
-- wallpaper stuff
hl.exec_cmd("awww-daemon --format xrgb")
-- hl.exec_cmd("mpvpaper '*' -o \"load-scripts=no no-audio --loop\" $livewallpaper")
-- Startup
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
hl.exec_cmd("systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
-- Polkit (Polkit Gnome / KDE)
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
-- startup apps
hl.exec_cmd("nm-applet --indicator")
hl.exec_cmd("swaync")
-- hl.exec_cmd("ags")
-- hl.exec_cmd("blueman-applet")
-- hl.exec_cmd("rog-control-center")
hl.exec_cmd("waybar")
hl.exec_cmd("qs") -- quickshell AGS Desktop Overview alternative
-- clipboard manager
hl.exec_cmd("wl-paste --type text --watch cliphist store")
hl.exec_cmd("wl-paste --type image --watch cliphist store")
-- Starting hypridle to start hyprlock
hl.exec_cmd("hypridle")
-- Float + center the Bitwarden extension pop-out (window rules can't catch it — see the script)
hl.exec_cmd(scriptsDir .. "/FloatBitwardenPopup.sh")
hl.dispatch(hl.dsp.focus({ workspace = 3 }))
-- NOTE: the trailing "&&" here is a stray leftover from the original .conf (dangling, does nothing) — ported as-is
hl.exec_cmd("/usr/bin/Plexamp.AppImage &&")
hl.exec_cmd("sleep 3 && vesktop")
hl.dispatch(hl.dsp.focus({ workspace = 1 }))
hl.exec_cmd("bitwarden-desktop --ozone-platform-hint=auto --silent")
-- Here are a list of features available but disabled by default
-- hl.exec_cmd("awww-daemon --format xrgb && awww img " .. os.getenv("HOME") .. "/Pictures/wallpapers/mecha-nostalgia.png") -- persistent wallpaper
-- xdg-desktop-portal-hyprland (should be auto starting. However, you can force to start)
-- hl.exec_cmd(scriptsDir .. "/PortalHyprland.sh")
end)

View File

@@ -0,0 +1,42 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Ported from hyprland.conf — see https://wiki.hyprland.org/
--
-- This mirrors the old source= chain 1:1, just as require()'d Lua modules instead
-- of hyprlang includes. See each file for its origin. The presets/ preset library
-- (anim-*.lua) is also ported but intentionally not required from here — only the
-- currently-active style (appearance/animations.lua) is wired in, exactly like before.
--
-- Modules live in subfolders sorted by concern (core/, appearance/, layout/, input/,
-- presets/) — require() paths below use dotted module names, which Lua resolves to
-- the matching subfolder (e.g. require("core.env") -> core/env.lua).
-- Initial boot script: applies initial wallpapers, theming, new settings etc.
-- suggest not changing/deleting this, including the reference file at ~/.config/hypr/.initial_startup_done
-- as long as that reference file is present, initial-boot.sh will not execute
hl.on("hyprland.start", function()
hl.exec_cmd(os.getenv("HOME") .. "/.config/hypr/initial-boot.sh")
end)
-- ## This is where you want to start tinkering
require("core.startup") -- put your start-up packages in core/startup.lua
require("core.env") -- Environment variables to load
require("layout.windowrules") -- all about Hyprland Window Rules and Layer Rules
require("appearance.decorations") -- Decorations config
require("appearance.animations") -- Animation config
require("input.keybinds") -- Put your own keybinds here
require("core.settings") -- Main Hyprland Settings
-- nwg-displays / hyprdynamicmonitors
require("layout.monitors")
require("layout.workspaces")
-- NOTE: the original hyprland.conf used a plain `exec` (not `exec-once`) here, so it
-- re-ran on every `hyprctl reload`, not just at startup. hl.on("hyprland.start", ...)
-- only fires once at actual compositor start — close enough for a one-shot gsettings
-- call, but worth knowing if you relied on the reload-time re-apply.
hl.on("hyprland.start", function()
hl.exec_cmd("gsettings set org.gnome.desktop.interface cursor-theme Future-black Cursors")
end)
-- HyprMod managed settings
require("core.hyprmod")

View File

@@ -0,0 +1,176 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Ported from UserConfigs/Keybinds.conf
-- See https://wiki.hypr.land/Configuring/Basics/Binds/ and .../Basics/Dispatchers/
local mainMod = "SUPER"
local scriptsDir = os.getenv("HOME") .. "/.config/hypr/scripts"
local userScripts = os.getenv("HOME") .. "/.config/hypr/UserScripts"
-- settings for user default apps
local edit = os.getenv("EDITOR") or "nvim"
-- These two are for UserKeybinds.conf & Waybar Modules
local term = "foot" -- Terminal
local files = "thunar" -- File Manager
-- Default Search Engine for ROFI Search (SUPER S)
local searchEngine = "https://www.google.com/search?q={}"
-- common shortcuts
hl.bind(mainMod .. " + SPACE", hl.dsp.exec_cmd(".config/rofi/launchers/type-4/launcher.sh")) -- desktop overview (if installed)
hl.bind(mainMod .. " + Q", hl.dsp.window.close()) -- close active (not kill)
hl.bind(mainMod .. " + SHIFT + Q", hl.dsp.exec_cmd(scriptsDir .. "/KillActiveProcess.sh")) -- Kill active process
hl.bind(mainMod .. " + Return", hl.dsp.exec_cmd(term)) -- terminal
hl.bind(mainMod .. " + F", hl.dsp.exec_cmd(files)) -- file manager
hl.bind(mainMod .. " + B", hl.dsp.exec_cmd("zen-browser")) -- Launch Zen Browser New Everytime
hl.bind(mainMod .. " + V", hl.dsp.exec_cmd(scriptsDir .. "/ClipManager.sh")) -- Clipboard Manager
hl.bind(mainMod .. " + C", hl.dsp.exec_cmd("code --ozone-platform=x11")) -- Visual Studio Code
hl.bind(mainMod .. " + O", hl.dsp.exec_cmd("obsidian --ozone-platform=x11")) -- Obsidian
hl.bind(mainMod .. " + X", hl.dsp.exec_cmd("vesktop")) -- Vesktop(Discord)
hl.bind(mainMod .. " + M", hl.dsp.exec_cmd("fdm")) -- Free Download Manager
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd("waypaper")) -- Wayland Wallpaper Setter
hl.bind(mainMod .. " + L", hl.dsp.exec_cmd("hyprlock")) -- Screen Locker
hl.bind(mainMod .. " + DELETE", hl.dsp.exec_cmd("~/.config/rofi/powermenu/type-2/powermenu.sh")) -- Power Menu
hl.bind(mainMod .. " + S", hl.dsp.exec_cmd("cd /home/thomas/hyprmod && uv run hyprmod")) -- Hyprmod - A Hyprland settings manager (if installed)
hl.bind(mainMod .. " + SHIFT + R", hl.dsp.exec_cmd(scriptsDir .. "/Refresh.sh")) -- Refresh waybar, swaync, rofirrr
hl.bind(mainMod .. " + N", hl.dsp.exec_cmd("swaync-client -t -sw")) -- swayNC notification panel
-- FEATURES / EXTRAS
hl.bind(mainMod .. " + SHIFT + F", hl.dsp.window.fullscreen({ mode = "fullscreen" })) -- whole full screen
hl.bind(mainMod .. " + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" })) -- Float Mode
-- Desktop zooming or magnifier
hl.bind(mainMod .. " + ALT + mouse_down", hl.dsp.exec_cmd([[hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')"]]))
hl.bind(mainMod .. " + ALT + mouse_up", hl.dsp.exec_cmd([[hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}')"]]))
-- Waybar / Bar related
hl.bind(mainMod .. " + CTRL + ALT + B", hl.dsp.exec_cmd("pkill -SIGUSR1 waybar")) -- Toggle hide/show waybar
-- FEATURES / EXTRAS (UserScripts)
hl.bind(mainMod .. " + SHIFT + A", hl.dsp.exec_cmd(scriptsDir .. "/Animations.sh")) -- hyprland animations menu
hl.bind(mainMod .. " + SHIFT + Z", hl.dsp.exec_cmd(userScripts .. "/ZshChangeTheme.sh")) -- Change oh-my-zsh theme
--------------------------------------------------------------------------------------
-- Default Keybinds
--------------------------------------------------------------------------------------
hl.bind("CTRL + ALT + Delete", hl.dsp.exit()) -- exit Hyprland
-- Master Layout
hl.bind(mainMod .. " + CTRL + D", hl.dsp.layout("removemaster"))
hl.bind(mainMod .. " + I", hl.dsp.layout("addmaster"))
hl.bind(mainMod .. " + J", hl.dsp.layout("cyclenext"))
hl.bind(mainMod .. " + K", hl.dsp.layout("cycleprev"))
hl.bind(mainMod .. " + CTRL + Return", hl.dsp.layout("swapwithmaster"))
-- Dwindle Layout
hl.bind(mainMod .. " + P", hl.dsp.window.pseudo()) -- dwindle
-- Special Keys / Hot Keys
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd(scriptsDir .. "/Volume.sh --inc"), { repeating = true, locked = true }) -- volume up
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd(scriptsDir .. "/Volume.sh --dec"), { repeating = true, locked = true }) -- volume down
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd(scriptsDir .. "/Volume.sh --toggle-mic"), { locked = true }) -- mic mute
hl.bind("XF86AudioMute", hl.dsp.exec_cmd(scriptsDir .. "/Volume.sh --toggle"), { locked = true }) -- mute
hl.bind("XF86Sleep", hl.dsp.exec_cmd("systemctl suspend"), { locked = true }) -- sleep button
hl.bind("XF86RFKill", hl.dsp.exec_cmd(scriptsDir .. "/AirplaneMode.sh"), { locked = true }) -- Airplane mode
-- media controls using keyboards
hl.bind("XF86MediaPlayPause", hl.dsp.exec_cmd(scriptsDir .. "/MediaCtrl.sh --pause"), { locked = true })
hl.bind("XF86AudioPause", hl.dsp.exec_cmd(scriptsDir .. "/MediaCtrl.sh --pause"), { locked = true })
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd(scriptsDir .. "/MediaCtrl.sh --pause"), { locked = true })
hl.bind("XF86AudioNext", hl.dsp.exec_cmd(scriptsDir .. "/MediaCtrl.sh --nxt"), { locked = true })
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd(scriptsDir .. "/MediaCtrl.sh --prv"), { locked = true })
hl.bind("XF86AudioStop", hl.dsp.exec_cmd(scriptsDir .. "/MediaCtrl.sh --stop"), { locked = true })
-- Resize windows
hl.bind(mainMod .. " + SHIFT + left", hl.dsp.window.resize({ x = -50, y = 0, relative = true }), { repeating = true })
hl.bind(mainMod .. " + SHIFT + right", hl.dsp.window.resize({ x = 50, y = 0, relative = true }), { repeating = true })
hl.bind(mainMod .. " + SHIFT + up", hl.dsp.window.resize({ x = 0, y = -50, relative = true }), { repeating = true })
hl.bind(mainMod .. " + SHIFT + down", hl.dsp.window.resize({ x = 0, y = 50, relative = true }), { repeating = true })
-- Move windows
hl.bind(mainMod .. " + CTRL + left", hl.dsp.window.move({ direction = "l" }))
hl.bind(mainMod .. " + CTRL + right", hl.dsp.window.move({ direction = "r" }))
hl.bind(mainMod .. " + CTRL + up", hl.dsp.window.move({ direction = "u" }))
hl.bind(mainMod .. " + CTRL + down", hl.dsp.window.move({ direction = "d" }))
-- Swap windows
hl.bind(mainMod .. " + ALT + left", hl.dsp.window.swap({ direction = "l" }))
hl.bind(mainMod .. " + ALT + right", hl.dsp.window.swap({ direction = "r" }))
hl.bind(mainMod .. " + ALT + up", hl.dsp.window.swap({ direction = "u" }))
hl.bind(mainMod .. " + ALT + down", hl.dsp.window.swap({ direction = "d" }))
-- Move focus with mainMod + arrow keys
hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "l" }))
hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "r" }))
hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "u" }))
hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "d" }))
-- Workspaces related
hl.bind(mainMod .. " + tab", hl.dsp.focus({ workspace = "m+1" }))
hl.bind(mainMod .. " + SHIFT + tab", hl.dsp.focus({ workspace = "m-1" }))
-- Special workspace
-- hl.bind(mainMod .. " + SHIFT + U", hl.dsp.window.move({ workspace = "special" }))
-- hl.bind(mainMod .. " + U", hl.dsp.workspace.toggle_special())
hl.bind(mainMod .. " + U", hl.dsp.workspace.toggle_special("nyx"))
hl.bind(mainMod .. " + SHIFT + U", hl.dsp.window.move({ workspace = "special:nyx" }))
-- The following mappings use key codes to better support various keyboard layouts
-- 1 is code:10, 2 is code:11, etc
-- Switch workspaces with mainMod + [0-9]
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
hl.bind(mainMod .. " + code:" .. code, hl.dsp.focus({ workspace = i })) -- code:10 = key 1 ... code:19 = key 0
end
-- Move active window and follow to workspace mainMod + SHIFT + [0-9]
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
hl.bind(mainMod .. " + SHIFT + code:" .. code, hl.dsp.window.move({ workspace = i, follow = true }))
end
hl.bind(mainMod .. " + SHIFT + bracketleft", hl.dsp.window.move({ workspace = -1, follow = true })) -- brackets [
hl.bind(mainMod .. " + SHIFT + bracketright", hl.dsp.window.move({ workspace = 1, follow = true })) -- brackets ]
-- Move active window to a workspace silently mainMod + CTRL + [0-9]
for i, code in ipairs({ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }) do
hl.bind(mainMod .. " + CTRL + code:" .. code, hl.dsp.window.move({ workspace = i, follow = false }))
end
hl.bind(mainMod .. " + CTRL + bracketleft", hl.dsp.window.move({ workspace = -1, follow = false })) -- brackets [
hl.bind(mainMod .. " + CTRL + bracketright", hl.dsp.window.move({ workspace = 1, follow = false })) -- brackets ]
-- Scroll through existing workspaces with mainMod + scroll
hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
hl.bind(mainMod .. " + period", hl.dsp.focus({ workspace = "e+1" }))
hl.bind(mainMod .. " + comma", hl.dsp.focus({ workspace = "e-1" }))
-- Move/resize windows with mainMod + LMB/RMB and dragging
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true }) -- mouse:272 = left click
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) -- mouse:273 = right click
--------------------------------------------------------------------------------------
-- Laptop specific keybinds
--------------------------------------------------------------------------------------
-- for disabling Touchpad. hyprctl devices to get device name.
local touchpadDevice = "asue1209:00-04f3:319f-touchpad"
hl.bind("XF86KbdBrightnessDown", hl.dsp.exec_cmd(scriptsDir .. "/BrightnessKbd.sh --dec"), { repeating = true }) -- decrease keyboard brightness
hl.bind("XF86KbdBrightnessUp", hl.dsp.exec_cmd(scriptsDir .. "/BrightnessKbd.sh --inc"), { repeating = true }) -- increase keyboard brightness
hl.bind("XF86Launch1", hl.dsp.exec_cmd("rog-control-center")) -- ASUS Armory crate button
hl.bind("XF86Launch3", hl.dsp.exec_cmd("asusctl led-mode -n")) -- FN+F4 Switch keyboard RGB profile
hl.bind("XF86Launch4", hl.dsp.exec_cmd("asusctl profile -n")) -- FN+F5 change of fan profiles (Quiet, Balance, Performance)
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd(scriptsDir .. "/Brightness.sh --dec"), { repeating = true }) -- decrease monitor brightness
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd(scriptsDir .. "/Brightness.sh --inc"), { repeating = true }) -- increase monitor brightness
hl.bind("XF86TouchpadToggle", hl.dsp.exec_cmd(scriptsDir .. "/TouchPad.sh")) -- disable touchpad
-- Screenshot keybindings using F6 (no PrintSrc button)
-- hl.bind(mainMod .. " + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --now")) -- screenshot
-- hl.bind(mainMod .. " + SHIFT + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --area")) -- screenshot (area)
-- hl.bind(mainMod .. " + CTRL + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --in5")) -- screenshot (5 secs delay)
-- hl.bind(mainMod .. " + ALT + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --in10")) -- screenshot (10 secs delay)
-- hl.bind("ALT + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --active")) -- screenshot (active window only)
hl.device({
name = touchpadDevice,
enabled = true,
})

View File

@@ -0,0 +1,14 @@
-- Generated with hyprdynamicmonitors freeze.
-- This is a template that you can edit, it is just a starter that pulled your current monitor setup,
-- adjust as needed.
-- You can use templates here etc, see https://github.com/fiffeek/hyprdynamicmonitors/blob/main/examples/basic/hyprconfigs/template.go.tmpl.
-- Monitors are given arbitrary tags (the "monitor" prefix and the ID coming from hyprland).
-- If you are using the TUI to edit, leave this at the end of the file (the last monitor config applies)
-- and leave the markers.
-- Ported from UserConfigs/monitors.conf — hyprdynamicmonitors already supports writing this file
-- directly as monitors.lua once hyprland.lua is active, so re-running it should stay compatible.
-- <<<<< TUI AUTO START
hl.monitor({ output = "desc:LG Electronics LG ULTRAWIDE 0x0002E68A", mode = "2560x1080@60.00000", position = "1920x-740", scale = 1.00000000, transform = 3, vrr = 0 })
hl.monitor({ output = "desc:HP Inc. HP 524pf VNG447017D", mode = "1920x1080@100.00000", position = "-1920x0", scale = 1.00000000, transform = 0, vrr = 0 })
hl.monitor({ output = "desc:Acer Technologies VG240Y S 0x126191DE", mode = "1920x1080@165.00301", position = "0x0", scale = 1.00000000, transform = 0, vrr = 1, bitdepth = 10, cm = "wide" })
-- <<<<< TUI AUTO END

View File

@@ -0,0 +1,203 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- For window rules and layer rules
-- Ported from UserConfigs/WindowRules.conf
-- See https://wiki.hypr.land/Configuring/Basics/Window-Rules/
-- windowrule - tags - add apps under appropriate tag to use the same settings
-- browser tags
hl.window_rule({ name = "windowrule-1", tag = "+browser", match = { class = "^([Ff]irefox|org.mozilla.firefox|[Ff]irefox-esr|[Ff]irefox-bin)$" } })
hl.window_rule({ name = "rustdesk", tag = "+rustdesk", match = { class = "^(rustdesk)$", xwayland = true } })
hl.window_rule({ name = "windowrule-2", tag = "+browser", match = { class = "^([Gg]oogle-chrome(-beta|-dev|-unstable)?)$" } })
hl.window_rule({ name = "windowrule-3", tag = "+browser", match = { class = "^(chrome-.+-Default)$" } }) -- Chrome PWAs
-- hl.window_rule({ name = "browser-chromium", tag = "+browser", match = { class = "^([Cc]hromium)$" } })
hl.window_rule({ name = "windowrule-4", tag = "+browser", match = { class = "^([Mm]icrosoft-edge(-stable|-beta|-dev|-unstable))$" } })
hl.window_rule({ name = "windowrule-5", tag = "+browser", match = { class = "^(Brave-browser(-beta|-dev|-unstable)?)$" } })
hl.window_rule({ name = "windowrule-6", tag = "+browser", match = { class = "^([Tt]horium-browser|[Cc]achy-browser)$" } })
hl.window_rule({ name = "windowrule-7", tag = "+browser", match = { class = "^(zen-alpha|zen)$" } })
-- notif tags
hl.window_rule({ name = "windowrule-8", tag = "+notif", match = { class = "^(swaync-control-center|swaync-notification-window|swaync-client|class)$" } })
-- HyprFlux settings tag
hl.window_rule({ name = "windowrule-9", tag = "+HyprFlux_Cheat", match = { title = "^(HyprFlux Quick Cheat Sheet)$" } })
hl.window_rule({ name = "windowrule-10", tag = "+HyprFlux_Settings", match = { title = "^(HyprFlux Settings)$" } })
hl.window_rule({ name = "windowrule-11", tag = "+HyprFlux-Settings", match = { class = "^(nwg-displays|nwg-look)$" } })
-- terminal tags
hl.window_rule({ name = "windowrule-12", tag = "+terminal", match = { class = "^(Alacritty|kitty|kitty-dropterm)$" } })
-- email tags
hl.window_rule({ name = "windowrule-13", tag = "+email", match = { class = "^([Tt]hunderbird|org.gnome.Evolution)$" } })
hl.window_rule({ name = "windowrule-14", tag = "+email", match = { class = "^(eu.betterbird.Betterbird)$" } })
-- project tags
hl.window_rule({ name = "windowrule-15", tag = "+projects", match = { class = "^(codium|codium-url-handler|VSCodium)$" } })
hl.window_rule({ name = "windowrule-16", tag = "+projects", match = { class = "^(VSCode|code-url-handler)$" } })
hl.window_rule({ name = "windowrule-17", tag = "+projects", match = { class = "^(jetbrains-.+)$" } }) -- JetBrains IDEs
-- screenshare tags
hl.window_rule({ name = "windowrule-18", tag = "+screenshare", match = { class = "^(com.obsproject.Studio)$" } })
-- IM tags
-- hl.window_rule({ tag = "+im", match = { class = "^([Dd]iscord|[Ww]ebCord|[Vv]esktop)$" } })
hl.window_rule({
name = "windowrule-19",
tag = "+im",
center = true,
float = true,
size = "monitor_w*0.6 monitor_h*0.7",
match = { class = "^([Ff]erdium)$" },
})
hl.window_rule({ name = "windowrule-20", tag = "+im", match = { class = "^([Ww]hatsapp-for-linux)$" } })
hl.window_rule({ name = "windowrule-21", tag = "+im", match = { class = "^(ZapZap|com.rtosta.zapzap)$" } })
hl.window_rule({ name = "windowrule-22", tag = "+im", match = { class = "^(org.telegram.desktop|io.github.tdesktop_x64.TDesktop)$" } })
hl.window_rule({ name = "windowrule-23", tag = "+im", match = { class = "^(teams-for-linux)$" } })
hl.window_rule({ name = "windowrule-24", tag = "+im", match = { class = "^(im.riot.Riot|Element)$" } }) -- Element Matrix client
-- game tags
hl.window_rule({ name = "windowrule-25", tag = "+games", match = { class = "^(gamescope)$" } })
hl.window_rule({ name = "windowrule-26", tag = "+games", match = { class = "^(steam_app_\\d+)$" } })
-- gamestore tags
hl.window_rule({ name = "windowrule-27", tag = "+gamestore", match = { class = "^([Ss]team)$" } })
hl.window_rule({ name = "windowrule-28", tag = "+gamestore", match = { title = "^([Ll]utris)$" } })
hl.window_rule({ name = "windowrule-29", tag = "+gamestore", match = { class = "^(com.heroicgameslauncher.hgl)$" } })
-- file-manager tags
hl.window_rule({ name = "windowrule-30", tag = "+file-manager", match = { class = "^([Tt]hunar|org.gnome.Nautilus|[Pp]cmanfm-qt)$" } })
hl.window_rule({ name = "windowrule-31", tag = "+file-manager", match = { class = "^(app.drey.Warp)$" } })
-- wallpaper tags
hl.window_rule({ name = "windowrule-32", tag = "+wallpaper", match = { class = "^([Ww]aytrogen)$" } })
-- multimedia tags
hl.window_rule({ name = "windowrule-33", tag = "+multimedia", match = { class = "^([Aa]udacious)$" } })
-- multimedia-video tags
hl.window_rule({ name = "windowrule-34", tag = "+multimedia_video", match = { class = "^([Mm]pv|vlc)$" } })
-- settings tags
hl.window_rule({ name = "windowrule-35", tag = "+settings", center = true, match = { title = "^(ROG Control)$" } })
hl.window_rule({ name = "windowrule-36", tag = "+settings", match = { class = "^(wihotspot(-gui)?)$" } }) -- wifi hotspot
hl.window_rule({ name = "windowrule-37", tag = "+settings", match = { class = "^([Bb]aobab|org.gnome.[Bb]aobab)$" } }) -- Disk usage analyzer
hl.window_rule({ name = "windowrule-38", tag = "+settings", match = { class = "^(gnome-disks|wihotspot(-gui)?)$" } })
hl.window_rule({ name = "windowrule-39", tag = "+settings", match = { title = "(Kvantum Manager)" } })
hl.window_rule({ name = "windowrule-40", tag = "+settings", match = { class = "^(file-roller|org.gnome.FileRoller)$" } }) -- archive manager
hl.window_rule({ name = "windowrule-41", tag = "+settings", match = { class = "^(nm-applet|nm-connection-editor|blueman-manager)$" } })
hl.window_rule({ name = "windowrule-42", tag = "+settings", center = true, match = { class = "^(pavucontrol|org.pulseaudio.pavucontrol|com.saivert.pwvucontrol)$" } })
hl.window_rule({ name = "windowrule-43", tag = "+settings", match = { class = "^(qt5ct|qt6ct|[Yy]ad)$" } })
hl.window_rule({ name = "windowrule-44", tag = "+settings", match = { class = "(xdg-desktop-portal-gtk)" } })
hl.window_rule({ name = "windowrule-45", tag = "+settings", match = { class = "^(org.kde.polkit-kde-authentication-agent-1)$" } })
hl.window_rule({ name = "windowrule-46", tag = "+settings", match = { class = "^([Rr]ofi)$" } })
-- Custom rule
hl.window_rule({ name = "windowrule-47", tile = true, match = { class = "^([Cc]hromium)$" } })
hl.window_rule({ name = "windowrule-48", size = "monitor_w*0.25 monitor_h*0.55", float = true, match = { class = "^(com.network.manager)$" } })
hl.window_rule({ name = "windowrule-49", workspace = "special:nyx", match = { class = "^([Vv]esktop|[Dd]iscord|[Ss]potify)$" } })
hl.window_rule({ name = "windowrule-255", size = "monitor_w*0.75 monitor_h*0.80", float = true, match = { class = "^([Bb]itwarden)$" } })
-- viewer tags
hl.window_rule({ name = "windowrule-50", tag = "+viewer", match = { class = "^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$" } }) -- system monitor
hl.window_rule({ name = "windowrule-51", tag = "+viewer", match = { class = "^(evince)$" } }) -- document viewer
hl.window_rule({ name = "windowrule-52", tag = "+viewer", match = { class = "^(eog|org.gnome.Loupe)$" } }) -- image viewer
-- Some special override rules
hl.window_rule({ name = "windowrule-53", no_blur = true, opacity = "1.0", match = { tag = "multimedia_video*" } })
-- POSITION
-- hl.window_rule({ center = true, match = { float = true } }) -- warning, it cause even the menu to float and center.
hl.window_rule({ name = "windowrule-54", center = true, float = true, size = "monitor_w*0.65 monitor_h*0.9", match = { tag = "HyprFlux_Cheat*" } })
hl.window_rule({ name = "windowrule-55", center = true, float = true, match = { class = "([Tt]hunar)", title = "negative:(.*[Tt]hunar.*)" } })
hl.window_rule({ name = "windowrule-56", center = true, float = true, match = { tag = "HyprFlux-Settings*" } })
hl.window_rule({ name = "windowrule-57", center = true, match = { title = "^(Keybindings)$" } })
hl.window_rule({ name = "windowrule-58", center = true, size = "monitor_w*0.6 monitor_h*0.7", match = { class = "^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$" } })
hl.window_rule({
name = "windowrule-59",
move = "monitor_w*0.61 monitor_h*0.07",
float = true,
size = "monitor_w*0.38 monitor_h*0.38",
pin = true,
keep_aspect_ratio = true,
match = { title = "^(Picture-in-Picture)$" },
})
-- hl.window_rule({ move = "72% 7%", match = { title = "^(Firefox)$" } })
-- windowrule to avoid idle for fullscreen apps
-- hl.window_rule({ idle_inhibit = "fullscreen", match = { class = "^(*)$" } })
-- hl.window_rule({ idle_inhibit = "fullscreen", match = { title = "^(*)$" } })
hl.window_rule({ name = "windowrule-60", idle_inhibit = "fullscreen", match = { fullscreen = true } })
-- Discord to workspace 3
-- NOTE: these two used the old single-line windowrule syntax in the source .conf (match packed
-- oddly with the rule); windowrule-49 above already sends the same apps to special:nyx, so this
-- looks like a pre-existing conflict in the original config rather than something introduced here —
-- ported literally, last-defined-wins as before.
hl.window_rule({ workspace = "3", match = { class = "^([Vv]esktop|[Dd]iscord|[Ss]potify|[Pp]lexamp)$" } })
hl.window_rule({ size = "20% 20%", match = { class = "^([Pp]lexamp)" } })
-- FLOAT
hl.window_rule({ name = "windowrule-75", float = true, opacity = "0.9 0.7", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "wallpaper*" } })
hl.window_rule({ name = "windowrule-76", float = true, opacity = "0.8 0.7", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "settings*" } })
hl.window_rule({ name = "windowrule-77", float = true, match = { tag = "viewer*" } })
hl.window_rule({ name = "windowrule-78", float = true, match = { class = "([Zz]oom|onedriver|onedriver-launcher)$" } })
hl.window_rule({ name = "windowrule-79", float = true, match = { class = "(org.gnome.Calculator)", title = "(Calculator)" } })
hl.window_rule({ name = "windowrule-80", float = true, match = { class = "^(mpv|com.github.rafostar.Clapper)$" } })
hl.window_rule({ name = "windowrule-81", float = true, match = { class = "^([Qq]alculate-gtk)$" } })
-- hl.window_rule({ float = true, match = { class = "^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$" } })
-- hl.window_rule({ float = true, match = { title = "^(Firefox)$" } })
hl.window_rule({ name = "windowrule-82", float = true, match = { class = "^(fdm|freedownloadmanager)$" } })
hl.window_rule({ name = "windowrule-83", float = true, match = { class = "^(proton-authenticator)$" } })
hl.window_rule({ name = "windowrule-84", float = true, match = { class = "^([Ww]indscribe)$" } })
-- windowrule - ######### float popups and dialogue #######
hl.window_rule({ name = "windowrule-85", float = true, center = true, match = { title = "^(Authentication Required)$" } })
hl.window_rule({ name = "windowrule-86", float = true, match = { class = "(codium|codium-url-handler|VSCodium)", title = "negative:(.*codium.*|.*VSCodium.*)" } })
hl.window_rule({ name = "windowrule-87", float = true, match = { class = "^(com.heroicgameslauncher.hgl)$", title = "negative:(Heroic Games Launcher)" } })
hl.window_rule({ name = "windowrule-88", float = true, match = { class = "^([Ss]team)$", title = "negative:^([Ss]team)$" } })
hl.window_rule({ name = "windowrule-89", float = true, size = "monitor_w*0.7 monitor_h*0.6", center = true, match = { title = "^(Add Folder to Workspace)$" } })
hl.window_rule({ name = "windowrule-90", float = true, size = "monitor_w*0.7 monitor_h*0.6", center = true, match = { title = "^(Save As)$" } })
hl.window_rule({ name = "windowrule-91", float = true, size = "monitor_w*0.7 monitor_h*0.6", match = { initial_title = "(Open Files)" } })
hl.window_rule({ name = "windowrule-92", float = true, center = true, size = "monitor_w*0.16 monitor_h*0.12", match = { title = "^(SDDM Background)$" } }) -- HyprFlux YAD for setting SDDM background
-- END of float popups and dialogue #######
-- OPACITY
-- hl.window_rule({ opacity = "0.9 0.7", match = { tag = "browser*" } })
-- hl.window_rule({ opacity = "0.9 0.8", match = { tag = "projects*" } })
-- hl.window_rule({ opacity = "0.94 0.86", match = { tag = "im*" } })
-- hl.window_rule({ opacity = "0.94 0.86", match = { tag = "multimedia*" } })
-- hl.window_rule({ opacity = "0.9 0.8", match = { tag = "file-manager*" } })
hl.window_rule({ name = "windowrule-93", opacity = "0.8 0.7", match = { tag = "terminal*" } })
-- hl.window_rule({ opacity = "0.82 0.75", match = { tag = "viewer*" } })
hl.window_rule({ name = "windowrule-94", opacity = "0.8 0.7", match = { class = "^(gedit|org.gnome.TextEditor|mousepad)$" } })
hl.window_rule({ name = "windowrule-95", opacity = "0.9 0.8", match = { class = "^(deluge)$" } })
hl.window_rule({ name = "windowrule-96", opacity = "0.9 0.8", match = { class = "^(seahorse)$" } }) -- gnome-keyring gui
-- hl.window_rule({ opacity = "0.95 0.75", match = { title = "^(Picture-in-Picture)$" } })
-- SIZE
-- hl.window_rule({ size = "25% 25%", match = { title = "^(Firefox)$" } })
-- PINNING
-- hl.window_rule({ pin = true, match = { title = "^(Firefox)$" } })
-- windowrule - extras
-- BLUR & FULLSCREEN
-- hl.window_rule({ active_border_color = "rgb(EE4B55)", inactive_border_color = "rgb(880808)", match = { fullscreen = true } })
-- hl.window_rule({ active_border_color = "rgb(282737)", inactive_border_color = "rgb(1E1D2D)", match = { float = true } })
-- hl.window_rule({ opacity = "0.8 0.8", match = { pin = true } })
--------------------------------------------------------------------------------------
-- LAYER RULES
--------------------------------------------------------------------------------------
hl.layer_rule({ name = "layerrule-1", blur = true, ignore_alpha = 0, match = { namespace = "rofi" } })
hl.layer_rule({ name = "layerrule-2", blur = true, ignore_alpha = 0, match = { namespace = "notifications" } })
hl.layer_rule({ name = "layerrule-3", blur = true, ignore_alpha = 0.5, match = { namespace = "quickshell:overview" } })
-- hl.layer_rule({ ignore_alpha = 0.5, match = { tag = "notif*" } })
-- hl.layer_rule({ no_anim = true (unset), match = { class = "^([Rr]ofi)$" } })
-- hl.layer_rule({ blur = true, match = { class = "^([Rr]ofi)$" } })
-- hl.layer_rule({ ignore_alpha = 0, match = { class = "<rofi>" } })
-- hl.layer_rule({ ignore_alpha = 0, match = { namespace = "overview" } })
-- hl.layer_rule({ blur = true, match = { namespace = "overview" } })

View File

@@ -0,0 +1,29 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- NOTE: This will be overwritten by NWG-Displays
-- once you use it and click apply.
-- (nwg-displays already supports writing this file directly as workspaces.lua once
-- hyprland.lua is active, so re-running it should stay compatible.)
--
-- Ported from UserConfigs/workspaces.conf
-- You can set workspace rules to achieve workspace-specific behaviors.
-- For instance, you can define a workspace where all windows are drawn without borders or gaps.
-- https://wiki.hypr.land/Configuring/Basics/Workspace-Rules/
-- Assigning workspace to a certain monitor. Below are just examples
hl.workspace_rule({ workspace = 1, monitor = "DP-2", default = true })
hl.workspace_rule({ workspace = 2, monitor = "DP-1", default = true })
hl.workspace_rule({ workspace = 3, monitor = "HDMI-A-1", default = true })
-- hl.workspace_rule({ workspace = 4, monitor = "eDP-1" })
-- hl.workspace_rule({ workspace = 5, monitor = "DP-2" })
-- hl.workspace_rule({ workspace = 6, monitor = "DP-2" })
-- hl.workspace_rule({ workspace = 7, monitor = "DP-2" })
-- hl.workspace_rule({ workspace = 8, monitor = "DP-2" })
-- example rules (from wiki)
-- hl.workspace_rule({ workspace = 3, rounding = false, decorate = false })
-- hl.workspace_rule({ workspace = "name:coding", rounding = false, decorate = false, gaps_in = 0, gaps_out = 0, border = false, monitor = "DP-1" })
-- hl.workspace_rule({ workspace = 8, border_size = 8 })
-- hl.workspace_rule({ workspace = "name:Hello", monitor = "DP-1", default = true })
-- hl.workspace_rule({ workspace = "name:gaming", monitor = "desc:Chimei Innolux Corporation 0x150C", default = true })
-- hl.workspace_rule({ workspace = 5, on_created_empty = "[float] firefox" })
-- hl.workspace_rule({ workspace = "special:scratchpad", on_created_empty = "foot" })

View File

@@ -0,0 +1,22 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Ported from animations/00-default.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 5, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "smoothOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 100, bezier = "liner", style = "loop" }) -- used by rainbow borders and rotating colors; speed clamped from 180 -> Lua hl.animation caps speed at 100
hl.animation({ leaf = "fade", enabled = true, speed = 3, bezier = "smoothOut" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "overshot" })
-- animations for -git or version >0.42.0
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 5, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })

View File

@@ -0,0 +1,25 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- old animations
-- Ported from animations/01-default - v2.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("myBezier", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("linear", { type = "bezier", points = { { 0.0, 0.0 }, { 1.0, 1.0 } } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("slow", { type = "bezier", points = { { 0, 0.85 }, { 0.3, 1 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.7, 0.6 }, { 0.1, 1.1 } } })
hl.curve("bounce", { type = "bezier", points = { { 1.1, 1.6 }, { 0.1, 0.85 } } })
hl.curve("sligshot", { type = "bezier", points = { { 1, -1 }, { 0.15, 1.25 } } })
hl.curve("nice", { type = "bezier", points = { { 0, 6.9 }, { 0.5, -4.20 } } })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 5, bezier = "slow", style = "popin" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "popin" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "linear" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 100, bezier = "linear", style = "loop" }) -- used by rainbow borders and rotating colors; speed clamped from 180 -> Lua hl.animation caps speed at 100
hl.animation({ leaf = "fade", enabled = true, speed = 5, bezier = "overshot" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })
hl.animation({ leaf = "windows", enabled = true, speed = 5, bezier = "bounce", style = "popin" })

View File

@@ -0,0 +1,4 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- Ported from animations/03- Disable Animation.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = false } })

View File

@@ -0,0 +1,39 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "End-4"
-- credit https://github.com/end-4/dots-hyprland
-- Ported from animations/END-4.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
-- Animation curves
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
hl.curve("md3_standard", { type = "bezier", points = { { 0.2, 0 }, { 0, 1 } } })
hl.curve("md3_decel", { type = "bezier", points = { { 0.05, 0.7 }, { 0.1, 1 } } })
hl.curve("md3_accel", { type = "bezier", points = { { 0.3, 0 }, { 0.8, 0.15 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.1 } } })
hl.curve("crazyshot", { type = "bezier", points = { { 0.1, 1.5 }, { 0.76, 0.92 } } })
hl.curve("hyprnostretch", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.0 } } })
hl.curve("menu_decel", { type = "bezier", points = { { 0.1, 1 }, { 0, 1 } } })
hl.curve("menu_accel", { type = "bezier", points = { { 0.38, 0.04 }, { 1, 0.07 } } })
hl.curve("easeInOutCirc", { type = "bezier", points = { { 0.85, 0 }, { 0.15, 1 } } })
hl.curve("easeOutCirc", { type = "bezier", points = { { 0, 0.55 }, { 0.45, 1 } } })
hl.curve("easeOutExpo", { type = "bezier", points = { { 0.16, 1 }, { 0.3, 1 } } })
hl.curve("softAcDecel", { type = "bezier", points = { { 0.26, 0.26 }, { 0.15, 1 } } })
hl.curve("md2", { type = "bezier", points = { { 0.4, 0 }, { 0.2, 1 } } }) -- use with .2s duration
-- Animation configs
hl.animation({ leaf = "windows", enabled = true, speed = 3, bezier = "md3_decel", style = "popin 60%" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 3, bezier = "md3_decel", style = "popin 60%" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "md3_accel", style = "popin 60%" })
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 3, bezier = "md3_decel" })
-- hl.animation({ leaf = "layers", enabled = true, speed = 2, bezier = "md3_decel", style = "slide" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 3, bezier = "menu_decel", style = "slide" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.6, bezier = "menu_accel" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 2, bezier = "menu_decel" })
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 4.5, bezier = "menu_accel" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 7, bezier = "menu_decel", style = "slide" })
-- hl.animation({ leaf = "workspaces", enabled = true, speed = 2.5, bezier = "softAcDecel", style = "slide" })
-- hl.animation({ leaf = "workspaces", enabled = true, speed = 7, bezier = "menu_decel", style = "slidefade 15%" })
-- hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "md3_decel", style = "slidefadevert 15%" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "md3_decel", style = "slidevert" })

View File

@@ -0,0 +1,21 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Default"
-- credit https://github.com/prasanthrangan/hyprdots
-- Ported from animations/HYDE - default.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 30, bezier = "liner", style = "once" })
hl.animation({ leaf = "fade", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 5, bezier = "wind", style = "slidevert" })

View File

@@ -0,0 +1,20 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Minimal-1"
-- credit https://github.com/prasanthrangan/hyprdots
-- Ported from animations/HYDE - minimal-1.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 30, bezier = "liner", style = "once" })
hl.animation({ leaf = "fade", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })

View File

@@ -0,0 +1,14 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Minimal-2"
-- credit https://github.com/prasanthrangan/hyprdots
-- Ported from animations/HYDE - minimal-2.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("quart", { type = "bezier", points = { { 0.25, 1 }, { 0.5, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "quart", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 6, bezier = "quart" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 6, bezier = "quart" })
hl.animation({ leaf = "fade", enabled = true, speed = 6, bezier = "quart" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "quart" })

View File

@@ -0,0 +1,40 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Optimized"
-- credit https://github.com/prasanthrangan/hyprdots
-- Ported from animations/HYDE - optimized.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.85 }, { 0.03, 0.97 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.07, 0.88 }, { 0.04, 0.99 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.20, -0.15 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.curve("md3_standard", { type = "bezier", points = { { 0.12, 0 }, { 0, 1 } } })
hl.curve("md3_decel", { type = "bezier", points = { { 0.05, 0.80 }, { 0.10, 0.97 } } })
hl.curve("md3_accel", { type = "bezier", points = { { 0.20, 0 }, { 0.80, 0.08 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.85 }, { 0.07, 1.04 } } })
hl.curve("crazyshot", { type = "bezier", points = { { 0.1, 1.22 }, { 0.68, 0.98 } } })
hl.curve("hyprnostretch", { type = "bezier", points = { { 0.05, 0.82 }, { 0.03, 0.94 } } })
hl.curve("menu_decel", { type = "bezier", points = { { 0.05, 0.82 }, { 0, 1 } } })
hl.curve("menu_accel", { type = "bezier", points = { { 0.20, 0 }, { 0.82, 0.10 } } })
hl.curve("easeInOutCirc", { type = "bezier", points = { { 0.75, 0 }, { 0.15, 1 } } })
hl.curve("easeOutCirc", { type = "bezier", points = { { 0, 0.48 }, { 0.38, 1 } } })
hl.curve("easeOutExpo", { type = "bezier", points = { { 0.10, 0.94 }, { 0.23, 0.98 } } })
hl.curve("softAcDecel", { type = "bezier", points = { { 0.20, 0.20 }, { 0.15, 1 } } })
hl.curve("md2", { type = "bezier", points = { { 0.30, 0 }, { 0.15, 1 } } })
hl.curve("OutBack", { type = "bezier", points = { { 0.28, 1.40 }, { 0.58, 1 } } })
hl.curve("easeInOutCirc", { type = "bezier", points = { { 0.78, 0 }, { 0.15, 1 } } })
hl.animation({ leaf = "border", enabled = true, speed = 1.6, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 82, bezier = "liner", style = "once" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 3.2, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 2.8, bezier = "easeOutCirc" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 3.0, bezier = "wind", style = "slide" })
hl.animation({ leaf = "fade", enabled = true, speed = 1.8, bezier = "md3_decel" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 1.8, bezier = "menu_decel", style = "slide" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "menu_accel" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.6, bezier = "menu_decel" })
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.8, bezier = "menu_accel" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 4.0, bezier = "menu_decel", style = "slide" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 2.3, bezier = "md3_decel", style = "slidefadevert 15%" })

View File

@@ -0,0 +1,32 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Vertical"
-- credit https://github.com/prasanthrangan/hyprdots
-- Ported from animations/HYDE - Vertical.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("fluent_decel", { type = "bezier", points = { { 0, 0.2 }, { 0.4, 1 } } })
hl.curve("easeOutCirc", { type = "bezier", points = { { 0, 0.55 }, { 0.45, 1 } } })
hl.curve("easeOutCubic", { type = "bezier", points = { { 0.33, 1 }, { 0.68, 1 } } })
hl.curve("easeinoutsine", { type = "bezier", points = { { 0.37, 0 }, { 0.63, 1 } } })
-- Windows
hl.animation({ leaf = "windowsIn", enabled = true, speed = 1.5, bezier = "easeinoutsine", style = "popin 60%" }) -- window open
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.5, bezier = "easeOutCubic", style = "popin 60%" }) -- window close.
hl.animation({ leaf = "windowsMove", enabled = true, speed = 1.5, bezier = "easeinoutsine", style = "slide" }) -- everything in between, moving, dragging, resizing.
-- Fading
hl.animation({ leaf = "fade", enabled = true, speed = 2.5, bezier = "fluent_decel" })
hl.animation({ leaf = "fadeLayersIn", enabled = false })
hl.animation({ leaf = "border", enabled = false })
-- Layers
hl.animation({ leaf = "layers", enabled = true, speed = 1.5, bezier = "easeinoutsine", style = "popin" })
-- Workspaces
-- styles: slide, slidevert, fade, slidefade, slidefadevert
-- hl.animation({ leaf = "workspaces", enabled = true, speed = 3, bezier = "fluent_decel", style = "slidefade 30%" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 3, bezier = "fluent_decel", style = "slidefadevert 30%" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 2, bezier = "fluent_decel", style = "slidefade 10%" })

View File

@@ -0,0 +1,52 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Me-1"
-- credit https://github.com/mahaveergurjar
-- Ported from animations/Mahaveer - me-1.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
-- Animation curves
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
-- hl.curve("linear", { type = "bezier", points = { {0,0}, {1,1} } })
hl.curve("md3_standard", { type = "bezier", points = { { 0.2, 0 }, { 0, 1 } } })
hl.curve("md3_decel", { type = "bezier", points = { { 0.05, 0.7 }, { 0.1, 1 } } })
hl.curve("md3_accel", { type = "bezier", points = { { 0.3, 0 }, { 0.8, 0.15 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.1 } } })
hl.curve("crazyshot", { type = "bezier", points = { { 0.1, 1.5 }, { 0.76, 0.92 } } })
hl.curve("hyprnostretch", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.0 } } })
hl.curve("menu_decel", { type = "bezier", points = { { 0.1, 1 }, { 0, 1 } } })
hl.curve("menu_accel", { type = "bezier", points = { { 0.38, 0.04 }, { 1, 0.07 } } })
hl.curve("easeInOutCirc", { type = "bezier", points = { { 0.85, 0 }, { 0.15, 1 } } })
hl.curve("easeOutCirc", { type = "bezier", points = { { 0, 0.55 }, { 0.45, 1 } } })
hl.curve("easeOutExpo", { type = "bezier", points = { { 0.16, 1 }, { 0.3, 1 } } })
hl.curve("softAcDecel", { type = "bezier", points = { { 0.26, 0.26 }, { 0.15, 1 } } })
hl.curve("md2", { type = "bezier", points = { { 0.4, 0 }, { 0.2, 1 } } }) -- use with .2s duration
-- Animation configs
-- hl.animation({ leaf = "windows", enabled = true, speed = 3, bezier = "md3_decel", style = "popin 60%" })
-- hl.animation({ leaf = "windowsIn", enabled = true, speed = 3, bezier = "md3_decel", style = "popin 60%" })
-- hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "md3_accel", style = "popin 60%" })
-- hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 30, bezier = "liner", style = "once" })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
-- hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 3, bezier = "md3_decel" })
-- hl.animation({ leaf = "layers", enabled = true, speed = 2, bezier = "md3_decel", style = "slide" })
hl.animation({ leaf = "layersIn", enabled = true, speed = 3, bezier = "menu_decel", style = "slide" })
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.6, bezier = "menu_accel" })
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 2, bezier = "menu_decel" })
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 4.5, bezier = "menu_accel" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 7, bezier = "menu_decel", style = "slide" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })
-- hl.animation({ leaf = "workspaces", enabled = true, speed = 2.5, bezier = "softAcDecel", style = "slide" })
-- hl.animation({ leaf = "workspaces", enabled = true, speed = 7, bezier = "menu_decel", style = "slidefade 15%" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "md3_decel", style = "slidefadevert 15%" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "md3_decel", style = "slidevert" })

View File

@@ -0,0 +1,27 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Me-2"
-- credit https://github.com/mahaveergurjar
-- Ported from animations/Mahaveer - me-2.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("smoothOut", { type = "bezier", points = { { 0.5, 0 }, { 0.99, 0.99 } } })
hl.curve("smoothIn", { type = "bezier", points = { { 0.5, -0.5 }, { 0.68, 1.5 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 5, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "smoothOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 100, bezier = "liner", style = "loop" }) -- used by rainbow borders and rotating colors; speed clamped from 180 -> Lua hl.animation caps speed at 100
hl.animation({ leaf = "fade", enabled = true, speed = 3, bezier = "smoothOut" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "overshot" })
-- animations for -git or version >0.42.0
hl.animation({ leaf = "workspacesIn", enabled = true, speed = 5, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "workspacesOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })

View File

@@ -0,0 +1,15 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Classic"
-- credit https://github.com/mylinuxforwork/dotfiles
-- Ported from animations/ML4W - classic.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("myBezier", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 7, bezier = "myBezier" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 7, bezier = "default", style = "popin 80%" })
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 8, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 7, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "default" })

View File

@@ -0,0 +1,20 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Dynamic"
-- credit https://github.com/mylinuxforwork/dotfiles
-- Ported from animations/ML4W - dynamic.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 30, bezier = "liner", style = "loop" })
hl.animation({ leaf = "fade", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })

View File

@@ -0,0 +1,24 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Fast"
-- credit https://github.com/mylinuxforwork/dotfiles
-- Ported from animations/ML4W - fast.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
hl.curve("md3_standard", { type = "bezier", points = { { 0.2, 0 }, { 0, 1 } } })
hl.curve("md3_decel", { type = "bezier", points = { { 0.05, 0.7 }, { 0.1, 1 } } })
hl.curve("md3_accel", { type = "bezier", points = { { 0.3, 0 }, { 0.8, 0.15 } } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.1 } } })
hl.curve("crazyshot", { type = "bezier", points = { { 0.1, 1.5 }, { 0.76, 0.92 } } })
hl.curve("hyprnostretch", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.0 } } })
hl.curve("fluent_decel", { type = "bezier", points = { { 0.1, 1 }, { 0, 1 } } })
hl.curve("easeInOutCirc", { type = "bezier", points = { { 0.85, 0 }, { 0.15, 1 } } })
hl.curve("easeOutCirc", { type = "bezier", points = { { 0, 0.55 }, { 0.45, 1 } } })
hl.curve("easeOutExpo", { type = "bezier", points = { { 0.16, 1 }, { 0.3, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 3, bezier = "md3_decel", style = "popin 60%" })
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 2.5, bezier = "md3_decel" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 3.5, bezier = "easeOutExpo", style = "slide" })
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "md3_decel", style = "slidevert" })

View File

@@ -0,0 +1,20 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "High"
-- credit https://github.com/mylinuxforwork/dotfiles
-- Ported from animations/ML4W - high.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("wind", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("winIn", { type = "bezier", points = { { 0.1, 1.1 }, { 0.1, 1.1 } } })
hl.curve("winOut", { type = "bezier", points = { { 0.3, -0.3 }, { 0, 1 } } })
hl.curve("liner", { type = "bezier", points = { { 1, 1 }, { 1, 1 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "wind", style = "slide" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "winIn", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "winOut", style = "slide" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 5, bezier = "wind", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 1, bezier = "liner" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 30, bezier = "liner", style = "once" })
hl.animation({ leaf = "fade", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 5, bezier = "wind" })

View File

@@ -0,0 +1,19 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Moving"
-- credit https://github.com/mylinuxforwork/dotfiles
-- Ported from animations/ML4W - moving.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("overshot", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.curve("smoothOut", { type = "bezier", points = { { 0.5, 0 }, { 0.99, 0.99 } } })
hl.curve("smoothIn", { type = "bezier", points = { { 0.5, -0.5 }, { 0.68, 1.5 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 5, bezier = "overshot", style = "slide" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 3, bezier = "smoothOut" })
hl.animation({ leaf = "windowsIn", enabled = true, speed = 3, bezier = "smoothOut" })
hl.animation({ leaf = "windowsMove", enabled = true, speed = 4, bezier = "smoothIn", style = "slide" })
hl.animation({ leaf = "border", enabled = true, speed = 5, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 5, bezier = "smoothIn" })
hl.animation({ leaf = "fadeDim", enabled = true, speed = 5, bezier = "smoothIn" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "default" })

View File

@@ -0,0 +1,15 @@
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
-- name "Standard"
-- credit https://github.com/mylinuxforwork/dotfiles
-- Ported from animations/ML4W - standard.conf (unused preset — not required by anything)
hl.config({ animations = { enabled = true } })
hl.curve("myBezier", { type = "bezier", points = { { 0.05, 0.9 }, { 0.1, 1.05 } } })
hl.animation({ leaf = "windows", enabled = true, speed = 7, bezier = "myBezier" })
hl.animation({ leaf = "windowsOut", enabled = true, speed = 7, bezier = "default", style = "popin 80%" })
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
hl.animation({ leaf = "borderangle", enabled = true, speed = 8, bezier = "default" })
hl.animation({ leaf = "fade", enabled = true, speed = 7, bezier = "default" })
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "default" })

View File

@@ -0,0 +1,73 @@
#!/bin/bash
# Float + resize (to 781x702) + center the Bitwarden browser-extension pop-out window.
#
# Why this exists instead of a window rule: Hyprland's `float`/`center`/`size`
# rules are "static" — evaluated exactly once, at the instant a window is
# created — and this popup opens with the generic title "Zen Browser" (same
# initialTitle/initialClass as the main browser window), only updating to
# "Extension: (Bitwarden Password Manager) - Bitwarden..." a moment after
# it's already mapped. By then the static rule has already missed its one
# chance and there's no field available at creation time to tell the popup
# apart from a normal browser window. See layout/windowrules.lua and
# https://github.com/hyprwm/Hyprland/issues/7402 for the same problem class.
#
# No socat/ncat is installed, so instead of an event-driven IPC listener this
# just polls `hyprctl clients` at a short interval with jq (already
# installed).
#
# Dispatch syntax note: Hyprland 0.55+'s Lua config changed `hyprctl dispatch`
# to take a literal Lua expression calling hl.dsp.* (e.g.
# `hyprctl dispatch 'hl.dsp.window.center()'`), not the old
# "centerwindow"-style dispatcher strings. Verified live against the real
# API in /usr/share/hypr/stubs/hl.meta.lua before wiring this in.
#
# Why it re-asserts for a few seconds instead of firing once: right after the
# popup leaves the tiled layout, Hyprland briefly re-tiles the sibling window
# to fill the freed space, and during that reflow this popup was observed
# reporting `floating: false` again for one poll tick — snapping it back to
# its old tiled geometry. Re-asserting float+center for a short settle window
# after first detection rides that out. After the settle window it's left
# alone, so a manual re-tile/move by you later isn't fought.
TITLE_PATTERN='Extension: \(Bitwarden Password Manager\)'
POLL_INTERVAL=0.3
SETTLE_TICKS=6 # ~2s of re-assertion after first detection, at POLL_INTERVAL
TARGET_WIDTH=781
TARGET_HEIGHT=702
declare -A ticks_left # address -> remaining re-assert ticks
while true; do
sleep "$POLL_INTERVAL"
# Forget any tracked window that's no longer open.
open_addresses=$(hyprctl clients -j | jq -r '.[].address')
for addr in "${!ticks_left[@]}"; do
grep -qF "$addr" <<<"$open_addresses" || unset 'ticks_left[$addr]'
done
# Process substitution (not a pipe) so the while loop runs in this shell
# and its writes to ticks_left actually persist across iterations.
while IFS= read -r win; do
title=$(jq -r '.title' <<<"$win")
[[ "$title" =~ $TITLE_PATTERN ]] || continue
address=$(jq -r '.address' <<<"$win")
floating=$(jq -r '.floating' <<<"$win")
# First time we've seen this window: start its settle window.
[[ -v ticks_left[$address] ]] || ticks_left[$address]=$SETTLE_TICKS
# Already floating and past its settle window: leave it alone.
[[ "$floating" == "true" && ${ticks_left[$address]} -le 0 ]] && continue
hyprctl dispatch "hl.dsp.focus({ window = \"address:$address\" })" >/dev/null
hyprctl dispatch 'hl.dsp.window.float({ action = "enable" })' >/dev/null
# resize({x=W,y=H}) sets the absolute size (not a delta) — verified live.
# Must happen before center(), since resizing shifts what "centered" means.
hyprctl dispatch "hl.dsp.window.resize({ x = $TARGET_WIDTH, y = $TARGET_HEIGHT })" >/dev/null
hyprctl dispatch 'hl.dsp.window.center()' >/dev/null
ticks_left[$address]=$((ticks_left[$address] - 1))
done < <(hyprctl clients -j | jq -c '.[] | {address, title, floating}')
done

View File

@@ -0,0 +1,25 @@
pragma Singleton
import QtQuick
QtObject {
readonly property string fontFamily: "CaskaydiaCove Nerd Font Mono"
readonly property string iconFontFamily: "Material Symbols Outlined"
// Material Symbols is a variable font. Left unset, glyphs fall back to the
// font's default optical size (24px), which is tuned for larger rendering
// and makes some icons (e.g. "lock") look heavier/bigger than others when
// drawn small — pin "opsz" near the actual render size to keep icons
// visually consistent. See Appearance.iconSize for the matching pixelSize.
readonly property var iconFontAxes: ({ "FILL": 0, "wght": 400, "GRAD": 0, "opsz": 20 })
readonly property int iconSize: 16
readonly property int fontSize: 13
readonly property int fontSizeSmall: 11
readonly property int radius: 14
readonly property int radiusSmall: 8
readonly property int barHeight: 34
readonly property int spacing: 10
readonly property int animationDuration: 150
}

View File

@@ -0,0 +1,62 @@
pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
// Wallpaper-driven palette, sourced from pywal's ~/.cache/wal/colors.json.
// Run `wal -i /path/to/wallpaper.png` to (re)generate it — this file watches
// the cache and updates live, no shell restart needed. Until that's been run
// once, everything falls back to a hand-picked dark palette below.
QtObject {
id: root
readonly property bool active: _parsed !== null
property var _parsed: null
// --- Fallback palette (used until pywal has run) ---
readonly property color fallbackBg: "#1a1b26"
readonly property color fallbackFg: "#c0caf5"
readonly property var fallbackPalette: [
"#15161e", "#f7768e", "#9ece6a", "#e0af68",
"#7aa2f7", "#bb9af7", "#7dcfff", "#a9b1d6",
"#414868", "#f7768e", "#9ece6a", "#e0af68",
"#7aa2f7", "#bb9af7", "#7dcfff", "#c0caf5"
]
function paletteColor(i) {
if (_parsed && _parsed.colors)
return _parsed.colors["color" + i] || fallbackPalette[i]
return fallbackPalette[i]
}
readonly property color background: _parsed && _parsed.special ? _parsed.special.background : fallbackBg
readonly property color foreground: _parsed && _parsed.special ? _parsed.special.foreground : fallbackFg
readonly property color primary: paletteColor(4)
// Named primaryText (not "onPrimary") because QML treats any "onXxx"
// identifier as a signal handler slot, not a regular property.
readonly property color primaryText: background
readonly property color urgent: paletteColor(1)
// paletteColor(8) alone renders quite dark/low-contrast for text (it's
// meant as a muted *background* tone in most wal palettes) — lighten it
// so muted labels stay legible against the bar's dark background.
readonly property color muted: Qt.lighter(paletteColor(8), 2.2)
readonly property color surface: Qt.lighter(background, 1.35)
// QtObject has no default property, so the FileView is attached via a
// property assignment rather than as a plain child element.
readonly property FileView _walFile: FileView {
path: Quickshell.env("HOME") + "/.cache/wal/colors.json"
watchChanges: true
onLoaded: {
try {
root._parsed = JSON.parse(text())
} catch (e) {
console.warn("Colors: failed to parse pywal cache:", e)
root._parsed = null
}
}
onLoadFailed: (error) => {
root._parsed = null
}
}
}

View File

@@ -0,0 +1,16 @@
pragma Singleton
import QtQuick
// Tailscale, Mullvad, and ProtonVPN all fight over the default route/DNS if
// more than one is active at once (see VpnIndicator.qml's header comment) —
// only one of the three should ever actually be connected. This singleton
// is the shared signal between the two widget files: VpnIndicator.qml
// writes Mullvad/ProtonVPN's live connection state here as it polls them;
// TailscaleIndicator.qml reads it to back off (`tailscale down`) rather
// than stay up alongside either one, and to refuse turning Tailscale back
// on while one is still connected.
QtObject {
property bool mullvadConnected: false
property bool protonConnected: false
readonly property bool commercialVpnActive: mullvadConnected || protonConnected
}

View File

@@ -0,0 +1,4 @@
module config
singleton Colors 1.0 Colors.qml
singleton Appearance 1.0 Appearance.qml
singleton VpnExclusivity 1.0 VpnExclusivity.qml

View File

@@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import "../../config"
Text {
Layout.maximumWidth: 320
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
text: Hyprland.activeToplevel ? Hyprland.activeToplevel.title : ""
}

View File

@@ -0,0 +1,57 @@
import Quickshell
import Quickshell.Wayland
import QtQuick
import QtQuick.Layouts
import "../../config"
PanelWindow {
id: bar
anchors {
top: true
left: true
right: true
}
implicitHeight: Appearance.barHeight
exclusiveZone: implicitHeight
color: "transparent"
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "quickshell:bar"
Rectangle {
anchors.fill: parent
color: Colors.background
}
RowLayout {
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
leftMargin: Appearance.spacing
}
spacing: Appearance.spacing
Workspaces { screen: bar.screen }
ActiveWindow {}
}
Clock {
anchors.centerIn: parent
}
RowLayout {
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: Appearance.spacing
}
spacing: Appearance.spacing
//Tray {}
TailscaleIndicator { barWindow: bar }
VpnIndicator { barWindow: bar }
NetworkIndicator { barWindow: bar }
BluetoothIndicator { barWindow: bar }
Volume { barWindow: bar }
PowerButton { barWindow: bar }
}
}

View File

@@ -0,0 +1,489 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Bluetooth
import "../../config"
Item {
id: root
implicitWidth: label.implicitWidth + 4
implicitHeight: Appearance.barHeight
// The bar's own PanelWindow — passed in from Bar.qml so this popup can
// anchor to the bar itself rather than to this icon, keeping it a fixed
// distance from the bar and the screen edge regardless of where this
// icon sits in the row.
required property var barWindow
readonly property var adapter: Bluetooth.defaultAdapter
// Scan only while the panel is open — same convention as the WiFi
// scannerEnabled binding in NetworkIndicator.qml.
Binding {
target: root.adapter
property: "discovering"
value: panel.visible && root.adapter.enabled
when: root.adapter !== null
}
// --- Device row helpers ----------------------------------------------
// Rows are plain objects rather than the live BluetoothDevice QObject —
// BlueZ churn (pairing timeouts, a device dropping out of range mid-scan)
// can destroy the object while a delegate still holds it, the same
// crash risk noted for WiFi networks in NetworkIndicator.qml. The actual
// object is re-resolved by address only when an action needs it, via
// deviceForAddress().
function deviceLabel(d) {
return (d.deviceName || d.name || d.address || "").trim()
}
// BlueZ's Icon property is a freedesktop icon-theme name (verified
// against a real paired device on this machine: "audio-headset") — map
// the common categories to a Material Symbols glyph, falling back to a
// generic "devices_other" for anything unrecognized.
function deviceIcon(icon) {
const i = icon || ""
if (i.indexOf("headset") !== -1 || i.indexOf("headphone") !== -1) return "headphones"
if (i.indexOf("mouse") !== -1) return "mouse"
if (i.indexOf("keyboard") !== -1) return "keyboard"
if (i.indexOf("gaming") !== -1 || i.indexOf("joystick") !== -1) return "sports_esports"
if (i.indexOf("tablet") !== -1) return "tablet"
if (i.indexOf("camera") !== -1) return "videocam"
if (i.indexOf("printer") !== -1) return "print"
if (i.indexOf("watch") !== -1) return "watch"
if (i.indexOf("tv") !== -1) return "tv"
if (i.indexOf("computer") !== -1) return "computer"
if (i.indexOf("card") !== -1) return "speaker"
return "devices_other"
}
function deviceRow(d) {
if (!d) return null
const label = root.deviceLabel(d)
if (!label) return null
return {
address: d.address || "",
label: label,
icon: root.deviceIcon(d.icon),
connected: !!d.connected,
paired: !!d.paired,
bonded: !!d.bonded,
trusted: !!d.trusted,
pairing: !!d.pairing,
batteryAvailable: !!d.batteryAvailable,
battery: d.battery || 0
}
}
readonly property var deviceRows: root.adapter
? root.adapter.devices.values.map(root.deviceRow).filter(function(r) { return r !== null })
: []
readonly property var connectedRows: root.deviceRows.filter(function(r) { return r.connected })
readonly property var knownRows: root.deviceRows.filter(function(r) {
return !r.connected && (r.paired || r.bonded || r.trusted)
})
readonly property var discoveredRows: root.deviceRows.filter(function(r) {
return !r.connected && !r.paired && !r.bonded && !r.trusted
})
function deviceForAddress(address) {
if (!root.adapter) return null
const devs = root.adapter.devices.values
for (let i = 0; i < devs.length; i++) {
if (devs[i].address === address) return devs[i]
}
return null
}
function connectDevice(row) {
const d = root.deviceForAddress(row.address)
if (!d) return
// Already-paired devices just reconnect; a fresh discovery result
// has to pair first (BlueZ's own agent handles the SSP/passkey
// handshake — there's no PIN-entry surface exposed by this API).
if (row.paired || row.bonded || row.trusted) d.connect()
else d.pair()
}
function disconnectDevice(row) {
const d = root.deviceForAddress(row.address)
if (d) d.disconnect()
}
function forgetDevice(row) {
const d = root.deviceForAddress(row.address)
if (!d) return
if (d.connected) d.disconnect()
d.forget()
}
readonly property bool anyConnected: root.connectedRows.length > 0
// Caps how tall the Paired/Available list gets before it scrolls
// instead of continuing to grow the popup — same convention as
// NetworkIndicator.qml's Known/Other Networks list.
readonly property int maxDeviceListHeight: 260
Text {
id: label
anchors.centerIn: parent
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: (root.adapter && root.adapter.enabled) ? Colors.foreground : Colors.muted
text: {
if (!root.adapter || !root.adapter.enabled) return "bluetooth_disabled"
return root.anyConnected ? "bluetooth_connected" : "bluetooth"
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
panel.visible = !panel.visible
grab.active = panel.visible
}
}
// --- Reusable pieces ---------------------------------------------------
component ToggleSwitch: Item {
id: toggleRoot
property bool checked: false
signal toggled()
implicitWidth: 34
implicitHeight: 18
Rectangle {
anchors.fill: parent
radius: 0
color: toggleRoot.checked ? Colors.primary : Colors.muted
opacity: toggleRoot.checked ? 1 : 0.35
}
Rectangle {
width: parent.height - 4
height: parent.height - 4
radius: 0
color: Colors.background
anchors.verticalCenter: parent.verticalCenter
x: toggleRoot.checked ? parent.width - width - 2 : 2
Behavior on x { NumberAnimation { duration: Appearance.animationDuration } }
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: toggleRoot.toggled()
}
}
// One device row. `showForget` hides the forget (x) button for
// discovered-but-unpaired devices, which have nothing to forget yet.
component DeviceRow: Rectangle {
id: devRow
required property var dev
property bool showForget: true
signal activate()
width: parent ? parent.width : 0
radius: 0
implicitHeight: rowContent.implicitHeight + 12
color: devRow.dev.connected
? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18)
: (rowMouse.containsMouse ? Colors.primary : "transparent")
RowLayout {
id: rowContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 6
spacing: 8
Text {
Layout.preferredWidth: 20
Layout.minimumWidth: 20
Layout.maximumWidth: 20
horizontalAlignment: Text.AlignLeft
text: devRow.dev.icon
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
Text {
Layout.fillWidth: true
text: devRow.dev.label
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSize
font.bold: devRow.dev.connected
color: rowMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
Text {
readonly property string statusText: {
if (devRow.dev.pairing) return "Pairing…"
if (devRow.dev.connected && devRow.dev.batteryAvailable)
return Math.round(devRow.dev.battery * 100) + "%"
if (devRow.dev.connected) return "Connected"
return ""
}
visible: statusText !== ""
text: statusText
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
Text {
id: forgetIcon
visible: devRow.showForget
text: "close"
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.muted
}
}
MouseArea {
id: rowMouse
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
height: rowContent.height + 12
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
// forgetIcon has no MouseArea of its own — nested inside
// rowContent, which sits underneath this full-row area in
// stacking order, so a separate MouseArea there would just be
// shadowed (see the identical fix in NetworkIndicator.qml's
// NetworkRow). Dispatch by click position against this single
// area instead.
onClicked: (mouse) => {
if (devRow.showForget && forgetIcon.visible) {
const forgetLeft = devRow.mapFromItem(forgetIcon, 0, 0).x
if (mouse.x >= forgetLeft - 8) {
root.forgetDevice(devRow.dev)
return
}
}
devRow.activate()
}
}
}
// --- Panel -------------------------------------------------------------
PopupWindow {
id: panel
visible: false
color: "transparent"
readonly property int contentWidth: 244
readonly property int contentPadding: 16
implicitWidth: contentWidth + contentPadding * 2
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
// Connected/paired/discovered devices come and go while the panel
// is open — animate the resize instead of snapping, same as the
// network panel's WiFi list.
Behavior on implicitHeight {
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
}
anchor {
window: root.barWindow
rect.x: 0
rect.y: 0
rect.width: root.barWindow.width
rect.height: root.barWindow.height
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins.bottom: -4
margins.right: 0
}
HyprlandFocusGrab {
id: grab
windows: [panel]
onCleared: panel.visible = false
}
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 3
Column {
id: mainColumn
anchors.fill: parent
anchors.margins: panel.contentPadding
spacing: 10
// Header
RowLayout {
width: parent.width
spacing: 8
Text {
text: label.text
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 30
color: Colors.foreground
}
Text {
Layout.fillWidth: true
text: "Bluetooth"
font.family: Appearance.fontFamily
font.pixelSize: 18
font.bold: true
color: Colors.foreground
}
ToggleSwitch {
visible: root.adapter !== null
checked: root.adapter !== null && root.adapter.enabled
onToggled: root.adapter.enabled = !root.adapter.enabled
}
}
Text {
width: parent.width
visible: root.adapter === null
text: "No Bluetooth adapter"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Text {
width: parent.width
visible: root.adapter !== null && !root.adapter.enabled
text: "Turn Bluetooth on to see devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
// Connected — always shown in full above the scrollable
// list, mirroring the network panel's layout.
Column {
width: parent.width
spacing: 4
visible: root.connectedRows.length > 0
Text {
text: "Connected"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.connectedRows
DeviceRow {
required property var modelData
dev: modelData
showForget: true
onActivate: root.disconnectDevice(dev)
}
}
}
}
Text {
width: parent.width
visible: root.adapter !== null && root.adapter.enabled && root.deviceRows.length === 0
text: (root.adapter && root.adapter.discovering) ? "Scanning for devices…" : "No devices found"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
// Paired/Available — capped height, scrolls once there are
// more devices than fit rather than growing the popup
// indefinitely.
Flickable {
id: deviceListFlick
width: parent.width
height: Math.min(deviceListColumn.implicitHeight, root.maxDeviceListHeight)
contentWidth: width
contentHeight: deviceListColumn.implicitHeight
clip: true
boundsBehavior: Flickable.StopAtBounds
visible: deviceListColumn.implicitHeight > 0
Column {
id: deviceListColumn
width: deviceListFlick.width
spacing: 6
Column {
width: parent.width
spacing: 4
visible: root.knownRows.length > 0
Text {
text: "Paired Devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.knownRows
DeviceRow {
required property var modelData
dev: modelData
showForget: true
onActivate: root.connectDevice(dev)
}
}
}
}
Column {
width: parent.width
spacing: 4
visible: root.adapter !== null && root.adapter.discovering && root.discoveredRows.length > 0
Text {
text: "Available Devices"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.discoveredRows
DeviceRow {
required property var modelData
dev: modelData
showForget: false
onActivate: root.connectDevice(dev)
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
import QtQuick
import "../../config"
Text {
id: root
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSize
font.bold: true
color: Colors.foreground
text: Qt.formatDateTime(new Date(), "ddd d MMM hh:mm")
Timer {
interval: 1000
running: true
repeat: true
onTriggered: root.text = Qt.formatDateTime(new Date(), "ddd d MMM hh:mm")
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,179 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
import "../../config"
Item {
id: root
implicitWidth: label.implicitWidth + 4
implicitHeight: Appearance.barHeight
// The bar's own PanelWindow — passed in from Bar.qml so this popup can
// anchor to the bar itself rather than to this icon, keeping it a fixed
// distance from the bar and the screen edge regardless of where this
// icon sits in the row.
required property var barWindow
Text {
id: label
anchors.centerIn: parent
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: Colors.foreground
text: "power_settings_new"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
menu.visible = !menu.visible
grab.active = menu.visible
}
}
Process { id: proc }
PopupWindow {
id: menu
visible: false
color: "transparent"
implicitWidth: 210
// Same padding as Volume.qml's panel.
readonly property int contentPadding: 16
readonly property int labelOffset: -135
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
anchor {
window: root.barWindow
// Unlike anchor.item, anchor.window doesn't default rect to the
// window's own bounds — set it explicitly so edges/gravity below
// resolve against the full bar (and thus the true screen edges)
// instead of a near-zero rect at the window's origin.
rect.x: 0
rect.y: 0
rect.width: root.barWindow.width
rect.height: root.barWindow.height
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins.bottom: -4
margins.right: 0
}
HyprlandFocusGrab {
id: grab
windows: [menu]
onCleared: menu.visible = false
}
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 3
Column {
id: mainColumn
anchors.fill: parent
anchors.margins: menu.contentPadding
spacing: 10
// Header — same title layout as Volume.qml's panel: the bar
// icon large, plus a bold title.
RowLayout {
id: header
width: parent.width
spacing: 8
Text {
text: label.text
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 30
color: Colors.foreground
}
Text {
Layout.fillWidth: true
text: "Power"
font.family: Appearance.fontFamily
font.pixelSize: 18
font.bold: true
color: Colors.foreground
}
}
Column {
width: parent.width
spacing: 2
Repeater {
id: list
model: [
{ label: "Lock", icon: "lock", cmd: ["hyprlock"] },
{ label: "Suspend", icon: "bedtime", cmd: ["systemctl", "suspend"] },
{ label: "Hibernate", icon: "ac_unit", cmd: ["systemctl", "hibernate"] },
{ label: "Log out", icon: "logout", cmd: ["hyprctl", "dispatch", "exit"] },
{ label: "Reboot", icon: "restart_alt", cmd: ["systemctl", "reboot"] },
{ label: "Shut down", icon: "power_settings_new", cmd: ["systemctl", "poweroff"] }
]
delegate: Rectangle {
id: entry
required property var modelData
width: parent ? parent.width : 0
height: 40
radius: Appearance.radiusSmall
color: (!entry.modelData.disabled && entryMouse.containsMouse) ? Colors.primary : "transparent"
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
spacing: 8
Text {
Layout.preferredWidth: 25
Layout.minimumWidth: 25
Layout.maximumWidth: 25
horizontalAlignment: Text.AlignLeft
text: entry.modelData.icon
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: entry.modelData.iconSize || 15
opacity: entry.modelData.disabled ? 0.4 : 1
color: entryMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
Text {
Layout.leftMargin: menu.labelOffset + (entry.modelData.textOffset || 0)
text: entry.modelData.label
horizontalAlignment: Text.AlignLeft
font.family: Appearance.fontFamily
font.pixelSize: 16
opacity: entry.modelData.disabled ? 0.4 : 1
color: entryMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
}
MouseArea {
id: entryMouse
anchors.fill: parent
enabled: !entry.modelData.disabled
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
proc.command = entry.modelData.cmd
proc.running = true
menu.visible = false
grab.active = false
}
}
}
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Services.SystemTray
import Quickshell.Widgets
import "../../config"
RowLayout {
spacing: Appearance.spacing * 0.6
Repeater {
model: SystemTray.items
delegate: IconImage {
id: trayIcon
required property var modelData
implicitWidth: 16
implicitHeight: 16
source: modelData.icon
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton)
trayIcon.modelData.activate()
else
trayIcon.modelData.secondaryActivate()
}
}
}
}
}

View File

@@ -0,0 +1,446 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Services.Pipewire
import "../../config"
Item {
id: root
implicitWidth: label.implicitWidth + 4
implicitHeight: Appearance.barHeight
// The bar's own PanelWindow — passed in from Bar.qml so this popup can
// anchor to the bar itself rather than to this icon, keeping it a fixed
// distance from the bar and the screen edge regardless of where this
// icon sits in the row.
required property var barWindow
readonly property var sink: Pipewire.defaultAudioSink
readonly property var source: Pipewire.defaultAudioSource
readonly property var nodes: Pipewire.nodes ? Pipewire.nodes.values : []
readonly property real outputVolume: (root.sink && root.sink.audio) ? root.sink.audio.volume : 0
readonly property bool outputMuted: (root.sink && root.sink.audio) ? root.sink.audio.muted : false
readonly property real inputVolume: (root.source && root.source.audio) ? root.source.audio.volume : 0
readonly property bool inputMuted: (root.source && root.source.audio) ? root.source.audio.muted : false
readonly property var candidateSinks: root.nodes.filter(function(n) { return n && n.isSink && !n.isStream })
readonly property var candidateSources: root.nodes.filter(function(n) { return n && !n.isSink && !n.isStream && n.audio })
PwObjectTracker { objects: root.candidateSinks }
PwObjectTracker { objects: root.candidateSources }
// Live mic level for the input gauge below — separate from inputVolume
// (the configured gain), this tracks the actual incoming signal.
// Only runs while the panel is open, to skip metering when unused.
PwNodePeakMonitor {
id: inputPeak
node: root.source
enabled: panel.visible
}
// --- Device label/icon heuristics, ported from omarchy's audio panel
// Model.js (github.com/basecamp/omarchy shell/plugins/panels/audio/Model.js),
// adapted to this project's Material Symbols icon set instead of Nerd Font.
function friendlyDeviceLabel(text) {
var out = String(text || "").trim()
out = out.replace(/^sof-soundwire\s+/i, "")
out = out.replace(/^built-?in audio\s+/i, "")
out = out.replace(/\s+Output$/i, "")
out = out.replace(/\s+Input$/i, "")
out = out.replace(/Microphones/gi, "Microphone")
return out
}
function deviceLabel(node) {
if (!node) return "Unknown"
var nick = friendlyDeviceLabel(node.nickname || "")
if (nick) return nick
return friendlyDeviceLabel(node.description || node.name || "Unknown")
}
function deviceBlob(node) {
if (!node) return ""
var p = node.properties || {}
return [node.name, node.description, node.nickname,
p["device.icon-name"] || "", p["device.product.name"] || ""].join(" ").toLowerCase()
}
function isHeadphoneNode(node) {
var blob = deviceBlob(node)
return blob.indexOf("headphone") !== -1 || blob.indexOf("headset") !== -1
|| blob.indexOf("earbud") !== -1 || blob.indexOf("earphone") !== -1
|| blob.indexOf("airpod") !== -1
}
function sinkIcon(node) {
if (!node) return "speaker"
if (isHeadphoneNode(node)) return "headphones"
var blob = deviceBlob(node)
if (blob.indexOf("bluetooth") !== -1) return "bluetooth_audio"
if (blob.indexOf("hdmi") !== -1 || blob.indexOf("display") !== -1) return "tv"
return "speaker"
}
function sourceIcon(node) {
if (!node) return "mic"
var blob = deviceBlob(node)
if (blob.indexOf("headset") !== -1) return "headphones"
if (blob.indexOf("bluetooth") !== -1) return "bluetooth_audio"
if (blob.indexOf("webcam") !== -1 || blob.indexOf("camera") !== -1) return "videocam"
return "mic"
}
Text {
id: label
anchors.centerIn: parent
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 16
color: Colors.foreground
text: (root.outputMuted || root.outputVolume <= 0) ? "volume_off"
: (root.outputVolume < 0.5 ? "volume_down" : "volume_up")
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
panel.visible = !panel.visible
grab.active = panel.visible
}
onWheel: (wheel) => {
if (!root.sink || !root.sink.audio) return
const step = 0.05
const delta = wheel.angleDelta.y > 0 ? step : -step
root.sink.audio.volume = Math.max(0, Math.min(1, root.sink.audio.volume + delta))
}
}
// --- Reusable pieces -----------------------------------------------
component VolumeSlider: Item {
id: sliderRoot
property real value: 0
property bool enabled: true
signal moved(real newValue)
implicitHeight: 16
Rectangle {
id: track
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 4
radius: 0
color: Colors.muted
opacity: 0.35
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: track.width * Math.max(0, Math.min(1, sliderRoot.value))
height: 4
radius: 0
color: Colors.primary
opacity: sliderRoot.enabled ? 1 : 0.4
}
Rectangle {
width: 12
height: 12
radius: 6
color: Colors.primary
opacity: sliderRoot.enabled ? 1 : 0.4
anchors.verticalCenter: parent.verticalCenter
x: Math.max(0, Math.min(track.width - width, track.width * sliderRoot.value - width / 2))
}
MouseArea {
anchors.fill: parent
enabled: sliderRoot.enabled
cursorShape: Qt.PointingHandCursor
function updateFromX(mx) {
sliderRoot.moved(Math.max(0, Math.min(1, mx / track.width)))
}
onPressed: (mouse) => updateFromX(mouse.x)
onPositionChanged: (mouse) => { if (pressed) updateFromX(mouse.x) }
}
}
// Read-only live level gauge — same track styling as VolumeSlider but no
// handle/interaction, filled from an actual signal level instead of a
// configured value.
component LevelMeter: Item {
id: meterRoot
property real level: 0
implicitHeight: 4
Rectangle {
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 4
radius: 0
color: Colors.muted
opacity: 0.25
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
height: 4
radius: 0
color: Colors.paletteColor(2)
width: parent.width * Math.max(0, Math.min(1, meterRoot.level))
Behavior on width { NumberAnimation { duration: 80 } }
}
}
component ToggleSwitch: Item {
id: toggleRoot
property bool checked: false
signal toggled()
implicitWidth: 34
implicitHeight: 18
Rectangle {
anchors.fill: parent
radius: 0
color: toggleRoot.checked ? Colors.primary : Colors.muted
opacity: toggleRoot.checked ? 1 : 0.35
}
Rectangle {
width: parent.height - 4
height: parent.height - 4
radius: 0
color: Colors.background
anchors.verticalCenter: parent.verticalCenter
x: toggleRoot.checked ? parent.width - width - 2 : 2
Behavior on x { NumberAnimation { duration: Appearance.animationDuration } }
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: toggleRoot.toggled()
}
}
component DeviceRow: Rectangle {
id: deviceRow
required property var node
required property string icon
required property bool active
signal activate()
width: parent ? parent.width : 0
height: 30
radius: 0
color: rowMouse.containsMouse ? Colors.primary
: (deviceRow.active ? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18) : "transparent")
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 8
Text {
Layout.preferredWidth: 18
Layout.minimumWidth: 18
Layout.maximumWidth: 18
horizontalAlignment: Text.AlignHCenter
text: deviceRow.icon
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: rowMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
Text {
Layout.fillWidth: true
text: root.deviceLabel(deviceRow.node)
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSize
font.bold: deviceRow.active
color: rowMouse.containsMouse ? Colors.primaryText : Colors.foreground
}
}
MouseArea {
id: rowMouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: deviceRow.activate()
}
}
// --- Panel -----------------------------------------------------------
PopupWindow {
id: panel
visible: false
color: "transparent"
// Padding between the panel's border and its content. implicitWidth
// is contentWidth + padding on both sides, so the content area
// itself stays a fixed size regardless of how much padding surrounds it.
readonly property int contentWidth: 244
readonly property int contentPadding: 16
implicitWidth: contentWidth + contentPadding * 2
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
anchor {
window: root.barWindow
// Unlike anchor.item, anchor.window doesn't default rect to the
// window's own bounds — set it explicitly so edges/gravity below
// resolve against the full bar (and thus the true screen edges)
// instead of a near-zero rect at the window's origin.
rect.x: 0
rect.y: 0
rect.width: root.barWindow.width
rect.height: root.barWindow.height
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins.bottom: -4
margins.right: 0
}
HyprlandFocusGrab {
id: grab
windows: [panel]
onCleared: panel.visible = false
}
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 3
Column {
id: mainColumn
anchors.fill: parent
anchors.margins: panel.contentPadding
spacing: 10
// Header
RowLayout {
width: parent.width
spacing: 8
Text {
text: label.text
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 40
color: Colors.foreground
}
Text {
Layout.fillWidth: true
text: "Audio"
font.family: Appearance.fontFamily
font.pixelSize: 18
font.bold: true
color: Colors.foreground
}
ToggleSwitch {
checked: !root.outputMuted
onToggled: if (root.sink && root.sink.audio) root.sink.audio.muted = !root.sink.audio.muted
}
}
// Output
Column {
width: parent.width
spacing: 6
RowLayout {
width: parent.width
Text {
Layout.fillWidth: true
text: "OUTPUT"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Text {
text: Math.round(root.outputVolume * 100) + "%"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
}
VolumeSlider {
width: parent.width
value: root.outputVolume
enabled: !!root.sink
onMoved: (v) => { if (root.sink && root.sink.audio) root.sink.audio.volume = v }
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.candidateSinks
DeviceRow {
required property var modelData
node: modelData
icon: root.sinkIcon(modelData)
active: !!(root.sink && modelData && root.sink.id === modelData.id)
onActivate: Pipewire.preferredDefaultAudioSink = modelData
}
}
}
}
// Input
Column {
width: parent.width
spacing: 6
RowLayout {
width: parent.width
Text {
Layout.fillWidth: true
text: "INPUT"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
Text {
text: Math.round(root.inputVolume * 100) + "%"
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
}
VolumeSlider {
width: parent.width
value: root.inputVolume
enabled: !!root.source
onMoved: (v) => { if (root.source && root.source.audio) root.source.audio.volume = v }
}
LevelMeter {
width: parent.width
level: inputPeak.peak
}
Column {
width: parent.width
spacing: 2
Repeater {
model: root.candidateSources
DeviceRow {
required property var modelData
node: modelData
icon: root.sourceIcon(modelData)
active: !!(root.source && modelData && root.source.id === modelData.id)
onActivate: Pipewire.preferredDefaultAudioSource = modelData
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,554 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import "../../config"
// A switcher between commercial VPN providers, distinct from
// TailscaleIndicator.qml — Tailscale is a mesh overlay network you generally
// leave running; these are traditional single-tunnel VPN clients where only
// one makes sense to have active at a time. Connecting one here disconnects
// the other first.
//
// V1 scope: connect (provider's own "fastest"/default) and disconnect per
// provider, live status, no region/server picker yet. Provider CLIs:
// - Mullvad: official `mullvad` CLI (mullvad-vpn-daemon package), has
// real JSON status output.
// - ProtonVPN: official `protonvpn` CLI (proton-vpn-cli package), plain-
// text status only — parsed defensively since its exact wording isn't
// guaranteed across versions.
Item {
id: root
implicitWidth: label.implicitWidth + 4
implicitHeight: Appearance.barHeight
required property var barWindow
property int refreshIntervalSec: 20
readonly property int _refreshIntervalMs: Math.max(5, Math.min(3600, refreshIntervalSec)) * 1000
// Whether each CLI is on PATH at all. Starts optimistic so the panel
// doesn't flash "not installed" before the first `which` check lands.
property bool mullvadInstalled: true
property bool protonInstalled: true
// "disconnected" | "connecting" | "connected" | "disconnecting"
property string mullvadState: "disconnected"
property string mullvadDetail: ""
property string protonState: "disconnected"
property string protonDetail: ""
readonly property bool anyConnected: mullvadState === "connected" || protonState === "connected"
// Set while switching from one provider to the other: holds which one
// to connect once the current one has finished disconnecting.
property string pendingConnect: ""
// Surfaced under the header for a failed connect/disconnect; cleared
// automatically after a few seconds.
property string actionStatus: ""
function shortLine(text) {
const line = String(text || "").trim().split("\n")[0] || ""
return line.length > 80 ? line.substring(0, 77) + "…" : line
}
function applyMullvadStatus(text) {
try {
const data = JSON.parse(text)
const state = String(data.state || "disconnected")
const loc = data.details && data.details.location
root.mullvadState = state
root.mullvadDetail = loc ? [loc.city, loc.country].filter(Boolean).join(", ") : ""
} catch (e) {
root.mullvadState = "disconnected"
root.mullvadDetail = ""
}
VpnExclusivity.mullvadConnected = root.mullvadState === "connected"
}
// No --json here — ProtonVPN's CLI only prints plain text, so this
// just looks for a "Status: …" line and, best-effort, a following
// "Server:"/"Country:" line if the CLI happens to print one.
function applyProtonStatus(text) {
const body = String(text || "")
const statusMatch = body.match(/^Status:\s*(.+)$/mi)
const raw = statusMatch ? statusMatch[1].trim().toLowerCase() : ""
root.protonState = raw.indexOf("connected") === 0 ? "connected" : "disconnected"
const serverMatch = body.match(/^Server:\s*(.+)$/mi)
const countryMatch = body.match(/^Country:\s*(.+)$/mi)
root.protonDetail = serverMatch ? serverMatch[1].trim() : (countryMatch ? countryMatch[1].trim() : "")
VpnExclusivity.protonConnected = root.protonState === "connected"
}
function refreshMullvad() {
if (!root.mullvadInstalled) {
if (!mullvadWhichProc.running) mullvadWhichProc.running = true
return
}
if (!mullvadStatusProc.running) {
mullvadStatusProc.running = true
mullvadPollWatchdog.restart()
}
}
function refreshProton() {
if (!root.protonInstalled) {
if (!protonWhichProc.running) protonWhichProc.running = true
return
}
if (!protonStatusProc.running) {
protonStatusProc.running = true
protonPollWatchdog.restart()
}
}
function refresh() {
root.refreshMullvad()
root.refreshProton()
}
function connectMullvad() {
if (mullvadConnectProc.running || mullvadDisconnectProc.running) return
if (root.protonState !== "disconnected") {
root.pendingConnect = "mullvad"
root.disconnectProton()
return
}
root.pendingConnect = ""
mullvadConnectProc.running = true
}
function connectProton() {
if (protonConnectProc.running || protonDisconnectProc.running) return
if (root.mullvadState !== "disconnected") {
root.pendingConnect = "protonvpn"
root.disconnectMullvad()
return
}
root.pendingConnect = ""
protonConnectProc.running = true
}
function disconnectMullvad() {
if (mullvadDisconnectProc.running) return
mullvadDisconnectProc.running = true
}
function disconnectProton() {
if (protonDisconnectProc.running) return
protonDisconnectProc.running = true
}
function toggleMullvad() {
if (mullvadConnectProc.running || mullvadDisconnectProc.running) return
if (root.mullvadState === "connected") root.disconnectMullvad()
else root.connectMullvad()
}
function toggleProton() {
if (protonConnectProc.running || protonDisconnectProc.running) return
if (root.protonState === "connected") root.disconnectProton()
else root.connectProton()
}
// Tailscale fights Mullvad/ProtonVPN for the default route/DNS, so fire
// this right after either one finishes connecting — a harmless no-op
// if Tailscale was already down. TailscaleIndicator.qml also backs
// itself off reactively (via VpnExclusivity) as a backstop, but doing
// it here too avoids waiting out its poll interval.
Process {
id: tailscaleDownProc
command: ["tailscale", "down"]
stdout: StdioCollector {}
stderr: StdioCollector {}
}
// --- Mullvad ---------------------------------------------------------
Process {
id: mullvadWhichProc
command: ["which", "mullvad"]
running: true
stdout: StdioCollector {}
onExited: (exitCode, exitStatus) => {
root.mullvadInstalled = exitCode === 0
if (root.mullvadInstalled) root.refreshMullvad()
}
}
Process {
id: mullvadStatusProc
command: ["mullvad", "status", "--json"]
stdout: StdioCollector {
id: mullvadStatusCollector
onStreamFinished: root.applyMullvadStatus(mullvadStatusCollector.text)
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0 && !mullvadWhichProc.running) mullvadWhichProc.running = true
}
}
Process {
id: mullvadConnectProc
command: ["mullvad", "connect", "--wait"]
stdout: StdioCollector {}
stderr: StdioCollector { id: mullvadConnectErr }
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
root.actionStatus = "Mullvad: " + root.shortLine(mullvadConnectErr.text || "connect failed")
actionStatusTimer.restart()
} else if (!tailscaleDownProc.running) {
tailscaleDownProc.running = true
}
root.refreshMullvad()
}
}
Process {
id: mullvadDisconnectProc
command: ["mullvad", "disconnect", "--wait"]
stdout: StdioCollector {}
stderr: StdioCollector { id: mullvadDisconnectErr }
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
root.actionStatus = "Mullvad: " + root.shortLine(mullvadDisconnectErr.text || "disconnect failed")
actionStatusTimer.restart()
}
root.refreshMullvad()
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
else if (root.pendingConnect === "protonvpn") { root.pendingConnect = ""; root.connectProton() }
}
}
Timer {
id: mullvadPollWatchdog
interval: 15000
repeat: false
onTriggered: if (mullvadStatusProc.running) mullvadStatusProc.running = false
}
// --- ProtonVPN ---------------------------------------------------------
Process {
id: protonWhichProc
command: ["which", "protonvpn"]
running: true
stdout: StdioCollector {}
onExited: (exitCode, exitStatus) => {
root.protonInstalled = exitCode === 0
if (root.protonInstalled) root.refreshProton()
}
}
Process {
id: protonStatusProc
command: ["protonvpn", "status"]
stdout: StdioCollector {
id: protonStatusCollector
onStreamFinished: root.applyProtonStatus(protonStatusCollector.text)
}
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0 && !protonWhichProc.running) protonWhichProc.running = true
}
}
Process {
id: protonConnectProc
command: ["protonvpn", "connect"]
stdout: StdioCollector {}
stderr: StdioCollector { id: protonConnectErr }
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
root.actionStatus = "ProtonVPN: " + root.shortLine(protonConnectErr.text || "connect failed")
actionStatusTimer.restart()
} else if (!tailscaleDownProc.running) {
tailscaleDownProc.running = true
}
root.refreshProton()
}
}
Process {
id: protonDisconnectProc
command: ["protonvpn", "disconnect"]
stdout: StdioCollector {}
stderr: StdioCollector { id: protonDisconnectErr }
onExited: (exitCode, exitStatus) => {
if (exitCode !== 0) {
root.actionStatus = "ProtonVPN: " + root.shortLine(protonDisconnectErr.text || "disconnect failed")
actionStatusTimer.restart()
}
root.refreshProton()
if (root.pendingConnect === "mullvad") { root.pendingConnect = ""; root.connectMullvad() }
else if (root.pendingConnect === "protonvpn") { root.pendingConnect = ""; root.connectProton() }
}
}
Timer {
id: protonPollWatchdog
interval: 15000
repeat: false
onTriggered: if (protonStatusProc.running) protonStatusProc.running = false
}
// --- Shared timers -----------------------------------------------------
Timer {
id: refreshTimer
interval: root._refreshIntervalMs
repeat: true
running: true
triggeredOnStart: true
onTriggered: root.refresh()
}
Timer {
id: actionStatusTimer
interval: 4000
repeat: false
onTriggered: root.actionStatus = ""
}
// --- Bar icon ------------------------------------------------------------
Text {
id: label
anchors.centerIn: parent
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: Appearance.iconSize
color: root.anyConnected ? Colors.primary : Colors.muted
text: "vpn_lock"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
panel.visible = !panel.visible
grab.active = panel.visible
}
}
// --- Reusable pieces -----------------------------------------------------
component ToggleSwitch: Item {
id: toggleRoot
property bool checked: false
property bool busy: false
signal toggled()
implicitWidth: 34
implicitHeight: 18
opacity: toggleRoot.enabled ? 1 : 0.4
Rectangle {
anchors.fill: parent
radius: 0
color: toggleRoot.checked ? Colors.primary : Colors.muted
opacity: (toggleRoot.checked ? 1 : 0.35) * (toggleRoot.busy ? 0.6 : 1)
}
Rectangle {
width: parent.height - 4
height: parent.height - 4
radius: 0
color: Colors.background
anchors.verticalCenter: parent.verticalCenter
x: toggleRoot.checked ? parent.width - width - 2 : 2
Behavior on x { NumberAnimation { duration: Appearance.animationDuration } }
}
MouseArea {
anchors.fill: parent
enabled: !toggleRoot.busy
cursorShape: toggleRoot.busy ? Qt.ArrowCursor : Qt.PointingHandCursor
onClicked: toggleRoot.toggled()
}
}
// A single provider's row — icon, name, live status line, and a toggle
// that connects/disconnects it (routed back through root so it can
// enforce the one-provider-at-a-time rule).
component ProviderRow: Rectangle {
id: provRow
property string label: ""
property string subIcon: "shield"
property string detailText: ""
property string state: "disconnected"
property bool installed: true
signal toggle()
width: parent ? parent.width : 0
radius: 0
implicitHeight: rowContent.implicitHeight + 12
color: provRow.state === "connected"
? Qt.rgba(Colors.primary.r, Colors.primary.g, Colors.primary.b, 0.18)
: "transparent"
RowLayout {
id: rowContent
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: 6
spacing: 8
Text {
Layout.preferredWidth: 20
Layout.minimumWidth: 20
Layout.maximumWidth: 20
horizontalAlignment: Text.AlignHCenter
text: provRow.subIcon
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 14
color: provRow.state === "connected" ? Colors.primary : Colors.foreground
}
ColumnLayout {
Layout.fillWidth: true
spacing: 1
Text {
Layout.fillWidth: true
text: provRow.label
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSize
font.bold: provRow.state === "connected"
color: Colors.foreground
}
Text {
Layout.fillWidth: true
text: !provRow.installed ? "Not installed"
: provRow.state === "connecting" ? "Connecting…"
: provRow.state === "disconnecting" ? "Disconnecting…"
: provRow.state === "connected" ? (provRow.detailText || "Connected")
: "Disconnected"
elide: Text.ElideRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
}
}
ToggleSwitch {
enabled: provRow.installed
checked: provRow.state === "connected" || provRow.state === "connecting"
busy: provRow.state === "connecting" || provRow.state === "disconnecting"
onToggled: provRow.toggle()
}
}
}
// --- Panel -----------------------------------------------------------
PopupWindow {
id: panel
visible: false
color: "transparent"
readonly property int contentWidth: 244
readonly property int contentPadding: 16
implicitWidth: contentWidth + contentPadding * 2
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
Behavior on implicitHeight {
NumberAnimation { duration: Appearance.animationDuration; easing.type: Easing.OutCubic }
}
onVisibleChanged: if (visible) root.refresh()
anchor {
window: root.barWindow
rect.x: 0
rect.y: 0
rect.width: root.barWindow.width
rect.height: root.barWindow.height
edges: Edges.Bottom | Edges.Right
gravity: Edges.Bottom | Edges.Left
margins.bottom: -4
margins.right: 0
}
HyprlandFocusGrab {
id: grab
windows: [panel]
onCleared: panel.visible = false
}
Rectangle {
anchors.fill: parent
radius: 0
color: Colors.surface
border.color: Colors.muted
border.width: 3
Column {
id: mainColumn
anchors.fill: parent
anchors.margins: panel.contentPadding
spacing: 10
RowLayout {
width: parent.width
spacing: 8
Text {
text: "vpn_lock"
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 22
color: root.anyConnected ? Colors.primary : Colors.foreground
}
Text {
Layout.fillWidth: true
text: "VPN"
font.family: Appearance.fontFamily
font.pixelSize: 18
font.bold: true
color: Colors.foreground
}
}
Text {
visible: root.actionStatus !== ""
width: parent.width
text: root.actionStatus
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
wrapMode: Text.WordWrap
}
Column {
width: parent.width
spacing: 2
ProviderRow {
label: "Mullvad"
subIcon: "shield"
state: root.mullvadState
detailText: root.mullvadDetail
installed: root.mullvadInstalled
onToggle: root.toggleMullvad()
}
ProviderRow {
label: "ProtonVPN"
subIcon: "gpp_good"
state: root.protonState
detailText: root.protonDetail
installed: root.protonInstalled
onToggle: root.toggleProton()
}
}
Text {
width: parent.width
text: "Connecting one disconnects the other — only one VPN tunnel at a time."
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
wrapMode: Text.WordWrap
}
}
}
}
}

View File

@@ -0,0 +1,44 @@
import QtQuick
import QtQuick.Layouts
import Quickshell.Hyprland
import "../../config"
RowLayout {
id: root
property var screen
spacing: 4
Repeater {
model: Hyprland.workspaces
delegate: Rectangle {
id: pill
required property var modelData
visible: modelData.monitor && root.screen && modelData.monitor.name === root.screen.name
implicitWidth: 26
implicitHeight: 22
radius: Appearance.radiusSmall
color: modelData.urgent ? Colors.urgent
: modelData.active ? Colors.primary
: "transparent"
Behavior on color { ColorAnimation { duration: Appearance.animationDuration } }
Text {
anchors.centerIn: parent
text: modelData.name
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: modelData.active ? Colors.primaryText : Colors.foreground
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Hyprland.dispatch("workspace " + modelData.name)
}
}
}
}

View File

@@ -0,0 +1,125 @@
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Pipewire
import QtQuick
import QtQuick.Layouts
import "../../config"
PanelWindow {
id: osd
visible: false
color: "transparent"
exclusiveZone: 0
// Top-right, just under the bar — same fixed distance from the bar and
// screen edge (Appearance.spacing) that every popup opened from the bar
// itself uses, so this lines up with them instead of floating
// bottom-center.
anchors { top: true; right: true }
margins.top: Appearance.spacing
margins.right: Appearance.spacing
implicitWidth: 272
implicitHeight: 54
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "quickshell:osd"
readonly property var sink: Pipewire.defaultAudioSink
PwObjectTracker { objects: osd.sink ? [osd.sink] : [] }
Timer {
id: hideTimer
interval: 1400
onTriggered: osd.visible = false
}
Connections {
target: osd.sink ? osd.sink.audio : null
function onVolumeChanged() { osd.visible = true; hideTimer.restart() }
function onMutedChanged() { osd.visible = true; hideTimer.restart() }
}
Rectangle {
anchors.fill: parent
radius: Appearance.radius
color: Colors.surface
border.color: Colors.muted
border.width: 1
RowLayout {
anchors.fill: parent
anchors.margins: 14
spacing: 12
Text {
font.family: Appearance.iconFontFamily
font.variableAxes: Appearance.iconFontAxes
font.pixelSize: 18
color: Colors.foreground
text: {
const audio = osd.sink ? osd.sink.audio : null
if (!audio || audio.muted || audio.volume <= 0) return "volume_off"
return audio.volume < 0.5 ? "volume_down" : "volume_up"
}
}
Item {
id: slider
Layout.fillWidth: true
implicitHeight: 16
readonly property real value: osd.sink && osd.sink.audio ? osd.sink.audio.volume : 0
readonly property bool enabled: !!osd.sink
Rectangle {
id: track
anchors.verticalCenter: parent.verticalCenter
width: parent.width
height: 4
radius: 0
color: Colors.muted
opacity: 0.35
}
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: track.width * Math.max(0, Math.min(1, slider.value))
height: 4
radius: 0
color: Colors.primary
opacity: slider.enabled ? 1 : 0.4
}
Rectangle {
width: 12
height: 12
radius: 6
color: Colors.primary
opacity: slider.enabled ? 1 : 0.4
anchors.verticalCenter: parent.verticalCenter
x: Math.max(0, Math.min(track.width - width, track.width * slider.value - width / 2))
}
MouseArea {
anchors.fill: parent
enabled: slider.enabled
cursorShape: Qt.PointingHandCursor
function updateFromX(mx) {
if (!osd.sink || !osd.sink.audio) return
osd.sink.audio.volume = Math.max(0, Math.min(1, mx / track.width))
}
onPressed: (mouse) => updateFromX(mouse.x)
onPositionChanged: (mouse) => { if (pressed) updateFromX(mouse.x) }
}
}
Text {
Layout.preferredWidth: 32
horizontalAlignment: Text.AlignRight
font.family: Appearance.fontFamily
font.pixelSize: Appearance.fontSizeSmall
color: Colors.muted
text: Math.round(slider.value * 100) + "%"
}
}
}
}

View File

@@ -0,0 +1,16 @@
import Quickshell
import "./modules/bar"
import "./modules/osd"
ShellRoot {
Variants {
model: Quickshell.screens
Bar {
required property var modelData
screen: modelData
}
}
VolumeOsd {}
}

View File

@@ -1,15 +1,15 @@
{ {
"modules-left": ["custom/notification", "battery", "clock#date", "clock","custom/updater", "custom/launcher"], "modules-left": ["custom/notification", "battery", "clock#date", "clock","custom/updater", "custom/launcher"],
"modules-center": ["hyprland/workspaces"], "modules-center": ["hyprland/workspaces"],
"modules-right": ["network", "bluetooth", "pulseaudio", "custom/power"], "modules-right": ["network", "bluetooth", "wireplumber", "wireplumber#microphone", "custom/power"],
"sway/mode": { "sway/mode": {
"format": "<span style=\"italic\">{}</span>" "format": "<span style=\"italic\">{}</span>"
}, },
"network": { "network": {
"format-wifi": "<span color='#962fb5' font_family='NotoSansMono Nerd Font' size='16pt' baseline_shift='-3.10pt'>{icon}</span><span letter_spacing='1000'> </span>{essid} ({signalStrength}%)", "format-wifi": "<span color='#962fb5'>{icon}<span letter_spacing='500'> </span></span>{essid}({signalStrength}%)",
"format-ethernet": "<span color='#962fb5' font_family='NotoSansMono Nerd Font' size='17pt' baseline_shift='-3.10pt'>{icon}</span><span letter_spacing='1000'> </span>Ethernet", "format-ethernet": "<span color='#962fb5'font='bold'>{icon}<span letter_spacing='500'> </span></span><span rise='1000'>Ethernet</span>",
"format-disconnected": "<span font_family='NotoSansMono Nerd Font' size='16pt' baseline_shift='-3.10pt'>󰅛</span><span letter_spacing='1000'> </span>Not Connected", "format-disconnected": "󰅛<span letter_spacing='500'> </span><span rise='1000'>Not Connected</span>",
"tooltip-format": "{ipaddr} via {gwaddr}", "tooltip-format": "{ipaddr} via {gwaddr}",
"tooltip-format-disconnected": "Disconnected", "tooltip-format-disconnected": "Disconnected",
"max-length": 50, "max-length": 50,
@@ -17,7 +17,7 @@
"on-click-right": "kitty -e nmtui", "on-click-right": "kitty -e nmtui",
"format-icons": { "format-icons": {
"wifi": ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"], "wifi": ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"],
"ethernet": "", "ethernet": "󰈀",
"disconnected": "󰅛" "disconnected": "󰅛"
} }
}, },
@@ -33,10 +33,10 @@
"spacing": 10 "spacing": 10
}, },
"clock": { "clock": {
"format": "<span size='16pt' baseline_shift='-3.10pt'></span><span letter_spacing='1000'> </span>{:%H:%M}" "format": "<span rise='300'>{:%H:%M}</span>"
}, },
"clock#date": { "clock#date": {
"format": " {:%A, %B %d, %Y}", "format": " <span rise='300'>{:%A, %B %d, %Y}</span>",
"tooltip-format": "<tt><small>{calendar}</small></tt>", "tooltip-format": "<tt><small>{calendar}</small></tt>",
"calendar": { "calendar": {
"mode" : "year", "mode" : "year",
@@ -55,27 +55,30 @@
"on-click-right": "mode" "on-click-right": "mode"
} }
}, },
"pulseaudio": { "wireplumber": {
"format": "{icon}{volume}% {format_source}", "format": "<span rise='300'>{icon}</span>{volume}%",
"format-muted": " {format_source}", "format-muted": "",
"format-bluetooth": "{icon} {volume}% {format_source}", "format-bluetooth": "{icon}{volume}%",
"format-bluetooth-muted": " {format_source}", "format-bluetooth-muted": "",
"format-source": "<span font_family='NotoSansMono Nerd Font' size='17pt' baseline_shift='-3.20pt'></span><span letter_spacing='200'> </span>{volume}%", "format-icons": [
"format-source-muted": "<span font_family='NotoSansMono Nerd Font' size='17pt' baseline_shift='-3.20pt'></span><span letter_spacing='200'> </span>{volume}%", "<span color='#808080'><span letter_spacing='500'> </span></span>",
"format-icons": { "<span color='#ffff66'><span letter_spacing='500'> </span></span>",
"default": [ "<span color='#00ff7f'><span letter_spacing='3000'> </span></span>"
"<span color='#808080' font_family='NotoSansMono Nerd Font' size='17pt' baseline_shift='-3.20pt'></span><span letter_spacing='600'> </span>", ],
"<span color='#ffff66' font_family='NotoSansMono Nerd Font' size='17pt' baseline_shift='-3.20pt'></span><span letter_spacing='600'> </span>", "on-click": "pwvucontrol"
"<span color='#00ff7f' font_family='NotoSansMono Nerd Font' size='17pt' baseline_shift='-3.20pt'></span><span letter_spacing='600'> </span>"] },
}, "wireplumber#microphone": {
"on-click": "pavucontrol" "node-type": "Audio/Source",
"format": "<span letter_spacing='100'> </span><span rise='300'>{volume}%</span>",
"format-muted": "<span letter_spacing='2000'> </span>{volume}%",
"on-click": "pwvucontrol"
}, },
"bluetooth": { "bluetooth": {
"format-on": "<span color='#00ff7f' size='13pt' baseline_shift='-2.0pt'></span> On", "format-on": "<span color='#00ff7f'><span letter_spacing='500'> </span></span>On",
"format-off": "<span color='#888888' size='13pt' baseline_shift='-2.00pt'>󰂲</span> Off", "format-off": "<span color='#888888'>󰂲 Off</span>",
"format-disabled": "<span color='#888888' size='13pt' baseline_shift='-2.00pt'>󰂲</span> Off", "format-disabled": "<span color='#888888'>󰂲 Off</span>",
"format-connected": "<span color='#0478d6' size='13pt' baseline_shift='-2.00pt'>󰂱</span> {device_alias}", "format-connected": "<span color='#0478d6'>󰂱<span letter_spacing='500'> </span></span>{device_alias}",
"format-connected-battery": "<span color='#0478d6' size='13pt' baseline_shift='-2.00pt'>󰂱 </span> {device_alias} <span color='#0478d6' size='16pt' baseline_shift='-3.10pt'>󰥈 </span> {device_battery_percentage}%", "format-connected-battery": "<span color='#0478d6'>󰂱<span letter_spacing='500'> </span></span>{device_alias}<span letter_spacing='500'> </span><span color='#0478d6'>󰥈<span letter_spacing='500'> </span></span>{device_battery_percentage}%",
"tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected", "tooltip-format": "{controller_alias}\t{controller_address}\n\n{num_connections} connected",
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}", "tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{num_connections} connected\n\n{device_enumerate}",
"tooltip-format-enumerate-connected": "{device_alias}\t{device_address}", "tooltip-format-enumerate-connected": "{device_alias}\t{device_address}",
@@ -97,7 +100,7 @@
"on-click-right": "swaymsg 'move scratchpad'" "on-click-right": "swaymsg 'move scratchpad'"
}, },
"custom/launcher": { "custom/launcher": {
"format":" ", "format":" ",
"tooltip-format": "Click To Launch Bitwarden", "tooltip-format": "Click To Launch Bitwarden",
"on-click": "exec bitwarden-desktop" "on-click": "exec bitwarden-desktop"
}, },
@@ -106,11 +109,11 @@
"warning": 30, "warning": 30,
"critical": 15 "critical": 15
}, },
"format": "{icon} {capacity}%", "format": "{icon}<span letter_spacing='1'> </span>{capacity}%",
"format-charging": "{icon} {capacity}%", "format-charging": "{icon}<span letter_spacing='1'> </span>{capacity}%",
"format-plugged": "{icon} {capacity}%", "format-plugged": "{icon}<span letter_spacing='1'> </span>{capacity}%",
"format-alt": "{time} {icon}", "format-alt": "{time}<span letter_spacing='1'> </span>{icon}",
"format-full": "󰁹 {capacity}%", "format-full": "󰁹<span letter_spacing='1'> </span>{capacity}%",
"format-icons": { "format-icons": {
"default": ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂"], "default": ["󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂"],
"charging": ["󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋"], "charging": ["󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋"],
@@ -137,13 +140,13 @@
"sort-by-number": true "sort-by-number": true
}, },
"custom/updater": { "custom/updater": {
"format": "<span color='#962fb5' font_family='NotoSansMono Nerd Font' size='16pt' baseline_shift='-3.10pt'>󰏖</span><span letter_spacing='1000'> </span>{}", "format": "󰏖 <span rise='300'>{}</span>",
"exec": "checkupdates | wc -l", "exec": "checkupdates | wc -l",
"exec-if": "[[ $(checkupdates | wc -l) ]]", "exec-if": "[[ $(checkupdates | wc -l) ]]",
"interval": 1300, "interval": 1300,
"on-click": "$HOME/.config/waybar/scripts/update-distro.sh", "on-click": "$HOME/.config/waybar/scripts/update-distro.sh",
"tooltip": true, "tooltip": true,
"tooltip-format": "Left Click: Update System\nArch (w/ notification)\nFedora, OpenSuse, Debian/Ubuntu click to update" "tooltip-format": "Left Click: Update System\nRight Click: Update Package Count\nArch (w/ notification)"
}, },
"custom/notification": { "custom/notification": {

View File

@@ -3,6 +3,7 @@
font-family: font-family:
Font Awesome, Font Awesome,
Roboto, Roboto,
"NotoSansM Nerd Font",
"NotoSansMono Nerd Font", "NotoSansMono Nerd Font",
"Font Awesome 6 Free", "Font Awesome 6 Free",
Arial, Arial,
@@ -18,10 +19,11 @@ window#waybar {
/*-----module groups----*/ /*-----module groups----*/
.modules-right { .modules-right {
margin: 2px 10px 0 0; margin: 2px 10px 0 0;
padding-top: 1px;
} }
#custom-notification { #custom-notification {
font-family: "NotoSansMono Nerd Font"; font-family: "NotoSansM Nerd Font", "NotoSansMono Nerd Font";
font-size: 13px; font-size: 13px;
background-color: rgba(0, 43, 51, 0.85); background-color: rgba(0, 43, 51, 0.85);
border-radius: 20px; border-radius: 20px;
@@ -39,6 +41,7 @@ window#waybar {
.modules-left { .modules-left {
margin: 2px 0 0 5px; margin: 2px 0 0 5px;
padding-top: 1px;
} }
/*-----modules indv----*/ /*-----modules indv----*/
#workspaces button { #workspaces button {
@@ -70,7 +73,8 @@ window#waybar {
border-radius: 20px; border-radius: 20px;
margin: 5px 3px; margin: 5px 3px;
} }
#pulseaudio { #wireplumber,
#wireplumber-microphone {
padding: 5px 10px 5px 15px; padding: 5px 10px 5px 15px;
background-color: rgba(0, 43, 51, 0.85); background-color: rgba(0, 43, 51, 0.85);
border-radius: 20px; border-radius: 20px;
@@ -120,7 +124,8 @@ window#waybar {
#idle_inhibitor.activated { #idle_inhibitor.activated {
color: #2dcc36; color: #2dcc36;
} }
#pulseaudio.muted { #wireplumber.muted,
#wireplumber-microphone.muted {
color: #cc3436; color: #cc3436;
} }
#battery.charging { #battery.charging {

View File

@@ -25,9 +25,9 @@ source $ZSH/oh-my-zsh.sh
alias cl='clear' alias cl='clear'
alias zed='zeditor' alias zed='zeditor'
alias ssh-gavin='ssh thoams@gavin.home' alias ssh-gavin='ssh thoams@192.168.1.83'
alias ssh-raspberry-pi='ssh thoams@raspberry-pi.home' alias ssh-raspberry-pi='ssh thoams@192.168.1.86'
alias ssh-ted='ssh thoams@ted.home' alias ssh-ted='ssh thoams@192.168.1.87'
# Set-up FZF key bindings (CTRL R for fuzzy history finder) # Set-up FZF key bindings (CTRL R for fuzzy history finder)
source <(fzf --zsh) source <(fzf --zsh)