Compare commits
85 Commits
features/m
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d10a1aec05 | |||
| 24d7bb280e | |||
| e544580963 | |||
| 5aaa3822a6 | |||
| d24141e2e0 | |||
| 81b051c95d | |||
| e563ec22d2 | |||
| 7d9905f73b | |||
| 7f11abf50f | |||
| 37c03fa03c | |||
| 20873c1ca6 | |||
| 6e1ea8ffc4 | |||
| 3eb8f6027b | |||
| 9e7d6284eb | |||
| 0e9dbf0abc | |||
| aefa68f24c | |||
| 02adb17b4e | |||
| 353d77c4fb | |||
| 3931597aca | |||
| 206bf15bf4 | |||
| 818971b29c | |||
| f861c7c31c | |||
| 6a8c8c92d8 | |||
| e23ce6647f | |||
| 5c6dad3e91 | |||
| 5827f18d05 | |||
| 561312c234 | |||
| 6e567dd1e1 | |||
| 9af1beeaae | |||
| 16a0d12b8f | |||
| e8113f4366 | |||
| ea11fe0eb6 | |||
| 19e7d3f673 | |||
| bf86ffe258 | |||
| 8d73d2a37e | |||
| 54b91a72e2 | |||
| 788d4939f0 | |||
| 9a038b74c3 | |||
| 954dedf7cd | |||
| 90d0e2bed0 | |||
| 14f72af3f2 | |||
| 836848dedd | |||
| fbaffc0816 | |||
| ccae379490 | |||
| 7f0fad5922 | |||
| b40d4a9fa4 | |||
| 19d6936aa6 | |||
| 4a57e9902f | |||
| 1201e6a4cc | |||
| 626c7dbcc1 | |||
| 35374800bf | |||
| 7cb7f10ff6 | |||
| 88484f8495 | |||
| 1babf2e334 | |||
| cc78daea77 | |||
| 0415804b67 | |||
| 75aa6b945b | |||
| 8b65136048 | |||
| 4bd3d327c6 | |||
| 786a3d191a | |||
| ad5efc9597 | |||
| 850da708c2 | |||
| de39c40460 | |||
| 72db6ca784 | |||
| 84828e58bc | |||
| 26b7bf4ecc | |||
| 5b58c27e80 | |||
| 82be653c04 | |||
| d395a155af | |||
| 243c90a809 | |||
| 38df0301f2 | |||
| 714fadc997 | |||
| 99e2225b51 | |||
| 4aaa1d2d98 | |||
| 14b7e748d4 | |||
| a0b9ea29fe | |||
| 007843db6e | |||
| 764652d826 | |||
| 8dedfe30fa | |||
| bc54a4b5b7 | |||
| d306270dc8 | |||
| 03b043d9d3 | |||
| 1ff92f3b79 | |||
| c64cf5ff12 | |||
| 9e532023bd |
24
README.md
24
README.md
@@ -39,13 +39,29 @@ mkdir Projects
|
||||
git clone ssh://git@gitea.thomasleewilliams.co.uk:2222/thomas/dotfiles.git
|
||||
```
|
||||
|
||||
Once that is complete, run this command:
|
||||
On a fresh install, run the bootstrap script. It installs drivers (auto-detected),
|
||||
every package in `system/packages.txt`, yay, flatpaks, then links the dotfiles and
|
||||
system config below:
|
||||
```console
|
||||
~/Projects/dotfiles/bootstrap.sh # asks about the optional SVT-AV1 + ffmpeg rebuild
|
||||
~/Projects/dotfiles/bootstrap.sh --svt-av1 # or --no-svt-av1 to skip the question
|
||||
```
|
||||
|
||||
To only link the dotfiles, run this command:
|
||||
```console
|
||||
stow -d ~/Projects/dotfiles/ -t ~/ dotfiles
|
||||
```
|
||||
|
||||
This will sync wallpapers/config files to where they are usually located
|
||||
|
||||
System-level config (e.g. pacman options) lives in `system/` and is installed with sudo:
|
||||
```console
|
||||
~/Projects/dotfiles/system/install.sh
|
||||
```
|
||||
|
||||
Full documentation (every config, keybinding and script) lives in the Outline wiki's
|
||||
**Dotfiles** collection: https://outline.twhomelab.co.uk/collection/dotfiles-frMIfJi2Hf
|
||||
|
||||
## 📦 Programs
|
||||
|
||||
List of programs and tools I use.
|
||||
@@ -54,12 +70,12 @@ List of programs and tools I use.
|
||||
| Component | Program |
|
||||
|-------------------|------------|
|
||||
|🪟 Windows Manager | [hyprland](https://github.com/hyprwm/Hyprland) |
|
||||
|🖥️ Terminal | [foot](https://codeberg.org/dnkl/foot) |
|
||||
|🐚 Shell | [zsh](https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH) / [theme](https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/refined.zsh-theme) |
|
||||
|🖥️ Terminal | [ghostty](https://ghostty.org/) / [foot](https://codeberg.org/dnkl/foot) (fallback) |
|
||||
|🐚 Shell | [zsh](https://github.com/ohmyzsh/ohmyzsh/wiki/Installing-ZSH) + oh-my-zsh / [oh-my-posh](https://ohmyposh.dev/) prompt (`.config/ohmyposh/zen.toml`) |
|
||||
|📁 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) |
|
||||
|🌐 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) / [quickshell](https://quickshell.org/) |
|
||||
|📊 Bar | [quickshell](https://quickshell.org/) (waybar retired, config kept) |
|
||||
|🚀 Launcher | [rofi](https://github.com/davatorium/rofi) |
|
||||
|🔒 Lockscreen | [hyprlock](https://github.com/hyprwm/hyprlock) |
|
||||
|🚪 Login Menu | [sddm](https://github.com/sddm/sddm) |
|
||||
|
||||
195
bootstrap.sh
Executable file
195
bootstrap.sh
Executable file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sets up a fresh Arch install: drivers, packages, dotfiles and system config.
|
||||
# Safe to re-run; anything already installed is skipped.
|
||||
#
|
||||
# Usage: ./bootstrap.sh [--svt-av1 | --no-svt-av1]
|
||||
# --svt-av1 also install svt-av1-essential-git and rebuild ffmpeg against it
|
||||
# --no-svt-av1 skip that without asking
|
||||
# (neither) ask
|
||||
set -euo pipefail
|
||||
|
||||
repo="$(cd "$(dirname "$0")" && pwd)"
|
||||
list="$repo/system/packages.txt"
|
||||
|
||||
svt_av1=ask
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--svt-av1) svt_av1=yes ;;
|
||||
--no-svt-av1) svt_av1=no ;;
|
||||
*) echo "Unknown option: $arg" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if (( EUID == 0 )); then
|
||||
echo "Run this as your normal user, not root (it uses sudo where needed)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
step() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
|
||||
|
||||
# Prints the packages under a [section] of packages.txt, skipping comments
|
||||
packages() {
|
||||
awk -v want="[$1]" '
|
||||
/^\[/ { section = $1; next }
|
||||
section == want { sub(/#.*/, ""); if (NF) print $1 }
|
||||
' "$list"
|
||||
}
|
||||
|
||||
# --- System config -----------------------------------------------------------
|
||||
|
||||
step "System config (pacman options)"
|
||||
"$repo/system/install.sh"
|
||||
|
||||
step "Enabling multilib (needed for Steam and 32-bit drivers)"
|
||||
if ! grep -q '^\[multilib\]' /etc/pacman.conf; then
|
||||
sudo sed -i '/^#\[multilib\]/,/^#Include/ s/^#//' /etc/pacman.conf
|
||||
fi
|
||||
sudo pacman -Syu --needed --noconfirm pciutils git base-devel
|
||||
|
||||
# --- Hardware ----------------------------------------------------------------
|
||||
|
||||
step "Detecting CPU and GPU"
|
||||
hw=()
|
||||
|
||||
case "$(grep -m1 '^vendor_id' /proc/cpuinfo | awk '{print $3}')" in
|
||||
AuthenticAMD) hw+=(amd-ucode); echo "CPU: AMD" ;;
|
||||
GenuineIntel) hw+=(intel-ucode); echo "CPU: Intel" ;;
|
||||
*) echo "CPU: unknown vendor, skipping microcode" ;;
|
||||
esac
|
||||
|
||||
# Vendor column of every display controller, e.g. "NVIDIA Corporation"
|
||||
gpus="$(lspci -mm | awk -F'"' '$2 ~ /VGA|3D|Display/ { print $4 }')"
|
||||
if grep -q NVIDIA <<<"$gpus"; then
|
||||
echo "GPU: NVIDIA"
|
||||
# nvidia-open only ships modules for the stock `linux` kernel; any other
|
||||
# kernel needs the DKMS build. Supports Turing (GTX 16xx / RTX 20xx) and newer.
|
||||
if pacman -Qq linux-lts linux-zen linux-hardened >/dev/null 2>&1; then
|
||||
hw+=(nvidia-open-dkms linux-headers)
|
||||
else
|
||||
hw+=(nvidia-open)
|
||||
fi
|
||||
hw+=(nvidia-utils lib32-nvidia-utils libva-nvidia-driver)
|
||||
fi
|
||||
if grep -q 'Advanced Micro Devices' <<<"$gpus"; then
|
||||
echo "GPU: AMD"
|
||||
hw+=(mesa lib32-mesa vulkan-radeon lib32-vulkan-radeon)
|
||||
fi
|
||||
if grep -q 'Intel Corporation' <<<"$gpus"; then
|
||||
echo "GPU: Intel"
|
||||
hw+=(mesa lib32-mesa vulkan-intel lib32-vulkan-intel intel-media-driver)
|
||||
fi
|
||||
|
||||
# Installed before the main list so Steam's 32-bit Vulkan dependency is
|
||||
# already satisfied by the right driver
|
||||
if (( ${#hw[@]} )); then
|
||||
sudo pacman -S --needed --noconfirm "${hw[@]}"
|
||||
fi
|
||||
|
||||
# --- Packages ----------------------------------------------------------------
|
||||
|
||||
step "Official repo packages"
|
||||
mapfile -t pkgs < <(packages pacman)
|
||||
sudo pacman -S --needed --noconfirm "${pkgs[@]}"
|
||||
|
||||
step "yay (AUR helper)"
|
||||
if ! command -v yay >/dev/null; then
|
||||
build="$(mktemp -d)"
|
||||
git clone https://aur.archlinux.org/yay-bin.git "$build/yay-bin"
|
||||
(cd "$build/yay-bin" && makepkg -si --noconfirm)
|
||||
rm -rf "$build"
|
||||
else
|
||||
echo "already installed"
|
||||
fi
|
||||
|
||||
step "AUR packages"
|
||||
mapfile -t pkgs < <(packages aur)
|
||||
yay -S --needed --noconfirm "${pkgs[@]}"
|
||||
|
||||
step "Flatpak apps"
|
||||
sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
mapfile -t pkgs < <(packages flatpak)
|
||||
sudo flatpak install -y --noninteractive flathub "${pkgs[@]}"
|
||||
|
||||
step "Apps from official install scripts"
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
for app in $(packages installer); do
|
||||
if command -v "$app" >/dev/null || [[ -x "$HOME/.local/bin/$app" ]]; then
|
||||
echo "$app: already installed"
|
||||
continue
|
||||
fi
|
||||
case "$app" in
|
||||
claude) curl -fsSL https://claude.ai/install.sh | bash ;;
|
||||
oh-my-posh) curl -fsSL https://ohmyposh.dev/install.sh | bash -s -- -d "$HOME/.local/bin" ;;
|
||||
*) echo "$app: no install recipe in bootstrap.sh, skipping" >&2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
step "oh-my-zsh and plugins"
|
||||
# .zshrc sources ~/.oh-my-zsh/oh-my-zsh.sh and loads two custom plugins from it.
|
||||
# A plain clone is all oh-my-zsh needs; its installer would also write a template
|
||||
# ~/.zshrc, which then conflicts with the stowed one.
|
||||
if [[ ! -d "$HOME/.oh-my-zsh" ]]; then
|
||||
git clone --depth 1 https://github.com/ohmyzsh/ohmyzsh.git "$HOME/.oh-my-zsh"
|
||||
else
|
||||
echo "oh-my-zsh: already installed"
|
||||
fi
|
||||
zsh_custom="$HOME/.oh-my-zsh/custom/plugins"
|
||||
for plugin in zsh-autosuggestions zsh-syntax-highlighting; do
|
||||
if [[ -d "$zsh_custom/$plugin" ]]; then
|
||||
echo "$plugin: already installed"
|
||||
else
|
||||
git clone --depth 1 "https://github.com/zsh-users/$plugin" "$zsh_custom/$plugin"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- Optional: SVT-AV1 essential + ffmpeg rebuild ----------------------------
|
||||
|
||||
if [[ $svt_av1 == ask ]]; then
|
||||
read -rp $'\nInstall svt-av1-essential-git and rebuild ffmpeg against it? (slow) [y/N] ' reply
|
||||
[[ $reply == [yY]* ]] && svt_av1=yes || svt_av1=no
|
||||
fi
|
||||
if [[ $svt_av1 == yes ]]; then
|
||||
step "svt-av1-essential-git"
|
||||
# Interactive on purpose: pacman asks to replace the conflicting svt-av1,
|
||||
# and --noconfirm would answer no
|
||||
yay -S --needed svt-av1-essential-git
|
||||
step "Rebuilding ffmpeg against svt-av1-essential-git"
|
||||
"$repo/system/rebuild-ffmpeg.sh"
|
||||
fi
|
||||
|
||||
# --- Services ----------------------------------------------------------------
|
||||
|
||||
step "Enabling services"
|
||||
# Not started now: sddm would take over the screen mid-script. They come up on reboot.
|
||||
mapfile -t units < <(packages services)
|
||||
sudo systemctl enable "${units[@]}"
|
||||
|
||||
# --- Dotfiles ----------------------------------------------------------------
|
||||
|
||||
step "Linking dotfiles with stow"
|
||||
# Create real parent dirs first, otherwise stow symlinks the whole directory
|
||||
# (e.g. ~/.config) into the repo and every app's files end up in git
|
||||
mkdir -p "$HOME/.config" "$HOME/.local/bin" "$HOME/.local/share/fonts" "$HOME/Pictures"
|
||||
# Sunshine keeps credentials and logs beside its config; those must stay out of git
|
||||
mkdir -p "$HOME/.config/sunshine"
|
||||
# systemd keeps its own wants/ symlinks beside the stowed user units
|
||||
mkdir -p "$HOME/.config/systemd/user"
|
||||
mkdir -p -m 700 "$HOME/.ssh"
|
||||
if ! stow -d "$repo" -t "$HOME" --restow dotfiles; then
|
||||
echo "stow hit conflicts (listed above). Move those files aside, then run:" >&2
|
||||
echo " stow -d \"$repo\" -t \"\$HOME\" dotfiles" >&2
|
||||
fi
|
||||
|
||||
step "Enabling user services"
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now vpn-notify.service
|
||||
|
||||
if [[ "$(getent passwd "$USER" | cut -d: -f7)" != */zsh ]]; then
|
||||
step "Setting zsh as login shell"
|
||||
chsh -s /usr/bin/zsh
|
||||
fi
|
||||
|
||||
# Picks up the stowed fonts in ~/.local/share/fonts (SF Pro Display for hyprlock)
|
||||
fc-cache -f "$HOME/.local/share/fonts" >/dev/null
|
||||
|
||||
step "Done — reboot to load drivers and start Hyprland"
|
||||
1
dotfiles/.config/environment.d/ssh-agent.conf
Normal file
1
dotfiles/.config/environment.d/ssh-agent.conf
Normal file
@@ -0,0 +1 @@
|
||||
SSH_AUTH_SOCK=${HOME}/.bitwarden-ssh-agent.sock
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://github.com/fastfetch-cli/fastfetch/raw/dev/doc/json_schema.json",
|
||||
"logo": {
|
||||
//"source": "~/.config/fastfetch/nixos.png",
|
||||
//"source": "~/.config/fastfetch/arch.png",
|
||||
//"type": "kitty-direct",
|
||||
"height": 15,
|
||||
"width": 30,
|
||||
|
||||
@@ -47,3 +47,11 @@ regular4=53BDFA # blue
|
||||
regular5=FAE994 # magenta
|
||||
regular6=99faf2 # cyan
|
||||
regular7=C7C7C7 # white
|
||||
|
||||
[key-bindings]
|
||||
clipboard-copy=Control+c XF86Copy
|
||||
clipboard-paste=Control+Shift+v XF86Paste
|
||||
|
||||
[text-bindings]
|
||||
# Ctrl+C now copies, so send the interrupt signal (^C) from Ctrl+Shift+C instead
|
||||
\x03=Control+Shift+c
|
||||
|
||||
67
dotfiles/.config/ghostty/config
Normal file
67
dotfiles/.config/ghostty/config
Normal file
@@ -0,0 +1,67 @@
|
||||
# Ported from foot/foot.ini — same look and bindings, so the two can be
|
||||
# tried side by side. Ghostty is now the default terminal ($term in
|
||||
# hypr/input/keybinds.lua). Check edits with `ghostty +validate-config`.
|
||||
|
||||
# --- Font / window ---
|
||||
font-family = JetBrainsMono Nerd Font
|
||||
font-size = 9
|
||||
|
||||
# foot: pad=12x12 center
|
||||
window-padding-x = 12
|
||||
window-padding-y = 12
|
||||
window-padding-balance = true
|
||||
|
||||
# foot: initial-window-size-pixels=1000x520. Ghostty sizes in cells, not
|
||||
# pixels — this is roughly the same at this font size. Only matters for
|
||||
# floating windows; tiled ones fill their slot either way.
|
||||
window-width = 135
|
||||
window-height = 31
|
||||
|
||||
# No title bar or tab strip — Hyprland draws the borders. (window-show-tab-bar
|
||||
# replaces the deprecated gtk-tabs-location = hidden.)
|
||||
window-decoration = none
|
||||
window-show-tab-bar = never
|
||||
|
||||
# --- Terminal type ---
|
||||
# foot forced TERM=xterm-256color everywhere so SSH'd hosts wouldn't lack
|
||||
# terminfo. Ghostty keeps its own richer TERM locally and only falls back
|
||||
# to xterm-256color (or installs its terminfo) when you ssh somewhere.
|
||||
shell-integration-features = ssh-env,ssh-terminfo
|
||||
|
||||
# --- Scrollback / mouse ---
|
||||
# foot kept 5000 lines; Ghostty's limit is in bytes and its 10MB default
|
||||
# is already well beyond that, so it's left alone.
|
||||
mouse-scroll-multiplier = 2
|
||||
mouse-hide-while-typing = false
|
||||
|
||||
# URLs: Ctrl+click opens them with xdg-open by default, like foot's url-mode.
|
||||
|
||||
# --- Cursor ---
|
||||
cursor-style = bar
|
||||
cursor-style-blink = true
|
||||
adjust-cursor-thickness = 1
|
||||
|
||||
# --- Colours ---
|
||||
background-opacity = 0.80
|
||||
background = #0a0e14
|
||||
foreground = #fffbf6
|
||||
selection-foreground = #b3b1ad
|
||||
selection-background = #b3b1ad
|
||||
|
||||
palette = 0=#01060e
|
||||
palette = 1=#eb4129
|
||||
palette = 2=#abe047
|
||||
palette = 3=#f9af4f
|
||||
palette = 4=#53bdfa
|
||||
palette = 5=#fae994
|
||||
palette = 6=#99faf2
|
||||
palette = 7=#c7c7c7
|
||||
|
||||
# --- Keybindings ---
|
||||
# foot: Ctrl+C copies, Ctrl+Shift+C sends the interrupt. "performable:"
|
||||
# makes Ctrl+C copy only when there's a selection — with nothing selected
|
||||
# it falls through and interrupts as normal. Ctrl+Shift+C still sends ^C
|
||||
# too, for muscle memory.
|
||||
keybind = performable:ctrl+c=copy_to_clipboard
|
||||
keybind = ctrl+shift+c=text:\x03
|
||||
# Ctrl+Shift+V paste is Ghostty's default already.
|
||||
@@ -3,5 +3,6 @@ file:///home/thomas/Documents
|
||||
file:///home/thomas/Downloads
|
||||
file:///home/thomas/Pictures
|
||||
file:///home/thomas/Music
|
||||
file:///home/thomas/Videos
|
||||
smb://192.168.1.84/media/
|
||||
smb://192.168.1.85/music/
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
gtk-theme-name=HyprFlux-Compact
|
||||
gtk-icon-theme-name=Papirus-Dark
|
||||
gtk-font-name=Adwaita Sans 11
|
||||
gtk-cursor-theme-name=Future-black Cursors
|
||||
gtk-cursor-theme-name=Bibata-Modern-Classic
|
||||
gtk-cursor-theme-size=24
|
||||
gtk-toolbar-style=GTK_TOOLBAR_ICONS
|
||||
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
gtk-theme-name=HyprFlux-Compact
|
||||
gtk-icon-theme-name=Papirus-Dark
|
||||
gtk-font-name=Adwaita Sans 11
|
||||
gtk-cursor-theme-name=Future-black Cursors
|
||||
gtk-cursor-theme-name=Bibata-Modern-Classic
|
||||
gtk-cursor-theme-size=24
|
||||
gtk-application-prefer-dark-theme=1
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# HyprFlux Dots
|
||||
|
||||
Settings have been massivily modified as to work in a more simple mannor
|
||||
|
||||
I will update the Hyprland-Dots wiki for guidance. Make sure to check out changelogs as well
|
||||
@@ -1,89 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# Environment variables. See https://wiki.hyprland.org/Configuring/Environment-variables/
|
||||
|
||||
# Set your defaults editor through ENV in ~/.config/hypr/UserConfigs/01-UserDefaults.conf
|
||||
|
||||
# environment-variables
|
||||
# Toolkit Backend Variables
|
||||
env = GDK_BACKEND,wayland,x11,*
|
||||
env = QT_QPA_PLATFORM,wayland;xcb
|
||||
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
|
||||
#env = SDL_VIDEODRIVER,wayland
|
||||
|
||||
# xdg Specifications
|
||||
env = XDG_CURRENT_DESKTOP,Hyprland
|
||||
env = XDG_SESSION_DESKTOP,Hyprland
|
||||
env = XDG_SESSION_TYPE,wayland
|
||||
|
||||
# QT Variables
|
||||
env = QT_AUTO_SCREEN_SCALE_FACTOR,1
|
||||
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
|
||||
env = QT_QPA_PLATFORMTHEME,qt5ct
|
||||
env = QT_QPA_PLATFORMTHEME,qt6ct
|
||||
|
||||
# hyprland-qt-support
|
||||
env = QT_QUICK_CONTROLS_STYLE,org.hyprland.style
|
||||
|
||||
# xwayland apps scale fix (useful if you are use monitor scaling).
|
||||
# Set same value if you use scaling in Monitors.conf
|
||||
# 1 is 100% 1.5 is 150%
|
||||
# see https://wiki.hyprland.org/Configuring/XWayland/
|
||||
env = GDK_SCALE,1
|
||||
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/
|
||||
env = HYPRCURSOR_THEME,Bibata-Modern-Classic
|
||||
env = HYPRCURSOR_SIZE,24
|
||||
|
||||
# firefox
|
||||
env = MOZ_ENABLE_WAYLAND,1
|
||||
|
||||
# electron >28 apps (may help) ##
|
||||
# https://www.electronjs.org/docs/latest/api/environment-variables
|
||||
env = ELECTRON_OZONE_PLATFORM_HINT,auto # auto selects Wayland if possible, X11 otherwise
|
||||
|
||||
# NVIDIA
|
||||
# This is from Hyprland Wiki. Below will be activated nvidia gpu detected
|
||||
# See hyprland wiki https://wiki.hyprland.org/Nvidia/#environment-variables
|
||||
|
||||
#env = LIBVA_DRIVER_NAME,nvidia
|
||||
#env = __GLX_VENDOR_LIBRARY_NAME,nvidia
|
||||
#env = NVD_BACKEND,direct
|
||||
#env = GSK_RENDERER,ngl
|
||||
|
||||
# additional ENV's for nvidia. Caution, activate with care
|
||||
#env = GBM_BACKEND,nvidia-drm
|
||||
|
||||
#env = __GL_GSYNC_ALLOWED,1 #adaptive Vsync
|
||||
#env = __NV_PRIME_RENDER_OFFLOAD,1
|
||||
#env = __VK_LAYER_NV_optimus,NVIDIA_only
|
||||
#env = WLR_DRM_NO_ATOMIC,1
|
||||
|
||||
# FOR VM and POSSIBLY NVIDIA
|
||||
# LIBGL_ALWAYS_SOFTWARE software mesa rendering
|
||||
#env = LIBGL_ALWAYS_SOFTWARE,1 # Warning. May cause hyprland to crash
|
||||
#env = WLR_RENDERER_ALLOW_SOFTWARE,1
|
||||
|
||||
# nvidia firefox (for hardware acceleration on FF)?
|
||||
# check this post https://github.com/elFarto/nvidia-vaapi-driver#configuration
|
||||
#env = MOZ_DISABLE_RDD_SANDBOX,1
|
||||
#env = EGL_PLATFORM,wayland
|
||||
|
||||
#### Aquamarine Environment Variables #### ( Hyprland > 0.45 )
|
||||
# https://wiki.hyprland.org/Configuring/Environment-variables/#aquamarine-environment-variables----ref-httpsgithubcomhyprwmaquamarineblobmaindocsenvmd---
|
||||
# env = AQ_TRACE,1 # Enables more verbose logging.
|
||||
# env = AQ_DRM_DEVICES,/dev/dri/card1:/dev/dri/card0 # Set an explicit list of DRM devices (GPUs) to use. It’s a colon-separated list of paths, with the first being the primary. E.g. /dev/dri/card1:/dev/dri/card0
|
||||
# env = AQ_MGPU_NO_EXPLICIT,1 # Disables explicit syncing on mgpu buffers
|
||||
# env = AQ_NO_MODIFIERS,1 # Disables modifiers for DRM buffers
|
||||
|
||||
#### Hyprland Environment Variables ####
|
||||
# https://wiki.hyprland.org/Configuring/Environment-variables/#hyprland-environment-variables
|
||||
# env = HYPRLAND_TRACE,1 # Enables more verbose logging.
|
||||
# env = HYPRLAND_NO_RT,1 # Disables realtime priority setting by Hyprland.
|
||||
# env = HYPRLAND_NO_SD_NOTIFY,1 # If systemd, disables the 'sd_notify' calls.
|
||||
# env = HYPRLAND_NO_SD_VARS,1 # Disables management of variables in systemd and dbus activation environments.
|
||||
@@ -1,209 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# This is where you put your own keybinds. Be Mindful to check as well ~/.config/hypr/configs/Keybinds.conf to avoid conflict
|
||||
# if you think I should replace the Pre-defined Keybinds in ~/.config/hypr/configs/Keybinds.conf , submit an issue or let me know in DC and present me a valid reason as to why, such as conflicting with global shortcuts, etc etc
|
||||
|
||||
# See https://wiki.hyprland.org/Configuring/Keywords/ for more settings and variables
|
||||
# See also Laptops.conf for laptops keybinds
|
||||
|
||||
# /* ---- ✴️ Variables ✴️ ---- */ #
|
||||
$mainMod = SUPER
|
||||
$scriptsDir = $HOME/.config/hypr/scripts
|
||||
$UserScripts = $HOME/.config/hypr/UserScripts
|
||||
|
||||
# settings for User defaults apps - set your default terminal and file manager on this file
|
||||
$edit=${EDITOR:-nvim}
|
||||
|
||||
# These two are for UserKeybinds.conf & Waybar Modules
|
||||
$term = foot # Terminal
|
||||
$files = thunar # File Manager
|
||||
|
||||
# Default Search Engine for ROFI Search (SUPER S)
|
||||
$Search_Engine = "https://www.google.com/search?q={}"
|
||||
|
||||
# common shortcuts
|
||||
bind = $mainMod, SPACE, exec, .config/rofi/launchers/type-4/launcher.sh # desktop overview (if installed)
|
||||
bind = $mainMod, Q, killactive, # close active (not kill)
|
||||
bind = $mainMod SHIFT, Q, exec, $scriptsDir/KillActiveProcess.sh # Kill active process
|
||||
bind = $mainMod, Return, exec, $term #terminal
|
||||
bind = $mainMod, F, exec, $files #file manager
|
||||
bind = $mainMod, B, exec, zen-browser # Launch Zen Browser New Everytime
|
||||
bind = $mainMod, V, exec, $scriptsDir/ClipManager.sh # Clipboard Manager
|
||||
bind = $mainMod, C, exec, code --ozone-platform=x11 # Visual Studio Code
|
||||
bind = $mainMod, O, exec, obsidian --ozone-platform=x11 # Obsidian
|
||||
bind = $mainMod, X, exec, vesktop # Vesktop(Discord)
|
||||
bind = $mainMod, M, exec, fdm # Free Downlaod Manager
|
||||
bind = $mainMod, W, exec, waypaper # Wayland Wallpaper Setter
|
||||
bind = $mainMod, L, exec, hyprlock # Screen Locker
|
||||
bind = $mainMod, DELETE, exec, ~/.config/rofi/powermenu/type-2/powermenu.sh # Power Menu
|
||||
bind = $mainMod, S, exec, cd /home/thomas/hyprmod && uv run hyprmod # Hyprmod - A Hyprland settings manager (if installed)
|
||||
bind = $mainMod SHIFT, R, exec, $scriptsDir/Refresh.sh # Refresh waybar, swaync, rofirrr
|
||||
bind = $mainMod, N, exec, swaync-client -t -sw # swayNC notification panel
|
||||
|
||||
# FEATURES / EXTRAS
|
||||
bind = $mainMod SHIFT, F, fullscreen # whole full screen
|
||||
bind = $mainMod SHIFT, SPACE, togglefloating, #Float Mode
|
||||
|
||||
|
||||
# Desktop zooming or magnifier
|
||||
bind = $mainMod ALT, mouse_down, exec, hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')"
|
||||
bind = $mainMod ALT, mouse_up, exec, hyprctl keyword cursor:zoom_factor "$(hyprctl getoption cursor:zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}')"
|
||||
|
||||
## NOTES for ja (Hyprland version 0.39 (Ubuntu 24.04))
|
||||
#bind = $mainMod ALT, mouse_down, exec, hyprctl keyword misc:cursor_zoom_factor "$(hyprctl getoption misc:cursor_zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor * 2.0}')"
|
||||
#bind = $mainMod ALT, mouse_up, exec, hyprctl keyword misc:cursor_zoom_factor "$(hyprctl getoption misc:cursor_zoom_factor | awk 'NR==1 {factor = $2; if (factor < 1) {factor = 1}; print factor / 2.0}')"
|
||||
|
||||
# Waybar / Bar related
|
||||
bind = $mainMod CTRL ALT, B, exec, pkill -SIGUSR1 waybar # Toggle hide/show waybar
|
||||
|
||||
# FEATURES / EXTRAS (UserScripts)
|
||||
bind = $mainMod SHIFT, A, exec, $scriptsDir/Animations.sh #hyprland animations menu
|
||||
bind = $mainMod SHIFT, Z, exec, $UserScripts/ZshChangeTheme.sh # Change oh-my-zsh theme
|
||||
|
||||
#######################################################################################################################################
|
||||
#Default Keybinds#
|
||||
#######################################################################################################################################
|
||||
|
||||
|
||||
bind = CTRL ALT, Delete, exec, hyprctl dispatch exit 0 # exit Hyprland
|
||||
|
||||
# Master Layout
|
||||
bind = $mainMod CTRL, D, layoutmsg, removemaster
|
||||
bind = $mainMod, I, layoutmsg, addmaster
|
||||
bind = $mainMod, J, layoutmsg, cyclenext
|
||||
bind = $mainMod, K, layoutmsg, cycleprev
|
||||
bind = $mainMod CTRL, Return, layoutmsg, swapwithmaster
|
||||
|
||||
# Dwindle Layout
|
||||
bind = $mainMod, P, pseudo, # dwindle
|
||||
|
||||
# Special Keys / Hot Keys
|
||||
bindel = , xf86audioraisevolume, exec, $scriptsDir/Volume.sh --inc # volume up
|
||||
bindel = , xf86audiolowervolume, exec, $scriptsDir/Volume.sh --dec # volume down
|
||||
bindl = , xf86AudioMicMute, exec, $scriptsDir/Volume.sh --toggle-mic # mic mute
|
||||
bindl = , xf86audiomute, exec, $scriptsDir/Volume.sh --toggle # mute
|
||||
bindl = , xf86Sleep, exec, systemctl suspend # sleep button
|
||||
bindl = , xf86Rfkill, exec, $scriptsDir/AirplaneMode.sh # Airplane mode
|
||||
|
||||
# media controls using keyboards
|
||||
bindl = , xf86AudioPlayPause, exec, $scriptsDir/MediaCtrl.sh --pause
|
||||
bindl = , xf86AudioPause, exec, $scriptsDir/MediaCtrl.sh --pause
|
||||
bindl = , xf86AudioPlay, exec, $scriptsDir/MediaCtrl.sh --pause
|
||||
bindl = , xf86AudioNext, exec, $scriptsDir/MediaCtrl.sh --nxt
|
||||
bindl = , xf86AudioPrev, exec, $scriptsDir/MediaCtrl.sh --prv
|
||||
bindl = , xf86audiostop, exec, $scriptsDir/MediaCtrl.sh --stop
|
||||
|
||||
# Resize windows
|
||||
binde = $mainMod SHIFT, left, resizeactive,-50 0
|
||||
binde = $mainMod SHIFT, right, resizeactive,50 0
|
||||
binde = $mainMod SHIFT, up, resizeactive,0 -50
|
||||
binde = $mainMod SHIFT, down, resizeactive,0 50
|
||||
|
||||
# Move windows
|
||||
bind = $mainMod CTRL, left, movewindow, l
|
||||
bind = $mainMod CTRL, right, movewindow, r
|
||||
bind = $mainMod CTRL, up, movewindow, u
|
||||
bind = $mainMod CTRL, down, movewindow, d
|
||||
|
||||
# Swap windows
|
||||
bind = $mainMod ALT, left, swapwindow, l
|
||||
bind = $mainMod ALT, right, swapwindow, r
|
||||
bind = $mainMod ALT, up, swapwindow, u
|
||||
bind = $mainMod ALT, down, swapwindow, d
|
||||
|
||||
# Move focus with mainMod + arrow keys
|
||||
bind = $mainMod, left, movefocus, l
|
||||
bind = $mainMod, right, movefocus, r
|
||||
bind = $mainMod, up, movefocus, u
|
||||
bind = $mainMod, down, movefocus, d
|
||||
|
||||
# Workspaces related
|
||||
bind = $mainMod, tab, workspace, m+1
|
||||
bind = $mainMod SHIFT, tab, workspace, m-1
|
||||
|
||||
# Special workspace
|
||||
# bind = $mainMod SHIFT, U, movetoworkspace, special
|
||||
# bind = $mainMod, U, togglespecialworkspace,
|
||||
bind = $mainMod, U, togglespecialworkspace, nyx
|
||||
bind = $mainMod SHIFT, U, movetoworkspace, special:nyx
|
||||
|
||||
|
||||
# The following mappings use the key codes to better support various keyboard layouts
|
||||
# 1 is code:10, 2 is code 11, etc
|
||||
# Switch workspaces with mainMod + [0-9]
|
||||
bind = $mainMod, code:10, workspace, 1 # NOTE: code:10 = key 1
|
||||
bind = $mainMod, code:11, workspace, 2 # NOTE: code:11 = key 2
|
||||
bind = $mainMod, code:12, workspace, 3 # NOTE: code:12 = key 3
|
||||
bind = $mainMod, code:13, workspace, 4 # NOTE: code:13 = key 4
|
||||
bind = $mainMod, code:14, workspace, 5 # NOTE: code:14 = key 5
|
||||
bind = $mainMod, code:15, workspace, 6 # NOTE: code:15 = key 6
|
||||
bind = $mainMod, code:16, workspace, 7 # NOTE: code:16 = key 7
|
||||
bind = $mainMod, code:17, workspace, 8 # NOTE: code:17 = key 8
|
||||
bind = $mainMod, code:18, workspace, 9 # NOTE: code:18 = key 9
|
||||
bind = $mainMod, code:19, workspace, 10 # NOTE: code:19 = key 0
|
||||
|
||||
# Move active window and follow to workspace mainMod + SHIFT [0-9]
|
||||
bind = $mainMod SHIFT, code:10, movetoworkspace, 1 # NOTE: code:10 = key 1
|
||||
bind = $mainMod SHIFT, code:11, movetoworkspace, 2 # NOTE: code:11 = key 2
|
||||
bind = $mainMod SHIFT, code:12, movetoworkspace, 3 # NOTE: code:12 = key 3
|
||||
bind = $mainMod SHIFT, code:13, movetoworkspace, 4 # NOTE: code:13 = key 4
|
||||
bind = $mainMod SHIFT, code:14, movetoworkspace, 5 # NOTE: code:14 = key 5
|
||||
bind = $mainMod SHIFT, code:15, movetoworkspace, 6 # NOTE: code:15 = key 6
|
||||
bind = $mainMod SHIFT, code:16, movetoworkspace, 7 # NOTE: code:16 = key 7
|
||||
bind = $mainMod SHIFT, code:17, movetoworkspace, 8 # NOTE: code:17 = key 8
|
||||
bind = $mainMod SHIFT, code:18, movetoworkspace, 9 # NOTE: code:18 = key 9
|
||||
bind = $mainMod SHIFT, code:19, movetoworkspace, 10 # NOTE: code:19 = key 0
|
||||
bind = $mainMod SHIFT, bracketleft, movetoworkspace, -1 # brackets [
|
||||
bind = $mainMod SHIFT, bracketright, movetoworkspace, +1 # brackets ]
|
||||
|
||||
# Move active window to a workspace silently mainMod + CTRL [0-9]
|
||||
bind = $mainMod CTRL, code:10, movetoworkspacesilent, 1 # NOTE: code:10 = key 1
|
||||
bind = $mainMod CTRL, code:11, movetoworkspacesilent, 2 # NOTE: code:11 = key 2
|
||||
bind = $mainMod CTRL, code:12, movetoworkspacesilent, 3 # NOTE: code:12 = key 3
|
||||
bind = $mainMod CTRL, code:13, movetoworkspacesilent, 4 # NOTE: code:13 = key 4
|
||||
bind = $mainMod CTRL, code:14, movetoworkspacesilent, 5 # NOTE: code:14 = key 5
|
||||
bind = $mainMod CTRL, code:15, movetoworkspacesilent, 6 # NOTE: code:15 = key 6
|
||||
bind = $mainMod CTRL, code:16, movetoworkspacesilent, 7 # NOTE: code:16 = key 7
|
||||
bind = $mainMod CTRL, code:17, movetoworkspacesilent, 8 # NOTE: code:17 = key 8
|
||||
bind = $mainMod CTRL, code:18, movetoworkspacesilent, 9 # NOTE: code:18 = key 9
|
||||
bind = $mainMod CTRL, code:19, movetoworkspacesilent, 10 # NOTE: code:19 = key 0
|
||||
bind = $mainMod CTRL, bracketleft, movetoworkspacesilent, -1 # brackets [
|
||||
bind = $mainMod CTRL, bracketright, movetoworkspacesilent, +1 # brackets ]
|
||||
|
||||
# Scroll through existing workspaces with mainMod + scroll
|
||||
bind = $mainMod, mouse_down, workspace, e+1
|
||||
bind = $mainMod, mouse_up, workspace, e-1
|
||||
bind = $mainMod, period, workspace, e+1
|
||||
bind = $mainMod, comma, workspace, e-1
|
||||
|
||||
# Move/resize windows with mainMod + LMB/RMB and dragging
|
||||
bindm = $mainMod, mouse:272, movewindow # NOTE: mouse:272 = left click
|
||||
bindm = $mainMod, mouse:273, resizewindow # NOTE: mouse:272 = right click
|
||||
|
||||
#########################################################################################################
|
||||
# Laptop specific keybinds
|
||||
#########################################################################################################
|
||||
|
||||
# for disabling Touchpad. hyprctl devices to get device name.
|
||||
$Touchpad_Device=asue1209:00-04f3:319f-touchpad
|
||||
|
||||
binde = , xf86KbdBrightnessDown, exec, $scriptsDir/BrightnessKbd.sh --dec # decrease keyboard brightness
|
||||
binde = , xf86KbdBrightnessUp, exec, $scriptsDir/BrightnessKbd.sh --inc # increase keyboard brightness
|
||||
bind = , xf86Launch1, exec, rog-control-center # ASUS Armory crate button
|
||||
bind = , xf86Launch3, exec, asusctl led-mode -n # FN+F4 Switch keyboard RGB profile
|
||||
bind = , xf86Launch4, exec, asusctl profile -n # FN+F5 change of fan profiles (Quite, Balance, Performance)
|
||||
binde = , xf86MonBrightnessDown, exec, $scriptsDir/Brightness.sh --dec # decrease monitor brightness
|
||||
binde = , xf86MonBrightnessUp, exec, $scriptsDir/Brightness.sh --inc # increase monitor brightness
|
||||
bind = , xf86TouchpadToggle, exec, $scriptsDir/TouchPad.sh # disable touchpad
|
||||
|
||||
# Screenshot keybindings using F6 (no PrinSrc button)
|
||||
#bind = $mainMod, F6, exec, $scriptsDir/ScreenShot.sh --now # screenshot
|
||||
#bind = $mainMod SHIFT, F6, exec, $scriptsDir/ScreenShot.sh --area # screenshot (area)
|
||||
#bind = $mainMod CTRL, F6, exec, $scriptsDir/ScreenShot.sh --in5 # # screenshot (5 secs delay)
|
||||
#bind = $mainMod ALT, F6, exec, $scriptsDir/ScreenShot.sh --in10 # screenshot (10 secs delay)
|
||||
#bind = ALT, F6, exec, $scriptsDir/ScreenShot.sh --active # screenshot (active window only)
|
||||
|
||||
$TOUCHPAD_ENABLED = true
|
||||
device {
|
||||
name = $Touchpad_Device
|
||||
enabled = $TOUCHPAD_ENABLED
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# NOTE, THIS FILE IS BEING USED by disabling Laptop display monitor behaviour when closing lid.
|
||||
# See notes on Laptops.conf
|
||||
|
||||
#monitor = eDP-1, preferred, auto, 1
|
||||
@@ -1,31 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# See https://wiki.hyprland.org/Configuring/Keywords/ for more variable settings
|
||||
# These configs are mostly for laptops. This is addemdum to Keybinds.conf
|
||||
|
||||
$mainMod = SUPER
|
||||
$scriptsDir = $HOME/.config/hypr/scripts
|
||||
$UserConfigs = $HOME/.config/hypr/UserConfigs
|
||||
|
||||
|
||||
# Below are useful when you are connecting your laptop in external display
|
||||
# Suggest you edit below for your laptop display
|
||||
# From WIKI This is to disable laptop monitor when lid is closed.
|
||||
# consult https://wiki.hyprland.org/hyprland-wiki/pages/Configuring/Binds/#switches
|
||||
#bindl = , switch:off:Lid Switch,exec,hyprctl keyword monitor "eDP-1, preferred, auto, 1"
|
||||
#bindl = , switch:on:Lid Switch,exec,hyprctl keyword monitor "eDP-1, disable"
|
||||
|
||||
|
||||
# WARNING! Using this method has some caveats!! USE THIS PART WITH SOME CAUTION!
|
||||
# CONS of doing this, is that you need to set up your wallpaper (SUPER W) and choose wallpaper.
|
||||
# CAVEATS! Sometimes the Main Laptop Monitor DOES NOT have display that it needs to re-connect your external monitor
|
||||
# One work around is to ensure that before shutting down laptop, MAKE SURE your laptop lid is OPEN!!
|
||||
# Make sure to comment (put # on the both the bindl = , switch ......) above
|
||||
# NOTE: Display for laptop are being generated into LaptopDisplay.conf
|
||||
# This part is to be use if you do not want your main laptop monitor to wake up during say wallpaper change etc
|
||||
|
||||
#bindl = , switch:off:Lid Switch,exec,echo "monitor = eDP-1, preferred, auto, 1" > $UserConfigs/LaptopDisplay.conf
|
||||
#bindl = , switch:on:Lid Switch,exec,echo "monitor = eDP-1, disable" > $UserConfigs/LaptopDisplay.conf
|
||||
|
||||
# for laptop-lid action (to erase the last entry)
|
||||
#exec-once = echo "monitor = eDP-1, preferred, auto, 1" > $HOME/.config/hypr/UserConfigs/LaptopDisplay.conf
|
||||
#
|
||||
@@ -1,43 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# Commands and Apps to be executed at launch
|
||||
|
||||
$scriptsDir = $HOME/.config/hypr/scripts
|
||||
|
||||
# wallpaper stuff
|
||||
exec-once = awww-daemon --format xrgb
|
||||
#exec-once = mpvpaper '*' -o "load-scripts=no no-audio --loop" $livewallpaper
|
||||
|
||||
# Startup
|
||||
exec-once = dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
|
||||
exec-once = systemctl --user import-environment WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
|
||||
|
||||
# Polkit (Polkit Gnome / KDE)
|
||||
exec-once = $scriptsDir/Polkit.sh
|
||||
|
||||
# starup apps
|
||||
exec-once = nm-applet --indicator
|
||||
exec-once = swaync
|
||||
#exec-once = ags
|
||||
#exec-once = blueman-applet
|
||||
#exec-once = rog-control-center
|
||||
exec-once = waybar
|
||||
exec-once = qs # quickshell AGS Desktop Overview alternative
|
||||
|
||||
#clipboard manager
|
||||
exec-once = wl-paste --type text --watch cliphist store
|
||||
exec-once = wl-paste --type image --watch cliphist store
|
||||
|
||||
# Starting hypridle to start hyprlock
|
||||
exec-once = hypridle
|
||||
|
||||
exec-once = hyprctl dispatch workspace 3
|
||||
exec-once = /usr/bin/Plexamp.AppImage &&
|
||||
exec-once = sleep 3 && vesktop
|
||||
exec-once = hyprctl dispatch workspace 1
|
||||
exec-once = bitwarden-desktop --ozone-platform-hint=auto --silent
|
||||
|
||||
# Here are list of features available but disabled by default
|
||||
# exec-once = awww-daemon --format xrgb && awww img $HOME/Pictures/wallpapers/mecha-nostalgia.png # persistent wallpaper
|
||||
|
||||
# xdg-desktop-portal-hyprland (should be auto starting. However, you can force to start)
|
||||
#exec-once = $scriptsDir/PortalHyprland.sh
|
||||
@@ -1,26 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = smoothOut, 0.5, 0, 0.99, 0.99
|
||||
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
|
||||
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 5, winIn, slide
|
||||
animation = windowsOut, 1, 3, smoothOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
|
||||
animation = fade, 1, 3, smoothOut
|
||||
animation = workspaces, 1, 5, overshot
|
||||
|
||||
# animations for -git or version >0.42.0
|
||||
animation = workspacesIn, 1, 5, winIn, slide
|
||||
animation = workspacesOut, 1, 5, winOut, slide
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# Decoration Settings
|
||||
|
||||
# Hyprland Wiki Links
|
||||
# Animation - https://wiki.hyprland.org/Configuring/Animations/
|
||||
# Decoration - https://wiki.hyprland.org/Configuring/Variables/#decoration
|
||||
|
||||
# HyprFlux static color palette
|
||||
source = $HOME/.config/hypr/colours/hyprflux-colors.conf
|
||||
|
||||
general {
|
||||
border_size = 0
|
||||
gaps_in = 2
|
||||
gaps_out = 4
|
||||
|
||||
# col.active_border = $color12
|
||||
# col.inactive_border = $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 = $color12
|
||||
color_inactive = $color10
|
||||
}
|
||||
|
||||
|
||||
blur {
|
||||
enabled = true
|
||||
size = 6
|
||||
passes = 2
|
||||
ignore_opacity = true
|
||||
new_optimizations = true
|
||||
special = true
|
||||
popups = true
|
||||
}
|
||||
}
|
||||
|
||||
group {
|
||||
col.border_active = $color15
|
||||
|
||||
groupbar {
|
||||
col.active = $color0
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# User Settings
|
||||
# This is where you put your own settings as this will not be touched during update
|
||||
# if the upgrade.sh is used.
|
||||
|
||||
# refer to Hyprland wiki for more info https://wiki.hyprland.org/Configuring/Variables/
|
||||
|
||||
# NOTE: some settings are in ~/.config/hypr/UserConfigs/UserDecorAnimations.conf
|
||||
|
||||
dwindle {
|
||||
preserve_split = true
|
||||
#smart_split = true
|
||||
special_scale_factor = 1
|
||||
}
|
||||
|
||||
master {
|
||||
new_status = master
|
||||
new_on_top = 1
|
||||
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 table see link above for proper variables
|
||||
tablet {
|
||||
transform = 0
|
||||
left_handed = 0
|
||||
}
|
||||
}
|
||||
|
||||
gestures {
|
||||
# workspace_swipe = true
|
||||
# workspace_swipe_fingers = 3
|
||||
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 = off
|
||||
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 = 0
|
||||
}
|
||||
|
||||
cursor {
|
||||
sync_gsettings_theme = true
|
||||
no_hardware_cursors = 2 # change to 1 if want to disable
|
||||
enable_hyprcursor = true
|
||||
warp_on_change_workspace = 2
|
||||
no_warps = true
|
||||
}
|
||||
@@ -1,744 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# For window rules and layerrules
|
||||
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
|
||||
|
||||
# NOTES: This is only for Hyprland > 0.48
|
||||
|
||||
# note for ja: This should NOT be implemented on Debian and Ubuntu
|
||||
|
||||
# windowrule - tags - add apps under appropriate tag to use the same settings
|
||||
# browser tags
|
||||
windowrule {
|
||||
name = windowrule-1
|
||||
tag = +browser
|
||||
match:class = ^([Ff]irefox|org.mozilla.firefox|[Ff]irefox-esr|[Ff]irefox-bin)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-2
|
||||
tag = +browser
|
||||
match:class = ^([Gg]oogle-chrome(-beta|-dev|-unstable)?)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-3
|
||||
tag = +browser
|
||||
match:class = ^(chrome-.+-Default)$ # Chrome PWAs
|
||||
}
|
||||
|
||||
# windowrule = tag +browser, class:^([Cc]hromium)$
|
||||
windowrule {
|
||||
name = windowrule-4
|
||||
tag = +browser
|
||||
match:class = ^([Mm]icrosoft-edge(-stable|-beta|-dev|-unstable))$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-5
|
||||
tag = +browser
|
||||
match:class = ^(Brave-browser(-beta|-dev|-unstable)?)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-6
|
||||
tag = +browser
|
||||
match:class = ^([Tt]horium-browser|[Cc]achy-browser)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-7
|
||||
tag = +browser
|
||||
match:class = ^(zen-alpha|zen)$
|
||||
}
|
||||
|
||||
|
||||
# notif tags
|
||||
windowrule {
|
||||
name = windowrule-8
|
||||
tag = +notif
|
||||
match:class = ^(swaync-control-center|swaync-notification-window|swaync-client|class)$
|
||||
}
|
||||
|
||||
|
||||
# HyprFlux settings tag
|
||||
windowrule {
|
||||
name = windowrule-9
|
||||
tag = +HyprFlux_Cheat
|
||||
match:title = ^(HyprFlux Quick Cheat Sheet)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-10
|
||||
tag = +HyprFlux_Settings
|
||||
match:title = ^(HyprFlux Settings)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-11
|
||||
tag = +HyprFlux-Settings
|
||||
match:class = ^(nwg-displays|nwg-look)$
|
||||
}
|
||||
|
||||
|
||||
# terminal tags
|
||||
windowrule {
|
||||
name = windowrule-12
|
||||
tag = +terminal
|
||||
match:class = ^(Alacritty|kitty|kitty-dropterm)$
|
||||
}
|
||||
|
||||
|
||||
# email tags
|
||||
windowrule {
|
||||
name = windowrule-13
|
||||
tag = +email
|
||||
match:class = ^([Tt]hunderbird|org.gnome.Evolution)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-14
|
||||
tag = +email
|
||||
match:class = ^(eu.betterbird.Betterbird)$
|
||||
}
|
||||
|
||||
|
||||
# project tags
|
||||
windowrule {
|
||||
name = windowrule-15
|
||||
tag = +projects
|
||||
match:class = ^(codium|codium-url-handler|VSCodium)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-16
|
||||
tag = +projects
|
||||
match:class = ^(VSCode|code-url-handler)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-17
|
||||
tag = +projects
|
||||
match:class = ^(jetbrains-.+)$ # JetBrains IDEs
|
||||
}
|
||||
|
||||
|
||||
# screenshare tags
|
||||
windowrule {
|
||||
name = windowrule-18
|
||||
tag = +screenshare
|
||||
match:class = ^(com.obsproject.Studio)$
|
||||
}
|
||||
|
||||
|
||||
# IM tags
|
||||
# windowrule = tag +im, class:^([Dd]iscord|[Ww]ebCord|[Vv]esktop)$
|
||||
windowrule {
|
||||
name = windowrule-19
|
||||
tag = +im
|
||||
center = on
|
||||
float = on
|
||||
size = (monitor_w*0.6) (monitor_h*0.7)
|
||||
match:class = ^([Ff]erdium)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-20
|
||||
tag = +im
|
||||
match:class = ^([Ww]hatsapp-for-linux)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-21
|
||||
tag = +im
|
||||
match:class = ^(ZapZap|com.rtosta.zapzap)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-22
|
||||
tag = +im
|
||||
match:class = ^(org.telegram.desktop|io.github.tdesktop_x64.TDesktop)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-23
|
||||
tag = +im
|
||||
match:class = ^(teams-for-linux)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-24
|
||||
tag = +im
|
||||
match:class = ^(im.riot.Riot|Element)$ # Element Matrix client
|
||||
}
|
||||
|
||||
|
||||
# game tags
|
||||
windowrule {
|
||||
name = windowrule-25
|
||||
tag = +games
|
||||
match:class = ^(gamescope)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-26
|
||||
tag = +games
|
||||
match:class = ^(steam_app_\d+)$
|
||||
}
|
||||
|
||||
|
||||
# gamestore tags
|
||||
windowrule {
|
||||
name = windowrule-27
|
||||
tag = +gamestore
|
||||
match:class = ^([Ss]team)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-28
|
||||
tag = +gamestore
|
||||
match:title = ^([Ll]utris)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-29
|
||||
tag = +gamestore
|
||||
match:class = ^(com.heroicgameslauncher.hgl)$
|
||||
}
|
||||
|
||||
|
||||
# file-manager tags
|
||||
windowrule {
|
||||
name = windowrule-30
|
||||
tag = +file-manager
|
||||
match:class = ^([Tt]hunar|org.gnome.Nautilus|[Pp]cmanfm-qt)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-31
|
||||
tag = +file-manager
|
||||
match:class = ^(app.drey.Warp)$
|
||||
}
|
||||
|
||||
|
||||
# wallpaper tags
|
||||
windowrule {
|
||||
name = windowrule-32
|
||||
tag = +wallpaper
|
||||
match:class = ^([Ww]aytrogen)$
|
||||
}
|
||||
|
||||
|
||||
# multimedia tags
|
||||
windowrule {
|
||||
name = windowrule-33
|
||||
tag = +multimedia
|
||||
match:class = ^([Aa]udacious)$
|
||||
}
|
||||
|
||||
|
||||
# multimedia-video tags
|
||||
windowrule {
|
||||
name = windowrule-34
|
||||
tag = +multimedia_video
|
||||
match:class = ^([Mm]pv|vlc)$
|
||||
}
|
||||
|
||||
|
||||
# settings tags
|
||||
windowrule {
|
||||
name = windowrule-35
|
||||
tag = +settings
|
||||
center = on
|
||||
match:title = ^(ROG Control)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-36
|
||||
tag = +settings
|
||||
match:class = ^(wihotspot(-gui)?)$ # wifi hotspot
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-37
|
||||
tag = +settings
|
||||
match:class = ^([Bb]aobab|org.gnome.[Bb]aobab)$ # Disk usage analyzer
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-38
|
||||
tag = +settings
|
||||
match:class = ^(gnome-disks|wihotspot(-gui)?)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-39
|
||||
tag = +settings
|
||||
match:title = (Kvantum Manager)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-40
|
||||
tag = +settings
|
||||
match:class = ^(file-roller|org.gnome.FileRoller)$ # archive manager
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-41
|
||||
tag = +settings
|
||||
match:class = ^(nm-applet|nm-connection-editor|blueman-manager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-42
|
||||
tag = +settings
|
||||
center = on
|
||||
match:class = ^(pavucontrol|org.pulseaudio.pavucontrol|com.saivert.pwvucontrol)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-43
|
||||
tag = +settings
|
||||
match:class = ^(qt5ct|qt6ct|[Yy]ad)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-44
|
||||
tag = +settings
|
||||
match:class = (xdg-desktop-portal-gtk)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-45
|
||||
tag = +settings
|
||||
match:class = ^(org.kde.polkit-kde-authentication-agent-1)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-46
|
||||
tag = +settings
|
||||
match:class = ^([Rr]ofi)$
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Custom rule
|
||||
windowrule {
|
||||
name = windowrule-47
|
||||
tile = on
|
||||
match:class = ^([Cc]hromium)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-48
|
||||
size = (monitor_w*0.25) (monitor_h*0.55)
|
||||
float = on
|
||||
match:class = ^(com.network.manager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-49
|
||||
workspace = special:nyx
|
||||
match:class = ^([Vv]esktop|[Dd]iscord|[Ss]potify)$
|
||||
}
|
||||
|
||||
|
||||
# viewer tags
|
||||
windowrule {
|
||||
name = windowrule-50
|
||||
tag = +viewer
|
||||
match:class = ^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$ # system monitor
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-51
|
||||
tag = +viewer
|
||||
match:class = ^(evince)$ # document viewer
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-52
|
||||
tag = +viewer
|
||||
match:class = ^(eog|org.gnome.Loupe)$ # image viewer
|
||||
}
|
||||
|
||||
|
||||
# Some special override rules
|
||||
windowrule {
|
||||
name = windowrule-53
|
||||
no_blur = on
|
||||
opacity = 1.0
|
||||
match:tag = multimedia_video*
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# POSITION
|
||||
# windowrule = center,floating:1 # warning, it cause even the menu to float and center.
|
||||
windowrule {
|
||||
name = windowrule-54
|
||||
center = on
|
||||
float = on
|
||||
size = (monitor_w*0.65) (monitor_h*0.9)
|
||||
match:tag = HyprFlux_Cheat*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-55
|
||||
center = on
|
||||
float = on
|
||||
match:class = ([Tt]hunar)
|
||||
match:title = negative:(.*[Tt]hunar.*)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-56
|
||||
center = on
|
||||
float = on
|
||||
match:tag = HyprFlux-Settings*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-57
|
||||
center = on
|
||||
match:title = ^(Keybindings)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-58
|
||||
center = on
|
||||
size = (monitor_w*0.6) (monitor_h*0.7)
|
||||
match:class = ^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-59
|
||||
move = ((monitor_w*0.61)) ((monitor_h*0.07))
|
||||
float = on
|
||||
size = (monitor_w*0.38) (monitor_h*0.38)
|
||||
pin = on
|
||||
keep_aspect_ratio = on
|
||||
match:title = ^(Picture-in-Picture)$
|
||||
}
|
||||
|
||||
#windowrule = move 72% 7%,title:^(Firefox)$
|
||||
|
||||
# windowrule to avoid idle for fullscreen apps
|
||||
#windowrule = idleinhibit fullscreen, class:^(*)$
|
||||
#windowrule = idleinhibit fullscreen, title:^(*)$
|
||||
windowrule {
|
||||
name = windowrule-60
|
||||
idle_inhibit = fullscreen
|
||||
match:fullscreen = 1
|
||||
}
|
||||
|
||||
|
||||
# windowrule move to workspace
|
||||
windowrule {
|
||||
name = windowrule-61
|
||||
workspace = 1
|
||||
match:tag = projects*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-62
|
||||
workspace = 1
|
||||
match:tag = email*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-63
|
||||
workspace = 2
|
||||
match:tag = browser*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-64
|
||||
workspace = 3
|
||||
match:class = ^([Tt]hunar)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-65
|
||||
workspace = 4
|
||||
match:tag = im*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-66
|
||||
workspace = 5
|
||||
match:tag = gamestore*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-67
|
||||
workspace = 8
|
||||
no_blur = on
|
||||
fullscreen = on
|
||||
match:tag = games*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-68
|
||||
workspace = 1
|
||||
match:class = ^(kitty)$
|
||||
match:title = ^(tmuxifier)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-69
|
||||
workspace = 9
|
||||
match:class = ^(virt-viewer)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-70
|
||||
workspace = 10
|
||||
match:class = ^([Oo]bsidian)$
|
||||
}
|
||||
|
||||
|
||||
# windowrule move to workspace (silent)
|
||||
windowrule {
|
||||
name = windowrule-71
|
||||
workspace = 4 silent
|
||||
match:tag = screenshare*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-72
|
||||
workspace = 9 silent
|
||||
match:class = ^(virt-manager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-73
|
||||
workspace = 9 silent
|
||||
match:class = ^(.virt-manager-wrapped)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-74
|
||||
workspace = 9 silent
|
||||
match:tag = multimedia*
|
||||
}
|
||||
|
||||
|
||||
# FLOAT
|
||||
windowrule {
|
||||
name = windowrule-75
|
||||
float = on
|
||||
opacity = 0.9 0.7
|
||||
size = (monitor_w*0.7) (monitor_h*0.7)
|
||||
match:tag = wallpaper*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-76
|
||||
float = on
|
||||
opacity = 0.8 0.7
|
||||
size = (monitor_w*0.7) (monitor_h*0.7)
|
||||
match:tag = settings*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-77
|
||||
float = on
|
||||
match:tag = viewer*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-78
|
||||
float = on
|
||||
match:class = ([Zz]oom|onedriver|onedriver-launcher)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-79
|
||||
float = on
|
||||
match:class = (org.gnome.Calculator)
|
||||
match:title = (Calculator)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-80
|
||||
float = on
|
||||
match:class = ^(mpv|com.github.rafostar.Clapper)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-81
|
||||
float = on
|
||||
match:class = ^([Qq]alculate-gtk)$
|
||||
}
|
||||
|
||||
#windowrule = float, class:^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
|
||||
#windowrule = float, title:^(Firefox)$
|
||||
windowrule {
|
||||
name = windowrule-82
|
||||
float = on
|
||||
match:class = ^(fdm|freedownloadmanager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-83
|
||||
float = on
|
||||
match:class = ^(proton-authenticator)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-84
|
||||
float = on
|
||||
match:class = ^([Ww]indscribe)$
|
||||
}
|
||||
|
||||
|
||||
|
||||
# windowrule - ######### float popups and dialogue #######
|
||||
windowrule {
|
||||
name = windowrule-85
|
||||
float = on
|
||||
center = on
|
||||
match:title = ^(Authentication Required)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-86
|
||||
float = on
|
||||
match:class = (codium|codium-url-handler|VSCodium)
|
||||
match:title = negative:(.*codium.*|.*VSCodium.*)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-87
|
||||
float = on
|
||||
match:class = ^(com.heroicgameslauncher.hgl)$
|
||||
match:title = negative:(Heroic Games Launcher)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-88
|
||||
float = on
|
||||
match:class = ^([Ss]team)$
|
||||
match:title = negative:^([Ss]team)$
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-89
|
||||
float = on
|
||||
size = (monitor_w*0.7) (monitor_h*0.6)
|
||||
center = on
|
||||
match:title = ^(Add Folder to Workspace)$
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-90
|
||||
float = on
|
||||
size = (monitor_w*0.7) (monitor_h*0.6)
|
||||
center = on
|
||||
match:title = ^(Save As)$
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-91
|
||||
float = on
|
||||
size = (monitor_w*0.7) (monitor_h*0.6)
|
||||
match:initial_title = (Open Files)
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-92
|
||||
float = on
|
||||
center = on
|
||||
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
|
||||
# windowrule = opacity 0.9 0.7, tag:browser*
|
||||
# windowrule = opacity 0.9 0.8, tag:projects*
|
||||
# windowrule = opacity 0.94 0.86, tag:im*
|
||||
# windowrule = opacity 0.94 0.86, tag:multimedia*
|
||||
# windowrule = opacity 0.9 0.8, tag:file-manager*
|
||||
windowrule {
|
||||
name = windowrule-93
|
||||
opacity = 0.8 0.7
|
||||
match:tag = terminal*
|
||||
}
|
||||
|
||||
# windowrule = opacity 0.82 0.75, tag:viewer*
|
||||
windowrule {
|
||||
name = windowrule-94
|
||||
opacity = 0.8 0.7
|
||||
match:class = ^(gedit|org.gnome.TextEditor|mousepad)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-95
|
||||
opacity = 0.9 0.8
|
||||
match:class = ^(deluge)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-96
|
||||
opacity = 0.9 0.8
|
||||
match:class = ^(seahorse)$ # gnome-keyring gui
|
||||
}
|
||||
|
||||
# windowrule = opacity 0.95 0.75, title:^(Picture-in-Picture)$
|
||||
|
||||
# SIZE
|
||||
|
||||
#windowrule = size 25% 25%, title:^(Firefox)$
|
||||
|
||||
# PINNING
|
||||
#windowrule = pin,title:^(Firefox)$
|
||||
|
||||
# windowrule - extras
|
||||
|
||||
# BLUR & FULLSCREEN
|
||||
|
||||
#windowrule = bordercolor rgb(EE4B55) rgb(880808), fullscreen:1
|
||||
#windowrule = bordercolor rgb(282737) rgb(1E1D2D), floating:1
|
||||
#windowrule = opacity 0.8 0.8, pinned:1
|
||||
|
||||
|
||||
|
||||
# LAYER RULES
|
||||
layerrule {
|
||||
name = layerrule-1
|
||||
blur = on
|
||||
ignore_alpha = 0
|
||||
match:namespace = rofi
|
||||
}
|
||||
|
||||
layerrule {
|
||||
name = layerrule-2
|
||||
blur = on
|
||||
ignore_alpha = 0
|
||||
match:namespace = notifications
|
||||
}
|
||||
|
||||
layerrule {
|
||||
name = layerrule-3
|
||||
blur = on
|
||||
ignore_alpha = 0.5
|
||||
match:namespace = quickshell:overview
|
||||
}
|
||||
|
||||
|
||||
#layerrule = ignorealpha 0.5, tag:notif*
|
||||
|
||||
#layerrule = ignorezero, class:^([Rr]ofi)$
|
||||
#layerrule = blur, class:^([Rr]ofi)$
|
||||
#layerrule = unset,class:^([Rr]ofi)$
|
||||
#layerrule = ignorezero, <rofi>
|
||||
|
||||
#layerrule = ignorezero, overview
|
||||
#layerrule = blur, overview
|
||||
@@ -1,666 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# For window rules and layerrules
|
||||
# See https://wiki.hyprland.org/Configuring/Window-Rules/ for more
|
||||
|
||||
# NOTES: This is only for Hyprland > 0.48
|
||||
|
||||
# note for ja: This should NOT be implemented on Debian and Ubuntu
|
||||
|
||||
# windowrule - tags - add apps under appropriate tag to use the same settings
|
||||
# browser tags
|
||||
windowrule {
|
||||
name = windowrule-1
|
||||
tag = +browser
|
||||
match:class = ^([Ff]irefox|org.mozilla.firefox|[Ff]irefox-esr|[Ff]irefox-bin)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = rustdesk
|
||||
tag = +rustdesk
|
||||
match:class = ^(rustdesk)$, xwayland
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-2
|
||||
tag = +browser
|
||||
match:class = ^([Gg]oogle-chrome(-beta|-dev|-unstable)?)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-3
|
||||
tag = +browser
|
||||
match:class = ^(chrome-.+-Default)$ # Chrome PWAs
|
||||
}
|
||||
|
||||
# windowrule = tag +browser, class:^([Cc]hromium)$
|
||||
windowrule {
|
||||
name = windowrule-4
|
||||
tag = +browser
|
||||
match:class = ^([Mm]icrosoft-edge(-stable|-beta|-dev|-unstable))$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-5
|
||||
tag = +browser
|
||||
match:class = ^(Brave-browser(-beta|-dev|-unstable)?)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-6
|
||||
tag = +browser
|
||||
match:class = ^([Tt]horium-browser|[Cc]achy-browser)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-7
|
||||
tag = +browser
|
||||
match:class = ^(zen-alpha|zen)$
|
||||
}
|
||||
|
||||
|
||||
# notif tags
|
||||
windowrule {
|
||||
name = windowrule-8
|
||||
tag = +notif
|
||||
match:class = ^(swaync-control-center|swaync-notification-window|swaync-client|class)$
|
||||
}
|
||||
|
||||
|
||||
# HyprFlux settings tag
|
||||
windowrule {
|
||||
name = windowrule-9
|
||||
tag = +HyprFlux_Cheat
|
||||
match:title = ^(HyprFlux Quick Cheat Sheet)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-10
|
||||
tag = +HyprFlux_Settings
|
||||
match:title = ^(HyprFlux Settings)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-11
|
||||
tag = +HyprFlux-Settings
|
||||
match:class = ^(nwg-displays|nwg-look)$
|
||||
}
|
||||
|
||||
|
||||
# terminal tags
|
||||
windowrule {
|
||||
name = windowrule-12
|
||||
tag = +terminal
|
||||
match:class = ^(Alacritty|kitty|kitty-dropterm)$
|
||||
}
|
||||
|
||||
|
||||
# email tags
|
||||
windowrule {
|
||||
name = windowrule-13
|
||||
tag = +email
|
||||
match:class = ^([Tt]hunderbird|org.gnome.Evolution)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-14
|
||||
tag = +email
|
||||
match:class = ^(eu.betterbird.Betterbird)$
|
||||
}
|
||||
|
||||
|
||||
# project tags
|
||||
windowrule {
|
||||
name = windowrule-15
|
||||
tag = +projects
|
||||
match:class = ^(codium|codium-url-handler|VSCodium)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-16
|
||||
tag = +projects
|
||||
match:class = ^(VSCode|code-url-handler)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-17
|
||||
tag = +projects
|
||||
match:class = ^(jetbrains-.+)$ # JetBrains IDEs
|
||||
}
|
||||
|
||||
|
||||
# screenshare tags
|
||||
windowrule {
|
||||
name = windowrule-18
|
||||
tag = +screenshare
|
||||
match:class = ^(com.obsproject.Studio)$
|
||||
}
|
||||
|
||||
|
||||
# IM tags
|
||||
# windowrule = tag +im, class:^([Dd]iscord|[Ww]ebCord|[Vv]esktop)$
|
||||
windowrule {
|
||||
name = windowrule-19
|
||||
tag = +im
|
||||
center = on
|
||||
float = on
|
||||
size = (monitor_w*0.6) (monitor_h*0.7)
|
||||
match:class = ^([Ff]erdium)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-20
|
||||
tag = +im
|
||||
match:class = ^([Ww]hatsapp-for-linux)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-21
|
||||
tag = +im
|
||||
match:class = ^(ZapZap|com.rtosta.zapzap)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-22
|
||||
tag = +im
|
||||
match:class = ^(org.telegram.desktop|io.github.tdesktop_x64.TDesktop)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-23
|
||||
tag = +im
|
||||
match:class = ^(teams-for-linux)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-24
|
||||
tag = +im
|
||||
match:class = ^(im.riot.Riot|Element)$ # Element Matrix client
|
||||
}
|
||||
|
||||
|
||||
# game tags
|
||||
windowrule {
|
||||
name = windowrule-25
|
||||
tag = +games
|
||||
match:class = ^(gamescope)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-26
|
||||
tag = +games
|
||||
match:class = ^(steam_app_\d+)$
|
||||
}
|
||||
|
||||
|
||||
# gamestore tags
|
||||
windowrule {
|
||||
name = windowrule-27
|
||||
tag = +gamestore
|
||||
match:class = ^([Ss]team)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-28
|
||||
tag = +gamestore
|
||||
match:title = ^([Ll]utris)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-29
|
||||
tag = +gamestore
|
||||
match:class = ^(com.heroicgameslauncher.hgl)$
|
||||
}
|
||||
|
||||
|
||||
# file-manager tags
|
||||
windowrule {
|
||||
name = windowrule-30
|
||||
tag = +file-manager
|
||||
match:class = ^([Tt]hunar|org.gnome.Nautilus|[Pp]cmanfm-qt)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-31
|
||||
tag = +file-manager
|
||||
match:class = ^(app.drey.Warp)$
|
||||
}
|
||||
|
||||
|
||||
# wallpaper tags
|
||||
windowrule {
|
||||
name = windowrule-32
|
||||
tag = +wallpaper
|
||||
match:class = ^([Ww]aytrogen)$
|
||||
}
|
||||
|
||||
|
||||
# multimedia tags
|
||||
windowrule {
|
||||
name = windowrule-33
|
||||
tag = +multimedia
|
||||
match:class = ^([Aa]udacious)$
|
||||
}
|
||||
|
||||
|
||||
# multimedia-video tags
|
||||
windowrule {
|
||||
name = windowrule-34
|
||||
tag = +multimedia_video
|
||||
match:class = ^([Mm]pv|vlc)$
|
||||
}
|
||||
|
||||
|
||||
# settings tags
|
||||
windowrule {
|
||||
name = windowrule-35
|
||||
tag = +settings
|
||||
center = on
|
||||
match:title = ^(ROG Control)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-36
|
||||
tag = +settings
|
||||
match:class = ^(wihotspot(-gui)?)$ # wifi hotspot
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-37
|
||||
tag = +settings
|
||||
match:class = ^([Bb]aobab|org.gnome.[Bb]aobab)$ # Disk usage analyzer
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-38
|
||||
tag = +settings
|
||||
match:class = ^(gnome-disks|wihotspot(-gui)?)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-39
|
||||
tag = +settings
|
||||
match:title = (Kvantum Manager)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-40
|
||||
tag = +settings
|
||||
match:class = ^(file-roller|org.gnome.FileRoller)$ # archive manager
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-41
|
||||
tag = +settings
|
||||
match:class = ^(nm-applet|nm-connection-editor|blueman-manager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-42
|
||||
tag = +settings
|
||||
center = on
|
||||
match:class = ^(pavucontrol|org.pulseaudio.pavucontrol|com.saivert.pwvucontrol)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-43
|
||||
tag = +settings
|
||||
match:class = ^(qt5ct|qt6ct|[Yy]ad)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-44
|
||||
tag = +settings
|
||||
match:class = (xdg-desktop-portal-gtk)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-45
|
||||
tag = +settings
|
||||
match:class = ^(org.kde.polkit-kde-authentication-agent-1)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-46
|
||||
tag = +settings
|
||||
match:class = ^([Rr]ofi)$
|
||||
}
|
||||
|
||||
# Custom rule
|
||||
windowrule {
|
||||
name = windowrule-47
|
||||
tile = on
|
||||
match:class = ^([Cc]hromium)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-48
|
||||
size = (monitor_w*0.25) (monitor_h*0.55)
|
||||
float = on
|
||||
match:class = ^(com.network.manager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-49
|
||||
workspace = special:nyx
|
||||
match:class = ^([Vv]esktop|[Dd]iscord|[Ss]potify)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-255
|
||||
size = (monitor_w*0.75) (monitor_h*0.80)
|
||||
float = on
|
||||
match:class = ^([Bb]itwarden)$
|
||||
}
|
||||
|
||||
# viewer tags
|
||||
windowrule {
|
||||
name = windowrule-50
|
||||
tag = +viewer
|
||||
match:class = ^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$ # system monitor
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-51
|
||||
tag = +viewer
|
||||
match:class = ^(evince)$ # document viewer
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-52
|
||||
tag = +viewer
|
||||
match:class = ^(eog|org.gnome.Loupe)$ # image viewer
|
||||
}
|
||||
|
||||
|
||||
# Some special override rules
|
||||
windowrule {
|
||||
name = windowrule-53
|
||||
no_blur = on
|
||||
opacity = 1.0
|
||||
match:tag = multimedia_video*
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# POSITION
|
||||
# windowrule = center,floating:1 # warning, it cause even the menu to float and center.
|
||||
windowrule {
|
||||
name = windowrule-54
|
||||
center = on
|
||||
float = on
|
||||
size = (monitor_w*0.65) (monitor_h*0.9)
|
||||
match:tag = HyprFlux_Cheat*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-55
|
||||
center = on
|
||||
float = on
|
||||
match:class = ([Tt]hunar)
|
||||
match:title = negative:(.*[Tt]hunar.*)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-56
|
||||
center = on
|
||||
float = on
|
||||
match:tag = HyprFlux-Settings*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-57
|
||||
center = on
|
||||
match:title = ^(Keybindings)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-58
|
||||
center = on
|
||||
size = (monitor_w*0.6) (monitor_h*0.7)
|
||||
match:class = ^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-59
|
||||
move = ((monitor_w*0.61)) ((monitor_h*0.07))
|
||||
float = on
|
||||
size = (monitor_w*0.38) (monitor_h*0.38)
|
||||
pin = on
|
||||
keep_aspect_ratio = on
|
||||
match:title = ^(Picture-in-Picture)$
|
||||
}
|
||||
|
||||
#windowrule = move 72% 7%,title:^(Firefox)$
|
||||
|
||||
# windowrule to avoid idle for fullscreen apps
|
||||
#windowrule = idleinhibit fullscreen, class:^(*)$
|
||||
#windowrule = idleinhibit fullscreen, title:^(*)$
|
||||
windowrule {
|
||||
name = windowrule-60
|
||||
idle_inhibit = fullscreen
|
||||
match:fullscreen = 1
|
||||
}
|
||||
|
||||
# Discord to workspace 3
|
||||
windowrule = match:class ^([Vv]esktop|[Dd]iscord|[Ss]potify|[Pp]lexamp)$, workspace 3
|
||||
windowrule = match:class ^([Pp]lexamp), size 20% 20%
|
||||
|
||||
# FLOAT
|
||||
windowrule {
|
||||
name = windowrule-75
|
||||
float = on
|
||||
opacity = 0.9 0.7
|
||||
size = (monitor_w*0.7) (monitor_h*0.7)
|
||||
match:tag = wallpaper*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-76
|
||||
float = on
|
||||
opacity = 0.8 0.7
|
||||
size = (monitor_w*0.7) (monitor_h*0.7)
|
||||
match:tag = settings*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-77
|
||||
float = on
|
||||
match:tag = viewer*
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-78
|
||||
float = on
|
||||
match:class = ([Zz]oom|onedriver|onedriver-launcher)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-79
|
||||
float = on
|
||||
match:class = (org.gnome.Calculator)
|
||||
match:title = (Calculator)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-80
|
||||
float = on
|
||||
match:class = ^(mpv|com.github.rafostar.Clapper)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-81
|
||||
float = on
|
||||
match:class = ^([Qq]alculate-gtk)$
|
||||
}
|
||||
|
||||
#windowrule = float, class:^([Ww]hatsapp-for-linux|ZapZap|com.rtosta.zapzap)$
|
||||
#windowrule = float, title:^(Firefox)$
|
||||
windowrule {
|
||||
name = windowrule-82
|
||||
float = on
|
||||
match:class = ^(fdm|freedownloadmanager)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-83
|
||||
float = on
|
||||
match:class = ^(proton-authenticator)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-84
|
||||
float = on
|
||||
match:class = ^([Ww]indscribe)$
|
||||
}
|
||||
|
||||
|
||||
|
||||
# windowrule - ######### float popups and dialogue #######
|
||||
windowrule {
|
||||
name = windowrule-85
|
||||
float = on
|
||||
center = on
|
||||
match:title = ^(Authentication Required)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-86
|
||||
float = on
|
||||
match:class = (codium|codium-url-handler|VSCodium)
|
||||
match:title = negative:(.*codium.*|.*VSCodium.*)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-87
|
||||
float = on
|
||||
match:class = ^(com.heroicgameslauncher.hgl)$
|
||||
match:title = negative:(Heroic Games Launcher)
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-88
|
||||
float = on
|
||||
match:class = ^([Ss]team)$
|
||||
match:title = negative:^([Ss]team)$
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-89
|
||||
float = on
|
||||
size = (monitor_w*0.7) (monitor_h*0.6)
|
||||
center = on
|
||||
match:title = ^(Add Folder to Workspace)$
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-90
|
||||
float = on
|
||||
size = (monitor_w*0.7) (monitor_h*0.6)
|
||||
center = on
|
||||
match:title = ^(Save As)$
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-91
|
||||
float = on
|
||||
size = (monitor_w*0.7) (monitor_h*0.6)
|
||||
match:initial_title = (Open Files)
|
||||
}
|
||||
|
||||
|
||||
windowrule {
|
||||
name = windowrule-92
|
||||
float = on
|
||||
center = on
|
||||
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
|
||||
# windowrule = opacity 0.9 0.7, tag:browser*
|
||||
# windowrule = opacity 0.9 0.8, tag:projects*
|
||||
# windowrule = opacity 0.94 0.86, tag:im*
|
||||
# windowrule = opacity 0.94 0.86, tag:multimedia*
|
||||
# windowrule = opacity 0.9 0.8, tag:file-manager*
|
||||
windowrule {
|
||||
name = windowrule-93
|
||||
opacity = 0.8 0.7
|
||||
match:tag = terminal*
|
||||
}
|
||||
|
||||
# windowrule = opacity 0.82 0.75, tag:viewer*
|
||||
windowrule {
|
||||
name = windowrule-94
|
||||
opacity = 0.8 0.7
|
||||
match:class = ^(gedit|org.gnome.TextEditor|mousepad)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-95
|
||||
opacity = 0.9 0.8
|
||||
match:class = ^(deluge)$
|
||||
}
|
||||
|
||||
windowrule {
|
||||
name = windowrule-96
|
||||
opacity = 0.9 0.8
|
||||
match:class = ^(seahorse)$ # gnome-keyring gui
|
||||
}
|
||||
|
||||
# windowrule = opacity 0.95 0.75, title:^(Picture-in-Picture)$
|
||||
|
||||
# SIZE
|
||||
|
||||
#windowrule = size 25% 25%, title:^(Firefox)$
|
||||
|
||||
# PINNING
|
||||
#windowrule = pin,title:^(Firefox)$
|
||||
|
||||
# windowrule - extras
|
||||
|
||||
# BLUR & FULLSCREEN
|
||||
|
||||
#windowrule = bordercolor rgb(EE4B55) rgb(880808), fullscreen:1
|
||||
#windowrule = bordercolor rgb(282737) rgb(1E1D2D), floating:1
|
||||
#windowrule = opacity 0.8 0.8, pinned:1
|
||||
|
||||
|
||||
|
||||
# LAYER RULES
|
||||
layerrule {
|
||||
name = layerrule-1
|
||||
blur = on
|
||||
ignore_alpha = 0
|
||||
match:namespace = rofi
|
||||
}
|
||||
|
||||
layerrule {
|
||||
name = layerrule-2
|
||||
blur = on
|
||||
ignore_alpha = 0
|
||||
match:namespace = notifications
|
||||
}
|
||||
|
||||
layerrule {
|
||||
name = layerrule-3
|
||||
blur = on
|
||||
ignore_alpha = 0.5
|
||||
match:namespace = quickshell:overview
|
||||
}
|
||||
|
||||
|
||||
#layerrule = ignorealpha 0.5, tag:notif*
|
||||
|
||||
#layerrule = ignorezero, class:^([Rr]ofi)$
|
||||
#layerrule = blur, class:^([Rr]ofi)$
|
||||
#layerrule = unset,class:^([Rr]ofi)$
|
||||
#layerrule = ignorezero, <rofi>
|
||||
|
||||
#layerrule = ignorezero, overview
|
||||
#layerrule = blur, overview
|
||||
@@ -1,12 +0,0 @@
|
||||
# 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 tui to edit, leave this at the end of your file (the last monitor config applies)
|
||||
# and leave the markers.
|
||||
# <<<<< TUI AUTO START
|
||||
monitor=desc:LG Electronics LG ULTRAWIDE 0x0002E68A,2560x1080@60.00000,1920x-740,1.00000000,transform,3,vrr,0
|
||||
monitor=desc:HP Inc. HP 524pf VNG447017D,1920x1080@100.00000,-1920x0,1.00000000,transform,0,vrr,0
|
||||
monitor=desc:Acer Technologies VG240Y S 0x126191DE,1920x1080@165.00301,0x0,1.00000000,transform,0,vrr,1,bitdepth,10,cm,wide
|
||||
# <<<<< TUI AUTO END
|
||||
@@ -1,30 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# NOTE: This will be overwritten by NWG-Displays
|
||||
# once you use and click apply.
|
||||
#
|
||||
# *********************************************************** #
|
||||
|
||||
# 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.hyprland.org/Configuring/Workspace-Rules/
|
||||
|
||||
# Assigning workspace to a certain monitor. Below are just examples
|
||||
workspace = 1, monitor:DP-2, default=true
|
||||
workspace = 2, monitor:DP-1, default=true
|
||||
workspace = 3, monitor:HDMI-A-1, default=true
|
||||
# workspace = 4, monitor:eDP-1
|
||||
# workspace = 5, monitor:DP-2
|
||||
# workspace = 6, monitor:DP-2
|
||||
# workspace = 7, monitor:DP-2
|
||||
# workspace = 8, monitor:DP-2
|
||||
|
||||
|
||||
# example rules (from wiki)
|
||||
# workspace = 3, rounding:false, decorate:false
|
||||
# workspace = name:coding, rounding:false, decorate:false, gapsin:0, gapsout:0, border:false, decorate:false, monitor:DP-1
|
||||
# workspace = 8,bordersize:8
|
||||
# workspace = name:Hello, monitor:DP-1, default:true
|
||||
# workspace = name:gaming, monitor:desc:Chimei Innolux Corporation 0x150C, default:true
|
||||
# workspace = 5, on-created-empty:[float] firefox
|
||||
# workspace = special:scratchpad, on-created-empty:foot
|
||||
@@ -1,3 +1,3 @@
|
||||
# Place your new scripts here.
|
||||
# If you need to edit a script from main script (~/.config/hypr/scripts), copy it on this directory, and edit.
|
||||
# Make sure to update as well the keybinds in ~/.config/hypr/UserConfigs directory if any script is linked to it
|
||||
# Make sure to update as well the keybinds in ~/.config/hypr/input/keybinds.lua if any script is linked to it
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = smoothOut, 0.5, 0, 0.99, 0.99
|
||||
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
|
||||
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 5, winIn, slide
|
||||
animation = windowsOut, 1, 3, smoothOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
|
||||
animation = fade, 1, 3, smoothOut
|
||||
animation = workspaces, 1, 5, overshot
|
||||
|
||||
# animations for -git or version >0.42.0
|
||||
animation = workspacesIn, 1, 5, winIn, slide
|
||||
animation = workspacesOut, 1, 5, winOut, slide
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# old animations
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = linear, 0.0, 0.0, 1.0, 1.0
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = slow, 0, 0.85, 0.3, 1
|
||||
bezier = overshot, 0.7, 0.6, 0.1, 1.1
|
||||
bezier = bounce, 1.1, 1.6, 0.1, 0.85
|
||||
bezier = sligshot, 1, -1, 0.15, 1.25
|
||||
bezier = nice, 0, 6.9, 0.5, -4.20
|
||||
|
||||
animation = windowsIn, 1, 5, slow, popin
|
||||
animation = windowsOut, 1, 5, winOut, popin
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 10, linear
|
||||
animation = borderangle, 1, 180, linear, loop #used by rainbow borders and rotating colors
|
||||
animation = fade, 1, 5, overshot
|
||||
animation = workspaces, 1, 5, wind
|
||||
animation = windows, 1, 5, bounce, popin
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
animations {
|
||||
enabled = no
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "End-4"
|
||||
# credit https://github.com/end-4/dots-hyprland
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
# Animation curves
|
||||
|
||||
bezier = linear, 0, 0, 1, 1
|
||||
bezier = md3_standard, 0.2, 0, 0, 1
|
||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
||||
bezier = menu_decel, 0.1, 1, 0, 1
|
||||
bezier = menu_accel, 0.38, 0.04, 1, 0.07
|
||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
||||
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
|
||||
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
|
||||
# Animation configs
|
||||
animation = windows, 1, 3, md3_decel, popin 60%
|
||||
animation = windowsIn, 1, 3, md3_decel, popin 60%
|
||||
animation = windowsOut, 1, 3, md3_accel, popin 60%
|
||||
animation = border, 1, 10, default
|
||||
animation = fade, 1, 3, md3_decel
|
||||
# animation = layers, 1, 2, md3_decel, slide
|
||||
animation = layersIn, 1, 3, menu_decel, slide
|
||||
animation = layersOut, 1, 1.6, menu_accel
|
||||
animation = fadeLayersIn, 1, 2, menu_decel
|
||||
animation = fadeLayersOut, 1, 4.5, menu_accel
|
||||
animation = workspaces, 1, 7, menu_decel, slide
|
||||
# animation = workspaces, 1, 2.5, softAcDecel, slide
|
||||
# animation = workspaces, 1, 7, menu_decel, slidefade 15%
|
||||
# animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
|
||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Vertical"
|
||||
# credit https://github.com/prasanthrangan/hyprdots
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = fluent_decel, 0, 0.2, 0.4, 1
|
||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
||||
bezier = easeOutCubic, 0.33, 1, 0.68, 1
|
||||
bezier = easeinoutsine, 0.37, 0, 0.63, 1
|
||||
|
||||
# Windows
|
||||
animation = windowsIn, 1, 1.5, easeinoutsine, popin 60% # window open
|
||||
animation = windowsOut, 1, 1.5, easeOutCubic, popin 60% # window close.
|
||||
animation = windowsMove, 1, 1.5, easeinoutsine, slide # everything in between, moving, dragging, resizing.
|
||||
|
||||
# Fading
|
||||
animation = fade, 1, 2.5, fluent_decel
|
||||
|
||||
animation = fadeLayersIn, 0
|
||||
animation = border, 0
|
||||
|
||||
|
||||
# Layers
|
||||
animation = layers, 1, 1.5, easeinoutsine, popin
|
||||
|
||||
# Workspaces
|
||||
#animation = workspaces, 1, 3, fluent_decel, slidefade 30% # styles: slide, slidevert, fade, slidefade, slidefadevert
|
||||
animation = workspaces, 1, 3, fluent_decel, slidefadevert 30% # styles: slide, slidevert, fade, slidefade, slidefadevert
|
||||
|
||||
animation = specialWorkspace, 1, 2, fluent_decel, slidefade 10%
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Default"
|
||||
# credit https://github.com/prasanthrangan/hyprdots
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 6, winIn, slide
|
||||
animation = windowsOut, 1, 5, winOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 30, liner, once
|
||||
animation = fade, 1, 10, default
|
||||
animation = workspaces, 1, 5, wind
|
||||
animation = specialWorkspace, 1, 5, wind, slidevert
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Minimal-1"
|
||||
# credit https://github.com/prasanthrangan/hyprdots-
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
|
||||
# █▄▄ █▀▀ ▀█ █ █▀▀ █▀█ █▀▀ █░█ █▀█ █░█ █▀▀
|
||||
# █▄█ ██▄ █▄ █ ██▄ █▀▄ █▄▄ █▄█ █▀▄ ▀▄▀ ██▄
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
|
||||
|
||||
#▄▀█ █▄░█ █ █▀▄▀█ ▄▀█ ▀█▀ █ █▀█ █▄░█
|
||||
#█▀█ █░▀█ █ █░▀░█ █▀█ ░█░ █ █▄█ █░▀█
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 6, winIn, slide
|
||||
animation = windowsOut, 1, 5, winOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 30, liner, once
|
||||
animation = fade, 1, 10, default
|
||||
animation = workspaces, 1, 5, wind
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Minimal-2"
|
||||
# credit https://github.com/prasanthrangan/hyprdots
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = quart, 0.25, 1, 0.5, 1
|
||||
|
||||
animation = windows, 1, 6, quart, slide
|
||||
animation = border, 1, 6, quart
|
||||
animation = borderangle, 1, 6, quart
|
||||
animation = fade, 1, 6, quart
|
||||
animation = workspaces, 1, 6, quart
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Optimized"
|
||||
# credit https://github.com/prasanthrangan/hyprdots
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = wind, 0.05, 0.85, 0.03, 0.97
|
||||
bezier = winIn, 0.07, 0.88, 0.04, 0.99
|
||||
bezier = winOut, 0.20, -0.15, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
bezier = md3_standard, 0.12, 0, 0, 1
|
||||
bezier = md3_decel, 0.05, 0.80, 0.10, 0.97
|
||||
bezier = md3_accel, 0.20, 0, 0.80, 0.08
|
||||
bezier = overshot, 0.05, 0.85, 0.07, 1.04
|
||||
bezier = crazyshot, 0.1, 1.22, 0.68, 0.98
|
||||
bezier = hyprnostretch, 0.05, 0.82, 0.03, 0.94
|
||||
bezier = menu_decel, 0.05, 0.82, 0, 1
|
||||
bezier = menu_accel, 0.20, 0, 0.82, 0.10
|
||||
bezier = easeInOutCirc, 0.75, 0, 0.15, 1
|
||||
bezier = easeOutCirc, 0, 0.48, 0.38, 1
|
||||
bezier = easeOutExpo, 0.10, 0.94, 0.23, 0.98
|
||||
bezier = softAcDecel, 0.20, 0.20, 0.15, 1
|
||||
bezier = md2, 0.30, 0, 0.15, 1
|
||||
|
||||
bezier = OutBack, 0.28, 1.40, 0.58, 1
|
||||
bezier = easeInOutCirc, 0.78, 0, 0.15, 1
|
||||
|
||||
animation = border, 1, 1.6, liner
|
||||
animation = borderangle, 1, 82, liner, once
|
||||
animation = windowsIn, 1, 3.2, winIn, slide
|
||||
animation = windowsOut, 1, 2.8, easeOutCirc
|
||||
animation = windowsMove, 1, 3.0, wind, slide
|
||||
animation = fade, 1, 1.8, md3_decel
|
||||
animation = layersIn, 1, 1.8, menu_decel, slide
|
||||
animation = layersOut, 1, 1.5, menu_accel
|
||||
animation = fadeLayersIn, 1, 1.6, menu_decel
|
||||
animation = fadeLayersOut, 1, 1.8, menu_accel
|
||||
animation = workspaces, 1, 4.0, menu_decel, slide
|
||||
animation = specialWorkspace, 1, 2.3, md3_decel, slidefadevert 15%
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Classic"
|
||||
# credit https://github.com/mylinuxforwork/dotfiles
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
|
||||
animation = windows, 1, 7, myBezier
|
||||
animation = windowsOut, 1, 7, default, popin 80%
|
||||
animation = border, 1, 10, default
|
||||
animation = borderangle, 1, 8, default
|
||||
animation = fade, 1, 7, default
|
||||
animation = workspaces, 1, 6, default
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Dynamic"
|
||||
# credit https://github.com/mylinuxforwork/dotfiles
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 6, winIn, slide
|
||||
animation = windowsOut, 1, 5, winOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 30, liner, loop
|
||||
animation = fade, 1, 10, default
|
||||
animation = workspaces, 1, 5, wind
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Fast"
|
||||
# credit https://github.com/mylinuxforwork/dotfiles
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = linear, 0, 0, 1, 1
|
||||
bezier = md3_standard, 0.2, 0, 0, 1
|
||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
||||
bezier = fluent_decel, 0.1, 1, 0, 1
|
||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
||||
animation = windows, 1, 3, md3_decel, popin 60%
|
||||
animation = border, 1, 10, default
|
||||
animation = fade, 1, 2.5, md3_decel
|
||||
animation = workspaces, 1, 3.5, easeOutExpo, slide
|
||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "High"
|
||||
# credit https://github.com/mylinuxforwork/dotfiles
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 6, winIn, slide
|
||||
animation = windowsOut, 1, 5, winOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 30, liner, once
|
||||
animation = fade, 1, 10, default
|
||||
animation = workspaces, 1, 5, wind
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Moving"
|
||||
# credit https://github.com/mylinuxforwork/dotfiles
|
||||
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = smoothOut, 0.5, 0, 0.99, 0.99
|
||||
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
|
||||
animation = windows, 1, 5, overshot, slide
|
||||
animation = windowsOut, 1, 3, smoothOut
|
||||
animation = windowsIn, 1, 3, smoothOut
|
||||
animation = windowsMove, 1, 4, smoothIn, slide
|
||||
animation = border, 1, 5, default
|
||||
animation = fade, 1, 5, smoothIn
|
||||
animation = fadeDim, 1, 5, smoothIn
|
||||
animation = workspaces, 1, 6, default
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Standard"
|
||||
# credit https://github.com/mylinuxforwork/dotfiles
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
|
||||
animation = windows, 1, 7, myBezier
|
||||
animation = windowsOut, 1, 7, default, popin 80%
|
||||
animation = border, 1, 10, default
|
||||
animation = borderangle, 1, 8, default
|
||||
animation = fade, 1, 7, default
|
||||
animation = workspaces, 1, 6, default
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Me-1"
|
||||
# credit https://github.com/mahaveergurjar
|
||||
|
||||
animations {
|
||||
enabled = true
|
||||
# Animation curves
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
# bezier = linear, 0, 0, 1, 1
|
||||
bezier = md3_standard, 0.2, 0, 0, 1
|
||||
bezier = md3_decel, 0.05, 0.7, 0.1, 1
|
||||
bezier = md3_accel, 0.3, 0, 0.8, 0.15
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.1
|
||||
bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
|
||||
bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
|
||||
bezier = menu_decel, 0.1, 1, 0, 1
|
||||
bezier = menu_accel, 0.38, 0.04, 1, 0.07
|
||||
bezier = easeInOutCirc, 0.85, 0, 0.15, 1
|
||||
bezier = easeOutCirc, 0, 0.55, 0.45, 1
|
||||
bezier = easeOutExpo, 0.16, 1, 0.3, 1
|
||||
bezier = softAcDecel, 0.26, 0.26, 0.15, 1
|
||||
bezier = md2, 0.4, 0, 0.2, 1 # use with .2s duration
|
||||
|
||||
# Animation configs
|
||||
# animation = windows, 1, 3, md3_decel, popin 60%
|
||||
# animation = windowsIn, 1, 3, md3_decel, popin 60%
|
||||
# animation = windowsOut, 1, 3, md3_accel, popin 60%
|
||||
# animation = windows, 1, 6, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 30, liner, once
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 6, winIn, slide
|
||||
animation = windowsOut, 1, 5, winOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
|
||||
# animation = border, 1, 10, default
|
||||
animation = fade, 1, 3, md3_decel
|
||||
# animation = layers, 1, 2, md3_decel, slide
|
||||
animation = layersIn, 1, 3, menu_decel, slide
|
||||
animation = layersOut, 1, 1.6, menu_accel
|
||||
animation = fadeLayersIn, 1, 2, menu_decel
|
||||
animation = fadeLayersOut, 1, 4.5, menu_accel
|
||||
animation = workspaces, 1, 7, menu_decel, slide
|
||||
animation = workspaces, 1, 5, wind
|
||||
# animation = workspaces, 1, 2.5, softAcDecel, slide
|
||||
# animation = workspaces, 1, 7, menu_decel, slidefade 15%
|
||||
animation = specialWorkspace, 1, 3, md3_decel, slidefadevert 15%
|
||||
animation = specialWorkspace, 1, 3, md3_decel, slidevert
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# name "Me-2"
|
||||
# credit https://github.com/mahaveergurjar
|
||||
|
||||
|
||||
animations {
|
||||
enabled = yes
|
||||
|
||||
bezier = wind, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = winIn, 0.1, 1.1, 0.1, 1.1
|
||||
bezier = winOut, 0.3, -0.3, 0, 1
|
||||
bezier = liner, 1, 1, 1, 1
|
||||
bezier = overshot, 0.05, 0.9, 0.1, 1.05
|
||||
bezier = smoothOut, 0.5, 0, 0.99, 0.99
|
||||
bezier = smoothIn, 0.5, -0.5, 0.68, 1.5
|
||||
|
||||
animation = windows, 1, 6, wind, slide
|
||||
animation = windowsIn, 1, 5, winIn, slide
|
||||
animation = windowsOut, 1, 3, smoothOut, slide
|
||||
animation = windowsMove, 1, 5, wind, slide
|
||||
animation = border, 1, 1, liner
|
||||
animation = borderangle, 1, 180, liner, loop #used by rainbow borders and rotating colors
|
||||
animation = fade, 1, 3, smoothOut
|
||||
animation = workspaces, 1, 5, overshot
|
||||
|
||||
# animations for -git or version >0.42.0
|
||||
animation = workspacesIn, 1, 5, winIn, slide
|
||||
animation = workspacesOut, 1, 5, winOut, slide
|
||||
}
|
||||
@@ -1,34 +1,6 @@
|
||||
-- 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").
|
||||
-- Active animation preset.
|
||||
-- The presets live in ../presets/anim-*.lua. Super+Shift+A (scripts/Animations.sh)
|
||||
-- picks one and rewrites the require() line below; edit it by hand to switch too,
|
||||
-- e.g. require("presets.anim-ml4w-classic"). Keep it to this single line.
|
||||
|
||||
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" })
|
||||
require("presets.anim-hyprflux")
|
||||
|
||||
@@ -24,7 +24,7 @@ hl.config({
|
||||
rounding = 10,
|
||||
|
||||
active_opacity = 1.0,
|
||||
inactive_opacity = 0.9,
|
||||
inactive_opacity = 1.0,
|
||||
fullscreen_opacity = 1.0,
|
||||
|
||||
dim_inactive = true,
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# HyprFlux static color palette
|
||||
|
||||
$background = rgb(010102)
|
||||
$foreground = rgb(FDF8FE)
|
||||
$color0 = rgb(313131)
|
||||
$color1 = rgb(09050C)
|
||||
$color2 = rgb(221647)
|
||||
$color3 = rgb(2C1A40)
|
||||
$color4 = rgb(5E3887)
|
||||
$color5 = rgb(7344A6)
|
||||
$color6 = rgb(BAB0BD)
|
||||
$color7 = rgb(F3ECF5)
|
||||
$color8 = rgb(AAA5AC)
|
||||
$color9 = rgb(0B0711)
|
||||
$color10 = rgb(2D1D5F)
|
||||
$color11 = rgb(3B2355)
|
||||
$color12 = rgb(7D4AB4)
|
||||
$color13 = rgb(9A5BDD)
|
||||
$color14 = rgb(F8EAFC)
|
||||
$color15 = rgb(F3ECF5)
|
||||
@@ -19,11 +19,7 @@ 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")
|
||||
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct") -- qt6ct also styles Qt5 apps that read it; qt5ct.conf is kept in sync
|
||||
|
||||
-- xwayland apps scale fix (useful if you use monitor scaling).
|
||||
-- Set same value if you use scaling in monitors.lua
|
||||
@@ -32,11 +28,17 @@ hl.env("QT_QUICK_CONTROLS_STYLE", "org.hyprland.style")
|
||||
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.
|
||||
-- Cursor: Bibata-Modern-Classic (AUR bibata-cursor-theme-bin, XCursor format).
|
||||
-- Same theme in gtk-*/settings.ini, the gsettings call in hyprland.lua and initial-boot.sh.
|
||||
-- There's no hyprcursor build installed, so Hyprland falls back to XCURSOR_THEME.
|
||||
-- https://wiki.hyprland.org/Hypr-Ecosystem/hyprcursor/
|
||||
hl.env("HYPRCURSOR_THEME", "Bibata-Modern-Classic")
|
||||
hl.env("HYPRCURSOR_SIZE", "24")
|
||||
hl.env("XCURSOR_THEME", "Bibata-Modern-Classic")
|
||||
hl.env("XCURSOR_SIZE", "24")
|
||||
|
||||
-- Default terminal for anything that asks $TERMINAL (rofi-sensible-terminal, scripts)
|
||||
hl.env("TERMINAL", "ghostty")
|
||||
|
||||
-- firefox
|
||||
hl.env("MOZ_ENABLE_WAYLAND", "1")
|
||||
@@ -46,13 +48,17 @@ hl.env("MOZ_ENABLE_WAYLAND", "1")
|
||||
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")
|
||||
-- Only when the NVIDIA kernel driver is loaded, so the same config works on the
|
||||
-- laptop (forcing LIBVA_DRIVER_NAME=nvidia there breaks VA-API video decoding).
|
||||
local nvidia = io.open("/proc/driver/nvidia/version")
|
||||
if nvidia then
|
||||
nvidia:close()
|
||||
hl.env("LIBVA_DRIVER_NAME", "nvidia")
|
||||
hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
|
||||
hl.env("NVD_BACKEND", "direct")
|
||||
hl.env("GSK_RENDERER", "ngl")
|
||||
end
|
||||
|
||||
-- additional ENV's for nvidia. Caution, activate with care
|
||||
-- hl.env("GBM_BACKEND", "nvidia-drm")
|
||||
@@ -85,3 +91,6 @@ hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto") -- auto selects Wayland if possib
|
||||
-- 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.
|
||||
|
||||
-- Bitwarden SSH agent (so GUI apps like VS Code see it, not just shells)
|
||||
hl.env("SSH_AUTH_SOCK", os.getenv("HOME") .. "/.bitwarden-ssh-agent.sock")
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
-- 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.
|
||||
|
||||
-- vrr forced off globally: the per-monitor vrr = 0 flag below does NOT override this when
|
||||
-- misc.vrr is 1 (tested live on Hyprland 0.56.2 — DP-2 stayed vrr:true until this global
|
||||
-- was changed). DP-2 (Acer VG240Y S) flickers under VRR when idle in fullscreen (mouse
|
||||
-- stationary -> no repaint damage -> frame delivery stalls near the panel's VRR floor).
|
||||
-- The other two monitors don't negotiate VRR at all, so this has no effect on them.
|
||||
hl.config({
|
||||
general = {
|
||||
allow_tearing = true,
|
||||
@@ -13,17 +18,19 @@ hl.config({
|
||||
},
|
||||
},
|
||||
misc = {
|
||||
vrr = 1,
|
||||
vrr = 0,
|
||||
},
|
||||
})
|
||||
|
||||
-- Monitors
|
||||
hl.monitor({ output = "DP-2", mode = "1920x1080@165.00Hz", position = "0x0", scale = 1, bitdepth = 10, cm = "wide" })
|
||||
hl.monitor({ output = "DP-2", mode = "1920x1080@165.00Hz", position = "0x0", scale = 1, bitdepth = 10, cm = "wide", vrr = 0 })
|
||||
|
||||
-- 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 + Print", hl.dsp.exec_cmd([[sh -c 'if command -v hyprshot >/dev/null; then hyprshot -o "$HOME/Pictures/Screenshots-Linux" -m region; else grim -g "$(slurp)" "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"; fi']])) -- drag an area (hyprshot if installed, else grim/slurp)
|
||||
hl.bind("SUPER + SHIFT + S", hl.dsp.exec_cmd([[sh -c 'grim -g "$(slurp)" "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"']])) -- region; Surface keyboard has no Print key
|
||||
hl.bind("SUPER + F6", hl.dsp.exec_cmd([[sh -c 'grim "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"']])) -- full screen
|
||||
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"))
|
||||
hl.bind("SUPER + SHIFT + Print", hl.dsp.exec_cmd([[sh -c 'if command -v hyprshot >/dev/null; then hyprshot -o "$HOME/Pictures/Screenshots-Linux" -m window; else grim "$HOME/Pictures/Screenshots-Linux/$(date +%Y-%m-%d_%H-%M-%S).png" && notify-send "Screenshot saved"; fi']])) -- window (hyprshot) or full screen (fallback)
|
||||
|
||||
@@ -13,6 +13,11 @@ hl.on("hyprland.start", function()
|
||||
-- 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")
|
||||
-- restart portals once the env is imported, otherwise screen sharing breaks
|
||||
hl.exec_cmd("sleep 1 && systemctl --user restart xdg-desktop-portal-hyprland && systemctl --user restart xdg-desktop-portal")
|
||||
|
||||
-- Sunshine (virtual monitor only on desktop; skipped on laptops, detected by a system battery)
|
||||
hl.exec_cmd("sleep 1 && ! ls -d /sys/class/power_supply/BAT* >/dev/null 2>&1 && hyprctl output create headless")
|
||||
|
||||
-- Polkit (Polkit Gnome / KDE)
|
||||
hl.exec_cmd(scriptsDir .. "/Polkit.sh")
|
||||
@@ -23,7 +28,7 @@ hl.on("hyprland.start", function()
|
||||
-- hl.exec_cmd("ags")
|
||||
-- hl.exec_cmd("blueman-applet")
|
||||
-- hl.exec_cmd("rog-control-center")
|
||||
hl.exec_cmd("waybar")
|
||||
-- hl.exec_cmd("waybar")
|
||||
hl.exec_cmd("qs") -- quickshell AGS Desktop Overview alternative
|
||||
|
||||
-- clipboard manager
|
||||
@@ -31,18 +36,22 @@ hl.on("hyprland.start", function()
|
||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||
|
||||
-- Starting hypridle to start hyprlock
|
||||
-- Sunshine streams hold an idle inhibitor (scripts/SunshineIdle.sh) so this never locks mid-stream
|
||||
hl.exec_cmd("hypridle")
|
||||
|
||||
-- Sunshine for streaming media
|
||||
hl.exec_cmd("sunshine")
|
||||
|
||||
-- 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("plexamp") -- plexamp-bin (AUR)
|
||||
hl.exec_cmd("sleep 3 && vesktop")
|
||||
hl.dispatch(hl.dsp.focus({ workspace = 1 }))
|
||||
hl.exec_cmd("bitwarden-desktop --ozone-platform-hint=auto --silent")
|
||||
hl.exec_cmd("seafile-applet")
|
||||
|
||||
-- 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
|
||||
|
||||
@@ -8,7 +8,7 @@ general {
|
||||
lock_cmd = pidof hyprlock || hyprlock # runs hyprlock if it is not already running (this is always run when "loginctl lock-session" is called)
|
||||
# unlock_cmd = killall hyprlock # kills hyprlock when unlocking (this is always run when "loginctl unlock-session" is called)
|
||||
before_sleep_cmd = loginctl lock-session # ensures that the session is locked before going to sleep
|
||||
after_sleep_cmd = hyprctl dispatch dpms on # turn of screen after sleep (not strictly necessary, but just in case)
|
||||
after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # turn of screen after sleep (not strictly necessary, but just in case)
|
||||
ignore_dbus_inhibit = false # whether to ignore dbus-sent idle-inhibit requests (used by e.g. firefox or steam)
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ general {
|
||||
# (disabled by default)
|
||||
# listener {
|
||||
# timeout = 30 # 30 seconds
|
||||
# on-timeout = pidof hyprlock && hyprctl dispatch dpms off # turns off the screen if hyprlock is active
|
||||
# on-resume = pidof hyprlock && hyprctl dispatch dpms on # command to run when activity is detected after timeout has fired.
|
||||
# on-timeout = pidof hyprlock && hyprctl dispatch 'hl.dsp.dpms({action = "off"})' # turns off the screen if hyprlock is active
|
||||
# on-resume = pidof hyprlock && hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # command to run when activity is detected after timeout has fired.
|
||||
# }
|
||||
|
||||
# Warn
|
||||
@@ -38,8 +38,8 @@ listener {
|
||||
# (disabled by default)
|
||||
# listener {
|
||||
# timeout = 630 # 10.5 min
|
||||
# on-timeout = hyprctl dispatch dpms off # command to run when timeout has passed
|
||||
# on-resume = hyprctl dispatch dpms on # command to run when activity is detected after timeout has fired.
|
||||
# on-timeout = hyprctl dispatch 'hl.dsp.dpms({action = "off"})' # command to run when timeout has passed
|
||||
# on-resume = hyprctl dispatch 'hl.dsp.dpms({action = "on"})' # command to run when activity is detected after timeout has fired.
|
||||
# }
|
||||
|
||||
# Suspend # disabled by default
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Generated by HyprMod
|
||||
|
||||
general:allow_tearing = 1
|
||||
general:snap:enabled = 0
|
||||
misc:vrr = 1
|
||||
|
||||
# Monitors
|
||||
monitor = DP-2, 1920x1080@165.00Hz, 0x0, 1, bitdepth, 10, cm, wide
|
||||
|
||||
# Keybinds
|
||||
unbind = SUPER SHIFT, Print
|
||||
unbind = SUPER, up
|
||||
bind = SUPER, Print, exec, hyprshot -o $HOME/Pictures/Screenshots-Linux -m region
|
||||
bind = SUPER, up, movefocus, u
|
||||
bind = SUPER SHIFT, Print, exec, hyprshot -o $HOME/Pictures/Screenshots-Linux -m window
|
||||
@@ -1,34 +0,0 @@
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# always refer to Hyprland wiki
|
||||
# https://wiki.hyprland.org/
|
||||
|
||||
# Initial boot script enable to apply initial wallpapers, theming, new settings etc.
|
||||
# suggest not to change this or delete this including deleting referrence file in ~/.config/hypr/.initial_startup_done
|
||||
# as long as the referrence file is present, this initial-boot.sh will not execute
|
||||
exec-once = $HOME/.config/hypr/initial-boot.sh
|
||||
|
||||
# ## This is where you want to start tinkering
|
||||
$UserConfigs = $HOME/.config/hypr/UserConfigs # User Configs directory path
|
||||
|
||||
source= $UserConfigs/Startup_Apps.conf # put your start-up packages on this file
|
||||
|
||||
source= $UserConfigs/ENVariables.conf # Environment variables to load
|
||||
|
||||
source= $UserConfigs/WindowRules.conf # all about Hyprland Window Rules and Layer Rules
|
||||
|
||||
source= $UserConfigs/UserDecorations.conf # Decorations config file
|
||||
|
||||
source= $UserConfigs/UserAnimations.conf # Animation config file
|
||||
|
||||
source= $UserConfigs/Keybinds.conf # Put your own keybinds here
|
||||
|
||||
source= $UserConfigs/UserSettings.conf # Main Hyprland Settings.
|
||||
|
||||
# nwg-displays
|
||||
source= $UserConfigs/monitors.conf
|
||||
source= $UserConfigs/workspaces.conf
|
||||
|
||||
exec = gsettings set org.gnome.desktop.interface cursor-theme Future-black Cursors
|
||||
|
||||
# HyprMod managed settings
|
||||
source = $HOME/.config/hypr/hyprland-gui.conf
|
||||
@@ -35,7 +35,7 @@ require("layout.workspaces")
|
||||
-- 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")
|
||||
hl.exec_cmd("gsettings set org.gnome.desktop.interface cursor-theme Bibata-Modern-Classic && gsettings set org.gnome.desktop.interface cursor-size 24")
|
||||
end)
|
||||
|
||||
-- HyprMod managed settings
|
||||
|
||||
@@ -113,7 +113,7 @@ label {
|
||||
text = cmd[update:1000] echo "$(~/.config/hypr/hyprlock/Scripts/songdetail.sh)"
|
||||
color = rgba(255, 255, 255, 0.6)
|
||||
font_size = 18
|
||||
font_family = JetBrains Mono Nerd, SF Pro Display Bold
|
||||
font_family = JetBrainsMono Nerd Font, SF Pro Display Bold
|
||||
position = 0, 90
|
||||
halign = center
|
||||
valign = bottom
|
||||
@@ -221,7 +221,7 @@ label {
|
||||
text = cmd[update:1000] echo "$(~/.config/hypr/hyprlock/Scripts/songdetail.sh)"
|
||||
color = rgba(255, 255, 255, 0.6)
|
||||
font_size = 28
|
||||
font_family = JetBrains Mono Nerd, SF Pro Display Bold
|
||||
font_family = JetBrainsMono Nerd Font, SF Pro Display Bold
|
||||
position = 0, 90
|
||||
halign = center
|
||||
valign = bottom
|
||||
|
||||
Binary file not shown.
@@ -13,7 +13,7 @@ kvantum_theme="catppuccin-mocha-blue"
|
||||
color_scheme="prefer-dark"
|
||||
gtk_theme="Material-DeepOcean-BL"
|
||||
icon_theme="Papirus-Dark"
|
||||
cursor_theme="Moga-Cursor"
|
||||
cursor_theme="Bibata-Modern-Classic"
|
||||
|
||||
# Check if a marker file exists.
|
||||
if [ ! -f "$HOME/.config/hypr/.initial_startup_done" ]; then
|
||||
|
||||
@@ -10,7 +10,7 @@ local userScripts = os.getenv("HOME") .. "/.config/hypr/UserScripts"
|
||||
local edit = os.getenv("EDITOR") or "nvim"
|
||||
|
||||
-- These two are for UserKeybinds.conf & Waybar Modules
|
||||
local term = "foot" -- Terminal
|
||||
local term = "ghostty" -- Terminal (foot is still installed as a fallback)
|
||||
local files = "thunar" -- File Manager
|
||||
|
||||
-- Default Search Engine for ROFI Search (SUPER S)
|
||||
@@ -31,7 +31,7 @@ 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 .. " + S", hl.dsp.exec_cmd("hyprmod")) -- Hyprmod - A Hyprland settings manager (AUR: hyprmod)
|
||||
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
|
||||
|
||||
@@ -40,8 +40,9 @@ hl.bind(mainMod .. " + SHIFT + F", hl.dsp.window.fullscreen({ mode = "fullscreen
|
||||
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}')"]]))
|
||||
-- (`hyprctl keyword` is rejected by the Lua config — "keyword can't work with non-legacy parsers" — so set it via eval)
|
||||
hl.bind(mainMod .. " + ALT + mouse_down", hl.dsp.exec_cmd([[hyprctl eval "hl.config({ 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 eval "hl.config({ 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
|
||||
@@ -116,23 +117,22 @@ hl.bind(mainMod .. " + SHIFT + tab", hl.dsp.focus({ workspace = "m-1" }))
|
||||
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
|
||||
-- Number-row keys (the Lua bind parser does not accept the code:NN keycode syntax)
|
||||
-- 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
|
||||
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
|
||||
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
|
||||
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 }))
|
||||
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
|
||||
hl.bind(mainMod .. " + SHIFT + " .. key, 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 }))
|
||||
for i, key in ipairs({ "1", "2", "3", "4", "5", "6", "7", "8", "9", "0" }) do
|
||||
hl.bind(mainMod .. " + CTRL + " .. key, 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 ]
|
||||
@@ -151,9 +151,6 @@ hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true }) --
|
||||
-- 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
|
||||
@@ -161,7 +158,7 @@ hl.bind("XF86Launch3", hl.dsp.exec_cmd("asusctl led-mode -n")) -- FN+F4 Switch k
|
||||
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
|
||||
hl.bind("XF86TouchpadToggle", hl.dsp.exec_cmd(scriptsDir .. "/TouchPad.sh")) -- toggle touchpad (finds the device itself)
|
||||
|
||||
-- Screenshot keybindings using F6 (no PrintSrc button)
|
||||
-- hl.bind(mainMod .. " + F6", hl.dsp.exec_cmd(scriptsDir .. "/ScreenShot.sh --now")) -- screenshot
|
||||
@@ -169,8 +166,3 @@ hl.bind("XF86TouchpadToggle", hl.dsp.exec_cmd(scriptsDir .. "/TouchPad.sh")) --
|
||||
-- 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,
|
||||
})
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
-- <<<<< 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" })
|
||||
hl.monitor({ output = "desc:Acer Technologies VG240Y S 0x126191DE", mode = "1920x1080@165.00301", position = "0x0", scale = 1.00000000, transform = 0, vrr = 0, bitdepth = 10, cm = "wide" })
|
||||
hl.monitor({ output = "HEADLESS-1", mode = "1728x1080@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })
|
||||
-- <<<<< TUI AUTO END
|
||||
|
||||
@@ -57,6 +57,11 @@ hl.window_rule({ name = "windowrule-24", tag = "+im", match = { class = "^(im.ri
|
||||
-- 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+)$" } })
|
||||
-- While Sunshine is streaming, open Steam games fullscreen on the headless stream output
|
||||
-- (workspace 9 -> HEADLESS-1). Old games like KOTOR fix their render size/scaling at launch
|
||||
-- and never adapt to a later resize, so they must start there. Off by default (local play
|
||||
-- unaffected); toggled by ~/.config/sunshine/scripts/sunshine-games.sh via the global.
|
||||
SunshineGamesRule = hl.window_rule({ name = "sunshine-games", enabled = false, workspace = "9", fullscreen = true, match = { class = "^(steam_app_\\d+)$" } })
|
||||
|
||||
-- gamestore tags
|
||||
hl.window_rule({ name = "windowrule-27", tag = "+gamestore", match = { class = "^([Ss]team)$" } })
|
||||
@@ -93,8 +98,13 @@ hl.window_rule({ name = "windowrule-46", tag = "+settings", match = { class = "^
|
||||
-- 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)$" } })
|
||||
-- System updater terminal (quickshell/scripts/update-system.sh) — float + center it instead of
|
||||
-- tiling into the current workspace. Matches the title the script launches it with, so it works
|
||||
-- for Ghostty and the foot fallback alike; initial_title so a later rename can't dodge it.
|
||||
-- Fixed pixel size rather than monitor_w/h fractions: HDMI-A-1 is rotated portrait (1080 wide),
|
||||
-- where a fraction of the width made it too narrow for package output; 1000x650 fits every screen.
|
||||
hl.window_rule({ name = "system-update-terminal", float = true, center = true, size = "1000 650", match = { initial_title = "^(update)$" } })
|
||||
|
||||
-- viewer tags
|
||||
hl.window_rule({ name = "windowrule-50", tag = "+viewer", match = { class = "^(gnome-system-monitor|org.gnome.SystemMonitor|io.missioncenter.MissionCenter)$" } }) -- system monitor
|
||||
@@ -128,22 +138,20 @@ hl.window_rule({
|
||||
-- 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.
|
||||
-- Discord / Spotify / Plexamp to workspace 3 (the portrait LG). An older rule also sent
|
||||
-- Vesktop/Discord/Spotify to special:nyx; this one always won, so that rule was removed.
|
||||
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-75", float = true, opacity = "0.9 0.9", size = "monitor_w*0.7 monitor_h*0.7", match = { tag = "wallpaper*" } })
|
||||
hl.window_rule({ name = "windowrule-76", float = true, opacity = "0.8 0.8", 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({ name = "galculator", float = true, center = true, size = "407 596", match = { class = "^([Gg]alculator)$" } })
|
||||
-- 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)$" } })
|
||||
@@ -162,17 +170,17 @@ hl.window_rule({ name = "windowrule-92", float = true, center = true, size = "mo
|
||||
-- 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)$" } })
|
||||
-- hl.window_rule({ opacity = "0.9 0.9", match = { tag = "browser*" } })
|
||||
-- hl.window_rule({ opacity = "0.9 0.9", match = { tag = "projects*" } })
|
||||
-- hl.window_rule({ opacity = "0.94 0.94", match = { tag = "im*" } })
|
||||
-- hl.window_rule({ opacity = "0.94 0.94", match = { tag = "multimedia*" } })
|
||||
-- hl.window_rule({ opacity = "0.9 0.9", match = { tag = "file-manager*" } })
|
||||
hl.window_rule({ name = "windowrule-93", opacity = "0.8 0.8", match = { tag = "terminal*" } })
|
||||
-- hl.window_rule({ opacity = "0.82 0.82", match = { tag = "viewer*" } })
|
||||
hl.window_rule({ name = "windowrule-94", opacity = "0.8 0.8", match = { class = "^(gedit|org.gnome.TextEditor|mousepad)$" } })
|
||||
hl.window_rule({ name = "windowrule-95", opacity = "0.9 0.9", match = { class = "^(deluge)$" } })
|
||||
hl.window_rule({ name = "windowrule-96", opacity = "0.9 0.9", match = { class = "^(seahorse)$" } }) -- gnome-keyring gui
|
||||
-- hl.window_rule({ opacity = "0.95 0.95", match = { title = "^(Picture-in-Picture)$" } })
|
||||
|
||||
-- SIZE
|
||||
-- hl.window_rule({ size = "25% 25%", match = { title = "^(Firefox)$" } })
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
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 = 9, monitor = "HEADLESS-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" })
|
||||
|
||||
31
dotfiles/.config/hypr/presets/anim-hyprflux.lua
Normal file
31
dotfiles/.config/hypr/presets/anim-hyprflux.lua
Normal file
@@ -0,0 +1,31 @@
|
||||
-- HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
-- name "HyprFlux (default)"
|
||||
-- The animation set that shipped as appearance/animations.lua (ported from
|
||||
-- UserConfigs/UserAnimations.conf). Selected by default; Super+Shift+A switches.
|
||||
|
||||
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" })
|
||||
@@ -1,31 +1,38 @@
|
||||
#!/bin/bash
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# For applying Animations from different users
|
||||
# Animation preset picker (Super+Shift+A).
|
||||
# Lists ~/.config/hypr/presets/anim-*.lua by their original HyprFlux preset name
|
||||
# (the "Ported from animations/<name>.conf" line, else `-- name "..."`), then
|
||||
# points appearance/animations.lua's require() at the chosen one and reloads.
|
||||
|
||||
iDIR="$HOME/.config/swaync/images"
|
||||
presets_dir="$HOME/.config/hypr/presets"
|
||||
active_file="$HOME/.config/hypr/appearance/animations.lua"
|
||||
rofi_theme="$HOME/.config/rofi/launchers/type-4/style-4.rasi"
|
||||
|
||||
# Check if rofi is already running
|
||||
if pidof rofi >/dev/null; then
|
||||
pkill rofi
|
||||
fi
|
||||
|
||||
# Variables
|
||||
iDIR="$HOME/.config/swaync/images"
|
||||
SCRIPTSDIR="$HOME/.config/hypr/scripts"
|
||||
animations_dir="$HOME/.config/hypr/animations"
|
||||
UserConfigs="$HOME/.config/hypr/UserConfigs"
|
||||
rofi_theme="$HOME/.config/rofi/config-Animations.rasi"
|
||||
msg='❗NOTE:❗ This will copy animations into UserAnimations.conf'
|
||||
# list of animation files, sorted alphabetically with numbers first
|
||||
animations_list=$(find -L "$animations_dir" -maxdepth 1 -type f | sed 's/.*\///' | sed 's/\.conf$//' | sort -V)
|
||||
# "Display name<TAB>module" per preset, sorted by name
|
||||
list=$(for f in "$presets_dir"/anim-*.lua; do
|
||||
module=$(basename "$f" .lua)
|
||||
name=$(sed -n 's/^-- Ported from animations\/\(.*\)\.conf.*/\1/p' "$f" | head -n1)
|
||||
[[ -z "$name" ]] && name=$(sed -n 's/^-- name "\(.*\)"$/\1/p' "$f" | head -n1)
|
||||
printf '%s\t%s\n' "${name:-$module}" "$module"
|
||||
done | sort -f)
|
||||
|
||||
# Rofi Menu
|
||||
chosen_file=$(echo "$animations_list" | rofi -i -dmenu -config $rofi_theme -mesg "$msg")
|
||||
current=$(sed -n 's/^require("presets\.\(.*\)")$/\1/p' "$active_file")
|
||||
current_name=$(awk -F'\t' -v m="$current" '$2 == m { print $1 }' <<<"$list")
|
||||
|
||||
# Check if a file was selected
|
||||
if [[ -n "$chosen_file" ]]; then
|
||||
full_path="$animations_dir/$chosen_file.conf"
|
||||
cp "$full_path" "$UserConfigs/UserAnimations.conf"
|
||||
notify-send -u low -i "$iDIR/ja.png" "$chosen_file" "Hyprland Animation Loaded"
|
||||
fi
|
||||
chosen=$(cut -f1 <<<"$list" | rofi -i -dmenu -p "Animations" \
|
||||
-theme "$rofi_theme" \
|
||||
-theme-str 'mainbox { children: [ "inputbar", "message", "listview" ]; }' \
|
||||
-mesg "Current: ${current_name:-$current}")
|
||||
[[ -z "$chosen" ]] && exit 0
|
||||
|
||||
sleep 1
|
||||
"$SCRIPTSDIR/RefreshNoWaybar.sh"
|
||||
module=$(awk -F'\t' -v n="$chosen" '$1 == n { print $2; exit }' <<<"$list")
|
||||
[[ -z "$module" ]] && exit 1
|
||||
|
||||
sed -i "s/^require(\"presets\.[^\"]*\")$/require(\"presets.$module\")/" "$active_file"
|
||||
hyprctl reload >/dev/null
|
||||
notify-send -u low -i "$iDIR/note.png" "$chosen" "Hyprland animation loaded"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# Clipboard Manager. This script uses cliphist, rofi, and wl-copy.
|
||||
|
||||
# Variables
|
||||
rofi_theme="$HOME/.config/rofi/config-clipboard.rasi"
|
||||
rofi_theme="$HOME/.config/rofi/launchers/type-4/style-4.rasi"
|
||||
msg='👀 **note** CTRL DEL = cliphist del (entry) or ALT DEL - cliphist wipe (all)'
|
||||
# Actions:
|
||||
# CTRL Del to delete an entry
|
||||
@@ -19,7 +19,9 @@ while true; do
|
||||
rofi -i -dmenu \
|
||||
-kb-custom-1 "Control-Delete" \
|
||||
-kb-custom-2 "Alt-Delete" \
|
||||
-config $rofi_theme < <(cliphist list) \
|
||||
-theme "$rofi_theme" \
|
||||
-theme-str 'mainbox { children: [ "inputbar", "message", "listview" ]; }' \
|
||||
< <(cliphist list) \
|
||||
-mesg "$msg"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
music_icon="$HOME/.config/swaync/icons/music.png"
|
||||
|
||||
# Use the app's own icon for known players
|
||||
case "$(playerctl metadata --format '{{playerName}}' 2>/dev/null)" in
|
||||
Plexamp) music_icon="com.plexamp.Plexamp" ;;
|
||||
esac
|
||||
|
||||
# Play the next track
|
||||
play_next() {
|
||||
playerctl next
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# Scripts for refreshing waybar, rofi, swaync
|
||||
# Scripts for refreshing quickshell, rofi, swaync (Waybar is retired — not restarted here)
|
||||
|
||||
SCRIPTSDIR=$HOME/.config/hypr/scripts
|
||||
UserScripts=$HOME/.config/hypr/UserScripts
|
||||
@@ -15,31 +15,24 @@ file_exists() {
|
||||
}
|
||||
|
||||
# Kill already running processes
|
||||
_ps=(waybar rofi swaync ags)
|
||||
_ps=(rofi swaync ags)
|
||||
for _prs in "${_ps[@]}"; do
|
||||
if pidof "${_prs}" >/dev/null; then
|
||||
pkill "${_prs}"
|
||||
fi
|
||||
done
|
||||
|
||||
# ensure waybar fully reloads
|
||||
killall -SIGUSR2 waybar
|
||||
|
||||
# quit ags & relaunch ags
|
||||
#ags -q && ags &
|
||||
|
||||
# quit quickshell & relaunch quickshell
|
||||
pkill qs && qs &
|
||||
pkill -x qs; qs &
|
||||
|
||||
# some process to kill
|
||||
for pid in $(pidof waybar rofi swaync ags swaybg); do
|
||||
for pid in $(pidof rofi swaync ags swaybg); do
|
||||
kill -SIGUSR1 "$pid"
|
||||
done
|
||||
|
||||
#Restart waybar
|
||||
sleep 1
|
||||
waybar &
|
||||
|
||||
# relaunch swaync
|
||||
sleep 0.5
|
||||
swaync > /dev/null 2>&1 &
|
||||
|
||||
29
dotfiles/.config/hypr/scripts/SunshineIdle.sh
Executable file
29
dotfiles/.config/hypr/scripts/SunshineIdle.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
# Keeps hypridle from locking the screen while Sunshine is streaming.
|
||||
# Hooked up as Sunshine's global_prep_cmd, so it runs for every app:
|
||||
# start -> hold a systemd idle inhibitor (hypridle respects these) and
|
||||
# unlock hyprlock if the session was already locked
|
||||
# stop -> release the inhibitor so normal idle locking resumes
|
||||
|
||||
pidfile="${XDG_RUNTIME_DIR:-/tmp}/sunshine-idle-inhibit.pid"
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if ! { [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile")" 2>/dev/null; }; then
|
||||
setsid systemd-inhibit --what=idle --who=Sunshine --why="Streaming" \
|
||||
sleep infinity >/dev/null 2>&1 &
|
||||
echo $! > "$pidfile"
|
||||
fi
|
||||
# SIGUSR1 tells hyprlock to unlock
|
||||
pkill -USR1 -x hyprlock
|
||||
;;
|
||||
stop)
|
||||
[ -f "$pidfile" ] && kill "$(cat "$pidfile")" 2>/dev/null
|
||||
rm -f "$pidfile"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 start|stop" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
@@ -1,32 +1,30 @@
|
||||
#!/bin/bash
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# For disabling touchpad.
|
||||
# Edit the Touchpad_Device on ~/.config/hypr/UserConfigs/Laptops.conf according to your system
|
||||
# use hyprctl devices to get your system touchpad device name
|
||||
# source https://github.com/hyprwm/Hyprland/discussions/4283?sort=new#discussioncomment-8648109
|
||||
# Toggles the laptop touchpad (XF86TouchpadToggle).
|
||||
# Finds the touchpad by name in `hyprctl devices`, so it works on any laptop
|
||||
# without hard-coding the device. The Lua config rejects `hyprctl keyword`,
|
||||
# so the device is switched with hl.device() through `hyprctl eval`.
|
||||
|
||||
notif="$HOME/.config/swaync/images/ja.png"
|
||||
notif="$HOME/.config/swaync/images/note.png"
|
||||
STATUS_FILE="$XDG_RUNTIME_DIR/touchpad.status"
|
||||
|
||||
export STATUS_FILE="$XDG_RUNTIME_DIR/touchpad.status"
|
||||
device=$(hyprctl devices -j | jq -r '[.mice[].name | select(test("touchpad"; "i"))][0] // empty')
|
||||
if [ -z "$device" ]; then
|
||||
notify-send -u low -i "$notif" "Touchpad" "No touchpad found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
enable_touchpad() {
|
||||
printf "true" >"$STATUS_FILE"
|
||||
notify-send -u low -i $notif " Enabling" " touchpad"
|
||||
hyprctl keyword '$TOUCHPAD_ENABLED' "true" -r
|
||||
}
|
||||
|
||||
disable_touchpad() {
|
||||
printf "false" >"$STATUS_FILE"
|
||||
notify-send -u low -i $notif " Disabling" " touchpad"
|
||||
hyprctl keyword '$TOUCHPAD_ENABLED' "false" -r
|
||||
}
|
||||
|
||||
if ! [ -f "$STATUS_FILE" ]; then
|
||||
enable_touchpad
|
||||
set_touchpad() {
|
||||
printf '%s' "$1" >"$STATUS_FILE"
|
||||
hyprctl eval "hl.device({ name = \"$device\", enabled = $1 })" >/dev/null
|
||||
if [ "$1" = true ]; then
|
||||
notify-send -u low -i "$notif" " Enabling" " touchpad"
|
||||
else
|
||||
if [ $(cat "$STATUS_FILE") = "true" ]; then
|
||||
disable_touchpad
|
||||
elif [ $(cat "$STATUS_FILE") = "false" ]; then
|
||||
enable_touchpad
|
||||
notify-send -u low -i "$notif" " Disabling" " touchpad"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$(cat "$STATUS_FILE" 2>/dev/null)" = "false" ]; then
|
||||
set_touchpad true
|
||||
else
|
||||
set_touchpad false
|
||||
fi
|
||||
|
||||
@@ -1,143 +1,37 @@
|
||||
#!/bin/bash
|
||||
# HyprFlux — https://github.com/ahmad9059/HyprFlux
|
||||
# Scripts for volume controls for audio and mic
|
||||
# Volume controls for audio and mic via wpctl (PipeWire).
|
||||
# Quickshell's VolumeOsd watches PipeWire directly, so no notifications here.
|
||||
|
||||
iDIR="$HOME/.config/swaync/icons"
|
||||
sDIR="$HOME/.config/hypr/scripts"
|
||||
SINK="@DEFAULT_AUDIO_SINK@"
|
||||
SOURCE="@DEFAULT_AUDIO_SOURCE@"
|
||||
STEP="5%"
|
||||
|
||||
# Get Volume
|
||||
# Print volume as an integer percent, or "Muted"
|
||||
get_volume() {
|
||||
volume=$(pamixer --get-volume)
|
||||
if [[ "$volume" -eq "0" ]]; then
|
||||
local out
|
||||
out=$(wpctl get-volume "$1")
|
||||
if [[ "$out" == *MUTED* ]]; then
|
||||
echo "Muted"
|
||||
else
|
||||
echo "$volume %"
|
||||
awk '{ printf "%d %%\n", $2 * 100 + 0.5 }' <<< "$out"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get icons
|
||||
get_icon() {
|
||||
current=$(get_volume)
|
||||
if [[ "$current" == "Muted" ]]; then
|
||||
echo "$iDIR/volume-mute.png"
|
||||
elif [[ "${current%\%}" -le 30 ]]; then
|
||||
echo "$iDIR/volume-low.png"
|
||||
elif [[ "${current%\%}" -le 60 ]]; then
|
||||
echo "$iDIR/volume-mid.png"
|
||||
else
|
||||
echo "$iDIR/volume-high.png"
|
||||
fi
|
||||
is_muted() {
|
||||
[[ "$(wpctl get-volume "$1")" == *MUTED* ]]
|
||||
}
|
||||
|
||||
# Notify
|
||||
notify_user() {
|
||||
if [[ "$(get_volume)" == "Muted" ]]; then
|
||||
notify-send -e -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume:" " Muted"
|
||||
else
|
||||
notify-send -e -h int:value:"$(get_volume | sed 's/%//')" -h string:x-canonical-private-synchronous:volume_notif -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$(get_icon)" " Volume Level:" " $(get_volume)" &&
|
||||
"$sDIR/Sounds.sh" --volume
|
||||
fi
|
||||
}
|
||||
# Raising/lowering while muted unmutes first, matching the old behaviour
|
||||
inc() { is_muted "$1" && wpctl set-mute "$1" 0 || wpctl set-volume -l 1.0 "$1" "$STEP+"; }
|
||||
dec() { is_muted "$1" && wpctl set-mute "$1" 0 || wpctl set-volume "$1" "$STEP-"; }
|
||||
|
||||
# Increase Volume
|
||||
inc_volume() {
|
||||
if [ "$(pamixer --get-mute)" == "true" ]; then
|
||||
toggle_mute
|
||||
else
|
||||
pamixer -i 5 --allow-boost --set-limit 150 && notify_user
|
||||
fi
|
||||
}
|
||||
|
||||
# Decrease Volume
|
||||
dec_volume() {
|
||||
if [ "$(pamixer --get-mute)" == "true" ]; then
|
||||
toggle_mute
|
||||
else
|
||||
pamixer -d 5 && notify_user
|
||||
fi
|
||||
}
|
||||
|
||||
# Toggle Mute
|
||||
toggle_mute() {
|
||||
if [ "$(pamixer --get-mute)" == "false" ]; then
|
||||
pamixer -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/volume-mute.png" " Mute"
|
||||
elif [ "$(pamixer --get-mute)" == "true" ]; then
|
||||
pamixer -u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$(get_icon)" " Volume:" " Switched ON"
|
||||
fi
|
||||
}
|
||||
|
||||
# Toggle Mic
|
||||
toggle_mic() {
|
||||
if [ "$(pamixer --default-source --get-mute)" == "false" ]; then
|
||||
pamixer --default-source -m && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone-mute.png" " Microphone:" " Switched OFF"
|
||||
elif [ "$(pamixer --default-source --get-mute)" == "true" ]; then
|
||||
pamixer -u --default-source u && notify-send -e -u low -h boolean:SWAYNC_BYPASS_DND:true -i "$iDIR/microphone.png" " Microphone:" " Switched ON"
|
||||
fi
|
||||
}
|
||||
# Get Mic Icon
|
||||
get_mic_icon() {
|
||||
current=$(pamixer --default-source --get-volume)
|
||||
if [[ "$current" -eq "0" ]]; then
|
||||
echo "$iDIR/microphone-mute.png"
|
||||
else
|
||||
echo "$iDIR/microphone.png"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get Microphone Volume
|
||||
get_mic_volume() {
|
||||
volume=$(pamixer --default-source --get-volume)
|
||||
if [[ "$volume" -eq "0" ]]; then
|
||||
echo "Muted"
|
||||
else
|
||||
echo "$volume %"
|
||||
fi
|
||||
}
|
||||
|
||||
# Notify for Microphone
|
||||
notify_mic_user() {
|
||||
volume=$(get_mic_volume)
|
||||
icon=$(get_mic_icon)
|
||||
notify-send -e -h int:value:"$volume" -h "string:x-canonical-private-synchronous:volume_notif" -h boolean:SWAYNC_BYPASS_DND:true -u low -i "$icon" " Mic Level:" " $volume"
|
||||
}
|
||||
|
||||
# Increase MIC Volume
|
||||
inc_mic_volume() {
|
||||
if [ "$(pamixer --default-source --get-mute)" == "true" ]; then
|
||||
toggle_mic
|
||||
else
|
||||
pamixer --default-source -i 5 && notify_mic_user
|
||||
fi
|
||||
}
|
||||
|
||||
# Decrease MIC Volume
|
||||
dec_mic_volume() {
|
||||
if [ "$(pamixer --default-source --get-mute)" == "true" ]; then
|
||||
toggle-mic
|
||||
else
|
||||
pamixer --default-source -d 5 && notify_mic_user
|
||||
fi
|
||||
}
|
||||
|
||||
# Execute accordingly
|
||||
if [[ "$1" == "--get" ]]; then
|
||||
get_volume
|
||||
elif [[ "$1" == "--inc" ]]; then
|
||||
inc_volume
|
||||
elif [[ "$1" == "--dec" ]]; then
|
||||
dec_volume
|
||||
elif [[ "$1" == "--toggle" ]]; then
|
||||
toggle_mute
|
||||
elif [[ "$1" == "--toggle-mic" ]]; then
|
||||
toggle_mic
|
||||
elif [[ "$1" == "--get-icon" ]]; then
|
||||
get_icon
|
||||
elif [[ "$1" == "--get-mic-icon" ]]; then
|
||||
get_mic_icon
|
||||
elif [[ "$1" == "--mic-inc" ]]; then
|
||||
inc_mic_volume
|
||||
elif [[ "$1" == "--mic-dec" ]]; then
|
||||
dec_mic_volume
|
||||
else
|
||||
get_volume
|
||||
fi
|
||||
case "$1" in
|
||||
--inc) inc "$SINK" ;;
|
||||
--dec) dec "$SINK" ;;
|
||||
--toggle) wpctl set-mute "$SINK" toggle ;;
|
||||
--toggle-mic) wpctl set-mute "$SOURCE" toggle ;;
|
||||
--mic-inc) inc "$SOURCE" ;;
|
||||
--mic-dec) dec "$SOURCE" ;;
|
||||
--get-mic) get_volume "$SOURCE" ;;
|
||||
*) get_volume "$SINK" ;;
|
||||
esac
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
accent_primary = "#007acc"
|
||||
accent_secondary = "#06b6d4"
|
||||
background = "#1e1e1e"
|
||||
foreground = "#d4d4d8"
|
||||
opacity = 0.91
|
||||
BIN
dotfiles/.config/quickshell/assets/bitwarden.png
Normal file
BIN
dotfiles/.config/quickshell/assets/bitwarden.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
27
dotfiles/.config/quickshell/assets/sunshine.svg
Normal file
27
dotfiles/.config/quickshell/assets/sunshine.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Sunshine_Logo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" width="256px" height="256px" viewBox="10 6 236 245" style="enable-background:new 0 0 256 256;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FDD107;}
|
||||
.st1{fill:#F89A1C;}
|
||||
.st2{fill:#EF3E23;}
|
||||
.st3{fill:#F26222;}
|
||||
</style>
|
||||
<path class="st0" d="M118.7688675,20.7120476c0,0-63.8333359,26-74.3333359,83.8333282s37.1666718,91.5,86.3333282,75.3333282
|
||||
s70.3333435-51,81.8333435-86.9999924c0,0-9.3333435,100.4999924-96.1666718,115.4999924s-118.1666641-50-82.1666641-119.8333282
|
||||
C44.2688675,67.0453796,80.5188675,29.6287155,118.7688675,20.7120476z"/>
|
||||
<path class="st1" d="M118.7688675,20.7120476c0,0-41.125,3.6666679-83.25,61.0416679s-28.125,139.125,34.25,149.375
|
||||
s115.8749924-44.875,133.5-82.375s15.1666718-61.4583282,9.75-77.8749924c0,0,0.6666718,36.4166641-13.3333282,59.6666641
|
||||
s-29.75,46.3333282-65.0833282,62.1666718s-74.1666718,13.75-95.4166718-19.25s-5.9166641-76.0833359-0.2916641-85.3333359
|
||||
S72.3938675,33.7953796,118.7688675,20.7120476z"/>
|
||||
<path class="st2" d="M73.0188675,39.6287155c0,0,38.125-28.125,76.8749924-28.125s63,28.25,68.5,52.25s6,54.125-11.5,87.6249924
|
||||
s-37.375,56-79.1249924,76.125s-84.625,2.75-84.625,2.75s25.9769745,25.8750153,71.0509872,16.5
|
||||
c45.0740051-9.375,82.2406769-40.875,98.4073486-69.5s28.7916565-57.3749924,27.6666565-92.2499924s-23.75-54.5-31.25-60.25
|
||||
s-23.1875-17.8125-58.1875-16.5625S86.4563675,29.8162155,73.0188675,39.6287155z"/>
|
||||
<path class="st3" d="M73.0188675,39.6287155c0,0,35-32.8125,82.4374924-32.8125s69.1875,24.8125,78.875,44.6875
|
||||
s21.8125,70-12.1875,122.9999924s-74.625,67.375-93.625,71.625s-42.4311447,4.269165-59.1114044-1.2299957
|
||||
c0,0,35.1947479,8.3966675,66.7780762-7.4366608s51.6666718-32.1666718,74.0833282-68.8333435
|
||||
s25.9166718-72.7499924,22.1666718-93.9166565s-12.1666718-42.4166718-36.5-56.3333359s-56.7291718-10.531251-74.4791641-4.531251
|
||||
S91.9876175,26.4099655,73.0188675,39.6287155z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -8,6 +8,11 @@ QtObject {
|
||||
// a visually distinct icon style from the rest of the bar (currently
|
||||
// just BluetoothIndicator.qml) without switching every icon over.
|
||||
readonly property string iconFontFamilyRounded: "Material Symbols Rounded"
|
||||
// Brand logos (YouTube, Spotify, Plex...), which Material Symbols doesn't
|
||||
// have — used by MediaPlayer.qml's per-source icon. The "Propo" build
|
||||
// draws Nerd Font icons at full size; the Mono one squeezes them into a
|
||||
// single character cell, which renders them noticeably smaller.
|
||||
readonly property string brandIconFontFamily: "CaskaydiaCove Nerd Font Propo"
|
||||
// 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
|
||||
@@ -26,4 +31,23 @@ QtObject {
|
||||
readonly property int spacing: 10
|
||||
|
||||
readonly property int animationDuration: 150
|
||||
|
||||
// Max characters for the bar's free-text labels before they're cut off
|
||||
// with "…" (ActiveWindow.qml and MediaPlayer.qml).
|
||||
readonly property int activeWindowMaxChars: 50
|
||||
readonly property int mediaMaxChars: 40
|
||||
// Tighter limits for narrow screens (the laptop's eDP-1 is 1440 logical
|
||||
// px wide), where the full-length labels nearly run into the centre
|
||||
// clock/weather group. Bar.qml switches when it's narrower than
|
||||
// compactBarWidth.
|
||||
readonly property int compactBarWidth: 1600
|
||||
readonly property int activeWindowMaxCharsCompact: 32
|
||||
readonly property int mediaMaxCharsCompact: 28
|
||||
|
||||
// Counts code points rather than UTF-16 units so emoji / some CJK
|
||||
// aren't split in half at the cut.
|
||||
function truncate(str, maxChars) {
|
||||
const chars = Array.from(str || "")
|
||||
return chars.length > maxChars ? chars.slice(0, maxChars - 1).join("").replace(/\s+$/, "") + "…" : chars.join("")
|
||||
}
|
||||
}
|
||||
|
||||
72
dotfiles/.config/quickshell/config/SystemUpdates.qml
Normal file
72
dotfiles/.config/quickshell/config/SystemUpdates.qml
Normal file
@@ -0,0 +1,72 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
|
||||
// Shared state for SystemUpdate.qml, the bar's package-update indicator.
|
||||
// Bar.qml is instantiated once per screen (see shell.qml's Variants), so
|
||||
// without this being a singleton, plugging in extra monitors would mean
|
||||
// running `checkupdates` that many times in parallel on every poll — which
|
||||
// isn't just wasteful, it's actively unreliable: concurrent invocations
|
||||
// race over checkupdates' own sync directory and can intermittently report
|
||||
// 0 pending updates. One shared Process here means exactly one `checkupdates`
|
||||
// run at a time, and every bar's icon reflects the same result.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// checkupdates syncs a throwaway copy of the pacman db, so it's not
|
||||
// free to run often — matches the old waybar custom/updater module's
|
||||
// ~21.7 minute poll interval (see .config/waybar/config).
|
||||
readonly property int refreshIntervalMs: 1300 * 1000
|
||||
|
||||
property int updateCount: 0
|
||||
property bool checking: false
|
||||
property bool updating: false
|
||||
|
||||
function refresh() {
|
||||
if (_checkProc.running) return
|
||||
root.checking = true
|
||||
_checkProc.running = true
|
||||
}
|
||||
|
||||
// scriptPath: absolute path to update-system.sh (resolved by the caller
|
||||
// via Quickshell.shellPath, which isn't available as a plain import
|
||||
// here without pulling in the whole Quickshell singleton for one call).
|
||||
function runUpdate(scriptPath) {
|
||||
if (_updateProc.running) return
|
||||
root.updating = true
|
||||
_updateProc.command = [scriptPath]
|
||||
_updateProc.running = true
|
||||
}
|
||||
|
||||
// QtObject has no default property, so these are attached via property
|
||||
// assignment rather than as plain child elements (same as Colors.qml's
|
||||
// FileView).
|
||||
readonly property Process _checkProc: Process {
|
||||
command: ["sh", "-c", "checkupdates 2>/dev/null | wc -l"]
|
||||
stdout: StdioCollector {
|
||||
id: checkCollector
|
||||
onStreamFinished: {
|
||||
root.updateCount = parseInt(checkCollector.text.trim(), 10) || 0
|
||||
root.checking = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runs update-system.sh, which opens its own terminal and blocks until
|
||||
// that terminal is closed — so this only exits once the user's actually
|
||||
// done updating, which is exactly when a re-check matters.
|
||||
readonly property Process _updateProc: Process {
|
||||
onExited: {
|
||||
root.updating = false
|
||||
root.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Timer _timer: Timer {
|
||||
interval: root.refreshIntervalMs
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,45 @@
|
||||
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.
|
||||
// Tailscale, Mullvad, ProtonVPN, and the home UniFi WireGuard tunnel all
|
||||
// fight over the default route/DNS if more than one is active at once (see
|
||||
// VpnIndicator.qml's header comment) — only one should ever actually be
|
||||
// connected. This singleton is the shared signal between the two widget
|
||||
// files: VpnIndicator.qml writes each tunnel's live connection state here
|
||||
// as it polls them; TailscaleIndicator.qml reads it to back off (`tailscale
|
||||
// down`) rather than stay up alongside one, to refuse turning Tailscale
|
||||
// back on while one is still connected, and to bring Tailscale up on its
|
||||
// own whenever none of them is.
|
||||
//
|
||||
// TailscaleIndicator.qml publishes its own state back here too, so
|
||||
// NetworkIndicator.qml can name whichever tunnel is up under the public IP.
|
||||
QtObject {
|
||||
property bool mullvadConnected: false
|
||||
property bool protonConnected: false
|
||||
readonly property bool commercialVpnActive: mullvadConnected || protonConnected
|
||||
property bool homeWgConnected: false
|
||||
readonly property bool commercialVpnActive: mullvadConnected || protonConnected || homeWgConnected
|
||||
|
||||
// Also published by VpnIndicator.qml: whether every provider has
|
||||
// reported its state at least once (until then the flags above are just
|
||||
// their defaults), and whether any of them is mid connect, disconnect or
|
||||
// switch (when "nothing connected" is only momentary). noVpnActive is
|
||||
// the settled "none of them is connected" that TailscaleIndicator.qml
|
||||
// auto-connects on.
|
||||
property bool vpnStatesKnown: false
|
||||
property bool vpnTransitioning: false
|
||||
readonly property bool noVpnActive: vpnStatesKnown && !commercialVpnActive && !vpnTransitioning
|
||||
|
||||
property bool tailscaleConnected: false
|
||||
// Name of the exit node in use, "" if none.
|
||||
property string tailscaleExitNode: ""
|
||||
|
||||
// e.g. "ProtonVPN", or "Tailscale (exit node: nas)"; "" when nothing is
|
||||
// up. Normally one name, but lists every tunnel it sees, since one can
|
||||
// briefly overlap another while switching.
|
||||
readonly property string activeVpnLabel: [
|
||||
mullvadConnected ? "Mullvad" : "",
|
||||
protonConnected ? "ProtonVPN" : "",
|
||||
homeWgConnected ? "Home (UniFi)" : "",
|
||||
tailscaleConnected ? "Tailscale" + (tailscaleExitNode ? " (exit node: " + tailscaleExitNode + ")" : "") : ""
|
||||
].filter(Boolean).join(", ")
|
||||
}
|
||||
|
||||
242
dotfiles/.config/quickshell/config/WeatherData.qml
Normal file
242
dotfiles/.config/quickshell/config/WeatherData.qml
Normal file
@@ -0,0 +1,242 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
// Live weather for the bar's Weather.qml icon. A singleton for the same
|
||||
// reason as SystemUpdates.qml: Bar.qml is instantiated once per monitor
|
||||
// (shell.qml's Variants), so without this being shared, every extra screen
|
||||
// would mean another set of network requests running in parallel.
|
||||
//
|
||||
// Location is auto-detected from the public IP (via ipwho.is, no API key)
|
||||
// and cached to disk (~/.cache/quickshell/weather-location.json) so it's
|
||||
// only ever looked up once, not on every quickshell restart — IP-geolocation
|
||||
// free tiers rate-limit fast, and a desktop's location barely ever changes
|
||||
// anyway. Only the weather itself re-polls on a timer. Conditions come from
|
||||
// Open-Meteo (api.open-meteo.com) — also free and keyless, and it does its
|
||||
// own °C/km-h conversion server-side via query params.
|
||||
//
|
||||
// Loosely modeled on omarchy's weather panel
|
||||
// (github.com/omacom/omarchy, quattro branch,
|
||||
// shell/plugins/panels/weather/Model.js) for the WMO weather-code
|
||||
// grouping, but the icons are this bar's own Material Symbols glyphs
|
||||
// rather than pulling in a separate icon font just for this.
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// Location barely ever changes for a desktop machine, so it's fetched
|
||||
// once (and cached); the weather itself is worth re-checking
|
||||
// periodically.
|
||||
readonly property int refreshIntervalMs: 15 * 60 * 1000
|
||||
|
||||
property bool locating: true
|
||||
property bool loading: false
|
||||
property bool hasLocation: false
|
||||
property string locationName: ""
|
||||
|
||||
property real latitude: 0
|
||||
property real longitude: 0
|
||||
|
||||
function saveLocation() {
|
||||
_locationCache.setText(JSON.stringify({
|
||||
latitude: root.latitude,
|
||||
longitude: root.longitude,
|
||||
name: root.locationName
|
||||
}))
|
||||
}
|
||||
|
||||
// Called from Weather.qml's search box once you pick a result — always
|
||||
// wins over whatever IP-based guess was cached before, and persists so
|
||||
// it sticks across restarts.
|
||||
function selectLocation(name, lat, lon) {
|
||||
root.latitude = lat
|
||||
root.longitude = lon
|
||||
root.locationName = name
|
||||
root.hasLocation = true
|
||||
root.locating = false
|
||||
root.saveLocation()
|
||||
root.refresh()
|
||||
}
|
||||
|
||||
property bool searching: false
|
||||
property var searchResults: []
|
||||
|
||||
function search(query) {
|
||||
const q = String(query || "").trim()
|
||||
if (q.length < 2) {
|
||||
root.searchResults = []
|
||||
return
|
||||
}
|
||||
root.searching = true
|
||||
_searchProc.command = ["curl", "-s", "--max-time", "6",
|
||||
"https://geocoding-api.open-meteo.com/v1/search?count=5&language=en&format=json&name=" + encodeURIComponent(q)]
|
||||
_searchProc.running = true
|
||||
}
|
||||
|
||||
property real tempC: 0
|
||||
property real feelsLikeC: 0
|
||||
property int humidity: 0
|
||||
property real windKmh: 0
|
||||
property int weatherCode: 0
|
||||
property bool isDay: true
|
||||
|
||||
// [{date, code, maxC, minC}] — tomorrow onward; today is already
|
||||
// covered by the current-conditions fields above.
|
||||
property var forecast: []
|
||||
|
||||
function locate() {
|
||||
if (_locateProc.running) return
|
||||
root.locating = true
|
||||
_locateProc.running = true
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
if (!root.hasLocation || _weatherProc.running) return
|
||||
root.loading = true
|
||||
_weatherProc.command = ["curl", "-s", "--max-time", "8",
|
||||
"https://api.open-meteo.com/v1/forecast?latitude=" + root.latitude
|
||||
+ "&longitude=" + root.longitude
|
||||
+ "¤t=temperature_2m,apparent_temperature,relative_humidity_2m,weather_code,is_day,wind_speed_10m"
|
||||
+ "&daily=weather_code,temperature_2m_max,temperature_2m_min"
|
||||
+ "&temperature_unit=celsius&wind_speed_unit=kmh&timezone=auto&forecast_days=4"]
|
||||
_weatherProc.running = true
|
||||
}
|
||||
|
||||
// Material Symbols Rounded glyph for a WMO weather code (see
|
||||
// https://open-meteo.com/en/docs#weathervariables), grouped the same
|
||||
// way omarchy's Model.js groups them into its own icon set.
|
||||
function iconFor(code, day) {
|
||||
if (code === 0) return day ? "sunny" : "clear_night"
|
||||
if (code === 1 || code === 2) return day ? "partly_cloudy_day" : "partly_cloudy_night"
|
||||
if (code === 3) return "cloud"
|
||||
if (code === 45 || code === 48) return "foggy"
|
||||
if ([51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82].indexOf(code) !== -1) return "rainy"
|
||||
if ([71, 73, 75, 77, 85, 86].indexOf(code) !== -1) return "weather_snowy"
|
||||
if (code === 95 || code === 96 || code === 99) return "thunderstorm"
|
||||
return "cloud"
|
||||
}
|
||||
|
||||
// QtObject has no default property, so these are attached via property
|
||||
// assignment rather than as plain child elements (same as Colors.qml's
|
||||
// FileView and SystemUpdates.qml's Process/Timer).
|
||||
|
||||
// Read once at startup; onLoaded/onLoadFailed (below) decide whether a
|
||||
// network lookup is still needed.
|
||||
readonly property FileView _locationCache: FileView {
|
||||
path: Quickshell.env("HOME") + "/.cache/quickshell/weather-location.json"
|
||||
onLoaded: {
|
||||
try {
|
||||
const data = JSON.parse(text())
|
||||
const lat = parseFloat(data.latitude)
|
||||
const lon = parseFloat(data.longitude)
|
||||
if (!isNaN(lat) && !isNaN(lon)) {
|
||||
root.latitude = lat
|
||||
root.longitude = lon
|
||||
root.locationName = data.name || ""
|
||||
root.hasLocation = true
|
||||
root.locating = false
|
||||
root.refresh()
|
||||
return
|
||||
}
|
||||
} catch (e) {
|
||||
// fall through to a fresh lookup below
|
||||
}
|
||||
root.locate()
|
||||
}
|
||||
onLoadFailed: root.locate()
|
||||
}
|
||||
|
||||
readonly property Process _locateProc: Process {
|
||||
command: ["curl", "-s", "--max-time", "6", "https://ipwho.is/"]
|
||||
stdout: StdioCollector {
|
||||
id: locateCollector
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(locateCollector.text)
|
||||
const lat = parseFloat(data.latitude)
|
||||
const lon = parseFloat(data.longitude)
|
||||
root.hasLocation = data.success !== false && !isNaN(lat) && !isNaN(lon)
|
||||
if (root.hasLocation) {
|
||||
root.latitude = lat
|
||||
root.longitude = lon
|
||||
root.locationName = data.city || data.region || data.country || ""
|
||||
root.saveLocation()
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Weather: locate parse failed:", e)
|
||||
root.hasLocation = false
|
||||
}
|
||||
root.locating = false
|
||||
if (root.hasLocation) root.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Process _weatherProc: Process {
|
||||
stdout: StdioCollector {
|
||||
id: weatherCollector
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(weatherCollector.text)
|
||||
const current = data.current || {}
|
||||
root.tempC = current.temperature_2m
|
||||
root.feelsLikeC = current.apparent_temperature
|
||||
root.humidity = current.relative_humidity_2m
|
||||
root.windKmh = current.wind_speed_10m
|
||||
root.weatherCode = current.weather_code
|
||||
root.isDay = current.is_day === 1
|
||||
|
||||
const daily = data.daily || {}
|
||||
const times = daily.time || []
|
||||
const days = []
|
||||
// Start at 1 — index 0 is today, already shown as the
|
||||
// current conditions above; this is "what's coming".
|
||||
for (let i = 1; i < times.length && days.length < 3; i++) {
|
||||
days.push({
|
||||
date: times[i],
|
||||
code: (daily.weather_code || [])[i],
|
||||
maxC: (daily.temperature_2m_max || [])[i],
|
||||
minC: (daily.temperature_2m_min || [])[i]
|
||||
})
|
||||
}
|
||||
root.forecast = days
|
||||
} catch (e) {
|
||||
console.warn("Weather: failed to parse Open-Meteo response:", e)
|
||||
}
|
||||
root.loading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Process _searchProc: Process {
|
||||
stdout: StdioCollector {
|
||||
id: searchCollector
|
||||
onStreamFinished: {
|
||||
try {
|
||||
const data = JSON.parse(searchCollector.text)
|
||||
const results = data.results || []
|
||||
root.searchResults = results.filter(function(r) {
|
||||
return r && r.name && r.latitude !== undefined && r.longitude !== undefined
|
||||
}).map(function(r) {
|
||||
return {
|
||||
name: r.name,
|
||||
description: [r.admin1, r.country].filter(function(p) { return !!p }).join(", "),
|
||||
latitude: r.latitude,
|
||||
longitude: r.longitude
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
root.searchResults = []
|
||||
}
|
||||
root.searching = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property Timer _timer: Timer {
|
||||
interval: root.refreshIntervalMs
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: root.refresh()
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,5 @@ module config
|
||||
singleton Colors 1.0 Colors.qml
|
||||
singleton Appearance 1.0 Appearance.qml
|
||||
singleton VpnExclusivity 1.0 VpnExclusivity.qml
|
||||
singleton SystemUpdates 1.0 SystemUpdates.qml
|
||||
singleton WeatherData 1.0 WeatherData.qml
|
||||
|
||||
@@ -4,10 +4,9 @@ import Quickshell.Hyprland
|
||||
import "../../config"
|
||||
|
||||
Text {
|
||||
Layout.maximumWidth: 320
|
||||
elide: Text.ElideRight
|
||||
property bool compact: false
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
text: Hyprland.activeToplevel ? Hyprland.activeToplevel.title : ""
|
||||
text: Hyprland.activeToplevel ? Appearance.truncate(Hyprland.activeToplevel.title, compact ? Appearance.activeWindowMaxCharsCompact : Appearance.activeWindowMaxChars) : ""
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ PanelWindow {
|
||||
exclusiveZone: implicitHeight
|
||||
color: "transparent"
|
||||
|
||||
// Narrow screen (laptop): shorten the left-side free-text labels so they
|
||||
// don't crowd the centre group.
|
||||
readonly property bool compact: width < Appearance.compactBarWidth
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.namespace: "quickshell:bar"
|
||||
|
||||
@@ -32,11 +36,16 @@ PanelWindow {
|
||||
}
|
||||
spacing: Appearance.spacing
|
||||
Workspaces { screen: bar.screen }
|
||||
ActiveWindow {}
|
||||
MediaPlayer { barWindow: bar; compact: bar.compact }
|
||||
ActiveWindow { compact: bar.compact }
|
||||
}
|
||||
|
||||
Clock {
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: Appearance.spacing
|
||||
Weather { barWindow: bar }
|
||||
Clock {}
|
||||
SystemUpdate { barWindow: bar }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -46,7 +55,7 @@ PanelWindow {
|
||||
rightMargin: Appearance.spacing
|
||||
}
|
||||
spacing: Appearance.spacing
|
||||
//Tray {}
|
||||
PinnedTray {}
|
||||
TailscaleIndicator { barWindow: bar }
|
||||
VpnIndicator { barWindow: bar }
|
||||
NetworkIndicator { barWindow: bar }
|
||||
|
||||
@@ -2,6 +2,7 @@ import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.UPower
|
||||
import "../../config"
|
||||
|
||||
@@ -14,7 +15,7 @@ import "../../config"
|
||||
// Nerd Font ones. The charge-threshold ("Holding"/limited-charge) concept from
|
||||
// the original is hardware-specific (vendor charge-limit sysfs knobs) and has
|
||||
// no equivalent in Quickshell's UPower binding, so it's dropped rather than
|
||||
// guessed at; "Battery health" (device.healthPercentage) is shown in its
|
||||
// guessed at; "Battery health" (computed from sysfs energy_full/design) is shown in its
|
||||
// place, using data UPower actually reports.
|
||||
Item {
|
||||
id: root
|
||||
@@ -51,10 +52,162 @@ Item {
|
||||
readonly property real fraction: root.batteryPresent ? Math.max(0, Math.min(1, root.device.percentage)) : 0
|
||||
readonly property bool low: root.batteryPresent && root.fraction <= 0.15 && !root.charging
|
||||
|
||||
// Quickshell's UPower binding has no charge-cycle property (see the
|
||||
// file-header comment on why the old charge-until threshold was
|
||||
// dropped) — it comes straight from sysfs instead.
|
||||
//
|
||||
// device.nativePath turned out to be empty on real hardware (tested
|
||||
// live), so rather than build a path from it, a shell glob finds
|
||||
// whichever BAT* directory actually exists — same shell-out-to-a-CLI
|
||||
// convention TailscaleIndicator.qml uses for its own live stats.
|
||||
// This barely changes during a session, so one read at startup is
|
||||
// enough; no polling timer.
|
||||
property int cycleCount: -1
|
||||
|
||||
Process {
|
||||
id: cycleCountProc
|
||||
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/cycle_count\" 2>/dev/null"]
|
||||
running: root.batteryPresent
|
||||
stdout: StdioCollector {
|
||||
id: cycleCountCollector
|
||||
onStreamFinished: {
|
||||
const n = parseInt(cycleCountCollector.text.trim(), 10)
|
||||
root.cycleCount = (isNaN(n) || n <= 0) ? -1 : n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// device.healthPercentage/healthSupported come from upowerd over D-Bus,
|
||||
// and upowerd has been observed reporting energy-full as a flat 0 (a
|
||||
// live daemon glitch, `upower -i` shows it too) while sysfs still has
|
||||
// the real numbers underneath — same class of unreliability that put
|
||||
// cycle_count on sysfs above instead of trusting upowerd's own
|
||||
// charge-cycles field. Health is computed the same way UPower computes
|
||||
// it (current full-charge capacity over the battery's original design
|
||||
// capacity), just read directly so a daemon hiccup doesn't blank it.
|
||||
property real energyFullWh: -1
|
||||
property real energyFullDesignWh: -1
|
||||
readonly property real healthPercent: (root.energyFullWh > 0 && root.energyFullDesignWh > 0)
|
||||
? Math.min(100, (root.energyFullWh / root.energyFullDesignWh) * 100) : -1
|
||||
|
||||
Process {
|
||||
id: energyFullProc
|
||||
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/energy_full\" 2>/dev/null"]
|
||||
running: root.batteryPresent
|
||||
stdout: StdioCollector {
|
||||
id: energyFullCollector
|
||||
onStreamFinished: {
|
||||
const n = parseFloat(energyFullCollector.text.trim())
|
||||
root.energyFullWh = (isNaN(n) || n <= 0) ? -1 : (n / 1000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: energyFullDesignProc
|
||||
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/energy_full_design\" 2>/dev/null"]
|
||||
running: root.batteryPresent
|
||||
stdout: StdioCollector {
|
||||
id: energyFullDesignCollector
|
||||
onStreamFinished: {
|
||||
const n = parseFloat(energyFullDesignCollector.text.trim())
|
||||
root.energyFullDesignWh = (isNaN(n) || n <= 0) ? -1 : (n / 1000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// device.timeToFull/timeToEmpty are computed by Quickshell from
|
||||
// energy/energyCapacity/changeRate — when upowerd's EnergyFull glitches
|
||||
// to 0 (the same daemon flake HEALTH routes around above), that formula
|
||||
// divides by a bogus capacity and comes out negative rather than a
|
||||
// real estimate (confirmed live: timeToFull sat at -3751 while `upower
|
||||
// -i` itself still reported a normal 26.7 minutes). This recomputes
|
||||
// the same estimate straight from sysfs as a fallback, only used when
|
||||
// Quickshell's own value isn't positive. Unlike cycle_count/design
|
||||
// capacity above, energy_now and power_now change continuously while
|
||||
// (dis)charging, so this polls on a timer instead of reading once.
|
||||
property real sysfsEnergyNowWh: -1
|
||||
property real sysfsPowerNowW: -1
|
||||
|
||||
readonly property real fallbackTimeToFullSeconds: (root.sysfsPowerNowW > 0 && root.energyFullWh > 0 && root.sysfsEnergyNowWh >= 0)
|
||||
? Math.max(0, (root.energyFullWh - root.sysfsEnergyNowWh) / root.sysfsPowerNowW * 3600) : -1
|
||||
readonly property real fallbackTimeToEmptySeconds: (root.sysfsPowerNowW > 0 && root.sysfsEnergyNowWh > 0)
|
||||
? (root.sysfsEnergyNowWh / root.sysfsPowerNowW * 3600) : -1
|
||||
|
||||
// Prefers upowerd's own live estimate when it's sane; only falls back
|
||||
// to the sysfs-computed one when upowerd's has gone zero/negative.
|
||||
readonly property real timeRemainingSeconds: {
|
||||
const primary = root.onBattery ? root.device.timeToEmpty : root.device.timeToFull
|
||||
if (primary > 0) return primary
|
||||
return root.onBattery ? root.fallbackTimeToEmptySeconds : root.fallbackTimeToFullSeconds
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: sysfsRateTimer
|
||||
interval: 15000
|
||||
repeat: true
|
||||
running: root.batteryPresent
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
if (!sysfsEnergyNowProc.running) sysfsEnergyNowProc.running = true
|
||||
if (!sysfsPowerNowProc.running) sysfsPowerNowProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: sysfsEnergyNowProc
|
||||
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/energy_now\" 2>/dev/null"]
|
||||
stdout: StdioCollector {
|
||||
id: sysfsEnergyNowCollector
|
||||
onStreamFinished: {
|
||||
const n = parseFloat(sysfsEnergyNowCollector.text.trim())
|
||||
root.sysfsEnergyNowWh = (isNaN(n) || n < 0) ? -1 : (n / 1000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: sysfsPowerNowProc
|
||||
command: ["sh", "-c", "cat \"$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)/power_now\" 2>/dev/null"]
|
||||
stdout: StdioCollector {
|
||||
id: sysfsPowerNowCollector
|
||||
onStreamFinished: {
|
||||
const n = parseFloat(sysfsPowerNowCollector.text.trim())
|
||||
root.sysfsPowerNowW = (isNaN(n) || n <= 0) ? -1 : (n / 1000000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Raw UPower state string (Charging/Discharging/PendingCharge/…) — more
|
||||
// granular than modeLabel below, which collapses those down to the
|
||||
// three phrases the hero text and STATE row show.
|
||||
readonly property string rawStateLabel: root.batteryPresent
|
||||
? UPowerDeviceState.toString(root.deviceState) : "—"
|
||||
|
||||
// Material Symbols' charging glyphs aren't an evenly-spaced "_bar"
|
||||
// scale like the non-charging ones below — they're fixed at these
|
||||
// seven breakpoints (confirmed against the installed font's ligature
|
||||
// table), so this picks the highest one at or below the current
|
||||
// percentage rather than rounding to an index.
|
||||
function chargingIcon(fraction) {
|
||||
const percent = fraction * 100
|
||||
const thresholds = [20, 30, 50, 60, 80, 90, 100]
|
||||
const icons = [
|
||||
"battery_charging_20", "battery_charging_30", "battery_charging_50", "battery_charging_60",
|
||||
"battery_charging_80", "battery_charging_90", "battery_charging_full"
|
||||
]
|
||||
for (let i = thresholds.length - 1; i >= 0; i--) {
|
||||
if (percent >= thresholds[i]) return icons[i]
|
||||
}
|
||||
// Below 20% there's no lower charging glyph — it's still the
|
||||
// truthful floor of what's available while current is flowing in.
|
||||
return icons[0]
|
||||
}
|
||||
|
||||
function batteryIcon() {
|
||||
if (!root.batteryPresent) return "battery_unknown"
|
||||
if (root.fullyCharged) return "battery_full"
|
||||
if (root.charging) return "battery_charging_full"
|
||||
if (root.charging) return root.chargingIcon(root.fraction)
|
||||
if (root.fraction <= 0.1) return "battery_alert"
|
||||
const bars = ["battery_0_bar", "battery_1_bar", "battery_2_bar", "battery_3_bar", "battery_4_bar", "battery_5_bar", "battery_full"]
|
||||
return bars[Math.max(0, Math.min(bars.length - 1, Math.round(root.fraction * (bars.length - 1))))]
|
||||
@@ -74,6 +227,32 @@ Item {
|
||||
return root.onBattery ? "On battery" : "Charging"
|
||||
}
|
||||
|
||||
// Info grid rows — same shape as NetworkIndicator.qml's statRows/
|
||||
// statCells: a flat label/value list, flattened again into individual
|
||||
// grid cells (label, value, label, value, …) so a 4-column GridLayout
|
||||
// lays them out as two label/value pairs per row.
|
||||
readonly property var statRows: [
|
||||
{
|
||||
label: "HEALTH",
|
||||
value: root.healthPercent > 0 ? Math.round(root.healthPercent) + "%" : "—"
|
||||
},
|
||||
{
|
||||
label: root.onBattery ? "TIME LEFT" : "TIME TO FULL",
|
||||
value: root.batteryPresent ? root.formatDuration(root.timeRemainingSeconds) : "—"
|
||||
},
|
||||
{ label: "STATUS", value: root.rawStateLabel },
|
||||
{ label: "CYCLES", value: root.cycleCount > 0 ? String(root.cycleCount) : "—" }
|
||||
]
|
||||
|
||||
readonly property var statCells: {
|
||||
const cells = []
|
||||
for (let i = 0; i < root.statRows.length; i++) {
|
||||
cells.push({ text: root.statRows[i].label, isLabel: true })
|
||||
cells.push({ text: root.statRows[i].value, isLabel: false })
|
||||
}
|
||||
return cells
|
||||
}
|
||||
|
||||
// Cute rotating status phrases while current is actually flowing —
|
||||
// ported as-is from omarchy's power panel, which pairs the same phrase
|
||||
// sets with the same charging/on-battery states.
|
||||
@@ -142,6 +321,34 @@ Item {
|
||||
return entries
|
||||
}
|
||||
|
||||
// --- Automatic power profile --------------------------------------
|
||||
// Battery-state-driven, on top of the manual picker above: below 20%
|
||||
// → Power Saver (protect what's left), plugged in → Performance,
|
||||
// unplugged and healthy → Balanced. Low battery wins even if a weak
|
||||
// charger is still plugged in — running low is the more urgent signal.
|
||||
// Performance only gets requested when the daemon actually reports it
|
||||
// as available, falling back to Balanced instead of erroring on
|
||||
// hardware without a performance profile (e.g. the ERROR this exact
|
||||
// widget hit before power-profiles-daemon was installed).
|
||||
//
|
||||
// This only ever writes when the *target* changes (crossing the 20%
|
||||
// line, plugging/unplugging) rather than on every poll, so a manual
|
||||
// pick from the buttons above still sticks until the next real
|
||||
// transition instead of being fought immediately.
|
||||
readonly property int desiredProfile: {
|
||||
if (root.fraction <= 0.20) return PowerProfile.PowerSaver
|
||||
if (!root.onBattery) return PowerProfiles.hasPerformanceProfile ? PowerProfile.Performance : PowerProfile.Balanced
|
||||
return PowerProfile.Balanced
|
||||
}
|
||||
|
||||
function applyDesiredProfile() {
|
||||
if (!root.batteryPresent) return
|
||||
if (PowerProfiles.profile !== root.desiredProfile) PowerProfiles.profile = root.desiredProfile
|
||||
}
|
||||
|
||||
onDesiredProfileChanged: root.applyDesiredProfile()
|
||||
Component.onCompleted: Qt.callLater(root.applyDesiredProfile)
|
||||
|
||||
Text {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
@@ -217,33 +424,16 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
component StatRow: RowLayout {
|
||||
id: statRow
|
||||
width: parent.width
|
||||
property string label: ""
|
||||
property string value: ""
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: statRow.label
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
Text {
|
||||
text: statRow.value
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
}
|
||||
|
||||
// --- Panel -----------------------------------------------------------
|
||||
|
||||
PopupWindow {
|
||||
id: panel
|
||||
visible: false
|
||||
color: "transparent"
|
||||
readonly property int contentWidth: 244
|
||||
// Wider than before (244) to give the 2-column info grid's
|
||||
// label/value pairs enough room per half without eliding —
|
||||
// same reasoning as NetworkIndicator.qml's own wider panel.
|
||||
readonly property int contentWidth: 280
|
||||
readonly property int contentPadding: 16
|
||||
implicitWidth: contentWidth + contentPadding * 2
|
||||
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
|
||||
@@ -350,34 +540,27 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Stats
|
||||
RowLayout {
|
||||
// Stats — a 2-column, 2-row info grid, same layout
|
||||
// NetworkIndicator.qml uses for its own live stats: a
|
||||
// 4-column GridLayout fed a flattened label/value/label/value
|
||||
// list, so each row holds two label/value pairs side by side
|
||||
// with the values right-aligned against their own half.
|
||||
GridLayout {
|
||||
width: parent.width
|
||||
spacing: 16
|
||||
columns: 4
|
||||
columnSpacing: 12
|
||||
rowSpacing: 5
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
StatRow { label: "STATE"; value: root.modeLabel }
|
||||
StatRow {
|
||||
label: "HEALTH"
|
||||
value: (root.batteryPresent && root.device.healthSupported)
|
||||
? Math.round(root.device.healthPercentage) + "%" : "—"
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Layout.fillWidth: true
|
||||
spacing: 4
|
||||
StatRow {
|
||||
label: root.onBattery ? "TIME LEFT" : "TIME TO FULL"
|
||||
value: root.batteryPresent
|
||||
? root.formatDuration(root.onBattery ? root.device.timeToEmpty : root.device.timeToFull)
|
||||
: "—"
|
||||
}
|
||||
StatRow {
|
||||
label: "RATE"
|
||||
value: root.batteryPresent ? Math.abs(root.device.changeRate).toFixed(1) + " W" : "—"
|
||||
Repeater {
|
||||
model: root.statCells
|
||||
Text {
|
||||
required property var modelData
|
||||
Layout.fillWidth: !modelData.isLabel
|
||||
horizontalAlignment: modelData.isLabel ? Text.AlignLeft : Text.AlignRight
|
||||
text: modelData.text
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: modelData.isLabel ? Colors.muted : Colors.foreground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
470
dotfiles/.config/quickshell/modules/bar/MediaPlayer.qml
Normal file
470
dotfiles/.config/quickshell/modules/bar/MediaPlayer.qml
Normal file
@@ -0,0 +1,470 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Services.Mpris
|
||||
import "../../config"
|
||||
|
||||
// Now-playing label. A playing player always wins; otherwise the label
|
||||
// sticks to whichever player was last playing, so pausing keeps the track
|
||||
// in the bar (dimmed). Hidden once that player stops or quits.
|
||||
//
|
||||
// Left-click opens a player panel (art, seek bar, transport, shuffle/repeat);
|
||||
// middle-click toggles play/pause; right-click skips to the next track.
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: row.implicitWidth
|
||||
implicitHeight: row.implicitHeight
|
||||
|
||||
// The bar's own PanelWindow, passed in from Bar.qml — the panel anchors
|
||||
// to the bar so it sits flush under it rather than under this label's
|
||||
// (shorter) text box.
|
||||
required property var barWindow
|
||||
// Set by Bar.qml on narrow screens — shortens the label.
|
||||
property bool compact: false
|
||||
|
||||
// Last player seen playing. Becomes null by itself if that player's
|
||||
// process exits and Quickshell destroys the object.
|
||||
property var lastPlayer: null
|
||||
|
||||
readonly property var player: {
|
||||
const players = Mpris.players.values
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
if (players[i].isPlaying)
|
||||
return players[i]
|
||||
}
|
||||
if (lastPlayer && lastPlayer.playbackState === MprisPlaybackState.Paused)
|
||||
return lastPlayer
|
||||
// Nothing has played since the shell started (e.g. right after a
|
||||
// reload) — fall back to any paused player that has a track loaded.
|
||||
for (let i = 0; i < players.length; i++) {
|
||||
if (players[i].playbackState === MprisPlaybackState.Paused && players[i].trackTitle)
|
||||
return players[i]
|
||||
}
|
||||
return null
|
||||
}
|
||||
readonly property bool paused: player !== null && !player.isPlaying
|
||||
|
||||
// --- Per-source bar icon ---
|
||||
// First match wins. `app` is tested against the player's identity +
|
||||
// desktop entry; `url` against the track's URL, which is how a browser
|
||||
// tab gets told apart (YouTube vs Twitch...) since the player itself is
|
||||
// just "Firefox"/"Zen" either way. `glyph` is a Nerd Font code point —
|
||||
// look new ones up at https://www.nerdfonts.com/cheat-sheet.
|
||||
readonly property var sources: [
|
||||
{ name: "YouTube", url: /youtube\.com|youtu\.be/i, glyph: 0xf16a, color: "#ff0033" },
|
||||
{ name: "Twitch", url: /twitch\.tv/i, glyph: 0xf0543, color: "#9146ff" },
|
||||
{ name: "SoundCloud", url: /soundcloud\.com/i, glyph: 0xf1be, color: "#ff5500" },
|
||||
{ name: "Spotify", app: /spotify/i, url: /open\.spotify\.com/i, glyph: 0xf1bc, color: "#1db954" },
|
||||
{ name: "Plexamp", app: /plex/i, glyph: 0xf06ba, color: "#e5a00d" },
|
||||
{ name: "VLC", app: /vlc/i, glyph: 0xf057c, color: "#ff8800" }
|
||||
]
|
||||
// Anything unmatched: a plain music note in the theme colour.
|
||||
readonly property var defaultSource: ({ name: "Audio", glyph: 0xf075a, color: Colors.primary })
|
||||
|
||||
readonly property var source: {
|
||||
if (!player)
|
||||
return defaultSource
|
||||
const app = [player.identity, player.desktopEntry].join(" ")
|
||||
const url = String((player.metadata && player.metadata["xesam:url"]) || "")
|
||||
return sources.find(s => (s.app && s.app.test(app)) || (s.url && s.url.test(url))) || defaultSource
|
||||
}
|
||||
|
||||
onPlayerChanged: {
|
||||
if (player && player.isPlaying)
|
||||
lastPlayer = player
|
||||
if (!player)
|
||||
panel.visible = false
|
||||
}
|
||||
|
||||
visible: player !== null
|
||||
|
||||
function formatTime(seconds) {
|
||||
if (!(seconds > 0))
|
||||
return "0:00"
|
||||
const s = Math.floor(seconds)
|
||||
const m = Math.floor(s / 60)
|
||||
const h = Math.floor(m / 60)
|
||||
const ss = String(s % 60).padStart(2, "0")
|
||||
return h > 0 ? h + ":" + String(m % 60).padStart(2, "0") + ":" + ss : m + ":" + ss
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
spacing: 6
|
||||
|
||||
// Source logo in its brand colour; greyed out while paused.
|
||||
Text {
|
||||
font.family: Appearance.brandIconFontFamily
|
||||
font.pixelSize: 14
|
||||
color: root.paused ? Colors.muted : root.source.color
|
||||
text: String.fromCodePoint(root.source.glyph)
|
||||
}
|
||||
|
||||
// CJK characters pull in a fallback font with a taller line box
|
||||
// (16px vs 13px, baseline 2.5px lower), and centring that taller box
|
||||
// dropped Japanese titles ~2px below ActiveWindow's text. So the label
|
||||
// sits in a box sized from the primary font alone, positioned so its
|
||||
// baseline lands where a plain-Latin label's would.
|
||||
FontMetrics { id: labelMetrics; font: label.font }
|
||||
|
||||
Item {
|
||||
implicitWidth: label.implicitWidth
|
||||
implicitHeight: Math.ceil(labelMetrics.height)
|
||||
|
||||
Text {
|
||||
id: label
|
||||
y: labelMetrics.ascent - baselineOffset
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: root.paused ? Colors.muted : Colors.foreground
|
||||
text: {
|
||||
if (!root.player)
|
||||
return ""
|
||||
const title = root.player.trackTitle || root.player.identity
|
||||
const artist = root.player.trackArtist
|
||||
return Appearance.truncate(artist ? artist + " — " + title : title, root.compact ? Appearance.mediaMaxCharsCompact : Appearance.mediaMaxChars)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (!root.player)
|
||||
return
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
// Line the panel's left edge up with this label. Read at
|
||||
// click time since the label's x shifts with the workspace
|
||||
// count and isn't something the anchor can bind to.
|
||||
panel.anchor.rect.x = root.mapToItem(null, 0, 0).x
|
||||
panel.visible = !panel.visible
|
||||
grab.active = panel.visible
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (root.player.canTogglePlaying)
|
||||
root.player.togglePlaying()
|
||||
} else if (root.player.canGoNext) {
|
||||
root.player.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MPRIS doesn't push position updates while playing (only on seeks and
|
||||
// state changes), so poll it — only while the panel is actually showing.
|
||||
Timer {
|
||||
running: panel.visible && root.player !== null && root.player.isPlaying
|
||||
interval: 1000
|
||||
repeat: true
|
||||
onTriggered: root.player.positionChanged()
|
||||
}
|
||||
|
||||
// --- Reusable pieces -----------------------------------------------
|
||||
|
||||
// Same look as Volume.qml's VolumeSlider. While dragging it shows the
|
||||
// drag position rather than `value`, so a seek bar doesn't fight the
|
||||
// player's own position updates mid-drag; `committed` fires on release.
|
||||
component MediaSlider: Item {
|
||||
id: sliderRoot
|
||||
property real value: 0
|
||||
property bool enabled: true
|
||||
property real dragValue: -1
|
||||
readonly property real shownValue: dragValue >= 0 ? dragValue : value
|
||||
signal moved(real newValue)
|
||||
signal committed(real newValue)
|
||||
implicitHeight: 16
|
||||
|
||||
Rectangle {
|
||||
id: track
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 4
|
||||
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.shownValue))
|
||||
height: 4
|
||||
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.shownValue - width / 2))
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: sliderRoot.enabled
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
function valueAt(mx) {
|
||||
return Math.max(0, Math.min(1, mx / track.width))
|
||||
}
|
||||
onPressed: (mouse) => {
|
||||
sliderRoot.dragValue = valueAt(mouse.x)
|
||||
sliderRoot.moved(sliderRoot.dragValue)
|
||||
}
|
||||
onPositionChanged: (mouse) => {
|
||||
if (!pressed)
|
||||
return
|
||||
sliderRoot.dragValue = valueAt(mouse.x)
|
||||
sliderRoot.moved(sliderRoot.dragValue)
|
||||
}
|
||||
onReleased: {
|
||||
sliderRoot.committed(sliderRoot.dragValue)
|
||||
sliderRoot.dragValue = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component IconButton: Item {
|
||||
id: btn
|
||||
property string icon: ""
|
||||
property int size: 22
|
||||
property bool enabled: true
|
||||
// Filled = the big primary-coloured play/pause button.
|
||||
property bool filled: false
|
||||
// Active = a toggle that's switched on (shuffle/repeat), tinted primary.
|
||||
property bool active: false
|
||||
signal clicked()
|
||||
implicitWidth: size + 16
|
||||
implicitHeight: size + 16
|
||||
opacity: enabled ? 1 : 0.35
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: btn.filled ? width / 2 : Appearance.radiusSmall
|
||||
color: btn.filled ? Colors.primary
|
||||
: btnMouse.containsMouse ? Qt.rgba(Colors.foreground.r, Colors.foreground.g, Colors.foreground.b, 0.12)
|
||||
: "transparent"
|
||||
Behavior on color { ColorAnimation { duration: Appearance.animationDuration } }
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: btn.icon
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
// Filled glyphs read better than outlines at transport-button size.
|
||||
font.variableAxes: ({ "FILL": 1, "wght": 400, "GRAD": 0, "opsz": 24 })
|
||||
font.pixelSize: btn.size
|
||||
color: btn.filled ? Colors.primaryText : btn.active ? Colors.primary : Colors.foreground
|
||||
}
|
||||
MouseArea {
|
||||
id: btnMouse
|
||||
anchors.fill: parent
|
||||
enabled: btn.enabled
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: btn.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
// --- Panel -----------------------------------------------------------
|
||||
|
||||
PopupWindow {
|
||||
id: panel
|
||||
visible: false
|
||||
color: "transparent"
|
||||
readonly property int artSize: 160
|
||||
readonly property int contentWidth: 470
|
||||
readonly property int contentPadding: 16
|
||||
implicitWidth: contentWidth + contentPadding * 2
|
||||
implicitHeight: Math.max(artSize, infoColumn.implicitHeight) + contentPadding * 2
|
||||
|
||||
anchor {
|
||||
window: root.barWindow
|
||||
// rect.x is set on click (see the MouseArea above).
|
||||
rect.y: 0
|
||||
rect.width: 1
|
||||
rect.height: root.barWindow.height
|
||||
edges: Edges.Bottom | Edges.Left
|
||||
gravity: Edges.Bottom | Edges.Right
|
||||
margins.bottom: -4
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
id: grab
|
||||
windows: [panel]
|
||||
onCleared: panel.visible = false
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.surface
|
||||
border.color: Colors.muted
|
||||
border.width: 3
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: panel.contentPadding
|
||||
spacing: 16
|
||||
|
||||
// Album art, or a placeholder note when the player has none.
|
||||
Rectangle {
|
||||
Layout.preferredWidth: panel.artSize
|
||||
Layout.preferredHeight: panel.artSize
|
||||
Layout.alignment: Qt.AlignTop
|
||||
color: Colors.background
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
visible: art.status !== Image.Ready
|
||||
text: "music_note"
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 56
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
Image {
|
||||
id: art
|
||||
anchors.fill: parent
|
||||
source: root.player ? root.player.trackArtUrl : ""
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize.width: panel.artSize * 2
|
||||
sourceSize.height: panel.artSize * 2
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: infoColumn
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
spacing: 8
|
||||
|
||||
// Track info
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
visible: text !== ""
|
||||
text: root.player ? root.player.trackArtist : ""
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSize
|
||||
color: Colors.foreground
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
text: root.player ? (root.player.trackTitle || root.player.identity) : ""
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: 15
|
||||
font.bold: true
|
||||
color: Colors.foreground
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
elide: Text.ElideRight
|
||||
visible: text !== ""
|
||||
text: root.player ? root.player.trackAlbum : ""
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
}
|
||||
|
||||
// Seek bar
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 4
|
||||
spacing: 8
|
||||
visible: root.player !== null && root.player.lengthSupported && root.player.length > 0
|
||||
|
||||
Text {
|
||||
Layout.preferredWidth: 36
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: root.player ? root.formatTime(seekSlider.dragValue >= 0
|
||||
? seekSlider.dragValue * root.player.length : root.player.position) : ""
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
MediaSlider {
|
||||
id: seekSlider
|
||||
Layout.fillWidth: true
|
||||
enabled: root.player !== null && root.player.canSeek && root.player.positionSupported
|
||||
value: root.player && root.player.length > 0 ? root.player.position / root.player.length : 0
|
||||
onCommitted: (v) => { if (root.player) root.player.position = v * root.player.length }
|
||||
}
|
||||
Text {
|
||||
Layout.preferredWidth: 36
|
||||
text: root.player ? root.formatTime(root.player.length) : ""
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
}
|
||||
|
||||
// Transport
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
spacing: 4
|
||||
|
||||
IconButton {
|
||||
icon: "shuffle"
|
||||
size: 18
|
||||
visible: root.player !== null && root.player.shuffleSupported
|
||||
active: root.player !== null && root.player.shuffle
|
||||
onClicked: root.player.shuffle = !root.player.shuffle
|
||||
}
|
||||
IconButton {
|
||||
icon: "replay_10"
|
||||
size: 18
|
||||
visible: root.player !== null && root.player.canSeek
|
||||
onClicked: root.player.seek(-10)
|
||||
}
|
||||
IconButton {
|
||||
icon: "skip_previous"
|
||||
enabled: root.player !== null && root.player.canGoPrevious
|
||||
onClicked: root.player.previous()
|
||||
}
|
||||
IconButton {
|
||||
icon: root.paused ? "play_arrow" : "pause"
|
||||
size: 26
|
||||
filled: true
|
||||
enabled: root.player !== null && root.player.canTogglePlaying
|
||||
onClicked: root.player.togglePlaying()
|
||||
}
|
||||
IconButton {
|
||||
icon: "skip_next"
|
||||
enabled: root.player !== null && root.player.canGoNext
|
||||
onClicked: root.player.next()
|
||||
}
|
||||
IconButton {
|
||||
icon: "forward_10"
|
||||
size: 18
|
||||
visible: root.player !== null && root.player.canSeek
|
||||
onClicked: root.player.seek(10)
|
||||
}
|
||||
// Cycles off -> repeat all -> repeat one -> off.
|
||||
IconButton {
|
||||
icon: root.player && root.player.loopState === MprisLoopState.Track ? "repeat_one" : "repeat"
|
||||
size: 18
|
||||
visible: root.player !== null && root.player.loopSupported
|
||||
active: root.player !== null && root.player.loopState !== MprisLoopState.None
|
||||
onClicked: root.player.loopState =
|
||||
root.player.loopState === MprisLoopState.None ? MprisLoopState.Playlist
|
||||
: root.player.loopState === MprisLoopState.Playlist ? MprisLoopState.Track
|
||||
: MprisLoopState.None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +222,82 @@ Item {
|
||||
root.passwordSsid = ""
|
||||
}
|
||||
|
||||
// --- Captive portal ---------------------------------------------------
|
||||
// NetworkManager tells "portal" (a walled-garden sign-in page sits
|
||||
// between us and the real internet) apart from "full" connectivity only
|
||||
// via an active probe — nmcli's cached CONNECTIVITY field just reflects
|
||||
// NM's own internal check, which runs every few minutes and is far too
|
||||
// slow to catch a portal right after joining a network. So this forces a
|
||||
// fresh probe itself, same shell-out approach as the stats below, and
|
||||
// runs it whenever we're connected — not gated to panel.visible like
|
||||
// those — since the whole point is noticing this before the popup is
|
||||
// ever opened.
|
||||
//
|
||||
// "limited" is treated the same as "portal", not just "full" vs.
|
||||
// everything-else: NM only reports "portal" when the probe request
|
||||
// actually comes back *different* (redirected/rewritten) from what it
|
||||
// expected. Plenty of real captive portals instead black-hole every
|
||||
// request that isn't to their own login domain, so the probe just times
|
||||
// out — which is also why this is slow to resolve — and NM reports
|
||||
// "limited" (it got a route/gateway, but the internet check itself
|
||||
// never landed) rather than "portal". From the user's side both look
|
||||
// identical: connected, but no real internet.
|
||||
property string connectivityState: "unknown"
|
||||
readonly property bool connectivityRestricted: root.connectivityState === "portal" || root.connectivityState === "limited"
|
||||
|
||||
// A plain, unencrypted HTTP page whose only job is to be interceptable
|
||||
// by a captive portal's redirect. An https:// URL mostly just gets
|
||||
// silently blocked instead of redirected, which looks identical to the
|
||||
// portal not existing at all.
|
||||
readonly property string captivePortalCheckUrl: "http://neverssl.com/"
|
||||
|
||||
// Auto-opened once per network join (mirrors TailscaleIndicator's
|
||||
// openAuthUrl/_authUrlOpened) so flapping connectivity on the same
|
||||
// network doesn't keep popping a new browser tab — the manual "Open"
|
||||
// button in the panel banner below still works regardless.
|
||||
property bool _portalAutoOpened: false
|
||||
property bool _wasConnected: false
|
||||
onConnectedDeviceChanged: {
|
||||
const isConnected = root.connectedDevice !== null
|
||||
if (isConnected && !root._wasConnected) {
|
||||
root._portalAutoOpened = false
|
||||
root.connectivityState = "unknown"
|
||||
}
|
||||
root._wasConnected = isConnected
|
||||
}
|
||||
|
||||
onConnectivityRestrictedChanged: {
|
||||
if (root.connectivityRestricted && !root._portalAutoOpened)
|
||||
root.openCaptivePortal()
|
||||
}
|
||||
|
||||
function openCaptivePortal() {
|
||||
root._portalAutoOpened = true
|
||||
Qt.openUrlExternally(root.captivePortalCheckUrl)
|
||||
}
|
||||
|
||||
Process {
|
||||
id: connectivityProc
|
||||
command: ["nmcli", "networking", "connectivity", "check"]
|
||||
stdout: StdioCollector {
|
||||
id: connectivityCollector
|
||||
onStreamFinished: root.connectivityState = connectivityCollector.text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: connectivityTimer
|
||||
// Poll quickly while things are still unresolved (fresh join, or an
|
||||
// active portal we're waiting to clear) and back off once
|
||||
// connectivity is confirmed full, rather than shelling out every
|
||||
// few seconds forever for no reason.
|
||||
interval: root.connectivityState === "full" ? 20000 : 3000
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
running: root.connectedDevice !== null
|
||||
onTriggered: if (!connectivityProc.running) connectivityProc.running = true
|
||||
}
|
||||
|
||||
// --- Live stats --------------------------------------------------
|
||||
// None of this comes from Quickshell's Networking API — it only knows
|
||||
// about devices/networks, not throughput, ping, or routing. So this
|
||||
@@ -499,6 +575,51 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Public (WAN-side) IP — nothing local knows this, the address on the
|
||||
// interface is the LAN one behind NAT, so it has to come from an
|
||||
// outside service. Polled much more slowly than the local stats since
|
||||
// it's a real HTTP request and rarely changes.
|
||||
property string publicIp: "—"
|
||||
|
||||
Process {
|
||||
id: publicIpProc
|
||||
command: ["curl", "-s", "--max-time", "5", "https://api.ipify.org"]
|
||||
stdout: StdioCollector {
|
||||
id: publicIpCollector
|
||||
onStreamFinished: {
|
||||
const ip = publicIpCollector.text.trim()
|
||||
root.publicIp = /^[\d.]+$|^[0-9a-fA-F:]+$/.test(ip) ? ip : "—"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Switching networks almost always changes the public IP, so don't
|
||||
// leave the old one up until the next slow poll.
|
||||
onConnectedNetworkNameChanged: {
|
||||
root.publicIp = "—"
|
||||
if (panel.visible && root.connectedDevice !== null && !publicIpProc.running)
|
||||
publicIpProc.running = true
|
||||
}
|
||||
|
||||
// Same for a VPN coming up or going down — the public IP is then the
|
||||
// tunnel's exit (or no longer is).
|
||||
Connections {
|
||||
target: VpnExclusivity
|
||||
function onActiveVpnLabelChanged() {
|
||||
root.publicIp = "—"
|
||||
if (panel.visible && root.connectedDevice !== null && !publicIpProc.running)
|
||||
publicIpProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 60000
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
running: panel.visible && root.connectedDevice !== null
|
||||
onTriggered: if (!publicIpProc.running) publicIpProc.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: bytesProc
|
||||
command: root.ifaceName
|
||||
@@ -595,6 +716,34 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Small warning badge over the icon's corner when the connection looks
|
||||
// restricted (a captive portal, confirmed or just suspected — see
|
||||
// connectivityRestricted above) — same treatment as TailscaleIndicator's
|
||||
// login-needed badge, so "this needs your attention" reads the same way
|
||||
// across indicators.
|
||||
Rectangle {
|
||||
visible: root.connectivityRestricted
|
||||
width: Math.max(7, label.font.pixelSize * 0.42)
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: Colors.urgent
|
||||
border.color: Colors.surface
|
||||
border.width: 1
|
||||
anchors.right: label.right
|
||||
anchors.bottom: label.bottom
|
||||
anchors.rightMargin: -1
|
||||
anchors.bottomMargin: -1
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "!"
|
||||
color: Colors.surface
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Math.max(6, parent.height * 0.75)
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
@@ -890,6 +1039,133 @@ Item {
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
visible: root.connectedDevice !== null
|
||||
|
||||
Text {
|
||||
text: "Public IP:"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.publicIp
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.foreground
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
// Which VPN (if any) the public IP above is coming through.
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
visible: root.connectedDevice !== null && VpnExclusivity.activeVpnLabel !== ""
|
||||
|
||||
Text {
|
||||
text: "VPN:"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width - x
|
||||
text: VpnExclusivity.activeVpnLabel
|
||||
elide: Text.ElideRight
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.foreground
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
|
||||
// Captive portal banner — shown when the connectivity probe
|
||||
// above finds either a confirmed sign-in page or a
|
||||
// "limited" result standing between this network and the
|
||||
// real internet (see connectivityRestricted). openCaptivePortal()
|
||||
// already fired once automatically when this was first
|
||||
// detected; this stays up as a manual retry in case that tab
|
||||
// got closed.
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
visible: root.connectivityRestricted
|
||||
radius: 0
|
||||
color: Qt.rgba(Colors.urgent.r, Colors.urgent.g, Colors.urgent.b, 0.18)
|
||||
border.color: Colors.urgent
|
||||
border.width: 1
|
||||
implicitHeight: portalBannerRow.implicitHeight + 12
|
||||
|
||||
RowLayout {
|
||||
id: portalBannerRow
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
text: "lock_open"
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 16
|
||||
color: Colors.urgent
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: root.connectivityState === "portal" ? "Sign-in required" : "No internet access"
|
||||
elide: Text.ElideRight
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSize
|
||||
font.bold: true
|
||||
color: Colors.foreground
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: root.connectivityState === "portal"
|
||||
? "This network needs a web sign-in first"
|
||||
: "This network may need a web sign-in first"
|
||||
elide: Text.ElideRight
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
implicitWidth: openPortalText.implicitWidth + 16
|
||||
implicitHeight: 24
|
||||
radius: 0
|
||||
color: openPortalMouse.containsMouse ? Colors.urgent : "transparent"
|
||||
border.color: Colors.urgent
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
id: openPortalText
|
||||
anchors.centerIn: parent
|
||||
text: "Open"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: openPortalMouse.containsMouse ? Colors.primaryText : Colors.urgent
|
||||
}
|
||||
MouseArea {
|
||||
id: openPortalMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.openCaptivePortal()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Live stats — ping/loss/throughput/IP/gateway for whichever
|
||||
// device is currently connected.
|
||||
GridLayout {
|
||||
|
||||
110
dotfiles/.config/quickshell/modules/bar/PinnedTray.qml
Normal file
110
dotfiles/.config/quickshell/modules/bar/PinnedTray.qml
Normal file
@@ -0,0 +1,110 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import "../../config"
|
||||
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
spacing: Appearance.spacing * 1.
|
||||
|
||||
|
||||
readonly property var pinned: [
|
||||
{
|
||||
name: "Bitwarden",
|
||||
icon: Qt.resolvedUrl("../../assets/bitwarden.png"),
|
||||
binary: "bitwarden",
|
||||
launch: ["bitwarden"],
|
||||
secondary: ["pkill", "bitwarden"]
|
||||
},
|
||||
{
|
||||
name: "Seafile",
|
||||
icon: "seafile",
|
||||
binary: "seafile-applet",
|
||||
launch: ["seafile-applet"],
|
||||
secondary: ["pkill", "seafile-applet"]
|
||||
},
|
||||
{
|
||||
name: "Sunshine",
|
||||
icon: Qt.resolvedUrl("../../assets/sunshine.svg"),
|
||||
binary: "sunshine",
|
||||
launch: ["systemctl", "--user", "start", "app-dev.lizardbyte.app.Sunshine.service"],
|
||||
secondary: ["xdg-open", "https://xol.twhomelab.tailnet:47990"],
|
||||
},
|
||||
]
|
||||
|
||||
function findTrayItem(name) {
|
||||
const needle = name.toLowerCase()
|
||||
for (const item of SystemTray.items.values) {
|
||||
if (item.id.toLowerCase().includes(needle) || item.title.toLowerCase().includes(needle))
|
||||
return item
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.pinned
|
||||
|
||||
delegate: Item {
|
||||
id: slot
|
||||
required property var modelData
|
||||
readonly property var trayItem: root.findTrayItem(modelData.name)
|
||||
readonly property bool running: trayItem !== null
|
||||
property bool installed: false // Application installation check
|
||||
|
||||
visible: slot.installed
|
||||
implicitWidth: Appearance.iconSize
|
||||
implicitHeight: Appearance.iconSize
|
||||
|
||||
Process {
|
||||
running: true
|
||||
command: ["which", slot.modelData.binary]
|
||||
onExited: (exitCode) => slot.installed = exitCode === 0
|
||||
}
|
||||
|
||||
IconImage {
|
||||
anchors.fill: parent
|
||||
// modelData.icon is a QUrl when it comes from
|
||||
// Qt.resolvedUrl() (Bitwarden/Sunshine's local assets), not
|
||||
// a QString — coerce to string before using String methods
|
||||
// on it, or this throws instead of ever matching.
|
||||
source: slot.running
|
||||
? slot.trayItem.icon
|
||||
: (String(slot.modelData.icon).includes("://")
|
||||
? slot.modelData.icon
|
||||
: Quickshell.iconPath(slot.modelData.icon))
|
||||
opacity: slot.running ? 1.0 : 0.5
|
||||
}
|
||||
|
||||
Process { id: launcher }
|
||||
Process { id: secondaryProc }
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -4
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (slot.modelData.secondary) {
|
||||
if (!secondaryProc.running) {
|
||||
secondaryProc.command = slot.modelData.secondary
|
||||
secondaryProc.running = true
|
||||
}
|
||||
} else if (slot.running) {
|
||||
slot.trayItem.secondaryActivate()
|
||||
}
|
||||
} else if (slot.running) {
|
||||
slot.trayItem.activate()
|
||||
} else if (!launcher.running) {
|
||||
launcher.command = slot.modelData.launch
|
||||
launcher.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ Item {
|
||||
{ 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: "Log out", icon: "logout", cmd: ["hyprctl", "dispatch", "hl.dsp.exit()"] },
|
||||
{ label: "Reboot", icon: "restart_alt", cmd: ["systemctl", "reboot"] },
|
||||
{ label: "Shut down", icon: "power_settings_new", cmd: ["systemctl", "poweroff"] }
|
||||
]
|
||||
|
||||
194
dotfiles/.config/quickshell/modules/bar/SystemUpdate.qml
Normal file
194
dotfiles/.config/quickshell/modules/bar/SystemUpdate.qml
Normal file
@@ -0,0 +1,194 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import "../../config"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: row.implicitWidth + 6
|
||||
implicitHeight: Appearance.barHeight
|
||||
|
||||
|
||||
required property var barWindow
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
id: label
|
||||
font.family: SystemUpdates.checking ? Appearance.iconFontFamilyRounded : Appearance.fontFamily
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: SystemUpdates.checking ? 15 : 22
|
||||
color: Colors.foreground
|
||||
text: SystemUpdates.checking ? "progress_activity" : ""
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: SystemUpdates.checking
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 900
|
||||
onRunningChanged: if (!running) label.rotation = 0
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: label.color
|
||||
text: SystemUpdates.updateCount
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
SystemUpdates.refresh()
|
||||
} else {
|
||||
panel.visible = !panel.visible
|
||||
grab.active = panel.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PopupWindow {
|
||||
id: panel
|
||||
visible: false
|
||||
color: "transparent"
|
||||
readonly property int contentWidth: 200
|
||||
readonly property int contentPadding: 16
|
||||
implicitWidth: contentWidth + contentPadding * 2
|
||||
implicitHeight: mainColumn.implicitHeight + contentPadding * 2
|
||||
|
||||
anchor {
|
||||
window: root.barWindow
|
||||
rect.x: 0
|
||||
rect.y: 0
|
||||
rect.width: root.barWindow.width
|
||||
rect.height: root.barWindow.height
|
||||
edges: Edges.Bottom
|
||||
gravity: Edges.Bottom
|
||||
margins.bottom: -4
|
||||
}
|
||||
|
||||
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: label.text
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: 23
|
||||
color: Colors.foreground
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: "System Update"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Colors.foreground
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSize
|
||||
color: Colors.foreground
|
||||
text: {
|
||||
if (SystemUpdates.updating) return "Update running in a terminal…"
|
||||
if (SystemUpdates.checking) return "Checking for updates…"
|
||||
if (SystemUpdates.updateCount === 0) return "System is up to date."
|
||||
return SystemUpdates.updateCount + (SystemUpdates.updateCount === 1 ? " update available." : " updates available.")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: updateBtn
|
||||
width: parent.width
|
||||
radius: 0
|
||||
implicitHeight: btnContent.implicitHeight + 12
|
||||
enabled: !SystemUpdates.updating
|
||||
opacity: SystemUpdates.updating ? 0.5 : 1
|
||||
color: btnMouse.containsMouse ? Colors.primary : "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -3
|
||||
radius: 0
|
||||
color: "transparent"
|
||||
border.color: Colors.foreground
|
||||
border.width: btnMouse.containsMouse ? 1 : 0
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: btnContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: 6
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
Layout.preferredWidth: 20
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
text: "download"
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 14
|
||||
color: btnMouse.containsMouse ? Colors.primaryText : Colors.foreground
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: "Update Now"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSize
|
||||
color: btnMouse.containsMouse ? Colors.primaryText : Colors.foreground
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btnMouse
|
||||
anchors.fill: parent
|
||||
enabled: !SystemUpdates.updating
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
SystemUpdates.runUpdate(Quickshell.shellPath("scripts/update-system.sh"))
|
||||
panel.visible = false
|
||||
grab.active = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,11 +52,15 @@ Item {
|
||||
property bool _loginPending: false
|
||||
property bool _authUrlOpened: false
|
||||
|
||||
// Set when the VPN-exclusivity guard (below) forces Tailscale down —
|
||||
// as opposed to you turning it off yourself — so it knows to bring
|
||||
// Tailscale back up automatically once Mullvad/ProtonVPN disconnect,
|
||||
// without overriding a deliberate manual "off".
|
||||
property bool _forcedDownByVpn: false
|
||||
// Tailscale is the fallback whenever none of the VPN providers is
|
||||
// connected — see maybeAutoConnect(). _manualOff is set when you switch
|
||||
// it off yourself with no provider connected, so that sticks; it's
|
||||
// cleared once a provider connects, so Tailscale still comes back after
|
||||
// that one disconnects. _autoConnectTried allows one automatic `up` per
|
||||
// stretch of no provider being connected, so a failing one doesn't
|
||||
// retry (and re-report its failure) on every poll.
|
||||
property bool _manualOff: false
|
||||
property bool _autoConnectTried: false
|
||||
|
||||
// Other logged-in Tailscale accounts on this machine (`tailscale switch
|
||||
// --list`) — separate tailnets/identities you can flip between, not
|
||||
@@ -147,6 +151,8 @@ Item {
|
||||
function applyStatus(text) {
|
||||
const next = root.parseTailscaleStatus(text)
|
||||
root.status = next
|
||||
VpnExclusivity.tailscaleConnected = next.running
|
||||
VpnExclusivity.tailscaleExitNode = next.running && next.activeExitNode ? next.activeExitNode.name : ""
|
||||
if (root._desired !== -1 && next.running === (root._desired === 1)) root._desired = -1
|
||||
if (next.running) {
|
||||
root._loginPending = false
|
||||
@@ -159,7 +165,6 @@ Item {
|
||||
if (VpnExclusivity.commercialVpnActive && !root.busy) {
|
||||
root.busy = true
|
||||
root._desired = 0
|
||||
root._forcedDownByVpn = true
|
||||
root.actionStatus = "Tailscale stopped — a VPN provider is active"
|
||||
actionStatusTimer.restart()
|
||||
actionProc.command = ["tailscale", "down"]
|
||||
@@ -168,6 +173,22 @@ Item {
|
||||
} else if (next.needsLogin && next.authUrl !== "" && !root._authUrlOpened) {
|
||||
root.openAuthUrl(next.authUrl)
|
||||
}
|
||||
root.maybeAutoConnect()
|
||||
}
|
||||
|
||||
// Only acts on a confirmed "Stopped": not NeedsLogin (`tailscale up`
|
||||
// would open a login page in the browser unasked), nor a daemon that
|
||||
// isn't answering yet.
|
||||
function shouldAutoConnect() {
|
||||
return VpnExclusivity.noVpnActive && !root._manualOff && !root._autoConnectTried
|
||||
&& root.installed && !root.busy && root.status.backendState === "Stopped"
|
||||
}
|
||||
|
||||
// Waits out autoConnectTimer first rather than connecting right away —
|
||||
// see there.
|
||||
function maybeAutoConnect() {
|
||||
if (!root.shouldAutoConnect()) autoConnectTimer.stop()
|
||||
else if (!autoConnectTimer.running) autoConnectTimer.start()
|
||||
}
|
||||
|
||||
function openAuthUrl(url) {
|
||||
@@ -192,10 +213,10 @@ Item {
|
||||
|
||||
function toggleTailscale() {
|
||||
if (root.busy || !root.installed) return
|
||||
// Any manual action overrides the guard's own memory of why
|
||||
// Tailscale went down — a deliberate "off" here should stick even
|
||||
// once the VPN it deferred to eventually disconnects.
|
||||
root._forcedDownByVpn = false
|
||||
// A deliberate "off" with no VPN connected shouldn't be undone by
|
||||
// auto-connect straight away.
|
||||
root._manualOff = root.active && !VpnExclusivity.commercialVpnActive
|
||||
autoConnectTimer.stop()
|
||||
if (root.active) {
|
||||
root.busy = true
|
||||
root._desired = 0
|
||||
@@ -204,7 +225,7 @@ Item {
|
||||
return
|
||||
}
|
||||
if (VpnExclusivity.commercialVpnActive) {
|
||||
root.actionStatus = "Disconnect Mullvad/ProtonVPN first"
|
||||
root.actionStatus = "Disconnect the active VPN first"
|
||||
actionStatusTimer.restart()
|
||||
return
|
||||
}
|
||||
@@ -325,19 +346,34 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-resumes Tailscale once neither commercial VPN is connected
|
||||
// anymore — but only if it was this guard that took Tailscale down in
|
||||
// the first place, never overriding a manual "off".
|
||||
// A provider connecting starts things afresh for when it next
|
||||
// disconnects: a manual "off" from before no longer applies, and
|
||||
// auto-connect gets a new attempt.
|
||||
Connections {
|
||||
target: VpnExclusivity
|
||||
function onCommercialVpnActiveChanged() {
|
||||
if (!VpnExclusivity.commercialVpnActive && root._forcedDownByVpn && !root.active) {
|
||||
root._forcedDownByVpn = false
|
||||
root.actionStatus = "Resuming Tailscale — VPN disconnected"
|
||||
actionStatusTimer.restart()
|
||||
root.bringTailscaleUp()
|
||||
if (VpnExclusivity.commercialVpnActive) {
|
||||
root._manualOff = false
|
||||
root._autoConnectTried = false
|
||||
}
|
||||
}
|
||||
function onNoVpnActiveChanged() { root.maybeAutoConnect() }
|
||||
}
|
||||
|
||||
// A short grace period before auto-connecting, as a backstop against
|
||||
// momentary gaps vpnTransitioning doesn't cover (e.g. one provider's
|
||||
// disconnect finishing a moment before the next one's connect starts).
|
||||
Timer {
|
||||
id: autoConnectTimer
|
||||
interval: 3000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (!root.shouldAutoConnect()) return
|
||||
root._autoConnectTried = true
|
||||
root.bringTailscaleUp()
|
||||
root.actionStatus = "Connecting Tailscale — no VPN active"
|
||||
actionStatusTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
// Background poll, independent of whether the panel is open, so the bar
|
||||
|
||||
@@ -5,7 +5,7 @@ import Quickshell.Widgets
|
||||
import "../../config"
|
||||
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing * 0.6
|
||||
spacing: Appearance.spacing
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
@@ -137,15 +137,25 @@ Item {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
// Scroll distance is accumulated rather than stepping per event:
|
||||
// a mouse wheel sends one 120-unit event per notch, but a touchpad
|
||||
// sends a stream of tiny events, which at a fixed step per event
|
||||
// made touchpad scrolling far too fast. One notch's worth (120) of
|
||||
// scrolling = one volume step, regardless of input device.
|
||||
property real wheelAccum: 0
|
||||
readonly property real volumeStep: 0.05
|
||||
readonly property real unitsPerStep: 120
|
||||
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))
|
||||
wheelAccum += wheel.angleDelta.y
|
||||
const steps = Math.trunc(wheelAccum / unitsPerStep)
|
||||
if (steps === 0) return
|
||||
wheelAccum -= steps * unitsPerStep
|
||||
root.sink.audio.volume = Math.max(0, Math.min(1, root.sink.audio.volume + steps * volumeStep))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
413
dotfiles/.config/quickshell/modules/bar/Weather.qml
Normal file
413
dotfiles/.config/quickshell/modules/bar/Weather.qml
Normal file
@@ -0,0 +1,413 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import "../../config"
|
||||
|
||||
// Weather indicator — modeled on omarchy's weather bar pill
|
||||
// (github.com/omacom/omarchy, quattro branch,
|
||||
// shell/plugins/panels/weather) but rebuilt in this bar's own style: the
|
||||
// actual location/polling logic lives in the WeatherData singleton (config/),
|
||||
// shared across every monitor's bar; this is just the icon + panel UI,
|
||||
// same split as SystemUpdate.qml.
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: row.implicitWidth + 2
|
||||
implicitHeight: Appearance.barHeight
|
||||
|
||||
// The bar's own PanelWindow — same barWindow-anchoring convention as
|
||||
// every other popup-having module here (Volume.qml, SystemUpdate.qml,
|
||||
// etc.), so the panel opens in a fixed spot rather than following this
|
||||
// icon's exact position in its RowLayout.
|
||||
required property var barWindow
|
||||
|
||||
readonly property bool busy: WeatherData.locating || WeatherData.loading
|
||||
|
||||
// Local to this popup instance — unlike WeatherData's own state, there's
|
||||
// no need to share an in-progress search across monitors' bars.
|
||||
property bool searchOpen: false
|
||||
property string searchQuery: ""
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
id: label
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 18
|
||||
color: Colors.foreground
|
||||
// Same spinner treatment as SystemUpdate.qml/
|
||||
// BluetoothIndicator.qml's scan spinner while the location or
|
||||
// conditions are still loading.
|
||||
text: root.busy ? "progress_activity" : (WeatherData.hasLocation ? WeatherData.iconFor(WeatherData.weatherCode, WeatherData.isDay) : "cloud_off")
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: root.busy
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 900
|
||||
onRunningChanged: if (!running) label.rotation = 0
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: WeatherData.hasLocation && !WeatherData.locating
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.foreground
|
||||
text: Math.round(WeatherData.tempC) + "°"
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
WeatherData.refresh()
|
||||
} else {
|
||||
panel.visible = !panel.visible
|
||||
grab.active = panel.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PopupWindow {
|
||||
id: panel
|
||||
visible: false
|
||||
color: "transparent"
|
||||
// Same contentWidth/contentPadding split as the other panels
|
||||
// (Volume.qml, SystemUpdate.qml, etc.) rather than a single magic
|
||||
// implicitWidth.
|
||||
readonly property int contentWidth: 220
|
||||
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. Bottom alone (no Left/Right)
|
||||
// anchors to the bar's bottom-center, which lines up with this
|
||||
// icon since it sits in the bar's centered RowLayout.
|
||||
rect.x: 0
|
||||
rect.y: 0
|
||||
rect.width: root.barWindow.width
|
||||
rect.height: root.barWindow.height
|
||||
edges: Edges.Bottom
|
||||
gravity: Edges.Bottom
|
||||
margins.bottom: -4
|
||||
}
|
||||
|
||||
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: label.text
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 28
|
||||
color: Colors.foreground
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
text: {
|
||||
if (WeatherData.locating) return "Locating…"
|
||||
if (!WeatherData.hasLocation) return "Location unavailable"
|
||||
return WeatherData.locationName || "Weather"
|
||||
}
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: 18
|
||||
font.bold: true
|
||||
color: Colors.foreground
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: WeatherData.hasLocation && !WeatherData.locating
|
||||
text: WeatherData.loading ? "Updating…" : "Feels like " + Math.round(WeatherData.feelsLikeC) + "°"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: WeatherData.hasLocation && !WeatherData.locating
|
||||
text: Math.round(WeatherData.tempC) + "°"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: 28
|
||||
font.bold: true
|
||||
color: Colors.foreground
|
||||
}
|
||||
|
||||
// Toggles the search box below — same tap target size
|
||||
// and hover feedback as the picker-target rows in
|
||||
// VpnIndicator.qml.
|
||||
Text {
|
||||
text: root.searchOpen ? "close" : "edit_location"
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 16
|
||||
color: editLocationMouse.containsMouse ? Colors.primary : Colors.muted
|
||||
|
||||
MouseArea {
|
||||
id: editLocationMouse
|
||||
anchors.fill: parent
|
||||
anchors.margins: -4
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.searchOpen = !root.searchOpen
|
||||
root.searchQuery = ""
|
||||
WeatherData.searchResults = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// City search — always available regardless of how the
|
||||
// current location was resolved (IP guess or a previous
|
||||
// search), so a wrong auto-detected city is one click away
|
||||
// from being fixed. Same search-box + result-list shape as
|
||||
// VpnIndicator.qml's Mullvad location picker.
|
||||
Column {
|
||||
visible: root.searchOpen
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
implicitHeight: 24
|
||||
radius: 0
|
||||
color: Colors.surface
|
||||
border.color: Colors.muted
|
||||
border.width: 1
|
||||
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
visible: citySearchInput.text.length === 0
|
||||
text: "Search city…"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
TextInput {
|
||||
id: citySearchInput
|
||||
anchors.fill: parent
|
||||
anchors.margins: 6
|
||||
color: Colors.foreground
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
clip: true
|
||||
focus: root.searchOpen
|
||||
text: root.searchQuery
|
||||
onTextChanged: {
|
||||
root.searchQuery = text
|
||||
searchDebounce.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: searchDebounce
|
||||
interval: 400
|
||||
onTriggered: WeatherData.search(root.searchQuery)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: WeatherData.searching
|
||||
text: "Searching…"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !WeatherData.searching && citySearchInput.text.length >= 2 && WeatherData.searchResults.length === 0
|
||||
text: "No matches"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
Flickable {
|
||||
width: parent.width
|
||||
height: Math.min(resultsColumn.implicitHeight, 150)
|
||||
contentWidth: width
|
||||
contentHeight: resultsColumn.implicitHeight
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
Column {
|
||||
id: resultsColumn
|
||||
width: parent.width
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: WeatherData.searchResults
|
||||
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
width: parent.width
|
||||
implicitHeight: resultText.implicitHeight + 8
|
||||
radius: 0
|
||||
color: resultMouse.containsMouse ? Colors.primary : "transparent"
|
||||
|
||||
Text {
|
||||
id: resultText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: 4
|
||||
elide: Text.ElideRight
|
||||
text: modelData.description ? (modelData.name + " — " + modelData.description) : modelData.name
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: resultMouse.containsMouse ? Colors.primaryText : Colors.foreground
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: resultMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
WeatherData.selectLocation(modelData.name, modelData.latitude, modelData.longitude)
|
||||
root.searchOpen = false
|
||||
root.searchQuery = ""
|
||||
WeatherData.searchResults = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Humidity / wind — same two-stat layout, just laid out as
|
||||
// a row of icon+value pairs rather than full sentences.
|
||||
RowLayout {
|
||||
visible: WeatherData.hasLocation && !WeatherData.locating
|
||||
width: parent.width
|
||||
spacing: 16
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text {
|
||||
text: "water_drop"
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 14
|
||||
color: Colors.muted
|
||||
}
|
||||
Text {
|
||||
text: WeatherData.humidity + "%"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.foreground
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 4
|
||||
Text {
|
||||
text: "air"
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 14
|
||||
color: Colors.muted
|
||||
}
|
||||
Text {
|
||||
text: Math.round(WeatherData.windKmh) + " km/h"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.foreground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: WeatherData.forecast.length > 0
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
visible: WeatherData.forecast.length > 0
|
||||
width: parent.width
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
model: WeatherData.forecast
|
||||
|
||||
delegate: ColumnLayout {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: Qt.formatDateTime(new Date(modelData.date + "T12:00:00"), "ddd")
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.muted
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: WeatherData.iconFor(modelData.code, true)
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 18
|
||||
color: Colors.foreground
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: Math.round(modelData.maxC) + "°/" + Math.round(modelData.minC) + "°"
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
color: Colors.foreground
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,8 @@ RowLayout {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Hyprland.dispatch("workspace " + modelData.name)
|
||||
// Hyprland's Lua config expects Lua syntax from dispatch
|
||||
onClicked: Hyprland.dispatch("hl.dsp.focus({ workspace = " + JSON.stringify(modelData.id > 0 ? modelData.id : modelData.name) + " })")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,13 @@ PanelWindow {
|
||||
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
|
||||
// Bottom-center: anchoring only the bottom edge lets the layer shell
|
||||
// center it horizontally.
|
||||
anchors { bottom: true }
|
||||
margins.bottom: Appearance.spacing * 4
|
||||
|
||||
implicitWidth: 272
|
||||
implicitHeight: 54
|
||||
implicitWidth: 300
|
||||
implicitHeight: 60
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.namespace: "quickshell:osd"
|
||||
@@ -49,13 +46,13 @@ PanelWindow {
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 14
|
||||
spacing: 12
|
||||
anchors.margins: 15
|
||||
spacing: 13
|
||||
|
||||
Text {
|
||||
font.family: Appearance.iconFontFamilyRounded
|
||||
font.variableAxes: Appearance.iconFontAxes
|
||||
font.pixelSize: 18
|
||||
font.pixelSize: 21
|
||||
color: Colors.foreground
|
||||
text: {
|
||||
const audio = osd.sink ? osd.sink.audio : null
|
||||
@@ -67,7 +64,7 @@ PanelWindow {
|
||||
Item {
|
||||
id: slider
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 16
|
||||
implicitHeight: 18
|
||||
|
||||
readonly property real value: osd.sink && osd.sink.audio ? osd.sink.audio.volume : 0
|
||||
readonly property bool enabled: !!osd.sink
|
||||
@@ -76,7 +73,7 @@ PanelWindow {
|
||||
id: track
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 4
|
||||
height: 5
|
||||
radius: 0
|
||||
color: Colors.muted
|
||||
opacity: 0.35
|
||||
@@ -85,15 +82,15 @@ PanelWindow {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
width: track.width * Math.max(0, Math.min(1, slider.value))
|
||||
height: 4
|
||||
height: 5
|
||||
radius: 0
|
||||
color: Colors.primary
|
||||
opacity: slider.enabled ? 1 : 0.4
|
||||
}
|
||||
Rectangle {
|
||||
width: 12
|
||||
height: 12
|
||||
radius: 6
|
||||
width: 14
|
||||
height: 14
|
||||
radius: 7
|
||||
color: Colors.primary
|
||||
opacity: slider.enabled ? 1 : 0.4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -113,10 +110,10 @@ PanelWindow {
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredWidth: 38
|
||||
horizontalAlignment: Text.AlignRight
|
||||
font.family: Appearance.fontFamily
|
||||
font.pixelSize: Appearance.fontSizeSmall
|
||||
font.pixelSize: 13
|
||||
color: Colors.muted
|
||||
text: Math.round(slider.value * 100) + "%"
|
||||
}
|
||||
|
||||
@@ -159,6 +159,21 @@ if [ $have_mullvad -eq 0 ] && [ $have_proton -eq 0 ]; then
|
||||
warn "neither Mullvad nor ProtonVPN CLI is installed — VpnIndicator.qml will show both as 'Not installed'"
|
||||
fi
|
||||
|
||||
# Keep in sync with homeWgConnection in VpnIndicator.qml.
|
||||
if nmcli -g connection.type connection show unifi-home >/dev/null 2>&1; then
|
||||
pass "NetworkManager profile 'unifi-home' exists — used by VpnIndicator.qml (Home/UniFi row)"
|
||||
else
|
||||
warn "no NetworkManager profile 'unifi-home' — import one from the VPN panel's 'Add config' button (optional)"
|
||||
fi
|
||||
|
||||
# The 'Add config' button's file chooser (scripts/PickFile.py) talks to
|
||||
# xdg-desktop-portal over D-Bus via PyGObject.
|
||||
if python3 -c "from gi.repository import Gio" >/dev/null 2>&1; then
|
||||
pass "PyGObject available — used by PickFile.py (VPN 'Add config' file chooser)"
|
||||
else
|
||||
warn "PyGObject (python-gobject) missing — the VPN panel's 'Add config' button can't open a file chooser"
|
||||
fi
|
||||
|
||||
# --- 4. VPN daemons + functional status (Mullvad/ProtonVPN/Tailscale) -------
|
||||
# The exclusivity logic (VpnExclusivity.qml backing off Tailscale when a
|
||||
# commercial VPN connects) is pure QML and ships with the dotfiles — it
|
||||
|
||||
84
dotfiles/.config/quickshell/scripts/ImportUnifiWireguard.sh
Executable file
84
dotfiles/.config/quickshell/scripts/ImportUnifiWireguard.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
# Imports a UniFi WireGuard VPN Server client config into NetworkManager
|
||||
# under the profile name VpnIndicator.qml expects (homeWgConnection,
|
||||
# "unifi-home" by default), so the bar's VPN panel can toggle it.
|
||||
#
|
||||
# Get the .conf from UniFi Network: Settings → VPN → VPN Server → your
|
||||
# WireGuard server → Add Client → Download.
|
||||
#
|
||||
# Usage: ./ImportUnifiWireguard.sh <client.conf> [profile-name] [--replace]
|
||||
# ./ImportUnifiWireguard.sh --pick [profile-name] [--replace]
|
||||
#
|
||||
# --pick opens a file chooser (PickFile.py) instead of taking a path — this
|
||||
# is what the VPN panel's "Add config" button runs. Exits 3 if cancelled.
|
||||
#
|
||||
# NetworkManager names both the profile and the interface after the file's
|
||||
# basename, so the config is copied to <profile-name>.conf first (interface
|
||||
# names max out at 15 chars). The private key ends up in a root-only file
|
||||
# under /etc/NetworkManager/system-connections — delete the downloaded
|
||||
# .conf afterwards.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
replace=0
|
||||
pick=0
|
||||
args=()
|
||||
for a in "$@"; do
|
||||
case "$a" in
|
||||
--replace) replace=1 ;;
|
||||
--pick) pick=1 ;;
|
||||
-h|--help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||
*) args+=("$a") ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ $pick -eq 1 ]; then
|
||||
conf=""
|
||||
name="${args[0]:-unifi-home}"
|
||||
rc=0
|
||||
conf=$("$(dirname "$0")/PickFile.py" "Select UniFi WireGuard config" "*.conf") || rc=$?
|
||||
if [ $rc -eq 1 ]; then echo "Cancelled" >&2; exit 3; fi
|
||||
if [ $rc -ne 0 ]; then echo "Couldn't open a file chooser" >&2; exit 1; fi
|
||||
else
|
||||
conf="${args[0]:-}"
|
||||
name="${args[1]:-unifi-home}"
|
||||
fi
|
||||
|
||||
if [ -z "$conf" ] || [ ! -f "$conf" ]; then
|
||||
echo "Usage: $0 <client.conf> [profile-name] [--replace]" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$name" =~ ^[A-Za-z0-9_-]{1,15}$ ]]; then
|
||||
echo "Profile name must be 1-15 chars of [A-Za-z0-9_-] (it doubles as the interface name)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if nmcli -g connection.id connection show "$name" >/dev/null 2>&1; then
|
||||
if [ $replace -eq 1 ]; then
|
||||
nmcli connection delete "$name" >/dev/null
|
||||
else
|
||||
echo "A NetworkManager profile named '$name' already exists — rerun with --replace to overwrite it" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
tmpdir=$(mktemp -d)
|
||||
trap 'rm -rf "$tmpdir"' EXIT
|
||||
install -m 600 "$conf" "$tmpdir/$name.conf"
|
||||
|
||||
if ! grep -qi '^\[Interface\]' "$tmpdir/$name.conf" || ! grep -qi '^\[Peer\]' "$tmpdir/$name.conf"; then
|
||||
echo "$(basename "$conf") doesn't look like a WireGuard config" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nmcli connection import type wireguard file "$tmpdir/$name.conf"
|
||||
# Only ever brought up deliberately from the bar — never on boot or on
|
||||
# joining a network, where it'd silently fight Tailscale/Mullvad/ProtonVPN.
|
||||
nmcli connection modify "$name" connection.autoconnect no
|
||||
# Import itself defaults to autoconnect=yes, so NetworkManager may already
|
||||
# have started bringing it up in the moment before the modify above.
|
||||
nmcli connection down "$name" >/dev/null 2>&1 || true
|
||||
|
||||
echo
|
||||
echo "Imported as '$name'. Toggle it from the VPN panel, or: nmcli connection up $name"
|
||||
echo "You can now delete $conf"
|
||||
74
dotfiles/.config/quickshell/scripts/PickFile.py
Executable file
74
dotfiles/.config/quickshell/scripts/PickFile.py
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
# Opens the xdg-desktop-portal file chooser and prints the picked file's
|
||||
# path. Quickshell has no usable file dialog of its own (QtQuick.Dialogs'
|
||||
# fallback renders inside the calling window, i.e. the tiny bar popup), and
|
||||
# no zenity/kdialog is installed — the portal goes through
|
||||
# xdg-desktop-portal-gtk, which is.
|
||||
#
|
||||
# Usage: PickFile.py [title] [glob ...]
|
||||
# Exits 0 and prints the path on a pick, 1 on cancel, 2 on error.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
from gi.repository import Gio, GLib
|
||||
|
||||
title = sys.argv[1] if len(sys.argv) > 1 else "Open File"
|
||||
globs = sys.argv[2:]
|
||||
|
||||
try:
|
||||
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
||||
except GLib.Error as e:
|
||||
print(f"No session bus: {e.message}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
# The portal answers on a Request object whose path is derived from our
|
||||
# unique bus name and handle_token, so subscribe to it before calling —
|
||||
# otherwise a fast response could arrive before we're listening.
|
||||
token = f"quickshell_{os.getpid()}"
|
||||
sender = bus.get_unique_name().lstrip(":").replace(".", "_")
|
||||
handle = f"/org/freedesktop/portal/desktop/request/{sender}/{token}"
|
||||
|
||||
loop = GLib.MainLoop()
|
||||
result = {"code": 2, "uris": []}
|
||||
|
||||
|
||||
def on_response(_conn, _sender, _path, _iface, _signal, params):
|
||||
code, results = params.unpack()
|
||||
result["code"] = code
|
||||
result["uris"] = results.get("uris", [])
|
||||
loop.quit()
|
||||
|
||||
|
||||
bus.signal_subscribe("org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request",
|
||||
"Response", handle, None, Gio.DBusSignalFlags.NONE, on_response)
|
||||
|
||||
options = {
|
||||
"handle_token": GLib.Variant("s", token),
|
||||
"modal": GLib.Variant("b", True),
|
||||
}
|
||||
filters = []
|
||||
if globs:
|
||||
filters.append((", ".join(globs), [(0, g) for g in globs]))
|
||||
filters.append(("All files", [(0, "*")]))
|
||||
options["filters"] = GLib.Variant("a(sa(us))", filters)
|
||||
downloads = os.path.expanduser("~/Downloads")
|
||||
if os.path.isdir(downloads):
|
||||
options["current_folder"] = GLib.Variant("ay", downloads.encode() + b"\0")
|
||||
|
||||
try:
|
||||
bus.call_sync("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
|
||||
"org.freedesktop.portal.FileChooser", "OpenFile",
|
||||
GLib.Variant("(ssa{sv})", ("", title, options)),
|
||||
GLib.VariantType("(o)"), Gio.DBusCallFlags.NONE, -1, None)
|
||||
except GLib.Error as e:
|
||||
print(f"File chooser portal unavailable: {e.message}", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
loop.run()
|
||||
|
||||
# Response codes: 0 = picked, 1 = cancelled, 2 = other failure.
|
||||
if result["code"] != 0 or not result["uris"]:
|
||||
sys.exit(1 if result["code"] == 1 else 2)
|
||||
print(unquote(urlparse(result["uris"][0]).path))
|
||||
58
dotfiles/.config/quickshell/scripts/VpnPackage.sh
Executable file
58
dotfiles/.config/quickshell/scripts/VpnPackage.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# Installs or uninstalls one of the VPN clients the bar's VPN panel drives
|
||||
# (VpnIndicator.qml's Install / Uninstall links), along with its system
|
||||
# service. Both packages are in Arch's official repos.
|
||||
#
|
||||
# Usage: ./VpnPackage.sh install|uninstall mullvad|proton
|
||||
#
|
||||
# This runs from the bar with no terminal to type a sudo password into, so
|
||||
# the root part goes through pkexec (the polkit agent's password dialog),
|
||||
# same as update-system.sh's Ghostty install. Exits 3 if that dialog is
|
||||
# dismissed.
|
||||
#
|
||||
# Services: proton-vpn-daemon's own pacman hooks enable proton.VPN.service
|
||||
# on install and disable it on removal. Mullvad's package only prints a
|
||||
# reminder, so its daemon is enabled/disabled here.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
action="${1:-}"
|
||||
vpn="${2:-}"
|
||||
|
||||
case "$vpn" in
|
||||
mullvad) pkg=mullvad-vpn-daemon ;;
|
||||
proton) pkg=proton-vpn-cli ;;
|
||||
*) echo "Usage: $0 install|uninstall mullvad|proton" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
case "$action" in
|
||||
install)
|
||||
root_cmd="pacman -S --needed --noconfirm $pkg"
|
||||
if [ "$vpn" = mullvad ]; then
|
||||
root_cmd+=" && systemctl enable --now mullvad-daemon.service"
|
||||
fi
|
||||
;;
|
||||
uninstall)
|
||||
# Take the tunnel down cleanly first, rather than have it pulled out
|
||||
# from under the network by the service stopping. For Mullvad, also
|
||||
# turn lockdown mode off — otherwise its block-everything firewall
|
||||
# rules can outlive the daemon.
|
||||
if [ "$vpn" = mullvad ] && command -v mullvad &> /dev/null; then
|
||||
mullvad lockdown-mode set off &> /dev/null || true
|
||||
mullvad disconnect --wait &> /dev/null || true
|
||||
elif [ "$vpn" = proton ] && command -v protonvpn &> /dev/null; then
|
||||
protonvpn disconnect &> /dev/null || true
|
||||
fi
|
||||
root_cmd="pacman -Rns --noconfirm $pkg"
|
||||
if [ "$vpn" = mullvad ]; then
|
||||
root_cmd="systemctl disable --now mullvad-daemon.service mullvad-early-boot-blocking.service &> /dev/null; $root_cmd"
|
||||
fi
|
||||
;;
|
||||
*) echo "Usage: $0 install|uninstall mullvad|proton" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
rc=0
|
||||
pkexec bash -c "$root_cmd" || rc=$?
|
||||
# 126: the password dialog was dismissed.
|
||||
if [ $rc -eq 126 ]; then echo "Cancelled" >&2; exit 3; fi
|
||||
exit $rc
|
||||
53
dotfiles/.config/quickshell/scripts/sync-dotfiles.sh
Executable file
53
dotfiles/.config/quickshell/scripts/sync-dotfiles.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/bin/bash
|
||||
# Run by update-system.sh inside the update terminal, before the system
|
||||
# update — pulls the dotfiles repo and re-stows it so both machines pick up
|
||||
# config changes on every update.
|
||||
#
|
||||
# Never fails the update: a pull that can't fast-forward (local commits or
|
||||
# uncommitted changes in the way) or a stow conflict just prints a warning
|
||||
# and leaves things as they were, then the system update carries on.
|
||||
|
||||
DOTFILES="$HOME/Projects/dotfiles"
|
||||
|
||||
warn() { printf '\033[1;33m%s\033[0m\n' "$*"; }
|
||||
info() { printf '\033[1;36m%s\033[0m\n' "$*"; }
|
||||
|
||||
if [ ! -d "$DOTFILES/.git" ]; then
|
||||
warn "Dotfiles repo not found at $DOTFILES — skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "Pulling dotfiles..."
|
||||
# Fetch and merge separately so the warning says which one went wrong — the
|
||||
# remote is SSH via Bitwarden's agent, so a failed fetch is almost always
|
||||
# Bitwarden being closed or locked.
|
||||
if ! git -C "$DOTFILES" fetch; then
|
||||
warn "Couldn't reach the dotfiles remote — is Bitwarden open and unlocked? Skipping stow."
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
# --ff-only so a diverged branch never produces a surprise merge commit;
|
||||
# sort that out by hand instead.
|
||||
if ! git -C "$DOTFILES" merge --ff-only; then
|
||||
warn "Couldn't fast-forward dotfiles (local changes or diverged branch) — skipping stow."
|
||||
echo
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v stow &> /dev/null; then
|
||||
warn "stow not installed — skipping."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
info "Applying dotfiles with stow..."
|
||||
# Real dirs first, or stow links the whole folder into the repo (see bootstrap.sh)
|
||||
mkdir -p "$HOME/.local/share/fonts"
|
||||
# -R restows: drops links for files removed from the repo and adds new ones.
|
||||
if stow -R -d "$DOTFILES" -t "$HOME" dotfiles; then
|
||||
info "Dotfiles applied."
|
||||
fc-cache "$HOME/.local/share/fonts" >/dev/null 2>&1
|
||||
else
|
||||
warn "stow hit a conflict — nothing was changed. Fix it and re-run:"
|
||||
warn " stow -R -d $DOTFILES -t ~ dotfiles"
|
||||
fi
|
||||
echo
|
||||
105
dotfiles/.config/quickshell/scripts/update-system.sh
Executable file
105
dotfiles/.config/quickshell/scripts/update-system.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
# Launched by SystemUpdate.qml (top bar, next to the clock) — opens a
|
||||
# terminal running whatever update command fits this distro, then notifies
|
||||
# once it's done. Ported from the old waybar custom/updater module
|
||||
# (.config/waybar/scripts/update-distro.sh); kept multi-distro even though
|
||||
# this machine is Arch, since these dotfiles aren't.
|
||||
#
|
||||
# The window it opens is floated + centered by a matching Hyprland rule
|
||||
# (system-update-terminal, in .config/hypr/layout/windowrules.lua),
|
||||
# same treatment as Bitwarden's window, rather than tiling into whatever
|
||||
# workspace you happened to be on.
|
||||
|
||||
iDIR="$HOME/.config/swaync/images"
|
||||
|
||||
# Printed inside the terminal before the actual update command runs, so it
|
||||
# reads as "updating" from the first frame rather than looking like an
|
||||
# empty/stuck window while pacman/apt/etc. take a moment to start printing.
|
||||
#
|
||||
# The terminal runs this in a fresh `bash -c`, which doesn't inherit shell
|
||||
# functions from this script's own environment (export -f only reaches bash
|
||||
# processes forked from *this* one, not whatever the terminal execs) — so
|
||||
# instead, the already-colored text is built once here with real escape
|
||||
# bytes ($'...'), then %q-quoted into a self-contained `echo '...'` that
|
||||
# reproduces those same bytes verbatim when the child bash parses it.
|
||||
msg=" Updating System — please wait "
|
||||
line=$(printf '=%.0s' $(seq 1 ${#msg}))
|
||||
banner_text=$'\033[1;36m'"$line"$'\033[0m\n\033[1;36m'"$msg"$'\033[0m\n\033[1;36m'"$line"$'\033[0m\n\n'
|
||||
BANNER="echo $(printf '%q' "$banner_text")"
|
||||
|
||||
# GHOSTTY_INSTALL is how to install the terminal on this distro, if it's
|
||||
# packaged there at all — only Arch ships it in the official repos.
|
||||
if command -v paru &> /dev/null; then
|
||||
CMD="paru -Syu"
|
||||
LABEL="Arch-based system"
|
||||
GHOSTTY_INSTALL="pacman -S --needed --noconfirm ghostty"
|
||||
elif command -v yay &> /dev/null; then
|
||||
CMD="yay -Syu"
|
||||
LABEL="Arch-based system"
|
||||
GHOSTTY_INSTALL="pacman -S --needed --noconfirm ghostty"
|
||||
elif command -v dnf &> /dev/null; then
|
||||
CMD="sudo dnf update --refresh -y"
|
||||
LABEL="Fedora system"
|
||||
elif command -v apt &> /dev/null; then
|
||||
CMD="sudo apt update && sudo apt upgrade -y"
|
||||
LABEL="Debian/Ubuntu system"
|
||||
elif command -v zypper &> /dev/null; then
|
||||
CMD="sudo zypper dup -y"
|
||||
LABEL="openSUSE system"
|
||||
else
|
||||
notify-send -i "$iDIR/error.png" -u critical "Unsupported system" "This script does not support your distribution."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Terminal: Ghostty, offering to install it if missing ---
|
||||
# This runs from the bar with no terminal of its own, so there's nowhere to
|
||||
# type a sudo password — the install goes through pkexec instead, which
|
||||
# pops up the polkit agent's graphical password prompt. Declining (or a
|
||||
# failed install) falls back to foot for this run, so updating never gets
|
||||
# blocked on the terminal choice.
|
||||
if ! command -v ghostty &> /dev/null; then
|
||||
if [ -n "$GHOSTTY_INSTALL" ]; then
|
||||
# -A waits for a click and prints the chosen action's name (nothing if
|
||||
# the notification is dismissed instead).
|
||||
choice=$(notify-send -i "$iDIR/error.png" -u critical \
|
||||
-A install="Install Ghostty" -A foot="Use foot this time" \
|
||||
"Ghostty not installed" "The system updater runs in Ghostty. Install it now?")
|
||||
if [ "$choice" = "install" ]; then
|
||||
if pkexec $GHOSTTY_INSTALL; then
|
||||
notify-send -i "$iDIR/profile-pic.jpg" -u low "Ghostty installed" "Starting the update..."
|
||||
else
|
||||
notify-send -i "$iDIR/error.png" "Ghostty install failed" "Using foot for this update."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
notify-send -i "$iDIR/error.png" "Ghostty not installed" \
|
||||
"Install Ghostty for the system updater — using foot for now."
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v ghostty &> /dev/null; then
|
||||
# --gtk-single-instance=false: otherwise, with a Ghostty window already
|
||||
# open, this would just hand the window off to that running instance and
|
||||
# return immediately — SystemUpdates.qml relies on this script blocking
|
||||
# until the update window is closed (that's when it re-checks the count),
|
||||
# and the "has been updated" notification below would fire up front.
|
||||
TERMINAL=(ghostty --gtk-single-instance=false --title=update -e)
|
||||
elif command -v foot &> /dev/null; then
|
||||
TERMINAL=(foot -T update)
|
||||
else
|
||||
notify-send -i "$iDIR/error.png" "Need a terminal:" "Neither Ghostty nor foot is installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Keeps the terminal open (whether the update succeeded or failed — read
|
||||
# always runs regardless, since it's ; not &&) until you press Enter,
|
||||
# instead of the terminal closing the instant the update command exits and
|
||||
# taking any output/errors with it.
|
||||
WAIT_PROMPT="echo; read -p 'Update finished — press Enter to close...'"
|
||||
|
||||
# Pull + re-stow the dotfiles first, so config changes from the other machine
|
||||
# land alongside the package update.
|
||||
SYNC="$(printf '%q' "$(dirname "$0")/sync-dotfiles.sh")"
|
||||
|
||||
"${TERMINAL[@]}" bash -c "$BANNER; $SYNC; $CMD; $WAIT_PROMPT"
|
||||
notify-send -i "$iDIR/profile-pic.jpg" -u low "$LABEL" 'has been updated.'
|
||||
@@ -4,7 +4,11 @@ import "./modules/osd"
|
||||
|
||||
ShellRoot {
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
// Exclude the Sunshine headless virtual monitor — nobody ever sees a
|
||||
// bar there, but PanelWindow's exclusiveZone still reserves space for
|
||||
// it, which left a black strip cut out of Steam Big Picture's
|
||||
// fullscreen area on that output.
|
||||
model: Quickshell.screens.filter(s => !s.name.startsWith("HEADLESS"))
|
||||
|
||||
Bar {
|
||||
required property var modelData
|
||||
|
||||
5
dotfiles/.config/sunshine/.gitignore
vendored
Normal file
5
dotfiles/.config/sunshine/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Machine-local Sunshine state lives next to the stowed config; never commit it
|
||||
credentials/
|
||||
*.log
|
||||
*.log.*
|
||||
sunshine_state.json
|
||||
47
dotfiles/.config/sunshine/apps.json
Normal file
47
dotfiles/.config/sunshine/apps.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"env": {
|
||||
"PATH": "$(PATH):$(HOME)/.local/bin"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"name": "Desktop",
|
||||
"image-path": "desktop.png"
|
||||
},
|
||||
{
|
||||
"name": "Low Res Desktop",
|
||||
"image-path": "desktop.png",
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "xrandr --output HDMI-1 --mode 1920x1080",
|
||||
"undo": "xrandr --output HDMI-1 --mode 1920x1200"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Steam Big Picture",
|
||||
"detached": [
|
||||
"setsid steam steam://open/bigpicture"
|
||||
],
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "/home/thomas/.config/sunshine/scripts/steam-bigpicture-prep.sh",
|
||||
"undo": "setsid steam steam://close/bigpicture"
|
||||
}
|
||||
],
|
||||
"image-path": "steam.png"
|
||||
},
|
||||
{
|
||||
"name": "Steam Big Picture (TV)",
|
||||
"detached": [
|
||||
"setsid steam steam://open/bigpicture"
|
||||
],
|
||||
"prep-cmd": [
|
||||
{
|
||||
"do": "/home/thomas/.config/sunshine/scripts/steam-bigpicture-tv-prep.sh",
|
||||
"undo": "/home/thomas/.config/sunshine/scripts/steam-bigpicture-tv-undo.sh"
|
||||
}
|
||||
],
|
||||
"image-path": "steam.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
17
dotfiles/.config/sunshine/scripts/steam-bigpicture-prep.sh
Executable file
17
dotfiles/.config/sunshine/scripts/steam-bigpicture-prep.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Run before Sunshine launches Steam Big Picture: focus the headless virtual
|
||||
# monitor and park the cursor on it, so Big Picture (which spawns on whatever
|
||||
# monitor currently has focus/the mouse) opens there instead of wherever the
|
||||
# mouse happened to be.
|
||||
set -e
|
||||
hyprctl dispatch 'hl.dsp.focus({monitor = "HEADLESS-1"})'
|
||||
hyprctl dispatch 'hl.dsp.cursor.move({x = 5344, y = 540})'
|
||||
|
||||
# Sunshine only flips the system default audio sink to sink-sunshine-stereo
|
||||
# after the streaming session/client handshake completes (~500ms after this
|
||||
# script runs and Steam is spawned) — see sunshine.log timestamps. If Steam's
|
||||
# own audio (SDL) initializes in that window, it binds to whatever was
|
||||
# default at the time (the real HDMI output) and never re-homes even after
|
||||
# Sunshine's swap. Set it here first so it's already correct before Steam
|
||||
# ever launches; Sunshine's own (redundant) swap right after is harmless.
|
||||
pactl set-default-sink sink-sunshine-stereo || true
|
||||
18
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-prep.sh
Executable file
18
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-prep.sh
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
# Run before Sunshine launches Steam Big Picture for the 4K TV target.
|
||||
# Switches the headless virtual monitor from its default 1728x1080@60
|
||||
# (used for the Steam Deck target) up to 3840x2160@60, then focuses it
|
||||
# and parks the cursor there so Big Picture opens on it. Reverted by
|
||||
# steam-bigpicture-tv-undo.sh once the stream ends.
|
||||
set -e
|
||||
# NOTE: this Hyprland build uses the Lua config layer, which rejects the
|
||||
# usual `hyprctl keyword monitor ...` syntax ("keyword can't work with
|
||||
# non-legacy parsers. Use eval.") — hl.monitor() via `hyprctl eval` is the
|
||||
# runtime equivalent of the hl.monitor() call in layout/monitors.lua.
|
||||
hyprctl eval 'hl.monitor({ output = "HEADLESS-1", mode = "3840x2160@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })'
|
||||
hyprctl dispatch 'hl.dsp.focus({monitor = "HEADLESS-1"})'
|
||||
hyprctl dispatch 'hl.dsp.cursor.move({x = 6400, y = 1080})'
|
||||
|
||||
# See steam-bigpicture-prep.sh for why this runs here instead of relying on
|
||||
# Sunshine's own post-handshake audio-sink swap.
|
||||
pactl set-default-sink sink-sunshine-stereo || true
|
||||
6
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-undo.sh
Executable file
6
dotfiles/.config/sunshine/scripts/steam-bigpicture-tv-undo.sh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# Undo for steam-bigpicture-tv-prep.sh: close Big Picture and restore
|
||||
# HEADLESS-1 to its default mode (the one the Steam Deck target expects).
|
||||
set -e
|
||||
setsid steam steam://close/bigpicture
|
||||
hyprctl eval 'hl.monitor({ output = "HEADLESS-1", mode = "1728x1080@60.00000", position = "4480x0", scale = 1.00000000, transform = 0, vrr = 0 })'
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user