Compare commits
182
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fc1fdbbbb | ||
|
|
dd8d93c387 | ||
|
|
b8832a0174 | ||
|
|
3f07d25858 | ||
|
|
df1dcdfad5 | ||
|
|
91f7273f41 | ||
|
|
f9eba1e8c5 | ||
|
|
b1edfb6fe4 | ||
|
|
1afa41526a | ||
|
|
08b16fa03f | ||
|
|
ccf46c40ef | ||
|
|
60a321e6f4 | ||
|
|
7d5c65be03 | ||
|
|
f8e7512e01 | ||
|
|
b7ce2c6e43 | ||
|
|
1f62256024 | ||
|
|
4ef2f01baa | ||
|
|
6042c0b1c0 | ||
|
|
0c4d132ee1 | ||
|
|
faa9a00716 | ||
|
|
27af4fd443 | ||
|
|
b16834fea6 | ||
|
|
8bfae70284 | ||
|
|
4279da999a | ||
|
|
54b8a82803 | ||
|
|
2e8292599a | ||
|
|
5562323eb8 | ||
|
|
2cc109f5e1 | ||
|
|
52d896b054 | ||
|
|
e4409ed6aa | ||
|
|
f0321432b4 | ||
|
|
d855a26bd9 | ||
|
|
607acb0a2d | ||
|
|
9b2fa80ab7 | ||
|
|
8a0a09dbb4 | ||
|
|
816ea68f9a | ||
|
|
3238623934 | ||
|
|
21aa9223df | ||
|
|
e2e03252e4 | ||
|
|
0ca83f74c7 | ||
|
|
d58c431199 | ||
|
|
3ff414fb1b | ||
|
|
77f8102148 | ||
|
|
0fd59c59e3 | ||
|
|
b1f9891849 | ||
|
|
6026bf308b | ||
|
|
24d8bfd641 | ||
|
|
14529c011f | ||
|
|
7378c18ab9 | ||
|
|
9ce7040b91 | ||
|
|
af0ba13573 | ||
|
|
09e5f1ad6b | ||
|
|
7541a45e77 | ||
|
|
1a91d00f2c | ||
|
|
19063a3e02 | ||
|
|
5e6dc9e533 | ||
|
|
ca64d4b9b6 | ||
|
|
b28edcd01f | ||
|
|
9c574f9eb7 | ||
|
|
d1b2cd2083 | ||
|
|
8c4dee0ca8 | ||
|
|
71ee18048e | ||
|
|
668983e16d | ||
|
|
17e23a6cf4 | ||
|
|
d18fe51553 | ||
|
|
9f9515ffe0 | ||
|
|
94353aa0bd | ||
|
|
3a2295ff53 | ||
|
|
de1b8a7673 | ||
|
|
39a26adcc2 | ||
|
|
eff0bc44ff | ||
|
|
c038c57278 | ||
|
|
420b7aa1a6 | ||
|
|
f22e405b50 | ||
|
|
3cfa592db9 | ||
|
|
81096e2d95 | ||
|
|
c415c4f176 | ||
|
|
86742da63d | ||
|
|
3c521cf5fa | ||
|
|
c087998710 | ||
|
|
b9800bfbab | ||
|
|
7b8270fdd6 | ||
|
|
6d3f888784 | ||
|
|
c2cd79b547 | ||
|
|
0a18290137 | ||
|
|
efc53435a2 | ||
|
|
67674d297e | ||
|
|
2d4b126f14 | ||
|
|
5671324eb2 | ||
|
|
83391e0453 | ||
|
|
172b099a04 | ||
|
|
cae72b5179 | ||
|
|
b3b8e0d66d | ||
|
|
787d2b121a | ||
|
|
1b323c2fa5 | ||
|
|
1ca571458c | ||
|
|
768801dbe4 | ||
|
|
375ecfcd95 | ||
|
|
ce95b34d19 | ||
|
|
9d430a3079 | ||
|
|
bac68d2bfb | ||
|
|
634a9ebe07 | ||
|
|
150f3cdb09 | ||
|
|
6377bfb8fd | ||
|
|
2bc12e6022 | ||
|
|
fe7c85e471 | ||
|
|
8a04e4f9d1 | ||
|
|
5347954a88 | ||
|
|
00a81edadd | ||
|
|
4bd3638012 | ||
|
|
7f540a58e5 | ||
|
|
7ac5355a77 | ||
|
|
a9eb90f457 | ||
|
|
b5c9156e64 | ||
|
|
3213a0989a | ||
|
|
fbf729b25c | ||
|
|
e37df9d838 | ||
|
|
673aac6aba | ||
|
|
c8eb344a0b | ||
|
|
65966200fe | ||
|
|
2130d6a1a5 | ||
|
|
9a99c6064e | ||
|
|
4f93522f7d | ||
|
|
a325ec807e | ||
|
|
c2b19f7545 | ||
|
|
925fc0276f | ||
|
|
65a8197419 | ||
|
|
ff67387c42 | ||
|
|
c42794c5e2 | ||
|
|
0964af7b73 | ||
|
|
15fb008954 | ||
|
|
f4c3ffed43 | ||
|
|
5f965daa6a | ||
|
|
5940d6b4bd | ||
|
|
190e3200e0 | ||
|
|
49ec4d8f6a | ||
|
|
173c00fca7 | ||
|
|
3a903934c0 | ||
|
|
dc6da5af34 | ||
|
|
922395d2b4 | ||
|
|
a5450fdb6d | ||
|
|
1ce5c732b7 | ||
|
|
f9cc15fb96 | ||
|
|
2728fb08fb | ||
|
|
f27a1dca11 | ||
|
|
03914e23af | ||
|
|
6a1bec849d | ||
|
|
fb27c5b309 | ||
|
|
d37d4a87de | ||
|
|
530a5590a6 | ||
|
|
e92c2ee5f9 | ||
|
|
5248883e4b | ||
|
|
67033f2a31 | ||
|
|
2dc9676822 | ||
|
|
89666e7485 | ||
|
|
7123bb4885 | ||
|
|
7461c88b98 | ||
|
|
11202d06d9 | ||
|
|
ac57cfa319 | ||
|
|
4c529afae7 | ||
|
|
372a0f60e5 | ||
|
|
19b8ec2542 | ||
|
|
210bdbdddc | ||
|
|
47137191dd | ||
|
|
ef2d49fbaa | ||
|
|
065b7747cf | ||
|
|
cfc2b82c06 | ||
|
|
e985792472 | ||
|
|
d9ee1ddce4 | ||
|
|
4fcfb9c45e | ||
|
|
50ea51263a | ||
|
|
53bec8ddbc | ||
|
|
b8ce2aef5c | ||
|
|
7c55264138 | ||
|
|
6150274903 | ||
|
|
c0ba11768a | ||
|
|
fba92951f9 | ||
|
|
11863984ca | ||
|
|
e4f1fe8e05 | ||
|
|
18cfd8b174 | ||
|
|
ca7d4e3460 | ||
|
|
3bb192238c |
+19
-3
@@ -1,7 +1,23 @@
|
||||
# Ignore Wireguard config of course!
|
||||
/config/wg/**
|
||||
# Ignore bash environment variables.
|
||||
/config/bash/env
|
||||
# Ignore backups of old config files
|
||||
/config/old/**
|
||||
/config/old
|
||||
# Ignore Wireguard config of course!
|
||||
/config/wg/**
|
||||
# Ignore Neovim lazy-lock file
|
||||
/config/dot/nvim/lazy-lock.json
|
||||
# Local visual-companion sessions and generated design mocks.
|
||||
/.superpowers/
|
||||
# Local linked worktrees used for reviewed feature development.
|
||||
/.worktrees/
|
||||
# Python helper bytecode is local runtime state.
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# Generated from the colour scheme; machine state, not configuration.
|
||||
/config/dot/kitty/current-theme.conf
|
||||
|
||||
# Generated from the colour scheme; machine state, not configuration.
|
||||
/config/dot/gtk-3.0/settings.ini
|
||||
/config/dot/gtk-4.0/settings.ini
|
||||
/config/dot/tmux/current-theme.conf
|
||||
|
||||
@@ -1,2 +1,62 @@
|
||||
# Panama
|
||||
## Formerly Sunhat, this is a personal config for Fedora, with the intention of helping a user set up their Fedora system with one command.
|
||||
|
||||
Formerly Sunhat. A personal config for Fedora, with the intention of helping a
|
||||
user set up their Fedora system with one command.
|
||||
|
||||
```sh
|
||||
git clone https://git.gbrown.org/gib/Panama.git ~/.local/share/Panama
|
||||
~/.local/share/Panama/install
|
||||
```
|
||||
|
||||
`install` runs everything in `setup/scripts/` in order:
|
||||
|
||||
| Script | Does |
|
||||
|---|---|
|
||||
| `install-packages` | Repos (RPM Fusion, Terra, Hyprland COPR), then the four package lists in `setup/packages/` |
|
||||
| `link-dotfiles` | Symlinks `config/dot/<name>` → `~/.config/<name>` |
|
||||
| `change-settings` | Copies `config/copy/` over `/`, applies gsettings, enables user services |
|
||||
|
||||
Existing configs are moved to `config/old/` rather than overwritten.
|
||||
|
||||
## Desktops
|
||||
|
||||
Panama configures **two** desktops that coexist. Both sessions stay available in
|
||||
GDM, so you can switch back and forth while you settle in.
|
||||
|
||||
### GNOME
|
||||
The original setup: Forge for tiling, Dash-to-Dock, Openbar, Vitals,
|
||||
AppIndicator support. Configured through `config/dot/forge/` and gsettings.
|
||||
|
||||
### Hyprland
|
||||
A from-scratch replacement built to reproduce the GNOME setup closely enough
|
||||
that muscle memory transfers — same keybinds, same panel contents, same dock,
|
||||
same Tokyo Night Moon palette.
|
||||
|
||||
| Piece | What it is |
|
||||
|---|---|
|
||||
| `config/dot/hypr/` | Compositor config. **Lua, not hyprlang** — see its README |
|
||||
| `config/dot/quickshell/` | The shell: bar, dock, Continuum overview, Settings, Screen Intelligence, focus sessions, quick settings, notifications, screenshot UI |
|
||||
| `config/dot/vicinae/` | Raycast-style launcher, themed |
|
||||
| `config/dot/uwsm/` | Session environment (see the uwsm caveat in the hypr README) |
|
||||
| `config/dot/wofi/` | Fallback launcher, in case the shell fails to start |
|
||||
| `config/dot/xdg-desktop-portal/` | Portal backend routing |
|
||||
|
||||
**Start here: [`config/dot/hypr/README.md`](config/dot/hypr/README.md)** — it
|
||||
covers the Lua migration, the uwsm environment gotcha, the HDR decision, the
|
||||
full keymap, and troubleshooting.
|
||||
|
||||
Log in as **"Hyprland (uwsm-managed)"**, not plain "Hyprland".
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
bin/ Small user-facing commands on PATH
|
||||
config/
|
||||
bash/ .bashrc, aliases, env (env is gitignored)
|
||||
copy/ Files copied verbatim over / (needs sudo)
|
||||
dot/ Symlinked into ~/.config
|
||||
old/ Backups of whatever was replaced (gitignored)
|
||||
setup/
|
||||
packages/ One package per line
|
||||
scripts/ Run in order by ./install
|
||||
```
|
||||
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import pyperclip
|
||||
import questionary
|
||||
|
||||
# List of directories to exclude
|
||||
EXCLUDED_DIRS = {'node_modules', '.next', '.venv', '.git', '__pycache__', '.idea', '.vscode', 'ui'}
|
||||
|
||||
def collect_files(project_path):
|
||||
"""
|
||||
Collects files from the project directory, excluding specified directories and filtering by extensions.
|
||||
Returns a list of file paths relative to the project directory.
|
||||
"""
|
||||
collected_files = []
|
||||
|
||||
for root, dirs, files in os.walk(project_path):
|
||||
# Exclude specified directories
|
||||
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
|
||||
|
||||
for file in files:
|
||||
file_path = Path(root) / file
|
||||
relative_path = file_path.relative_to(project_path)
|
||||
collected_files.append(relative_path)
|
||||
|
||||
return collected_files
|
||||
|
||||
def main():
|
||||
# Parse command-line arguments
|
||||
parser = argparse.ArgumentParser(description='Generate Markdown from selected files.')
|
||||
parser.add_argument('path', nargs='?', default='.', help='Path to the project directory')
|
||||
args = parser.parse_args()
|
||||
|
||||
project_path = Path(args.path).resolve()
|
||||
if not project_path.is_dir():
|
||||
print(f"Error: '{project_path}' is not a directory.")
|
||||
sys.exit(1)
|
||||
|
||||
# Collect files from the project directory
|
||||
file_list = collect_files(project_path)
|
||||
|
||||
if not file_list:
|
||||
print("No files found in the project directory with the specified extensions.")
|
||||
sys.exit(1)
|
||||
|
||||
# Sort file_list for better organization
|
||||
file_list.sort()
|
||||
|
||||
# Interactive file selection using questionary
|
||||
print("\nSelect the files you want to include:")
|
||||
selected_files = questionary.checkbox(
|
||||
"Press space to select files, and Enter when you're done:",
|
||||
choices=[str(f) for f in file_list]
|
||||
).ask()
|
||||
|
||||
if not selected_files:
|
||||
print("No files selected.")
|
||||
sys.exit(1)
|
||||
|
||||
# Generate markdown
|
||||
markdown_lines = []
|
||||
markdown_lines.append('')
|
||||
|
||||
for selected_file in selected_files:
|
||||
file_path = project_path / selected_file
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
# Determine the language for code block from file extension
|
||||
language = file_path.suffix.lstrip('.')
|
||||
markdown_lines.append(f'{selected_file}')
|
||||
markdown_lines.append(f'```{language}')
|
||||
markdown_lines.append(content)
|
||||
markdown_lines.append('```')
|
||||
markdown_lines.append('')
|
||||
except Exception as e:
|
||||
print(f"Error reading file {selected_file}: {e}")
|
||||
|
||||
markdown_text = '\n'.join(markdown_lines)
|
||||
|
||||
# Copy markdown content to clipboard
|
||||
pyperclip.copy(markdown_text)
|
||||
print("Markdown content has been copied to the clipboard.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Check if required libraries are installed
|
||||
try:
|
||||
import questionary
|
||||
import pyperclip
|
||||
except ImportError as e:
|
||||
missing_module = e.name
|
||||
print(f"Error: Missing required module '{missing_module}'.")
|
||||
print(f"Please install it by running: pip install {missing_module}")
|
||||
sys.exit(1)
|
||||
|
||||
main()
|
||||
Executable
+227
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# panama – Helper for managing the Panama dotfiles/repo
|
||||
# Version 1.0
|
||||
# Author: Gabriel Brown
|
||||
#
|
||||
# Commands:
|
||||
# update Commit & sync local changes (or just pull if clean)
|
||||
# edit Open the Panama repo in Neovim
|
||||
# help Show this help
|
||||
#
|
||||
# Designed to grow: add new subcommands as cmd_<name> functions and
|
||||
# register them in the dispatcher / usage block below.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PROGRAM=$(basename "$0")
|
||||
VERSION="1.0"
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Locate the Panama repo (this script lives in <repo>/bin/panama)
|
||||
# ----------------------------------------------------------------------------
|
||||
SCRIPT_PATH=$(readlink -f "${BASH_SOURCE[0]}")
|
||||
PANAMA_DIR=$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Pretty output
|
||||
# ----------------------------------------------------------------------------
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ $(tput colors 2>/dev/null || echo 0) -ge 8 ]]; then
|
||||
BOLD=$(tput bold); RESET=$(tput sgr0)
|
||||
RED=$(tput setaf 1); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3)
|
||||
BLUE=$(tput setaf 4); MAGENTA=$(tput setaf 5); CYAN=$(tput setaf 6)
|
||||
else
|
||||
BOLD=""; RESET=""; RED=""; GREEN=""; YELLOW=""; BLUE=""; MAGENTA=""; CYAN=""
|
||||
fi
|
||||
|
||||
info() { printf '%s==>%s %s\n' "${BLUE}${BOLD}" "$RESET" "$*"; }
|
||||
ok() { printf '%s✓%s %s\n' "${GREEN}${BOLD}" "$RESET" "$*"; }
|
||||
warn() { printf '%s!%s %s\n' "${YELLOW}${BOLD}" "$RESET" "$*"; }
|
||||
err() { printf '%s✗%s %s\n' "${RED}${BOLD}" "$RESET" "$*" >&2; }
|
||||
header(){ printf '\n%s%s%s\n' "${MAGENTA}${BOLD}" "$*" "$RESET"; }
|
||||
|
||||
# Ask a yes/no question. Returns 0 for yes, 1 for no. Default = no.
|
||||
confirm() {
|
||||
local prompt="$1" reply
|
||||
printf '%s?%s %s %s[y/N]%s ' "${CYAN}${BOLD}" "$RESET" "$prompt" "$BOLD" "$RESET"
|
||||
read -r reply || true
|
||||
[[ "$reply" =~ ^[Yy]([Ee][Ss])?$ ]]
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Usage
|
||||
# ----------------------------------------------------------------------------
|
||||
usage() {
|
||||
cat <<EOF
|
||||
${BOLD}$PROGRAM${RESET} – manage the Panama repo (${PANAMA_DIR})
|
||||
|
||||
${BOLD}Usage:${RESET}
|
||||
$PROGRAM <command> [options]
|
||||
|
||||
${BOLD}Commands:${RESET}
|
||||
${GREEN}update${RESET} Review, commit & sync local changes. If the working tree is
|
||||
clean it simply runs 'git pull'.
|
||||
${GREEN}edit${RESET} Open the Panama repo in Neovim.
|
||||
${GREEN}help${RESET} Show this help (also -h, --help).
|
||||
|
||||
${BOLD}Options:${RESET}
|
||||
-h, --help Show this help and exit
|
||||
--version Show version and exit
|
||||
|
||||
${BOLD}Examples:${RESET}
|
||||
$PROGRAM update
|
||||
$PROGRAM edit
|
||||
EOF
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Guard: make sure we're in a git repo
|
||||
# ----------------------------------------------------------------------------
|
||||
require_git_repo() {
|
||||
if ! git -C "$PANAMA_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
||||
err "'$PANAMA_DIR' is not a git repository."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Command: update
|
||||
# ----------------------------------------------------------------------------
|
||||
cmd_update() {
|
||||
require_git_repo
|
||||
cd "$PANAMA_DIR"
|
||||
|
||||
info "Panama repo: ${BOLD}${PANAMA_DIR}${RESET}"
|
||||
|
||||
# Any changes in the working tree? (modified, staged, or untracked)
|
||||
if [[ -z "$(git status --porcelain)" ]]; then
|
||||
info "Working tree is clean — pulling latest changes."
|
||||
if git pull --ff-only; then
|
||||
ok "Already in sync."
|
||||
else
|
||||
err "git pull failed."
|
||||
exit 1
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
# Show what changed
|
||||
header "Changed files"
|
||||
git -c color.status=always status --short
|
||||
|
||||
header "Diff"
|
||||
# Tracked changes (staged + unstaged) relative to the last commit.
|
||||
git --no-pager -c color.diff=always diff HEAD
|
||||
|
||||
# Untracked files won't appear in 'git diff', so show them as new files.
|
||||
local untracked
|
||||
untracked=$(git ls-files --others --exclude-standard)
|
||||
if [[ -n "$untracked" ]]; then
|
||||
while IFS= read -r f; do
|
||||
[[ -z "$f" ]] && continue
|
||||
git --no-pager -c color.diff=always diff --no-index -- /dev/null "$f" || true
|
||||
done <<< "$untracked"
|
||||
fi
|
||||
|
||||
echo
|
||||
if ! confirm "Commit these changes?"; then
|
||||
warn "Aborted — no changes committed."
|
||||
return
|
||||
fi
|
||||
|
||||
# Commit message
|
||||
local msg
|
||||
printf '%s?%s Commit message: ' "${CYAN}${BOLD}" "$RESET"
|
||||
read -r msg || true
|
||||
if [[ -z "${msg// }" ]]; then
|
||||
msg="Update $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
warn "No message given — using: ${BOLD}${msg}${RESET}"
|
||||
fi
|
||||
|
||||
# Is the local branch up to date with its upstream?
|
||||
info "Checking whether the repo is up to date..."
|
||||
if git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
|
||||
git fetch --quiet
|
||||
local local_rev remote_rev base_rev
|
||||
local_rev=$(git rev-parse @)
|
||||
remote_rev=$(git rev-parse '@{u}')
|
||||
base_rev=$(git merge-base @ '@{u}')
|
||||
|
||||
if [[ "$local_rev" == "$remote_rev" ]]; then
|
||||
ok "Repo is up to date."
|
||||
elif [[ "$local_rev" == "$base_rev" ]]; then
|
||||
warn "Repo is behind upstream — stashing, pulling, then re-applying."
|
||||
info "Stashing local changes..."
|
||||
git stash push --include-untracked -m "panama-update-$(date +%s)" >/dev/null
|
||||
|
||||
if ! git pull --ff-only; then
|
||||
err "git pull failed — restoring your changes."
|
||||
git stash pop || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
info "Re-applying stashed changes..."
|
||||
if ! git stash pop; then
|
||||
err "Conflict while re-applying changes. Resolve it, then commit manually."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
warn "Local branch has diverged from upstream — committing locally only."
|
||||
fi
|
||||
else
|
||||
warn "No upstream configured for this branch — committing locally only."
|
||||
fi
|
||||
|
||||
# Commit everything
|
||||
info "Committing changes..."
|
||||
git add -A
|
||||
git commit -m "$msg"
|
||||
ok "Committed: ${BOLD}${msg}${RESET}"
|
||||
|
||||
echo
|
||||
if confirm "Push the changes now?"; then
|
||||
info "Pushing..."
|
||||
if git push; then
|
||||
ok "Pushed to remote."
|
||||
else
|
||||
err "git push failed."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
info "Done — changes committed locally but not pushed."
|
||||
fi
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Command: edit
|
||||
# ----------------------------------------------------------------------------
|
||||
cmd_edit() {
|
||||
if ! command -v nvim >/dev/null 2>&1; then
|
||||
err "Neovim (nvim) is not installed or not on PATH."
|
||||
exit 1
|
||||
fi
|
||||
info "Opening ${BOLD}${PANAMA_DIR}${RESET} in Neovim."
|
||||
cd "$PANAMA_DIR"
|
||||
exec nvim .
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Dispatcher
|
||||
# ----------------------------------------------------------------------------
|
||||
main() {
|
||||
local cmd="${1:-}"
|
||||
case "$cmd" in
|
||||
update) shift; cmd_update "$@" ;;
|
||||
edit) shift; cmd_edit "$@" ;;
|
||||
help|-h|--help|"") usage ;;
|
||||
--version) printf '%s %s\n' "$PROGRAM" "$VERSION" ;;
|
||||
*)
|
||||
err "Unknown command: '$cmd'"
|
||||
echo
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Executable
BIN
Binary file not shown.
@@ -24,3 +24,8 @@ else
|
||||
fi
|
||||
export PATH
|
||||
fi
|
||||
. "$HOME/.cargo/env"
|
||||
|
||||
# >>> Codex installer >>>
|
||||
export PATH="/home/gib/.local/bin:$PATH"
|
||||
# <<< Codex installer <<<
|
||||
|
||||
@@ -10,6 +10,11 @@ alias shutdown="systemctl poweroff"
|
||||
alias update-grub="sudo grub-mkconfig -o /etc/grub2-efi.cfg"
|
||||
alias nvidia-smi-docker='sudo docker run --rm --gpus all --privileged nvidia/cuda:12.8.1-base-ubuntu24.04 nvidia-smi'
|
||||
alias ncconnect='sudo docker exec -u www-data -it nextcloud-aio-nextcloud bash'
|
||||
alias avante='nvim -c "lua vim.defer_fn(function()require(\"avante.api\").zen_mode()end, 100)"'
|
||||
alias nlc='nvim leetcode.nvim'
|
||||
alias clauded='claude --allow-dangerously-skip-permissions'
|
||||
alias claudedr='claude --allow-dangerously-skip-permissions --resume'
|
||||
alias commit-repo='git add -A && git commit -m "Update stuff" && git push'
|
||||
|
||||
# Docker Shortcuts
|
||||
alias docker-up='sudo docker compose up -d'
|
||||
@@ -35,6 +40,7 @@ alias ...='cd ../..'
|
||||
alias ....='cd ../../..'
|
||||
|
||||
# Tools
|
||||
alias vim='vimx'
|
||||
alias n='nvim'
|
||||
alias n.="nvim ."
|
||||
alias ns="nvim src"
|
||||
|
||||
+17
-4
@@ -16,13 +16,26 @@ export ANDROID_SDK_HOME="$HOME/.local/share/Android"
|
||||
export GOPATH="$HOME/.local/share/go"
|
||||
export DOTNETPATH="$HOME/.dotnet/tools"
|
||||
|
||||
# Podman
|
||||
# export DOCKER_HOST=unix:///run/user/1000/podman/podman.sock
|
||||
|
||||
# Set complete path
|
||||
export PATH="$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PANAMA_PATH/bin:$BUN_INSTALL/bin:$CARGO_PATH/bin:$PNPM_HOME:$PYENV_ROOT/bin:$HOME/.rbenv/bin:/usr/lib/ccache/bin/:$GOPATH/bin:$DOTNETPATH"
|
||||
export PATH="$HOME/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PANAMA_PATH/bin:$BUN_INSTALL/bin:$CARGO_PATH/bin:$PNPM_HOME/bin:$PYENV_ROOT/bin:$HOME/.rbenv/bin:/usr/lib/ccache/bin/:$GOPATH/bin:$DOTNETPATH"
|
||||
|
||||
# Nvm
|
||||
set -h
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
|
||||
source /etc/profile.d/nvm.sh
|
||||
# Auto-switch Node version when entering a directory with .nvmrc
|
||||
_nvm_auto_use() {
|
||||
if [[ -f .nvmrc ]]; then
|
||||
nvm use --silent
|
||||
fi
|
||||
}
|
||||
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND; }_nvm_auto_use"
|
||||
|
||||
# Auto-start or attach tmux for SSH interactive shells
|
||||
if [[ -n "$SSH_CONNECTION" && -z "$TMUX" && $- == *i* ]]; then
|
||||
exec tmux new-session -A -s main
|
||||
fi
|
||||
|
||||
# Zoxide
|
||||
eval "$(zoxide init bash)"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# see `man dnf.conf` for defaults and possible options
|
||||
|
||||
[main]
|
||||
fastestmirror=True
|
||||
max_parallel_downloads=10
|
||||
defaultyes=True
|
||||
keepcache=True
|
||||
deltarpm=True
|
||||
@@ -0,0 +1,18 @@
|
||||
# Stable, colon-free symlinks for the DRM devices.
|
||||
#
|
||||
# Why this exists: Hyprland's AQ_DRM_DEVICES selects which GPU renders, but
|
||||
# - /dev/dri/cardN numbering is assigned at boot and is not stable, and
|
||||
# - /dev/dri/by-path/ names contain colons, which AQ_DRM_DEVICES itself uses
|
||||
# as its separator, so they cannot be used.
|
||||
#
|
||||
# These rules are keyed to specific PCI addresses and simply do not match on
|
||||
# other machines; hypr/env.lua checks that the symlink exists before setting
|
||||
# AQ_DRM_DEVICES, so a non-match is harmless.
|
||||
#
|
||||
# Apply without rebooting: sudo udevadm control --reload && sudo udevadm trigger
|
||||
|
||||
# desktop-gib: Radeon RX 7800 XT (discrete, drives DP-2)
|
||||
KERNEL=="card*", KERNELS=="0000:03:00.0", SUBSYSTEM=="drm", SUBSYSTEMS=="pci", SYMLINK+="dri/amd-dgpu"
|
||||
|
||||
# desktop-gib: Granite Ridge integrated graphics
|
||||
KERNEL=="card*", KERNELS=="0000:12:00.0", SUBSYSTEM=="drm", SUBSYSTEMS=="pci", SYMLINK+="dri/amd-igpu"
|
||||
@@ -0,0 +1,614 @@
|
||||
"""nautilus extension: nautilus_open_any_terminal"""
|
||||
|
||||
# based on: https://github.com/gnunn1/tilix/blob/master/data/nautilus/open-tilix.py
|
||||
|
||||
import ast
|
||||
import re
|
||||
import shlex
|
||||
from dataclasses import dataclass, field
|
||||
from functools import cache
|
||||
from gettext import gettext, translation
|
||||
from os.path import expanduser
|
||||
from subprocess import Popen
|
||||
from typing import Optional
|
||||
from urllib.parse import quote, unquote, urlparse
|
||||
|
||||
from gi import require_version
|
||||
|
||||
try:
|
||||
require_version("Nautilus", "4.1")
|
||||
except ValueError:
|
||||
require_version("Nautilus", "4.0")
|
||||
|
||||
require_version("Gtk", "4.0")
|
||||
|
||||
from gi.repository import Nautilus as FileManager
|
||||
API_VERSION = "4.1"
|
||||
|
||||
from gi.repository import Gio, GLib, GObject, Gtk # noqa: E402 pylint: disable=wrong-import-position
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Terminal:
|
||||
"""Data class representing a terminal configuration."""
|
||||
|
||||
name: str
|
||||
workdir_arguments: Optional[list[str]] = None
|
||||
new_tab_arguments: Optional[list[str]] = None
|
||||
new_window_arguments: Optional[list[str]] = None
|
||||
command_arguments: list[str] = field(default_factory=lambda: ["-e"])
|
||||
flatpak_package: Optional[str] = None
|
||||
|
||||
|
||||
_ = gettext
|
||||
for localedir in [expanduser("~/.local/share/locale"), "/usr/share/locale"]:
|
||||
try:
|
||||
trans = translation("nautilus-open-any-terminal", localedir)
|
||||
trans.install()
|
||||
_ = trans.gettext
|
||||
break
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
TERMINALS = {
|
||||
"alacritty": Terminal("Alacritty"),
|
||||
"app2unit-term": Terminal("app2unit-term"),
|
||||
"blackbox": Terminal(
|
||||
"Black Box",
|
||||
workdir_arguments=["--working-directory"],
|
||||
command_arguments=["-c"],
|
||||
flatpak_package="com.raggesilver.BlackBox",
|
||||
),
|
||||
"blackbox-terminal": Terminal(
|
||||
"Black Box",
|
||||
workdir_arguments=["--working-directory"],
|
||||
command_arguments=["-c"],
|
||||
),
|
||||
"bobcat": Terminal(
|
||||
"Bobcat",
|
||||
workdir_arguments=["--working-dir"],
|
||||
command_arguments=["--"],
|
||||
),
|
||||
"cool-retro-term": Terminal("cool-retro-term", workdir_arguments=["--workdir"]),
|
||||
"custom": Terminal(_("Terminal"), command_arguments=[]),
|
||||
"contour": Terminal(
|
||||
"Contour",
|
||||
workdir_arguments=["--working-directory"],
|
||||
flatpak_package="org.contourterminal.Contour",
|
||||
),
|
||||
"cosmic-term": Terminal("COSMIC Terminal"),
|
||||
"deepin-terminal": Terminal("Deepin Terminal"),
|
||||
"ddterm": Terminal(
|
||||
"Drop down Terminal extension",
|
||||
workdir_arguments=["--working-directory"],
|
||||
flatpak_package="com.github.amezin.ddterm",
|
||||
),
|
||||
"foot": Terminal("Foot"),
|
||||
"footclient": Terminal("FootClient"),
|
||||
"ghostty": Terminal("Ghostty"),
|
||||
"gnome-terminal": Terminal("Terminal", new_tab_arguments=["--tab"], command_arguments=["--"]),
|
||||
"guake": Terminal("Guake", workdir_arguments=["--show", "--new-tab"]),
|
||||
"kermit": Terminal("Kermit"),
|
||||
"kgx": Terminal("Console", new_tab_arguments=["--tab"]),
|
||||
"kitty": Terminal("Kitty"),
|
||||
"konsole": Terminal("Konsole", new_tab_arguments=["--new-tab"]),
|
||||
"mate-terminal": Terminal("Mate Terminal", new_tab_arguments=["--tab"]),
|
||||
"mlterm": Terminal("Mlterm"),
|
||||
"ptyxis": Terminal(
|
||||
"Ptyxis",
|
||||
workdir_arguments=["-d"],
|
||||
command_arguments=["--"],
|
||||
new_tab_arguments=["--tab"],
|
||||
new_window_arguments=["--new-window"],
|
||||
flatpak_package="app.devsuite.Ptyxis",
|
||||
),
|
||||
"ptyxis-nightly": Terminal(
|
||||
"Ptyxis",
|
||||
workdir_arguments=["-d"],
|
||||
command_arguments=["--"],
|
||||
new_tab_arguments=["--tab"],
|
||||
new_window_arguments=["--new-window"],
|
||||
flatpak_package="org.gnome.Ptyxis.Devel",
|
||||
),
|
||||
"qterminal": Terminal("QTerminal"),
|
||||
"rio": Terminal("Rio"),
|
||||
"sakura": Terminal("Sakura"),
|
||||
"st": Terminal("Simple Terminal"),
|
||||
"tabby": Terminal("Tabby", command_arguments=["run"], workdir_arguments=["open"]),
|
||||
"terminator": Terminal("Terminator", new_tab_arguments=["--new-tab"]),
|
||||
"terminology": Terminal("Terminology"),
|
||||
"terminus": Terminal("Terminus"),
|
||||
"termite": Terminal("Termite"),
|
||||
"tilix": Terminal("Tilix", flatpak_package="com.gexperts.Tilix"),
|
||||
"urxvt": Terminal("rxvt-unicode"),
|
||||
"urxvtc": Terminal("urxvtc"),
|
||||
"uwsm-terminal": Terminal("uwsm-terminal"),
|
||||
"uxterm": Terminal("UXTerm"),
|
||||
"warp": Terminal(
|
||||
"Warp",
|
||||
new_tab_arguments=["--virtual-arg-for-tabs"], # This is just to indicate tab support
|
||||
),
|
||||
"wezterm": Terminal(
|
||||
"Wez's Terminal Emulator",
|
||||
workdir_arguments=["--cwd"],
|
||||
new_tab_arguments=["start", "--new-tab"],
|
||||
new_window_arguments=["start"],
|
||||
flatpak_package="org.wezfurlong.wezterm",
|
||||
),
|
||||
"xfce4-terminal": Terminal("Xfce Terminal", new_tab_arguments=["--tab"]),
|
||||
"xterm": Terminal("XTerm"),
|
||||
}
|
||||
|
||||
FLATPAK_PARMS = ["off", "system", "user"]
|
||||
|
||||
terminal = "gnome-terminal"
|
||||
terminal_cmd: list[str] = None # type: ignore
|
||||
terminal_data: Terminal = TERMINALS["gnome-terminal"]
|
||||
new_tab = False
|
||||
flatpak = FLATPAK_PARMS[0]
|
||||
custom_local_command: str
|
||||
custom_remote_command: str
|
||||
|
||||
GSETTINGS_PATH = "com.github.stunkymonkey.nautilus-open-any-terminal"
|
||||
GSETTINGS_KEYBINDINGS = "keybindings"
|
||||
GSETTINGS_BIND_REMOTE = "bind-remote"
|
||||
GSETTINGS_TERMINAL = "terminal"
|
||||
GSETTINGS_NEW_TAB = "new-tab"
|
||||
GSETTINGS_FLATPAK = "flatpak"
|
||||
GSETTINGS_USE_GENERIC_TERMINAL_NAME = "use-generic-terminal-name"
|
||||
GSETTINGS_CUSTOM_LOCAL_COMMAND = "custom-local-command"
|
||||
GSETTINGS_CUSTOM_REMOTE_COMMAND = "custom-remote-command"
|
||||
REMOTE_URI_SCHEME = ["ftp", "sftp"]
|
||||
|
||||
|
||||
# Adapted from https://www.freedesktop.org/software/systemd/man/latest/os-release.html
|
||||
def read_os_release():
|
||||
"""Read and parse the OS release information."""
|
||||
possible_os_release_paths = ["/etc/os-release", "/usr/lib/os-release"]
|
||||
for file_path in possible_os_release_paths:
|
||||
try:
|
||||
with open(file_path, mode="r", encoding="utf-8") as os_release:
|
||||
for line_number, line in enumerate(os_release, start=1):
|
||||
line = line.rstrip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
result = re.match(r"([A-Z][A-Z_0-9]+)=(.*)", line)
|
||||
if result:
|
||||
name, val = result.groups()
|
||||
if val and val[0] in "\"'":
|
||||
val = ast.literal_eval(val)
|
||||
yield name, val
|
||||
else:
|
||||
raise OSError(f"{file_path}:{line_number}: bad line {line!r}")
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
|
||||
@cache
|
||||
def distro_id() -> set[str]:
|
||||
"""get the set of distribution ids"""
|
||||
try:
|
||||
os_release = dict(read_os_release())
|
||||
except OSError:
|
||||
return set(["unknown"])
|
||||
ids = [os_release["ID"]]
|
||||
if id_like := os_release.get("ID_LIKE"):
|
||||
ids.extend(id_like.split(" "))
|
||||
return set(ids)
|
||||
|
||||
|
||||
def parse_custom_command(command: str, data: str | list[str]) -> list[str]:
|
||||
"""Substitute every '%s' in the command with data and split it into arguments"""
|
||||
if isinstance(data, str):
|
||||
data = [data]
|
||||
|
||||
return shlex.split(command.replace("%s", shlex.join(data)))
|
||||
|
||||
|
||||
def run_command_in_terminal(command: list[str], *, cwd: str | None = None):
|
||||
if terminal == "custom":
|
||||
cmd = parse_custom_command(custom_remote_command, command)
|
||||
else:
|
||||
cmd = terminal_cmd.copy()
|
||||
if cwd and terminal_data.workdir_arguments:
|
||||
cmd.extend(terminal_data.workdir_arguments)
|
||||
cmd.append(cwd)
|
||||
cmd.extend(terminal_data.command_arguments)
|
||||
cmd.extend(command)
|
||||
|
||||
Popen(cmd, cwd=cwd) # pylint: disable=consider-using-with
|
||||
|
||||
|
||||
def ssh_command_from_uri(uri: str, *, is_directory: bool):
|
||||
"""Creates an ssh command that executes or cd's into remote uri"""
|
||||
result = urlparse(uri)
|
||||
cmd = ["ssh", "-t"]
|
||||
if result.username:
|
||||
cmd.append(f"{result.username}@{result.hostname}")
|
||||
else:
|
||||
cmd.append(result.hostname) # type: ignore
|
||||
|
||||
if result.port:
|
||||
cmd.append("-p")
|
||||
cmd.append(str(result.port))
|
||||
|
||||
target = shlex.quote(unquote(result.path))
|
||||
if is_directory:
|
||||
cmd.extend(["cd", target, ";", "exec", "${SHELL:-/bin/sh}", "-l"])
|
||||
else:
|
||||
cmd.extend(["exec", target])
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
def open_remote_terminal_in_uri(uri: str):
|
||||
"""Open a new remote terminal"""
|
||||
run_command_in_terminal(ssh_command_from_uri(uri, is_directory=True))
|
||||
|
||||
|
||||
def open_local_terminal_in_uri(uri: str):
|
||||
"""open the new terminal with correct path"""
|
||||
result = urlparse(uri)
|
||||
filename = unquote(result.path)
|
||||
if result.scheme == "admin":
|
||||
run_command_in_terminal(["sudo", "-s"], cwd=filename)
|
||||
return
|
||||
|
||||
if terminal == "warp":
|
||||
# Force new_tab to be considered even without traditional tab arguments
|
||||
Popen( # pylint: disable=consider-using-with
|
||||
["xdg-open", f"warp://action/new_{'tab' if new_tab else 'window'}?path={result.path}"]
|
||||
)
|
||||
return
|
||||
|
||||
cmd = terminal_cmd.copy()
|
||||
if terminal == "custom":
|
||||
cmd = parse_custom_command(custom_local_command, filename)
|
||||
elif filename and terminal_data.workdir_arguments:
|
||||
cmd.extend(terminal_data.workdir_arguments)
|
||||
cmd.append(filename)
|
||||
|
||||
Popen(cmd, cwd=filename) # pylint: disable=consider-using-with
|
||||
|
||||
|
||||
def directory_menu_item_id(*, foreground: bool, remote: bool):
|
||||
return f"OpenTerminal::open{'_' if foreground else '_bg_'}{'remote' if remote else 'file'}_item"
|
||||
|
||||
|
||||
def executable_menu_item_id(*, remote: bool):
|
||||
return f"OpenTerminal::execute{'_remote_' if remote else '_file_'}item"
|
||||
|
||||
|
||||
def get_directory_menu_items(
|
||||
file: FileManager.FileInfo, callback, *, foreground: bool, terminal_name: str | None = None
|
||||
):
|
||||
items = []
|
||||
remote = file.get_uri_scheme() in REMOTE_URI_SCHEME
|
||||
terminal_name = terminal_name or terminal_data.name
|
||||
|
||||
if remote:
|
||||
if foreground:
|
||||
REMOTE_LABEL = _("Open in Remote {}")
|
||||
REMOTE_TIP = _("Open Remote {} in {}")
|
||||
LOCAL_LABEL = _("Open in Local {}")
|
||||
LOCAL_TIP = _("Open Local {} in {}")
|
||||
tip = REMOTE_TIP.format(terminal_name, file.get_name())
|
||||
else:
|
||||
REMOTE_LABEL = _("Open Remote {} Here")
|
||||
REMOTE_TIP = _("Open Remote {} in This Directory")
|
||||
LOCAL_LABEL = _("Open Local {} Here")
|
||||
LOCAL_TIP = _("Open Local {} in This Directory")
|
||||
tip = REMOTE_TIP.format(terminal_name)
|
||||
|
||||
item = FileManager.MenuItem(
|
||||
name=directory_menu_item_id(foreground=foreground, remote=True),
|
||||
label=REMOTE_LABEL.format(terminal_name),
|
||||
tip=tip,
|
||||
)
|
||||
item.connect("activate", callback, file, True)
|
||||
items.append(item)
|
||||
elif foreground:
|
||||
LOCAL_LABEL = _("Open in {}")
|
||||
LOCAL_TIP = _("Open {} in {}")
|
||||
else:
|
||||
LOCAL_LABEL = _("Open {} Here")
|
||||
LOCAL_TIP = _("Open {} in This Directory")
|
||||
|
||||
# Let wezterm handle opening a local terminal
|
||||
if terminal == "wezterm" and flatpak == "off":
|
||||
return items
|
||||
|
||||
if foreground:
|
||||
tip = LOCAL_TIP.format(terminal_name, file.get_name())
|
||||
else:
|
||||
tip = LOCAL_TIP.format(terminal_name)
|
||||
|
||||
item = FileManager.MenuItem(
|
||||
name=directory_menu_item_id(foreground=foreground, remote=False),
|
||||
label=LOCAL_LABEL.format(terminal_name),
|
||||
tip=tip,
|
||||
)
|
||||
item.connect("activate", callback, file, False)
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
|
||||
def get_executable_menu_items(file: FileManager.FileInfo, callback, *, terminal_name: str | None = None):
|
||||
items = []
|
||||
remote = file.get_uri_scheme() in REMOTE_URI_SCHEME
|
||||
terminal_name = terminal_name or terminal_data.name
|
||||
|
||||
if remote:
|
||||
REMOTE_LABEL = _("Execute in Remote {}")
|
||||
REMOTE_TIP = _("Execute {} in {} via SSH")
|
||||
LOCAL_LABEL = _("Execute in Local {}")
|
||||
LOCAL_TIP = _("Execute {} in Local {}")
|
||||
|
||||
tip = REMOTE_TIP.format(file.get_name(), terminal_name)
|
||||
item = FileManager.MenuItem(
|
||||
name=executable_menu_item_id(remote=True),
|
||||
label=REMOTE_LABEL.format(terminal_name),
|
||||
tip=tip,
|
||||
)
|
||||
item.connect("activate", callback, file, True)
|
||||
items.append(item)
|
||||
else:
|
||||
LOCAL_LABEL = _("Execute in {}")
|
||||
LOCAL_TIP = _("Execute {} in {}")
|
||||
|
||||
tip = LOCAL_TIP.format(file.get_name(), terminal_name)
|
||||
item = FileManager.MenuItem(
|
||||
name=executable_menu_item_id(remote=False),
|
||||
label=LOCAL_LABEL.format(terminal_name),
|
||||
tip=tip,
|
||||
)
|
||||
item.connect("activate", callback, file, False)
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
|
||||
def is_executable(file: Gio.File) -> bool:
|
||||
try:
|
||||
attributes = file.query_info("access::can-execute", Gio.FileQueryInfoFlags.NONE)
|
||||
except GLib.Error:
|
||||
return False
|
||||
return attributes.get_attribute_boolean("access::can-execute")
|
||||
|
||||
|
||||
def set_terminal_args(*_args):
|
||||
# pylint: disable=possibly-used-before-assignment
|
||||
"""set the terminal_cmd to the correct values"""
|
||||
global new_tab
|
||||
global flatpak
|
||||
global terminal_cmd
|
||||
global terminal_data
|
||||
global custom_local_command
|
||||
global custom_remote_command
|
||||
value = _gsettings.get_string(GSETTINGS_TERMINAL)
|
||||
newer_tab = _gsettings.get_boolean(GSETTINGS_NEW_TAB)
|
||||
flatpak = FLATPAK_PARMS[_gsettings.get_enum(GSETTINGS_FLATPAK)]
|
||||
new_terminal_data = TERMINALS.get(value)
|
||||
if not new_terminal_data:
|
||||
print(f'open-any-terminal: unknown terminal "{value}"')
|
||||
return
|
||||
|
||||
global terminal
|
||||
terminal = value
|
||||
terminal_data = new_terminal_data
|
||||
if newer_tab and terminal_data.new_tab_arguments:
|
||||
new_tab = newer_tab
|
||||
new_tab_text = "opening in a new tab"
|
||||
else:
|
||||
new_tab_text = "opening a new window"
|
||||
if newer_tab and not terminal_data.new_tab_arguments:
|
||||
new_tab_text += " (terminal does not support tabs)"
|
||||
if flatpak != FLATPAK_PARMS[0] and terminal_data.flatpak_package is not None:
|
||||
terminal_cmd = ["flatpak", "run", "--" + flatpak, terminal_data.flatpak_package]
|
||||
flatpak_text = f"with flatpak as {flatpak}"
|
||||
else:
|
||||
terminal_cmd = [terminal]
|
||||
if terminal == "blackbox" and "fedora" in distro_id():
|
||||
# It's called like this on fedora
|
||||
terminal_cmd[0] = "blackbox-terminal"
|
||||
flatpak = FLATPAK_PARMS[0]
|
||||
flatpak_text = ""
|
||||
|
||||
if terminal == "custom":
|
||||
terminal_cmd = []
|
||||
custom_local_command = _gsettings.get_string(GSETTINGS_CUSTOM_LOCAL_COMMAND)
|
||||
custom_remote_command = _gsettings.get_string(GSETTINGS_CUSTOM_REMOTE_COMMAND)
|
||||
elif new_tab and terminal_data.new_tab_arguments:
|
||||
terminal_cmd.extend(terminal_data.new_tab_arguments)
|
||||
elif terminal_data.new_window_arguments:
|
||||
terminal_cmd.extend(terminal_data.new_window_arguments)
|
||||
|
||||
print(f'open-any-terminal: terminal is set to "{terminal}" {new_tab_text} {flatpak_text}')
|
||||
|
||||
|
||||
if API_VERSION == ("4.0", "4.1"):
|
||||
|
||||
class OpenAnyTerminalShortcutProvider(GObject.GObject, FileManager.MenuProvider):
|
||||
"""Provide keyboard shortcuts for opening terminals in Nautilus."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.previous_cwd = expanduser("~")
|
||||
|
||||
gsettings_source = Gio.SettingsSchemaSource.get_default()
|
||||
if gsettings_source.lookup(GSETTINGS_PATH, True):
|
||||
self._gsettings = Gio.Settings.new(GSETTINGS_PATH)
|
||||
self._setup_keybindings()
|
||||
|
||||
def get_background_items(self, current_folder: FileManager.FileInfo):
|
||||
"""Update current URI when folder changes."""
|
||||
if current_folder:
|
||||
if current_folder.get_uri_scheme() in REMOTE_URI_SCHEME:
|
||||
folder_path = current_folder.get_uri()
|
||||
else:
|
||||
folder_path = current_folder.get_location().get_path()
|
||||
|
||||
if folder_path and folder_path != self.previous_cwd:
|
||||
self.previous_cwd = folder_path
|
||||
return []
|
||||
|
||||
def _open_terminal(self, *_args):
|
||||
"""Open the terminal at the specified URI."""
|
||||
if self._gsettings.get_boolean(GSETTINGS_BIND_REMOTE):
|
||||
open_remote_terminal_in_uri(self.previous_cwd)
|
||||
else:
|
||||
open_local_terminal_in_uri(self.previous_cwd)
|
||||
|
||||
def _setup_keybindings(self):
|
||||
"""Set up custom keybindings for the extension."""
|
||||
self.app = Gtk.Application.get_default()
|
||||
if self.app is None:
|
||||
print("No Gtk.Application found. Keybindings cannot be set.")
|
||||
return
|
||||
|
||||
action = Gio.SimpleAction.new("open_any_terminal", None)
|
||||
action.connect("activate", self._open_terminal)
|
||||
self.app.add_action(action)
|
||||
self._bind_shortcut()
|
||||
self._gsettings.connect("changed", self._update_shortcut)
|
||||
|
||||
def _update_shortcut(self, _gsettings, key):
|
||||
"""remove keybinding"""
|
||||
if key == GSETTINGS_KEYBINDINGS:
|
||||
self.app.set_accels_for_action("app.open_any_terminal", [])
|
||||
self._bind_shortcut()
|
||||
|
||||
def _bind_shortcut(self):
|
||||
"""Parse and update keybindings when settings change."""
|
||||
shortcut = self._gsettings.get_string(GSETTINGS_KEYBINDINGS)
|
||||
|
||||
if not shortcut:
|
||||
self.app.set_accels_for_action("app.open_any_terminal", [])
|
||||
return
|
||||
|
||||
valid, key, mods = Gtk.accelerator_parse(shortcut)
|
||||
if not valid:
|
||||
print("Invalid shortcut in GSettings: %r", shortcut)
|
||||
self.app.set_accels_for_action("app.open_any_terminal", [])
|
||||
return
|
||||
|
||||
normalized = Gtk.accelerator_name(key, mods)
|
||||
self.app.set_accels_for_action("app.open_any_terminal", [normalized])
|
||||
|
||||
elif API_VERSION in ("3.0", "2.0"):
|
||||
|
||||
class OpenAnyTerminalShortcutProviderLegacy(GObject.GObject, FileManager.LocationWidgetProvider):
|
||||
"""Provide keyboard shortcuts for opening terminals in Nautilus/Caja."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
gsettings_source = Gio.SettingsSchemaSource.get_default()
|
||||
if gsettings_source.lookup(GSETTINGS_PATH, True):
|
||||
self._gsettings = Gio.Settings.new(GSETTINGS_PATH)
|
||||
self._gsettings.connect("changed", self._bind_shortcut)
|
||||
self._create_accel_group()
|
||||
self._window = None
|
||||
self._uri = None
|
||||
|
||||
def _create_accel_group(self):
|
||||
self._accel_group = Gtk.AccelGroup()
|
||||
shortcut = self._gsettings.get_string(GSETTINGS_KEYBINDINGS)
|
||||
key, mod = Gtk.accelerator_parse(shortcut)
|
||||
self._accel_group.connect(key, mod, Gtk.AccelFlags.VISIBLE, self._open_terminal)
|
||||
|
||||
def _bind_shortcut(self, _gsettings, key):
|
||||
if key == GSETTINGS_KEYBINDINGS:
|
||||
self._accel_group.disconnect(self._open_terminal)
|
||||
self._create_accel_group()
|
||||
|
||||
def _open_terminal(self, *_args):
|
||||
if _gsettings.get_boolean(GSETTINGS_BIND_REMOTE):
|
||||
open_local_terminal_in_uri(self._uri)
|
||||
else:
|
||||
open_remote_terminal_in_uri(self._uri)
|
||||
|
||||
def get_widget(self, uri, window):
|
||||
"""follows uri and sets the correct window"""
|
||||
self._uri = uri
|
||||
if self._window:
|
||||
self._window.remove_accel_group(self._accel_group)
|
||||
if self._gsettings:
|
||||
window.add_accel_group(self._accel_group)
|
||||
self._window = window
|
||||
|
||||
|
||||
class OpenAnyTerminalExtension(GObject.GObject, FileManager.MenuProvider):
|
||||
"""Provide context menu items for opening terminals in Nautilus."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
gsettings_source = Gio.SettingsSchemaSource.get_default()
|
||||
if gsettings_source.lookup(GSETTINGS_PATH, True):
|
||||
self._gsettings = Gio.Settings.new(GSETTINGS_PATH)
|
||||
|
||||
def _get_terminal_name(self):
|
||||
if self._gsettings.get_boolean(GSETTINGS_USE_GENERIC_TERMINAL_NAME):
|
||||
return _("Terminal")
|
||||
return None
|
||||
|
||||
def _menu_dir_activate_cb(self, menu, file_, remote: bool):
|
||||
if remote:
|
||||
open_remote_terminal_in_uri(file_.get_uri())
|
||||
else:
|
||||
if file_.get_uri_scheme() == "smb":
|
||||
file_uri = "file://" + quote(file_.get_location().get_path())
|
||||
else:
|
||||
file_uri = file_.get_uri()
|
||||
open_local_terminal_in_uri(file_uri)
|
||||
|
||||
def _menu_exe_activate_cb(self, menu, file_, remote: bool):
|
||||
if remote:
|
||||
cmd = ssh_command_from_uri(file_.get_uri(), is_directory=False)
|
||||
else:
|
||||
result = urlparse(file_.get_uri())
|
||||
file = unquote(result.path)
|
||||
|
||||
if result.scheme == "admin":
|
||||
cmd = ["sudo", file]
|
||||
elif terminal in ["xterm", "uxterm"]:
|
||||
cmd = [f"exec {shlex.quote(file)}"]
|
||||
else:
|
||||
cmd = [file]
|
||||
run_command_in_terminal(cmd)
|
||||
|
||||
def get_file_items(self, *args):
|
||||
"""Generates a list of menu items for a file or folder in the Nautilus file manager."""
|
||||
# `args` will be `[files: List[Nautilus.FileInfo]]` in Nautilus 4.0 API,
|
||||
# and `[window: Gtk.Widget, files: List[Nautilus.FileInfo]]` in Nautilus 3.0 API.
|
||||
|
||||
files = args[-1]
|
||||
|
||||
if len(files) != 1:
|
||||
return []
|
||||
file_ = files[0]
|
||||
|
||||
if file_.is_directory():
|
||||
return get_directory_menu_items(
|
||||
file_, self._menu_dir_activate_cb, foreground=True, terminal_name=self._get_terminal_name()
|
||||
)
|
||||
|
||||
if is_executable(file_.get_location()):
|
||||
return get_executable_menu_items(file_, self._menu_exe_activate_cb, terminal_name=self._get_terminal_name())
|
||||
|
||||
return []
|
||||
|
||||
def get_background_items(self, *args):
|
||||
"""Generates a list of background menu items for a file or folder in the Nautilus file manager."""
|
||||
# `args` will be `[folder: Nautilus.FileInfo]` in Nautilus 4.0 API,
|
||||
# and `[window: Gtk.Widget, file: Nautilus.FileInfo]` in Nautilus 3.0 API.
|
||||
|
||||
file_ = args[-1]
|
||||
return get_directory_menu_items(
|
||||
file_, self._menu_dir_activate_cb, foreground=False, terminal_name=self._get_terminal_name()
|
||||
)
|
||||
|
||||
|
||||
source = Gio.SettingsSchemaSource.get_default()
|
||||
if source is not None and source.lookup(GSETTINGS_PATH, True):
|
||||
_gsettings = Gio.Settings.new(GSETTINGS_PATH)
|
||||
_gsettings.connect("changed", set_terminal_args)
|
||||
set_terminal_args()
|
||||
@@ -0,0 +1,57 @@
|
||||
# Tokyo Night Day for btop, the light counterpart to tokyonight-moon.
|
||||
#
|
||||
# btop ships no Tokyo Night light variant at all, and the nearest stock light
|
||||
# theme (flat-remix-light) is a different palette that happens to have a similar
|
||||
# background. Same colours as kitty/themes/tokyonight-day.conf so the terminal
|
||||
# and what runs inside it cannot disagree.
|
||||
#
|
||||
# Gradients keep the same low -> middle -> high meaning as the dark theme, using
|
||||
# Day's darker, more saturated accents: the light versions of these hues are too
|
||||
# faint to read as a filled meter.
|
||||
|
||||
theme[main_bg]="#e1e2e7"
|
||||
theme[main_fg]="#3760bf"
|
||||
theme[title]="#3760bf"
|
||||
theme[hi_fg]="#2e7de9"
|
||||
theme[selected_bg]="#c4c8da"
|
||||
theme[selected_fg]="#2e7de9"
|
||||
theme[inactive_fg]="#7079a8"
|
||||
theme[proc_misc]="#587539"
|
||||
|
||||
theme[cpu_box]="#a8aecb"
|
||||
theme[mem_box]="#a8aecb"
|
||||
theme[net_box]="#a8aecb"
|
||||
theme[proc_box]="#a8aecb"
|
||||
theme[div_line]="#c4c8da"
|
||||
|
||||
theme[temp_start]="#2e7de9"
|
||||
theme[temp_mid]="#8c6c3e"
|
||||
theme[temp_end]="#f52a65"
|
||||
|
||||
theme[cpu_start]="#2e7de9"
|
||||
theme[cpu_mid]="#9854f1"
|
||||
theme[cpu_end]="#f52a65"
|
||||
|
||||
theme[free_start]="#c4c8da"
|
||||
theme[free_mid]="#007197"
|
||||
theme[free_end]="#2e7de9"
|
||||
|
||||
theme[cached_start]="#007197"
|
||||
theme[cached_mid]="#2e7de9"
|
||||
theme[cached_end]="#9854f1"
|
||||
|
||||
theme[available_start]="#8c6c3e"
|
||||
theme[available_mid]="#b15c00"
|
||||
theme[available_end]="#f52a65"
|
||||
|
||||
theme[used_start]="#587539"
|
||||
theme[used_mid]="#8c6c3e"
|
||||
theme[used_end]="#f52a65"
|
||||
|
||||
theme[download_start]="#c4c8da"
|
||||
theme[download_mid]="#2e7de9"
|
||||
theme[download_end]="#007197"
|
||||
|
||||
theme[upload_start]="#c4c8da"
|
||||
theme[upload_mid]="#9854f1"
|
||||
theme[upload_end]="#7847bd"
|
||||
@@ -0,0 +1,56 @@
|
||||
# Tokyo Night Moon for btop, matched to Panama's palette.
|
||||
#
|
||||
# btop ships a "tokyo-night" theme, but it is the Night variant (#1a1b26). The
|
||||
# rest of this desktop is Moon (#222436), and two Tokyo Nights side by side read
|
||||
# as a mistake rather than as a choice.
|
||||
#
|
||||
# The *_start/_mid/_end triples are gradients btop draws meters with: low,
|
||||
# middle, and high. They run blue -> yellow -> red so a saturated resource is
|
||||
# obvious at a glance without reading the number.
|
||||
|
||||
theme[main_bg]="#222436"
|
||||
theme[main_fg]="#c8d3f5"
|
||||
theme[title]="#c8d3f5"
|
||||
theme[hi_fg]="#82aaff"
|
||||
theme[selected_bg]="#3b4261"
|
||||
theme[selected_fg]="#82aaff"
|
||||
theme[inactive_fg]="#636da6"
|
||||
theme[proc_misc]="#a5e8b5"
|
||||
|
||||
theme[cpu_box]="#4d5685"
|
||||
theme[mem_box]="#4d5685"
|
||||
theme[net_box]="#4d5685"
|
||||
theme[proc_box]="#4d5685"
|
||||
theme[div_line]="#3b4261"
|
||||
|
||||
theme[temp_start]="#82aaff"
|
||||
theme[temp_mid]="#ffc777"
|
||||
theme[temp_end]="#ff757f"
|
||||
|
||||
theme[cpu_start]="#82aaff"
|
||||
theme[cpu_mid]="#c099ff"
|
||||
theme[cpu_end]="#ff757f"
|
||||
|
||||
theme[free_start]="#3b4261"
|
||||
theme[free_mid]="#589ed7"
|
||||
theme[free_end]="#86e1fc"
|
||||
|
||||
theme[cached_start]="#86e1fc"
|
||||
theme[cached_mid]="#82aaff"
|
||||
theme[cached_end]="#c099ff"
|
||||
|
||||
theme[available_start]="#ffc777"
|
||||
theme[available_mid]="#ff966c"
|
||||
theme[available_end]="#ff757f"
|
||||
|
||||
theme[used_start]="#a5e8b5"
|
||||
theme[used_mid]="#ffc777"
|
||||
theme[used_end]="#ff757f"
|
||||
|
||||
theme[download_start]="#3b4261"
|
||||
theme[download_mid]="#82aaff"
|
||||
theme[download_end]="#86e1fc"
|
||||
|
||||
theme[upload_start]="#3b4261"
|
||||
theme[upload_mid]="#c099ff"
|
||||
theme[upload_end]="#fca7ea"
|
||||
@@ -1,7 +1,7 @@
|
||||
# Available matches
|
||||
| Trigger | Replace |
|
||||
|--------------|--------------------------------------------------------|
|
||||
| :block: | \```🖰\n``` |
|
||||
|--------------|-----------------------------------------------------------------|
|
||||
| :block: | \```🖰<br>``` |
|
||||
| :code: | \`🖰` |
|
||||
| :h1: | # |
|
||||
| :h2: | ## |
|
||||
@@ -14,9 +14,10 @@
|
||||
| :strike: | \~\~🖰~~ |
|
||||
| :url: | \[🖰]() |
|
||||
| :image: | !\[](🖰) |
|
||||
| :horizontal: | ___\n |
|
||||
| :horizontal: | ___<br> |
|
||||
| :task: | - [ ] |
|
||||
| :donetask: | - [x] |
|
||||
| :collapse: | \<details>\<summary>🖰\</summary>\n\<p>\n\n\</p>\n\</details> |
|
||||
| :collapse: | \<details><br>\<summary>\</summary><br>🖱<br>\</details> |
|
||||
| :comment: | \<\!--- 🖱 --> |
|
||||
|
||||
**Note: The 🖰 symbol is where your mouse cursor will be after the trigger and \n represents a new line.**
|
||||
**Note**: The symbol "🖰" denotes where your mouse cursor will be after the trigger.
|
||||
|
||||
@@ -2,6 +2,6 @@ author: "Jos\xE9 Ferreira"
|
||||
description: A simple package to make writing Markdown easier
|
||||
name: markdown-shortcuts
|
||||
title: Markdown shortcuts
|
||||
version: 0.1.0
|
||||
version: 0.2.0
|
||||
homepage: "https://github.com/jpmvferreira/espanso-mega-pack"
|
||||
tags: ["markdown", "development", "writing"]
|
||||
@@ -1,65 +1,83 @@
|
||||
name: markdown-shortcuts
|
||||
parent: default
|
||||
|
||||
matches:
|
||||
- triggers: [":block:", ":mb:"]
|
||||
- trigger: ":block:"
|
||||
label: "Markdown - Code block"
|
||||
replace: |-
|
||||
```$|$
|
||||
```
|
||||
force_clipboard: true
|
||||
|
||||
- triggers: [":code:", ":mc:"]
|
||||
- trigger: ":code:"
|
||||
label: "Markdown - Inline code"
|
||||
replace: |-
|
||||
`$|$`
|
||||
force_clipboard: true
|
||||
|
||||
- trigger: ":h1:"
|
||||
label: "Markdown - Header1"
|
||||
replace: "#"
|
||||
|
||||
- trigger: ":h2:"
|
||||
label: "Markdown - Header2"
|
||||
replace: "##"
|
||||
|
||||
- trigger: ":h3:"
|
||||
label: "Markdown - Header3"
|
||||
replace: "###"
|
||||
|
||||
- trigger: ":h4:"
|
||||
label: "Markdown - Header4"
|
||||
replace: "####"
|
||||
|
||||
- trigger: ":h5:"
|
||||
label: "Markdown - Header5"
|
||||
replace: "#####"
|
||||
|
||||
- trigger: ":h6:"
|
||||
label: "Markdown - Header6"
|
||||
replace: "######"
|
||||
|
||||
- trigger: ":bold:"
|
||||
label: "Markdown - Bold"
|
||||
replace: "**$|$**"
|
||||
|
||||
- trigger: ":italic:"
|
||||
label: "Markdown - Italic"
|
||||
replace: "*$|$*"
|
||||
|
||||
- trigger: ":strike:"
|
||||
label: "Markdown - Strikethrough"
|
||||
replace: "~~$|$~~"
|
||||
|
||||
- trigger: ":url:"
|
||||
label: "Markdown - URL"
|
||||
replace: "[$|$]()"
|
||||
|
||||
- triggers: [":image:", ":img:"]
|
||||
- trigger: ":img:"
|
||||
label: "Markdown - Image"
|
||||
replace: ""
|
||||
|
||||
- triggers: [":horizontal:", ":mh:"]
|
||||
- trigger: ":horizontal:"
|
||||
label: "Markdown - Horizontal line"
|
||||
replace: "___"
|
||||
|
||||
- triggers: [":task:", ":mt:"]
|
||||
- trigger: ":task:"
|
||||
label: "Markdown - Task"
|
||||
replace: "- [ ] "
|
||||
|
||||
- triggers: [":taskdone:", ":mtd:"]
|
||||
- trigger: ":taskdone:"
|
||||
label: "Markdown - Task completed"
|
||||
replace: "- [x] "
|
||||
|
||||
- triggers: [":collapse:", ":mcol:"]
|
||||
- trigger: ":collapse:"
|
||||
label: "Markdown - Collapse block"
|
||||
replace: |-
|
||||
<details>
|
||||
<summary></summary>
|
||||
|
||||
$|$
|
||||
</details>
|
||||
force_clipboard: true
|
||||
|
||||
- trigger: ":comment:"
|
||||
label: "Markdown - Comment"
|
||||
replace: "<!--- $|$ -->"
|
||||
force_clipboard: true
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
{
|
||||
"wmClass": "Spotify",
|
||||
"mode": "float"
|
||||
},
|
||||
{
|
||||
"wmClass": "bitwarden",
|
||||
"mode": "float"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -29,41 +29,47 @@
|
||||
}
|
||||
|
||||
.window-tiled-border {
|
||||
border-width: 4px;
|
||||
border-color: rgb(130,170,255);
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-split-border {
|
||||
border-width: 4px;
|
||||
border-color: rgb(130,170,255);
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
/* forge-36le: St does not paint an asymmetric (single-side) border when a
|
||||
* border-radius is set, so the directional hint never rendered. Drop the radius
|
||||
* on the one-edge variants; St paints a straight single-side border reliably. */
|
||||
|
||||
.window-split-horizontal {
|
||||
border-left-width: 0;
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.window-split-vertical {
|
||||
border-left-width: 0;
|
||||
border-top-width: 0;
|
||||
border-right-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.window-stacked-border {
|
||||
border-width: 4px;
|
||||
border-color: rgb(130,170,255);
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-tabbed-border {
|
||||
border-width: 4px;
|
||||
border-color: rgb(130,170,255);
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
@@ -74,7 +80,7 @@
|
||||
|
||||
.window-tabbed-tab {
|
||||
background-color: rgba(54, 47, 45, 1);
|
||||
border-color: rgba(130,170,255,0.6);
|
||||
border-color: rgba(98,160,234,0.6);
|
||||
border-width: 1px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
@@ -83,7 +89,7 @@
|
||||
}
|
||||
|
||||
.window-tabbed-tab-active {
|
||||
background-color: rgb(130,170,255);
|
||||
background-color: rgb(98,160,234);
|
||||
color: black;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
@@ -101,32 +107,84 @@
|
||||
}
|
||||
|
||||
.window-floated-border {
|
||||
border-width: 4px;
|
||||
border-color: rgb(130,170,255);
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-tilepreview-tiled {
|
||||
border-width: 1px;
|
||||
border-color: rgba(130,170,255,0.3);
|
||||
border-color: rgba(98,160,234,0.3);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(130,170,255,0.2);
|
||||
background-color: rgba(98,160,234,0.2);
|
||||
}
|
||||
|
||||
.window-tilepreview-stacked {
|
||||
border-width: 1px;
|
||||
border-color: rgba(130,170,255,0.3);
|
||||
border-color: rgba(98,160,234,0.3);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(130,170,255,0.2);
|
||||
background-color: rgba(98,160,234,0.2);
|
||||
}
|
||||
|
||||
.window-tilepreview-swap {
|
||||
border-width: 1px;
|
||||
border-color: rgba(162, 247, 43, 0.4);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(162, 247, 43, 0.4);
|
||||
}
|
||||
|
||||
.window-tilepreview-tabbed {
|
||||
border-width: 1px;
|
||||
border-color: rgba(130,170,255,0.3);
|
||||
border-color: rgba(98,160,234,0.3);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(130,170,255,0.2);
|
||||
background-color: rgba(98,160,234,0.2);
|
||||
}
|
||||
|
||||
/* Keybindings Cheatsheet Overlay */
|
||||
|
||||
.forge-cheatsheet {
|
||||
background-color: #2d2d2d;
|
||||
border-radius: 16px;
|
||||
padding: 24px 32px;
|
||||
border: 1px solid #4d4d4d;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.forge-cheatsheet-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-category {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #78aeed;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-row {
|
||||
font-size: 12px;
|
||||
color: #eeeeec;
|
||||
margin: 4px 0;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-key {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
color: #f5c211;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-desc {
|
||||
color: #d0cfcc;
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
.tiled {
|
||||
color: rgba(236, 94, 94, 1);
|
||||
opacity: 1;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.split {
|
||||
color: rgba(255, 246, 108, 1);
|
||||
opacity: 1;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.stacked {
|
||||
color: rgba(247, 162, 43, 1);
|
||||
opacity: 1;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.tabbed {
|
||||
color: rgba(17, 199, 224, 1);
|
||||
opacity: 1;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.floated {
|
||||
color: rgba(180, 167, 214, 1);
|
||||
border-width: 3px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.window-tiled-border {
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-split-border {
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
/* forge-36le: St does not paint an asymmetric (single-side) border when a
|
||||
* border-radius is set, so the directional hint never rendered. Drop the radius
|
||||
* on the one-edge variants; St paints a straight single-side border reliably. */
|
||||
|
||||
.window-split-horizontal {
|
||||
border-left-width: 0;
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.window-split-vertical {
|
||||
border-left-width: 0;
|
||||
border-top-width: 0;
|
||||
border-right-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.window-stacked-border {
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-tabbed-border {
|
||||
border-width: 3px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-tabbed-bg {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.window-tabbed-tab {
|
||||
background-color: rgba(54, 47, 45, 1);
|
||||
border-color: rgba(98,160,234,0.6);
|
||||
border-width: 1px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
margin: 1px;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.window-tabbed-tab-active {
|
||||
background-color: rgb(98,160,234);
|
||||
color: black;
|
||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.window-tabbed-tab-close {
|
||||
padding: 3px;
|
||||
margin: 4px;
|
||||
border-radius: 16px;
|
||||
width: 16px;
|
||||
background-color: #e06666;
|
||||
}
|
||||
|
||||
.window-tabbed-tab-icon {
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
.window-floated-border {
|
||||
border-width: 1px;
|
||||
border-color: rgb(98,160,234);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.window-tilepreview-tiled {
|
||||
border-width: 1px;
|
||||
border-color: rgba(98,160,234,0.3);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(98,160,234,0.2);
|
||||
}
|
||||
|
||||
.window-tilepreview-stacked {
|
||||
border-width: 1px;
|
||||
border-color: rgba(98,160,234,0.3);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(98,160,234,0.2);
|
||||
}
|
||||
|
||||
.window-tilepreview-swap {
|
||||
border-width: 1px;
|
||||
border-color: rgba(162, 247, 43, 0.4);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(162, 247, 43, 0.4);
|
||||
}
|
||||
|
||||
.window-tilepreview-tabbed {
|
||||
border-width: 1px;
|
||||
border-color: rgba(98,160,234,0.3);
|
||||
border-style: solid;
|
||||
border-radius: 14px;
|
||||
background-color: rgba(98,160,234,0.2);
|
||||
}
|
||||
|
||||
/* Keybindings Cheatsheet Overlay */
|
||||
|
||||
.forge-cheatsheet {
|
||||
background-color: #2d2d2d;
|
||||
border-radius: 16px;
|
||||
padding: 24px 32px;
|
||||
border: 1px solid #4d4d4d;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.forge-cheatsheet-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-category {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: #78aeed;
|
||||
margin-top: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-row {
|
||||
font-size: 12px;
|
||||
color: #eeeeec;
|
||||
margin: 4px 0;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-key {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
color: #f5c211;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.forge-cheatsheet-desc {
|
||||
color: #d0cfcc;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
file:///home/gib/Documents Documents
|
||||
file:///home/gib/Documents/Code Code
|
||||
file:///home/gib/Downloads Downloads
|
||||
file:///home/gib/Music Music
|
||||
file:///home/gib/Pictures Pictures
|
||||
file:///home/gib/Videos Videos
|
||||
file:///home/gib/.local/share/Panama Panama
|
||||
smb://server-gib.local/homes/ Gib's Server
|
||||
@@ -0,0 +1,78 @@
|
||||
@define-color accent_color rgba(192,153,255,0.8);
|
||||
@define-color accent_bg_color rgba(130,170,255,0.8);
|
||||
@define-color accent_fg_color #ffffff;
|
||||
@define-color destructive_color #ff7b63;
|
||||
@define-color destructive_bg_color #c01c28;
|
||||
@define-color destructive_fg_color #ffffff;
|
||||
@define-color success_color #8ff0a4;
|
||||
@define-color success_bg_color #26a269;
|
||||
@define-color success_fg_color #ffffff;
|
||||
@define-color warning_color #f8e45c;
|
||||
@define-color warning_bg_color #cd9309;
|
||||
@define-color warning_fg_color rgba(0, 0, 0, 0.8);
|
||||
@define-color error_color #ff7b63;
|
||||
@define-color error_bg_color #c01c28;
|
||||
@define-color error_fg_color #ffffff;
|
||||
@define-color window_bg_color rgba(46,47,61,0.98);
|
||||
@define-color window_fg_color #ffffff;
|
||||
@define-color view_bg_color rgba(46,47,61,0.98);
|
||||
@define-color view_fg_color #ffffff;
|
||||
@define-color headerbar_bg_color rgba(46,47,61,0.98);
|
||||
@define-color headerbar_fg_color #ffffff;
|
||||
@define-color headerbar_border_color #ffffff;
|
||||
@define-color headerbar_backdrop_color @window_bg_color;
|
||||
@define-color headerbar_shade_color rgba(46,47,61,0.05);
|
||||
@define-color card_bg_color rgba(180, 200, 255, 0.15);
|
||||
@define-color card_fg_color #ffffff;
|
||||
@define-color card_shade_color rgba(0, 0, 0, 0.36);
|
||||
@define-color dialog_bg_color rgba(46,47,61,0.98);
|
||||
@define-color dialog_fg_color #ffffff;
|
||||
@define-color popover_bg_color rgba(46,47,61,0.98);
|
||||
@define-color popover_fg_color #ffffff;
|
||||
@define-color shade_color rgba(0, 0, 0, 0);
|
||||
@define-color scrollbar_outline_color rgba(0, 0, 0, 0.5);
|
||||
@define-color blue_1 #99c1f1;
|
||||
@define-color blue_2 #62a0ea;
|
||||
@define-color blue_3 #3584e4;
|
||||
@define-color blue_4 #1c71d8;
|
||||
@define-color blue_5 #1a5fb4;
|
||||
@define-color green_1 #8ff0a4;
|
||||
@define-color green_2 #57e389;
|
||||
@define-color green_3 #33d17a;
|
||||
@define-color green_4 #2ec27e;
|
||||
@define-color green_5 #26a269;
|
||||
@define-color yellow_1 #f9f06b;
|
||||
@define-color yellow_2 #f8e45c;
|
||||
@define-color yellow_3 #f6d32d;
|
||||
@define-color yellow_4 #f5c211;
|
||||
@define-color yellow_5 #e5a50a;
|
||||
@define-color orange_1 #ffbe6f;
|
||||
@define-color orange_2 #ffa348;
|
||||
@define-color orange_3 #ff7800;
|
||||
@define-color orange_4 #e66100;
|
||||
@define-color orange_5 #c64600;
|
||||
@define-color red_1 #f66151;
|
||||
@define-color red_2 #ed333b;
|
||||
@define-color red_3 #e01b24;
|
||||
@define-color red_4 #c01c28;
|
||||
@define-color red_5 #a51d2d;
|
||||
@define-color purple_1 #dc8add;
|
||||
@define-color purple_2 #c061cb;
|
||||
@define-color purple_3 #9141ac;
|
||||
@define-color purple_4 #813d9c;
|
||||
@define-color purple_5 #613583;
|
||||
@define-color brown_1 #cdab8f;
|
||||
@define-color brown_2 #b5835a;
|
||||
@define-color brown_3 #986a44;
|
||||
@define-color brown_4 #865e3c;
|
||||
@define-color brown_5 #63452c;
|
||||
@define-color light_1 #ffffff;
|
||||
@define-color light_2 #f6f5f4;
|
||||
@define-color light_3 #deddda;
|
||||
@define-color light_4 #c0bfbc;
|
||||
@define-color light_5 #9a9996;
|
||||
@define-color dark_1 #77767b;
|
||||
@define-color dark_2 #5e5c64;
|
||||
@define-color dark_3 #3d3846;
|
||||
@define-color dark_4 #241f31;
|
||||
@define-color dark_5 #000000;
|
||||
@@ -0,0 +1,34 @@
|
||||
# GENERATED FILE -- edit settings.ini.template instead.
|
||||
#
|
||||
# The theme name and dark preference below follow Panama's colour
|
||||
# scheme, so this file is regenerated on every switch and is not
|
||||
# committed. Under GNOME, gnome-settings-daemon publishes these over
|
||||
# XSETTINGS and this file is ignored; under Hyprland there is no
|
||||
# settings daemon, so for GTK3 it is authoritative -- which is why it
|
||||
# has to change with the scheme rather than being pinned to dark.
|
||||
#
|
||||
[Settings]
|
||||
# These values must match `gsettings get org.gnome.desktop.interface ...`.
|
||||
#
|
||||
# Under GNOME, gnome-settings-daemon publishes settings over XSETTINGS and this
|
||||
# file is effectively ignored. Under Hyprland there is no settings daemon, so
|
||||
# this file becomes authoritative for GTK3 — which is why it previously named
|
||||
# themes that aren't installed (Tahoe-Dark, WhiteSur-cursors) without anything
|
||||
# appearing broken.
|
||||
gtk-theme-name=@GTK_THEME@
|
||||
gtk-icon-theme-name=Adwaita
|
||||
gtk-font-name=Adwaita Sans 11
|
||||
gtk-cursor-theme-name=oreo_blue_cursors
|
||||
gtk-cursor-theme-size=24
|
||||
gtk-application-prefer-dark-theme=@PREFER_DARK@
|
||||
|
||||
gtk-toolbar-style=GTK_TOOLBAR_ICONS
|
||||
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
|
||||
gtk-button-images=0
|
||||
gtk-menu-images=0
|
||||
gtk-enable-event-sounds=1
|
||||
gtk-enable-input-feedback-sounds=0
|
||||
gtk-xft-antialias=1
|
||||
gtk-xft-hinting=1
|
||||
gtk-xft-hintstyle=hintslight
|
||||
gtk-xft-rgba=rgb
|
||||
Executable
+598
@@ -0,0 +1,598 @@
|
||||
/* GTK NAMED COLORS ---------------- use responsibly! */
|
||||
@define-color accent_bg_color RGB(130 170 255 / 80%);
|
||||
|
||||
@define-color destructive_bg_color #c01c28;
|
||||
@define-color destructive_fg_color white;
|
||||
@define-color success_bg_color #26a269;
|
||||
@define-color success_fg_color white;
|
||||
@define-color warning_bg_color #cd9309;
|
||||
@define-color warning_fg_color RGB(0 0 0 / 80%);
|
||||
@define-color error_bg_color #c01c28;
|
||||
@define-color error_fg_color white;
|
||||
@define-color accent_color oklab(from @accent_bg_color max(l, 0.85) a b);
|
||||
@define-color destructive_color oklab(from @destructive_bg_color max(l, 0.85) a b);
|
||||
@define-color success_color oklab(from @success_bg_color max(l, 0.85) a b);
|
||||
@define-color warning_color oklab(from @warning_bg_color max(l, 0.85) a b);
|
||||
@define-color error_color oklab(from @error_bg_color max(l, 0.85) a b);
|
||||
|
||||
@define-color window_bg_color #2e2f3d;
|
||||
@define-color window_fg_color white;
|
||||
@define-color view_bg_color @window_bg_color
|
||||
@define-color view_fg_color white;
|
||||
@define-color headerbar_bg_color @window_bg_color
|
||||
@define-color headerbar_fg_color white;
|
||||
@define-color headerbar_border_color white;
|
||||
@define-color headerbar_backdrop_color @window_bg_color;
|
||||
@define-color headerbar_shade_color RGB(46 47 61 / 8%);
|
||||
@define-color headerbar_darker_shade_color RGB(0, 0, 6, 0.9);
|
||||
|
||||
@define-color sidebar_bg_color #3c3c4b;
|
||||
@define-color sidebar_fg_color white;
|
||||
@define-color sidebar_backdrop_color #3c3c4b;
|
||||
@define-color sidebar_shade_color RGB(60 60 75/8%);
|
||||
@define-color sidebar_border_color RGB(75 75 90/36%);
|
||||
@define-color secondary_sidebar_bg_color #3c3c4b;
|
||||
@define-color secondary_sidebar_fg_color white;
|
||||
@define-color secondary_sidebar_backdrop_color #3c3c4b;
|
||||
@define-color secondary_sidebar_shade_color RGB(0 0 6/25%);
|
||||
@define-color secondary_sidebar_border_color RGB(0 0 6/36%);
|
||||
@define-color card_bg_color RGB(180 200 255/15%);
|
||||
@define-color card_fg_color white;
|
||||
@define-color card_shade_color RGB(46 47 61/8%);
|
||||
@define-color dialog_bg_color @window_bg_color;
|
||||
@define-color dialog_fg_color white;
|
||||
@define-color popover_bg_color @window_bg_color;
|
||||
@define-color popover_fg_color white;
|
||||
@define-color popover_shade_color RGB(0 0 6/25%);
|
||||
@define-color thumbnail_bg_color @window_bg_color;
|
||||
@define-color thumbnail_fg_color white;
|
||||
@define-color shade_color RGB(0 0 6/25%);
|
||||
@define-color scrollbar_outline_color RGB(0 0 12/95%);
|
||||
@define-color blue_1 #99c1f1;
|
||||
@define-color blue_2 #62a0ea;
|
||||
@define-color blue_3 #3584e4;
|
||||
@define-color blue_4 #1c71d8;
|
||||
@define-color blue_5 #1a5fb4;
|
||||
@define-color green_1 #8ff0a4;
|
||||
@define-color green_2 #57e389;
|
||||
@define-color green_3 #33d17a;
|
||||
@define-color green_4 #2ec27e;
|
||||
@define-color green_5 #26a269;
|
||||
@define-color yellow_1 #f9f06b;
|
||||
@define-color yellow_2 #f8e45c;
|
||||
@define-color yellow_3 #f6d32d;
|
||||
@define-color yellow_4 #f5c211;
|
||||
@define-color yellow_5 #e5a50a;
|
||||
@define-color orange_1 #ffbe6f;
|
||||
@define-color orange_2 #ffa348;
|
||||
@define-color orange_3 #ff7800;
|
||||
@define-color orange_4 #e66100;
|
||||
@define-color orange_5 #c64600;
|
||||
@define-color red_1 #f66151;
|
||||
@define-color red_2 #ed333b;
|
||||
@define-color red_3 #e01b24;
|
||||
@define-color red_4 #c01c28;
|
||||
@define-color red_5 #a51d2d;
|
||||
@define-color purple_1 #dc8add;
|
||||
@define-color purple_2 #c061cb;
|
||||
@define-color purple_3 #9141ac;
|
||||
@define-color purple_4 #813d9c;
|
||||
@define-color purple_5 #613583;
|
||||
@define-color brown_1 #cdab8f;
|
||||
@define-color brown_2 #b5835a;
|
||||
@define-color brown_3 #986a44;
|
||||
@define-color brown_4 #865e3c;
|
||||
@define-color brown_5 #63452c;
|
||||
@define-color light_1 #ffffff;
|
||||
@define-color light_2 #f6f5f4;
|
||||
@define-color light_3 #deddda;
|
||||
@define-color light_4 #c0bfbc;
|
||||
@define-color light_5 #9a9996;
|
||||
@define-color dark_1 #77767b;
|
||||
@define-color dark_2 #5e5c64;
|
||||
@define-color dark_3 #3d3846;
|
||||
@define-color dark_4 #241f31;
|
||||
@define-color dark_5 #000000;
|
||||
|
||||
:root {
|
||||
--standalone-color-oklab: max(l, 0.85) a b;
|
||||
--accent-color: oklab(
|
||||
from var(--accent-bg-color) var(--standalone-color-oklab)
|
||||
);
|
||||
--destructive-color: oklab(
|
||||
from var(--destructive-bg-color) var(--standalone-color-oklab)
|
||||
);
|
||||
--success-color: oklab(
|
||||
from var(--success-bg-color) var(--standalone-color-oklab)
|
||||
);
|
||||
--warning-color: oklab(
|
||||
from var(--warning-bg-color) var(--standalone-color-oklab)
|
||||
);
|
||||
--error-color: oklab(
|
||||
from var(--error-bg-color) var(--standalone-color-oklab)
|
||||
);
|
||||
--active-toggle-bg-color: rgb(255 255 255 / 20%);
|
||||
--active-toggle-fg-color: #ffffff;
|
||||
--overview-bg-color: #2e2f3d;
|
||||
--overview-fg-color: #ffffff;
|
||||
}
|
||||
|
||||
@import '../gtk-3.0/libadwaita.css';
|
||||
@import '../gtk-3.0/libadwaita-tweaks.css';
|
||||
|
||||
* {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
window {
|
||||
border-radius: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.maximize window {
|
||||
border-radius: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
window.maximize {
|
||||
border-radius: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
/* More customizations */
|
||||
/* GTK3 */
|
||||
button.titlebutton {
|
||||
color: transparent;
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
padding: 0;
|
||||
margin: 0 2px;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
button.titlebutton:backdrop {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*****************
|
||||
* Title buttons *
|
||||
*****************/
|
||||
.raven .expander-button,
|
||||
.raven .raven-header:not(.top) button.image-button,
|
||||
widget > box.terminal-titlebar > button.image-button,
|
||||
widget > box.terminal-titlebar > button.image-button.toggle,
|
||||
.terminix-session-sidebar button.tilix-sidebar-close-button,
|
||||
.tilix-session-sidebar button.tilix-sidebar-close-button,
|
||||
.nemo-window toolbar.primary-toolbar > toolitem > box.raised > button,
|
||||
.nemo-window toolbar.primary-toolbar > toolitem > box.linked.raised > button,
|
||||
.nautilus-window headerbar > revealer > button,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton,
|
||||
headerbar button.titlebutton,
|
||||
.csd headerbar button.titlebutton,
|
||||
.csd headerbar.default-decoration button.titlebutton,
|
||||
.titlebar button.titlebutton,
|
||||
.csd .titlebar button.titlebutton,
|
||||
.titlebar.default-decoration button.titlebutton,
|
||||
.csd .titlebar.default-decoration button.titlebutton,
|
||||
row button.image-button,
|
||||
check,
|
||||
radio,
|
||||
headerbar.default-decoration button.titlebutton,
|
||||
headerbar button.suggested-action,
|
||||
headerbar button.destructive-action,
|
||||
headerbar button:not(.suggested-action):not(.destructive-action),
|
||||
button.close,
|
||||
button.circular {
|
||||
border-radius: 9999px;
|
||||
-gtk-outline-radius: 9999px;
|
||||
}
|
||||
|
||||
headerbar,
|
||||
.titlebar {
|
||||
padding: 0 14px;
|
||||
}
|
||||
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.close,
|
||||
headerbar button.titlebutton.close,
|
||||
.titlebar button.titlebutton.close {
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
padding: 0;
|
||||
margin: 0 2px;
|
||||
color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
background-color: #f25056;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.maximize,
|
||||
headerbar button.titlebutton.maximize,
|
||||
.titlebar button.titlebutton.maximize {
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
padding: 0;
|
||||
margin: 0 2px;
|
||||
color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
background-color: #39ea49;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.minimize,
|
||||
headerbar button.titlebutton.minimize,
|
||||
.titlebar button.titlebutton.minimize {
|
||||
min-width: 12px;
|
||||
min-height: 12px;
|
||||
padding: 0;
|
||||
margin: 0 2px;
|
||||
color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
background-color: #fac536;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
#MozillaGtkWidget.background
|
||||
headerbar.titlebar.default-decoration
|
||||
button.titlebutton.close:hover,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.close:hover,
|
||||
headerbar button.titlebutton.close:hover,
|
||||
.csd headerbar button.titlebutton.close:hover,
|
||||
headerbar.default-decoration button.titlebutton.close:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:hover,
|
||||
.titlebar button.titlebutton.close:hover,
|
||||
.csd .titlebar button.titlebutton.close:hover,
|
||||
.titlebar.default-decoration button.titlebutton.close:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:hover {
|
||||
padding: 2px;
|
||||
margin: -2px 0;
|
||||
background-color: #f25056;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
#MozillaGtkWidget.background
|
||||
headerbar.titlebar.default-decoration
|
||||
button.titlebutton.maximize:hover,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.maximize:hover,
|
||||
headerbar button.titlebutton.maximize:hover,
|
||||
.csd headerbar button.titlebutton.maximize:hover,
|
||||
headerbar.default-decoration button.titlebutton.maximize:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:hover,
|
||||
.titlebar button.titlebutton.maximize:hover,
|
||||
.csd .titlebar button.titlebutton.maximize:hover,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:hover {
|
||||
padding: 2px;
|
||||
margin: -2px 0;
|
||||
background-color: #39ea49;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
#MozillaGtkWidget.background
|
||||
headerbar.titlebar.default-decoration
|
||||
button.titlebutton.minimize:hover,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.minimize:hover,
|
||||
headerbar button.titlebutton.minimize:hover,
|
||||
.csd headerbar button.titlebutton.minimize:hover,
|
||||
headerbar.default-decoration button.titlebutton.minimize:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:hover,
|
||||
.titlebar button.titlebutton.minimize:hover,
|
||||
.csd .titlebar button.titlebutton.minimize:hover,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:hover {
|
||||
padding: 2px;
|
||||
margin: -2px 0;
|
||||
background-color: #fac536;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.close:hover,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.maximize:hover,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.minimize:hover,
|
||||
headerbar button.titlebutton.close:hover,
|
||||
headerbar button.titlebutton.close:backdrop:hover,
|
||||
headerbar button.titlebutton.maximize:hover,
|
||||
headerbar button.titlebutton.maximize:backdrop:hover,
|
||||
headerbar button.titlebutton.minimize:hover,
|
||||
headerbar button.titlebutton.minimize:backdrop:hover,
|
||||
.csd headerbar button.titlebutton.close:hover,
|
||||
.csd headerbar button.titlebutton.close:backdrop:hover,
|
||||
.csd headerbar button.titlebutton.maximize:hover,
|
||||
.csd headerbar button.titlebutton.maximize:backdrop:hover,
|
||||
.csd headerbar button.titlebutton.minimize:hover,
|
||||
.csd headerbar button.titlebutton.minimize:backdrop:hover,
|
||||
headerbar.default-decoration button.titlebutton.close:hover,
|
||||
headerbar.default-decoration button.titlebutton.close:backdrop:hover,
|
||||
headerbar.default-decoration button.titlebutton.maximize:hover,
|
||||
headerbar.default-decoration button.titlebutton.maximize:backdrop:hover,
|
||||
headerbar.default-decoration button.titlebutton.minimize:hover,
|
||||
headerbar.default-decoration button.titlebutton.minimize:backdrop:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:backdrop:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:backdrop:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:hover,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:backdrop:hover,
|
||||
.titlebar button.titlebutton.close:hover,
|
||||
.titlebar button.titlebutton.close:backdrop:hover,
|
||||
.titlebar button.titlebutton.maximize:hover,
|
||||
.titlebar button.titlebutton.maximize:backdrop:hover,
|
||||
.titlebar button.titlebutton.minimize:hover,
|
||||
.titlebar button.titlebutton.minimize:backdrop:hover,
|
||||
.csd .titlebar button.titlebutton.close:hover,
|
||||
.csd .titlebar button.titlebutton.close:backdrop:hover,
|
||||
.csd .titlebar button.titlebutton.maximize:hover,
|
||||
.csd .titlebar button.titlebutton.maximize:backdrop:hover,
|
||||
.csd .titlebar button.titlebutton.minimize:hover,
|
||||
.csd .titlebar button.titlebutton.minimize:backdrop:hover,
|
||||
.titlebar.default-decoration button.titlebutton.close:hover,
|
||||
.titlebar.default-decoration button.titlebutton.close:backdrop:hover,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:hover,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:backdrop:hover,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:hover,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:backdrop:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:backdrop:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:backdrop:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:hover,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:backdrop:hover {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.close:active,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.maximize:active,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.minimize:active,
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.close:checked,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.maximize:checked,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.minimize:checked,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.close:backdrop:active,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.maximize:backdrop:active,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.minimize:backdrop:active,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.close:backdrop:checked,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.maximize:backdrop:checked,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.minimize:backdrop:checked,
|
||||
headerbar button.titlebutton.close:active,
|
||||
headerbar button.titlebutton.close:checked,
|
||||
headerbar button.titlebutton.close:backdrop:active,
|
||||
headerbar button.titlebutton.close:backdrop:checked,
|
||||
headerbar button.titlebutton.maximize:active,
|
||||
headerbar button.titlebutton.maximize:checked,
|
||||
headerbar button.titlebutton.maximize:backdrop:active,
|
||||
headerbar button.titlebutton.maximize:backdrop:checked,
|
||||
headerbar button.titlebutton.minimize:active,
|
||||
headerbar button.titlebutton.minimize:checked,
|
||||
headerbar button.titlebutton.minimize:backdrop:active,
|
||||
headerbar button.titlebutton.minimize:backdrop:checked,
|
||||
.csd headerbar button.titlebutton.close:active,
|
||||
.csd headerbar button.titlebutton.close:checked,
|
||||
.csd headerbar button.titlebutton.close:backdrop:active,
|
||||
.csd headerbar button.titlebutton.close:backdrop:checked,
|
||||
.csd headerbar button.titlebutton.maximize:active,
|
||||
.csd headerbar button.titlebutton.maximize:checked,
|
||||
.csd headerbar button.titlebutton.maximize:backdrop:active,
|
||||
.csd headerbar button.titlebutton.maximize:backdrop:checked,
|
||||
.csd headerbar button.titlebutton.minimize:active,
|
||||
.csd headerbar button.titlebutton.minimize:checked,
|
||||
.csd headerbar button.titlebutton.minimize:backdrop:active,
|
||||
.csd headerbar button.titlebutton.minimize:backdrop:checked,
|
||||
headerbar.default-decoration button.titlebutton.close:active,
|
||||
headerbar.default-decoration button.titlebutton.close:checked,
|
||||
headerbar.default-decoration button.titlebutton.close:backdrop:active,
|
||||
headerbar.default-decoration button.titlebutton.close:backdrop:checked,
|
||||
headerbar.default-decoration button.titlebutton.maximize:active,
|
||||
headerbar.default-decoration button.titlebutton.maximize:checked,
|
||||
headerbar.default-decoration button.titlebutton.maximize:backdrop:active,
|
||||
headerbar.default-decoration button.titlebutton.maximize:backdrop:checked,
|
||||
headerbar.default-decoration button.titlebutton.minimize:active,
|
||||
headerbar.default-decoration button.titlebutton.minimize:checked,
|
||||
headerbar.default-decoration button.titlebutton.minimize:backdrop:active,
|
||||
headerbar.default-decoration button.titlebutton.minimize:backdrop:checked,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:active,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:checked,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:backdrop:active,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:backdrop:checked,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:active,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:checked,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:backdrop:active,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:backdrop:checked,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:active,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:checked,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:backdrop:active,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:backdrop:checked,
|
||||
.titlebar button.titlebutton.close:active,
|
||||
.titlebar button.titlebutton.close:checked,
|
||||
.titlebar button.titlebutton.close:backdrop:active,
|
||||
.titlebar button.titlebutton.close:backdrop:checked,
|
||||
.titlebar button.titlebutton.maximize:active,
|
||||
.titlebar button.titlebutton.maximize:checked,
|
||||
.titlebar button.titlebutton.maximize:backdrop:active,
|
||||
.titlebar button.titlebutton.maximize:backdrop:checked,
|
||||
.titlebar button.titlebutton.minimize:active,
|
||||
.titlebar button.titlebutton.minimize:checked,
|
||||
.titlebar button.titlebutton.minimize:backdrop:active,
|
||||
.titlebar button.titlebutton.minimize:backdrop:checked,
|
||||
.csd .titlebar button.titlebutton.close:active,
|
||||
.csd .titlebar button.titlebutton.close:checked,
|
||||
.csd .titlebar button.titlebutton.close:backdrop:active,
|
||||
.csd .titlebar button.titlebutton.close:backdrop:checked,
|
||||
.csd .titlebar button.titlebutton.maximize:active,
|
||||
.csd .titlebar button.titlebutton.maximize:checked,
|
||||
.csd .titlebar button.titlebutton.maximize:backdrop:active,
|
||||
.csd .titlebar button.titlebutton.maximize:backdrop:checked,
|
||||
.csd .titlebar button.titlebutton.minimize:active,
|
||||
.csd .titlebar button.titlebutton.minimize:checked,
|
||||
.csd .titlebar button.titlebutton.minimize:backdrop:active,
|
||||
.csd .titlebar button.titlebutton.minimize:backdrop:checked,
|
||||
.titlebar.default-decoration button.titlebutton.close:active,
|
||||
.titlebar.default-decoration button.titlebutton.close:checked,
|
||||
.titlebar.default-decoration button.titlebutton.close:backdrop:active,
|
||||
.titlebar.default-decoration button.titlebutton.close:backdrop:checked,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:active,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:checked,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:backdrop:active,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:backdrop:checked,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:active,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:checked,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:backdrop:active,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:backdrop:checked,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:active,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:checked,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:backdrop:active,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:backdrop:checked,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:active,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:checked,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:backdrop:active,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:backdrop:checked,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:active,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:checked,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:backdrop:active,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:backdrop:checked {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.solid-csd headerbar:not(.default-decoration) button.titlebutton.close:backdrop,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.maximize:backdrop,
|
||||
.solid-csd
|
||||
headerbar:not(.default-decoration)
|
||||
button.titlebutton.minimize:backdrop,
|
||||
headerbar button.titlebutton.close:backdrop,
|
||||
headerbar button.titlebutton.maximize:backdrop,
|
||||
headerbar button.titlebutton.minimize:backdrop,
|
||||
.csd headerbar button.titlebutton.close:backdrop,
|
||||
.csd headerbar button.titlebutton.maximize:backdrop,
|
||||
.csd headerbar button.titlebutton.minimize:backdrop,
|
||||
headerbar.default-decoration button.titlebutton.close:backdrop,
|
||||
headerbar.default-decoration button.titlebutton.maximize:backdrop,
|
||||
headerbar.default-decoration button.titlebutton.minimize:backdrop,
|
||||
.csd headerbar.default-decoration button.titlebutton.close:backdrop,
|
||||
.csd headerbar.default-decoration button.titlebutton.maximize:backdrop,
|
||||
.csd headerbar.default-decoration button.titlebutton.minimize:backdrop,
|
||||
.titlebar button.titlebutton.close:backdrop,
|
||||
.titlebar button.titlebutton.maximize:backdrop,
|
||||
.titlebar button.titlebutton.minimize:backdrop,
|
||||
.csd .titlebar button.titlebutton.close:backdrop,
|
||||
.csd .titlebar button.titlebutton.maximize:backdrop,
|
||||
.csd .titlebar button.titlebutton.minimize:backdrop,
|
||||
.titlebar.default-decoration button.titlebutton.close:backdrop,
|
||||
.titlebar.default-decoration button.titlebutton.maximize:backdrop,
|
||||
.titlebar.default-decoration button.titlebutton.minimize:backdrop,
|
||||
.csd .titlebar.default-decoration button.titlebutton.close:backdrop,
|
||||
.csd .titlebar.default-decoration button.titlebutton.maximize:backdrop,
|
||||
.csd .titlebar.default-decoration button.titlebutton.minimize:backdrop {
|
||||
color: transparent;
|
||||
background-color: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
button.titlebutton image {
|
||||
margin: -1px;
|
||||
}
|
||||
|
||||
/* gtk4 */
|
||||
|
||||
/*********************
|
||||
* GtkWindowControls *
|
||||
*********************/
|
||||
windowcontrols > button {
|
||||
min-height: 12px;
|
||||
min-width: 12px;
|
||||
padding: 0px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
windowcontrols > button.minimize,
|
||||
windowcontrols > button.maximize,
|
||||
windowcontrols > button.close {
|
||||
color: transparent;
|
||||
background: none;
|
||||
}
|
||||
|
||||
windowcontrols > button.minimize > image {
|
||||
background-color: #fdbe04;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
windowcontrols > button.minimize:hover > image {
|
||||
padding: 3px;
|
||||
margin-right: -1px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
windowcontrols > button.maximize > image {
|
||||
background-color: #38c76a;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
windowcontrols > button.maximize:hover > image {
|
||||
padding: 3px;
|
||||
margin-right: -1px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
windowcontrols > button.close > image {
|
||||
background-color: #fd5f51;
|
||||
box-shadow: inset 0 -1px 0 0 alpha(white, 0.2),
|
||||
inset 0 1px 0 0 alpha(white, 0.3), inset 1px 0 0 0 alpha(white, 0.07),
|
||||
inset -1px 0 0 0 alpha(white, 0.07), 0 1px 3px rgba(0, 0, 0, 0.12),
|
||||
0 1px 2px rgba(0, 0, 0, 0.24);
|
||||
}
|
||||
|
||||
windowcontrols > button.close:hover > image {
|
||||
padding: 3px;
|
||||
margin-right: -1px;
|
||||
margin-left: -1px;
|
||||
}
|
||||
windowcontrols > button > image {
|
||||
padding: 2px;
|
||||
}
|
||||
Executable
+1042
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xbel version="1.0"
|
||||
xmlns:bookmark="http://www.freedesktop.org/standards/desktop-bookmarks"
|
||||
xmlns:mime="http://www.freedesktop.org/standards/shared-mime-info"
|
||||
>
|
||||
<bookmark href="smb://server.gib/" added="2025-08-14T15:11:25.449506Z" modified="2025-08-14T15:11:25.449511Z" visited="2025-08-14T15:11:25.449508Z">
|
||||
<title>Windows shares on server.gib</title>
|
||||
<info>
|
||||
<metadata owner="http://freedesktop.org">
|
||||
<bookmark:applications>
|
||||
<bookmark:application name="org.gnome.Nautilus" exec="'org.gnome.Nautilus %u'" modified="2025-08-14T15:11:25.449509Z" count="1"/>
|
||||
</bookmark:applications>
|
||||
</metadata>
|
||||
</info>
|
||||
</bookmark>
|
||||
</xbel>
|
||||
@@ -0,0 +1,20 @@
|
||||
# GENERATED FILE -- edit settings.ini.template instead.
|
||||
#
|
||||
# The theme name and dark preference below follow Panama's colour
|
||||
# scheme, so this file is regenerated on every switch and is not
|
||||
# committed. Under GNOME, gnome-settings-daemon publishes these over
|
||||
# XSETTINGS and this file is ignored; under Hyprland there is no
|
||||
# settings daemon, so for GTK3 it is authoritative -- which is why it
|
||||
# has to change with the scheme rather than being pinned to dark.
|
||||
#
|
||||
[Settings]
|
||||
# libadwaita apps normally take their dark preference from the
|
||||
# org.freedesktop.appearance portal (served by xdg-desktop-portal-gtk, which
|
||||
# reads gsettings). This file is the fallback for plain GTK4 apps and for the
|
||||
# window before the portal answers.
|
||||
gtk-application-prefer-dark-theme=@PREFER_DARK@
|
||||
gtk-theme-name=@GTK_THEME@
|
||||
gtk-icon-theme-name=Adwaita
|
||||
gtk-font-name=Adwaita Sans 11
|
||||
gtk-cursor-theme-name=oreo_blue_cursors
|
||||
gtk-cursor-theme-size=24
|
||||
@@ -0,0 +1 @@
|
||||
{"THEME_NAME":"Tahoe-Dark"}
|
||||
@@ -0,0 +1,114 @@
|
||||
# Panama desktop parity
|
||||
|
||||
This is the living inventory for the GNOME-to-Hyprland transition. It records
|
||||
real behavior, not a one-for-one count of extensions: an enabled extension with
|
||||
no configured action is not treated as missing functionality.
|
||||
|
||||
Last live audit: 2026-08-17, Fedora 44, Hyprland 0.56.2, Quickshell 0.3.0.
|
||||
|
||||
## Daily desktop surfaces
|
||||
|
||||
| Capability | Panama implementation | Status |
|
||||
|---|---|---|
|
||||
| App launcher and run dialog | Vicinae; Wofi emergency fallback | Live |
|
||||
| App grid | Vicinae applications provider | Live |
|
||||
| Hidden applications | GNOME App Hider list in Vicinae provider config | Live |
|
||||
| Tiling and dynamic workspaces | Hyprland Lua config, Forge keymap preserved | Live |
|
||||
| Overview | Continuum filmstrip, large workspace preview, cross-workspace search | Live |
|
||||
| Minimise | `SUPER+SHIFT+X` sends to scratchpad; `SUPER+X` restores | Live |
|
||||
| Dock | Quickshell dock with GNOME favourites and intellihide | Live |
|
||||
| Panel | Workspaces, CPU/RAM/GPU, clock, weather, quiet event capsule, media, clipboard, tray, status | Live |
|
||||
| Clipboard history | Vicinae owns capture/storage; Quickshell provides the panel | Live |
|
||||
| Notifications | Quickshell server, toasts, grouped history, DND, clear actions | Live |
|
||||
| Date menu | Two-column Daybook with Agenda, Ongoing activity, Notifications, month calendar, weather, and media | Live |
|
||||
| Ongoing activity | Focus, Caffeine, recording, screen sharing, camera, and microphone state with direct controls | Live |
|
||||
| Calendar agenda | Existing GNOME, Google, iCloud, and Nextcloud sources through Evolution Data Server | Live |
|
||||
| Control Center | Wi-Fi, Bluetooth, audio input/output, Caffeine, Night Light, Focus, Home Assistant favourites, and KDE Connect phone continuity | Live |
|
||||
| Focus sessions | Persistent workspace-bound timer, DND/caffeine coordination, Signal Glass controls | Live |
|
||||
| Brightness | Appears only when a backlight device exists | Live, not applicable on this monitor |
|
||||
| AppIndicator / tray | Native StatusNotifierItem host and themed menus | Live |
|
||||
| Screenshot and recording | Quickshell picker, grim/slurp/satty/wf-recorder | Live |
|
||||
| Screen text and code recognition | Local Tesseract/ZBar Read mode, selectable Prism result sheet | Live after package install |
|
||||
| Lock, idle, and power | hyprlock, hypridle, login1, Quickshell power menu | Live |
|
||||
| Wallpaper | hyprpaper | Live |
|
||||
| Keyring and authorization | gnome-keyring and hyprpolkitagent | Live |
|
||||
| Portals and screen sharing | xdg-desktop-portal-hyprland plus GTK portal | Live |
|
||||
| Removable media | udiskie plus udisks notifications | Live |
|
||||
| Autostart apps | Nextcloud, Bitwarden, and RustDesk system service/tray | Live |
|
||||
| Printer administration | CUPS with the `system-config-printer` graphical interface | Live |
|
||||
| System settings | The Settings app for display policy, appearance, desktop, sound, focus, shortcuts, and services; labelled GNOME hardware/account handoffs | Live |
|
||||
| System health and recovery | Settings → System Health, `Panama: Check System Health` in Vicinae, a degraded-only bar indicator, redacted reports, and bounded Panama-owned repairs | Live |
|
||||
|
||||
## System health and recovery
|
||||
|
||||
Panama stays silent while the desktop is healthy. A compact bar indicator
|
||||
appears only for actionable warnings or errors and opens the same **System
|
||||
Health** page available from Settings and the Vicinae command **Panama: Check
|
||||
System Health**. The terminal summary is available with:
|
||||
|
||||
```bash
|
||||
~/.config/quickshell/scripts/panama-doctor --summary
|
||||
```
|
||||
|
||||
The doctor reports authored, redacted observations about Panama-owned services,
|
||||
tools, links, and configured integrations. It does not read secrets, clipboard
|
||||
or notification contents, calendar events, SSIDs, or device addresses. Repairs
|
||||
are a small allow-list: Panama user services, Panama-owned links and launcher
|
||||
commands, duplicate Panama Caffeine inhibitors, and a confirmed shell restart.
|
||||
They never install packages, invoke `sudo`, delete user data, or rewrite
|
||||
arbitrary configuration.
|
||||
|
||||
Generic Fedora configuration remains with the system tools that own it. The
|
||||
final System Health card hands network settings, users, sharing, colour
|
||||
profiles, and digital wellbeing to their exact GNOME Settings panels rather
|
||||
than presenting inert Hyprland controls.
|
||||
|
||||
## GNOME extension migration
|
||||
|
||||
| GNOME extension | Result |
|
||||
|---|---|
|
||||
| Forge | Replaced by Hyprland tiling and the original keymap |
|
||||
| Dash to Dock | Replaced by the Quickshell dock |
|
||||
| AppIndicator | Replaced by the Quickshell system tray |
|
||||
| Clipboard Indicator | Replaced by the bar clipboard panel and Vicinae history |
|
||||
| Emoji Copy | Replaced by Vicinae emoji search on `SUPER+.` |
|
||||
| Vitals | Replaced by the CPU, memory, and GPU bar readouts |
|
||||
| Weather O'Clock | Replaced by bar weather and the date-menu weather card |
|
||||
| Caffeine | Replaced by a real logind inhibitor in quick settings |
|
||||
| Blur My Shell / Openbar / User Theme | Replaced by the Prism shell and compositor blur |
|
||||
| Bluetooth Quick Connect | Replaced by the full Bluetooth picker |
|
||||
| Wi-Fi QR | Deliberately omitted; it is not useful enough to justify another credential-reading surface |
|
||||
| GSConnect | Replaced by capability-aware KDE Connect phone continuity in Control Center; the paired iPhone exposes file, clipboard, and Ring actions when reachable |
|
||||
| Home Assistant | Replaced by secure favourites in Control Center; explicit private environment values take precedence over the existing GNOME extension and Secret Service setup |
|
||||
| Custom Hot Corners Extended | No action was configured, so there is no behavior to port |
|
||||
| App Hider | Hidden app list is now applied in Vicinae |
|
||||
| Allow Locked Remote Desktop | Mutter-specific patch; RustDesk is independent, but lock-screen access needs a real remote test |
|
||||
|
||||
## System UI still worth adding
|
||||
|
||||
These are not blockers for normal use, but they are the remaining places where
|
||||
a polished general-purpose desktop can go beyond the current shell.
|
||||
|
||||
1. A real external RustDesk test while hyprlock is active. Local process state
|
||||
cannot prove what a remote client sees or whether input is accepted.
|
||||
|
||||
KDE Connect, the printer UI, Tesseract, and ZBar are installed and remain
|
||||
tracked in `setup/packages/hyprland-packages`.
|
||||
|
||||
## Global menu decision
|
||||
|
||||
A macOS-style global application menu is not being shipped. Quickshell can
|
||||
render a `DBusMenu` that an application already exports, but it does not provide
|
||||
an application-menu registrar or a reliable focused-window-to-menu association.
|
||||
Hyprland has no cross-toolkit global-menu protocol, and the daily applications
|
||||
audited here do not consistently export one: GTK 4/Nautilus and Electron apps
|
||||
are the important failures.
|
||||
|
||||
That would produce a premium-looking empty strip for most windows and a working
|
||||
menu for a minority, which is worse than omitting it. Relevant references:
|
||||
|
||||
- <https://quickshell.org/docs/v0.3.0/types/Quickshell/DBusMenu/>
|
||||
- <https://github.com/hyprwm/Hyprland/discussions/1358>
|
||||
|
||||
A future focused-window menu could expose compositor actions such as float,
|
||||
move, fullscreen, and close. It should not be labelled as an application menu.
|
||||
@@ -0,0 +1,409 @@
|
||||
# Panama :: Hyprland
|
||||
|
||||
A Hyprland desktop built to reproduce the GNOME + Forge setup it replaces, so
|
||||
that muscle memory transfers unchanged.
|
||||
|
||||
## The one thing to know first
|
||||
|
||||
**Hyprland 0.55 replaced hyprlang with Lua.** `hyprland.conf` still loads on
|
||||
0.56 as a legacy fallback and is removed in 0.57. Everything here is Lua.
|
||||
|
||||
The authoritative option reference for the installed build is on disk:
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `/usr/share/hypr/stubs/hl.meta.lua` | Generated type stub — the exact option list for this build |
|
||||
| `/usr/share/hypr/hyprland.lua` | Upstream example config |
|
||||
|
||||
Wire the stub into your editor with `.luarc.json` in this directory for
|
||||
autocomplete.
|
||||
|
||||
**The Hypr ecosystem tools did NOT move to Lua.** `hyprlock.conf`,
|
||||
`hypridle.conf`, `hyprpaper.conf` and `hyprtoolkit.conf` are still hyprlang.
|
||||
Don't "fix" them.
|
||||
|
||||
## Layout
|
||||
|
||||
| File | Contents |
|
||||
|---|---|
|
||||
| `hyprland.lua` | Entry point. Each `require()` is its own error scope |
|
||||
| `prefs.lua` | Reads the settings file the Settings app writes. See below |
|
||||
| `env.lua` | Environment. Note the uwsm caveat below |
|
||||
| `monitors.lua` | DP-2 geometry, scaling, and the HDR decision |
|
||||
| `looks.lua` | Colours, blur, glow, shadows, animations, VRR, scanout |
|
||||
| `input.lua` | Keyboard/mouse. Click-to-focus, like GNOME |
|
||||
| `rules.lua` | Window rules, gaming rules, layer rules for the shell |
|
||||
| `keybinds.lua` | The full keymap |
|
||||
| `autostart.lua` | Session startup |
|
||||
| `overrides.lua` | Per-machine escape hatch, loaded last |
|
||||
| `hyprlock.conf` / `hypridle.conf` / `hyprpaper.conf` / `hyprtoolkit.conf` | Ecosystem tools (hyprlang) |
|
||||
|
||||
Validate any change without leaving your session:
|
||||
|
||||
```sh
|
||||
Hyprland --verify-config
|
||||
```
|
||||
|
||||
## Generated configuration
|
||||
|
||||
Two ecosystem tools cannot read the shared settings file, and their configs live
|
||||
in this directory — which is a symlink into the Panama repository, so writing to
|
||||
them at runtime would put machine state into a tracked file. Both are therefore
|
||||
generated elsewhere:
|
||||
|
||||
| Tool | Generated to | Pointed there by |
|
||||
|---|---|---|
|
||||
| `hypridle` | `$XDG_STATE_HOME/panama/hypridle.conf` | a systemd user drop-in installed by `panama-idle install` |
|
||||
| `hyprpaper` | not generated — the wallpaper is applied over IPC and re-applied at shell start | — |
|
||||
|
||||
`quickshell/scripts/panama-idle` regenerates the hypridle config from the
|
||||
settings store and restarts the daemon. `hypridle.conf` in this directory
|
||||
remains the shipped default and is what runs when the drop-in is not installed;
|
||||
The Settings app shows which of the two states you are in rather than presenting
|
||||
controls that quietly do nothing.
|
||||
|
||||
Remove the drop-in and go back to the shipped config with:
|
||||
|
||||
```sh
|
||||
~/.config/quickshell/scripts/panama-idle remove
|
||||
```
|
||||
|
||||
## Settings: one file, both sides
|
||||
|
||||
`~/.config/panama/settings.json` is shared with the Quickshell side. The
|
||||
relationship is:
|
||||
|
||||
- **This config is the default.** Every adjustable value is written
|
||||
`prefs.get("key", <shipped value>)`, so the config still works standalone with
|
||||
no settings file at all.
|
||||
- **The JSON is the truth.** Hyprland and Quickshell both read it.
|
||||
- **The Settings app is the editor.** It writes the file *and* applies the change
|
||||
live, so nothing needs a reload and the two sides cannot drift apart.
|
||||
|
||||
To add an adjustable setting: add an entry to
|
||||
`quickshell/config/PreferenceSchema.qml` with a `hypr` block naming the
|
||||
`hl.config` path, then read it here with `prefs.get`. Nothing else is needed —
|
||||
persistence, validation, reset, and the live write are all derived from that
|
||||
entry.
|
||||
|
||||
`prefs.lua` never raises. A missing, empty, truncated, malformed, or
|
||||
wrong-typed settings file costs you your customisations and nothing else;
|
||||
`tests/hypr/prefs-fallback-contract.sh` pins that, including that Hyprland still
|
||||
accepts the config in each of those states.
|
||||
|
||||
### Adding a keybind: use `bind`, not `hl.bind`
|
||||
|
||||
Every bind in `keybinds.lua` goes through a local `bind()` wrapper that
|
||||
substitutes the chord from a stored override, so shortcuts can be moved from
|
||||
the Settings app without editing this file.
|
||||
|
||||
```lua
|
||||
bind(mod .. " + Q", hl.dsp.window.close(), { description = "Close window" })
|
||||
```
|
||||
|
||||
Only the **chord** is ever taken from settings — the action is always the Lua
|
||||
value written here. A stored override can therefore move a shortcut but can
|
||||
never make one do something else, which is what makes reading overrides from a
|
||||
file the user can edit safe.
|
||||
|
||||
Overrides are keyed by the **shipped chord**, not the description. Descriptions
|
||||
are not unique: "Calculator" is both `SUPER + C` and the `XF86Calculator`
|
||||
hardware key, and keying by description moved both onto the same new chord,
|
||||
silently costing the hardware key.
|
||||
|
||||
An override whose value is not a plausible chord is ignored in favour of the
|
||||
shipped one, so a hand-edited `settings.json` cannot cost you a keymap.
|
||||
|
||||
### Keybind descriptions are required
|
||||
|
||||
Every `hl.bind` must pass a `description`. Hyprland reports Lua-defined binds
|
||||
with dispatcher `__lua` and a bytecode offset as the argument, so a bind without
|
||||
one has nothing readable beside its chord, and the Settings app drops it from the
|
||||
Input & Shortcuts page rather than showing a mystery row.
|
||||
`tests/quickshell/keybinds-contract.sh` fails if any bind lacks a description, so
|
||||
this cannot regress silently.
|
||||
|
||||
```lua
|
||||
hl.bind(mod .. " + Q", hl.dsp.window.close(), { description = "Close window" })
|
||||
```
|
||||
|
||||
The page groups shortcuts by what the description says they do, so a new bind
|
||||
lands in the right section with no change to the UI.
|
||||
|
||||
### Never use `hyprctl keyword`
|
||||
|
||||
On a Lua-configured Hyprland it refuses the write, prints
|
||||
`keyword can't work with non-legacy parsers` to **stdout**, and still **exits 0**:
|
||||
|
||||
```sh
|
||||
$ hyprctl getoption decoration:rounding -j # → "int": 18
|
||||
$ hyprctl keyword decoration:rounding 4 # → the refusal above
|
||||
$ echo $? # → 0
|
||||
$ hyprctl getoption decoration:rounding -j # → "int": 18, unchanged
|
||||
```
|
||||
|
||||
Use `hyprctl eval 'hl.config({ ... })'` instead. Note that `eval` *also* exits 0
|
||||
on syntax and runtime errors, reporting them as an `error:` line on stdout — so
|
||||
for either command, the only trustworthy signal that a write landed is reading
|
||||
the value back with `hyprctl getoption`.
|
||||
|
||||
## The look
|
||||
|
||||
Tokyo Night Moon, with two accents that come from the tmux theme:
|
||||
|
||||
| Token | Value | Role |
|
||||
|---|---|---|
|
||||
| `accent` | `#82aaff` | Carries every state meaning — focused, active, on |
|
||||
| `accentSecondary` | `#b172b0` | Never used alone; only the far end of a gradient |
|
||||
|
||||
The signature is **the prism**: the two accents meeting. It appears in exactly
|
||||
four places, and nowhere else —
|
||||
|
||||
1. A one-pixel hairline along the top edge of every glass surface (the bar,
|
||||
dock, popovers), running blue on the left to orchid on the right and fading
|
||||
out before the corners. See `quickshell/widgets/PrismEdge.qml`.
|
||||
2. The focused window's border — `general.col.active_border`, blue→orchid at
|
||||
115°. Unfocused windows get no colour at all, because the gradient only
|
||||
means something if one window on screen is wearing it.
|
||||
3. The active workspace pill in the bar.
|
||||
4. Slider fills.
|
||||
|
||||
The restraint is the point. The pink stops being special the moment it's used
|
||||
as a flat fill, so it never is.
|
||||
|
||||
### Typography
|
||||
|
||||
**Adwaita Sans for everything the user reads as text — no monospace in the UI.**
|
||||
A monospaced clock or percentage reads as terminal output pasted into a panel,
|
||||
which is the opposite of the intent.
|
||||
|
||||
The Nerd Font is still used, but only to draw **icon glyphs** — it is the
|
||||
pragmatic alternative to freedesktop symbolic icons, which ship with a
|
||||
hardcoded `#2e3436` fill that Qt (unlike GTK) will not recolour. Where a
|
||||
themed freedesktop icon is wanted instead, `quickshell/widgets/ThemedIcon.qml`
|
||||
paints a palette colour through the icon's alpha.
|
||||
|
||||
Anything whose digits change in place — the clock, the vitals percentages, the
|
||||
recording timer, the selection readout — sets `font.features:
|
||||
Theme.tabularFigures`. Tabular figures share one advance width, so the text
|
||||
stops twitching as numbers tick without reaching for a monospaced face.
|
||||
|
||||
Window rounding is 18, matching the shell's popover radius, so a window and a
|
||||
panel next to each other read as the same family of object.
|
||||
|
||||
**On animated gradient borders:** Hyprland can rotate the border gradient
|
||||
continuously with `borderangle` + `style = "loop"`. Don't. The wiki is explicit
|
||||
that it forces a full-refresh-rate repaint forever, even when no border is
|
||||
visible — at 4500×3000 that is pure idle GPU burn. This config uses
|
||||
`style = "once"`, so the gradient sweeps into place when a window takes focus
|
||||
and then costs nothing.
|
||||
|
||||
## Session: use the uwsm one
|
||||
|
||||
Log in as **"Hyprland (uwsm-managed)"**, not plain "Hyprland".
|
||||
|
||||
`xdg-desktop-portal-hyprland`'s systemd unit requires `graphical-session.target`,
|
||||
and Fedora ships no `hyprland-session.target`. Without uwsm that target never
|
||||
activates and **screen sharing in OBS, Sunshine and Zoom silently fails.**
|
||||
|
||||
### The uwsm environment gotcha
|
||||
|
||||
`hl.env()` in `env.lua` exports into the *compositor's* environment, **not** the
|
||||
systemd user manager. Anything started as a user unit — `vicinae.service`,
|
||||
`hyprpaper.service`, `hyprpolkitagent.service`, `hypridle.service` — and every
|
||||
app those launch will not see it.
|
||||
|
||||
That is why `~/.config/uwsm/env` and `~/.config/uwsm/env-hyprland` exist and
|
||||
duplicate the important variables. Keep them in sync with `env.lua`; `env.lua`
|
||||
remains the fallback for the plain session.
|
||||
|
||||
## HDR — read this before turning it on
|
||||
|
||||
GNOME ran this panel in bt2100/HDR. This config deliberately does not.
|
||||
|
||||
On Hyprland 0.56.x, `cm = "hdr"` currently breaks **screencopy**: `grim`
|
||||
screenshots come back empty, and OBS/Sunshine capture and hyprlock's blurred
|
||||
background go with them. Root cause is still open upstream.
|
||||
|
||||
So the desktop runs SDR at 10-bit (`cm = "auto"`) and HDR is handed to
|
||||
fullscreen games only, via `render.cm_auto_hdr = 1` in `looks.lua`. Games get
|
||||
HDR; screenshots keep working.
|
||||
|
||||
To try full-time HDR anyway, `overrides.lua` has the block and the ordered list
|
||||
of workarounds to reach for when things break.
|
||||
|
||||
## Keymap
|
||||
|
||||
The mental model is unchanged from Forge:
|
||||
|
||||
- **SUPER** acts on windows
|
||||
- **ALT** acts on workspaces
|
||||
- **SUPER + CTRL** changes layout structure
|
||||
|
||||
### Windows
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `SUPER + H/J/K/L` (or arrows) | Focus |
|
||||
| `SUPER + SHIFT + H/J/K/L` | Move window |
|
||||
| `SUPER + CTRL + H/J/K/L` | Swap window |
|
||||
| `SUPER + SHIFT + Y/O` · `B/M` | Wider · narrower |
|
||||
| `SUPER + SHIFT + I/U` · `P/N` | Taller · shorter |
|
||||
| `SUPER + [` / `]` / `=` | Shrink / expand / reset split |
|
||||
| `SUPER + Q` | Close |
|
||||
| `SUPER + U` | Fullscreen |
|
||||
| `SUPER + CTRL + C` | Toggle float |
|
||||
| `SUPER + CTRL + SHIFT + C` | Pin (nearest thing to Forge's "always float") |
|
||||
| `SUPER + CTRL + G` / `Z` / `V` | Toggle split / preselect right / preselect down |
|
||||
| `SUPER + Tab` / `SHIFT + Tab` | Cycle windows |
|
||||
| `SUPER + SHIFT + grave` | Last window |
|
||||
| `SUPER + X` / `SUPER + SHIFT + X` | Restore scratchpad / minimise to scratchpad |
|
||||
|
||||
### Workspaces (dynamic, like GNOME)
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `ALT + H` / `ALT + L` | Previous / next workspace |
|
||||
| `ALT + SHIFT + H/L` | Move window to previous / next |
|
||||
| `ALT + 1..9`, `ALT + 0` | Jump to workspace |
|
||||
| `ALT + SHIFT + 1..9` | Send window to workspace |
|
||||
| `SUPER + scroll` | Change workspace |
|
||||
|
||||
### Launcher and shell
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `SUPER + A` / `R` / `Space` | Launcher (vicinae) — all three, pick your favourite |
|
||||
| `SUPER + SHIFT + R` | Fallback launcher (wofi) if the shell is broken |
|
||||
| `SUPER + V` | Clipboard history |
|
||||
| `SUPER + .` | Emoji picker |
|
||||
| `SUPER + S` | Quick settings |
|
||||
| `SUPER + I` | Settings |
|
||||
| `SUPER + SHIFT + F` | Start or reveal focus session |
|
||||
| `SUPER + B` | Notification centre |
|
||||
| `SUPER + grave` | Workspace overview |
|
||||
| `Print` | Screenshot / record picker |
|
||||
| `SHIFT` / `ALT + Print` | Screenshot screen / window immediately |
|
||||
| `SUPER + SHIFT + S` | Screen Intelligence — read text and codes from a selection |
|
||||
| `SUPER + SHIFT + P` | Colour picker |
|
||||
| `CTRL + ALT + L` | Lock (SUPER+L is "focus right") |
|
||||
| `CTRL + ALT + Delete` | Power menu |
|
||||
|
||||
### Apps
|
||||
`SUPER + T` terminal · `N` neovim · `W` browser · `F` files · `C` calculator ·
|
||||
`E` mail · `I` Settings · `CTRL + SHIFT + Esc` system monitor. GNOME
|
||||
Settings remains searchable in Vicinae for hardware and account panels.
|
||||
|
||||
## Calendar and notifications
|
||||
|
||||
Click the clock to open the two-column Daybook on **Agenda**. The month grid,
|
||||
today's schedule, Up Next, weather, and conditional media controls stay visible
|
||||
together. Its right pane has three first-class pages:
|
||||
|
||||
- **Agenda** — the selected day's calendar.
|
||||
- **Ongoing** — Focus, Caffeine, recording, screen sharing, camera, and
|
||||
microphone activity, visible until each state ends.
|
||||
- **Notifications** — grouped history, Do Not Disturb, and clear actions.
|
||||
|
||||
`SUPER + B` opens Notifications directly. Switching pages preserves the
|
||||
selected date and visible month. Brief completed events such as screenshots and
|
||||
device changes remain in Signal Glass instead of accumulating in Ongoing.
|
||||
|
||||
Calendar data comes from Evolution Data Server, so the Google, iCloud,
|
||||
Nextcloud, and local calendars already configured for GNOME remain the source
|
||||
of truth. Panama never stores account credentials. Use GNOME Calendar or Online
|
||||
Accounts to add and manage accounts, and click an event in Daybook to hand off
|
||||
editing and full detail to GNOME Calendar.
|
||||
|
||||
The right side of the bar shows a small calendar capsule only from 15 minutes
|
||||
before a timed event until 5 minutes after it starts. It displays relative time
|
||||
(`12m` or `Now`); the event title is available on hover but otherwise stays out
|
||||
of the bar. All-day events never trigger the capsule.
|
||||
|
||||
## Control Center
|
||||
|
||||
Click the right-side status cluster or press `SUPER + S` for Panama's Control
|
||||
Center. It is attached directly beneath the bar and keeps Wi-Fi, Bluetooth,
|
||||
Caffeine, Night Light, Focus, audio input/output, user, settings, and power in
|
||||
one place. Home and Phone continue the same surface rather than opening extra
|
||||
dashboard windows.
|
||||
|
||||
Home shows the first four selected favourites at rest and every selected light
|
||||
when expanded. Use **Settings → Home & Phone** to choose favourites,
|
||||
set Panama-only aliases, and arrange their order. Dragging a brightness control
|
||||
only previews the value; releasing it sends one brightness request. A normal
|
||||
power toggle leaves Home Assistant responsible for restoring its previous
|
||||
level.
|
||||
|
||||
Credentials stay private in the gitignored `config/bash/env` file, with the
|
||||
existing GNOME extension and Secret Service setup retained as a compatibility
|
||||
fallback. Favourites, aliases, and order live in Quickshell state. No shell
|
||||
restart is required after changing credentials; close and reopen Control Center
|
||||
to refresh. If Home Assistant is offline, the last known values stay visible
|
||||
with a stale-state label and Retry action.
|
||||
|
||||
Phone uses KDE Connect for the capabilities the paired iPhone actually
|
||||
advertises: Send File, Send Clipboard, and Ring. The device remains visible
|
||||
while iOS suspends KDE Connect, but those actions stay disabled until it
|
||||
reconnects. Messages opens BlueBubbles independently of KDE Connect. No battery
|
||||
percentage is invented when iOS reports none. Active file sends appear in
|
||||
Daybook's Ongoing page; successful sends become a quiet recent exchange.
|
||||
|
||||
## Screen Intelligence
|
||||
|
||||
`SUPER + SHIFT + S` opens the capture picker directly in Selection + Read
|
||||
mode. Read is also available beside Screenshot and Record under `Print`, and
|
||||
works on a whole display, a window, or a region. Tesseract recognizes English
|
||||
text locally; ZBar recognizes QR codes and barcodes. The result sheet can copy
|
||||
text, search it, translate it, or open a detected web address. No pixels leave
|
||||
the workstation unless one of those explicit network actions is selected.
|
||||
|
||||
## Deliberate deviations from GNOME
|
||||
|
||||
These are the places a 1:1 port was impossible, and what was done instead:
|
||||
|
||||
- **Per-edge resize.** Forge resized one named edge; Hyprland resizes along an
|
||||
axis and lets the layout pick the edge. The eight Forge keys collapse to four
|
||||
behaviours, keeping the horizontal/vertical and grow/shrink pairing.
|
||||
- **Overview on `SUPER + grave`, not a bare SUPER tap.** Tap-detection on a
|
||||
modifier misfires when you're quick with SUPER combos.
|
||||
- **Lock on `CTRL + ALT + L`.** `SUPER + L` is "focus right" in this keymap.
|
||||
- **`SUPER + grave` was Forge's "cycle windows of same app"**, which Hyprland
|
||||
has no equivalent for. "Last window" moved to `SUPER + SHIFT + grave`.
|
||||
- **`gnome-control-center` is launched with `XDG_CURRENT_DESKTOP=GNOME`**,
|
||||
because it hard-refuses to start otherwise. Panels backed by system services
|
||||
work; panels backed by GNOME Shell (Displays, Keyboard Shortcuts,
|
||||
Multitasking, Appearance) are inert — Hyprland owns those now.
|
||||
|
||||
## Gaming
|
||||
|
||||
`content = "game"` on the window rules in `rules.lua` is the keystone — it is
|
||||
what `misc.vrr = 3`, `render.direct_scanout = 2` and `cursor.no_break_fs_vrr = 2`
|
||||
key off. Games additionally get blur, animation, shadow and dim disabled, plus
|
||||
`immediate` for tearing.
|
||||
|
||||
Tearing only takes effect when the game is fullscreen and the only thing on
|
||||
screen — no bar, no notifications.
|
||||
|
||||
## Do not install a notification daemon
|
||||
|
||||
`mako`, `dunst` and `SwayNotificationCenter` all register
|
||||
`Name=org.freedesktop.Notifications` for D-Bus activation. Installing any of
|
||||
them creates a startup race against Quickshell's notification server, and
|
||||
whoever wins keeps the name. If one gets installed as a dependency,
|
||||
`systemctl --user mask <name>.service`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Nothing autostarts** — check `systemctl --user status hyprland-session.target`
|
||||
and `systemctl --user show-environment | grep WAYLAND_DISPLAY`. An empty
|
||||
environment means uwsm didn't export it and units with
|
||||
`ConditionEnvironment=WAYLAND_DISPLAY` silently skip.
|
||||
|
||||
**Screen sharing missing** — `busctl --user list | grep impl.portal` should show
|
||||
`xdg-desktop-portal-hyprland`.
|
||||
|
||||
**Qt apps look wrong** — `QT_QPA_PLATFORMTHEME` must be `gtk3`. It is a single
|
||||
value, not a list: Qt splits on `:` and uses only the first token.
|
||||
|
||||
**Wrong GPU / won't start** — `AQ_DRM_DEVICES` points at
|
||||
`/dev/dri/amd-dgpu`, created by `config/copy/etc/udev/rules.d/99-panama-gpu.rules`.
|
||||
Both `env.lua` and `uwsm/env-hyprland` guard on the symlink existing, so a
|
||||
missing rule degrades to "let aquamarine choose" rather than failing to start.
|
||||
@@ -0,0 +1,60 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Autostart
|
||||
--
|
||||
-- `exec-once` no longer exists in 0.56; startup is an event handler.
|
||||
--
|
||||
-- Session: Fedora installs two sessions, "Hyprland" and "Hyprland
|
||||
-- (uwsm-managed)". Panama targets the uwsm one, because
|
||||
-- xdg-desktop-portal-hyprland's systemd unit requires graphical-session.target
|
||||
-- and Fedora ships no hyprland-session.target -- without uwsm, screen sharing
|
||||
-- in OBS/Sunshine/Zoom silently fails. The target is started explicitly below
|
||||
-- so the plain session works too.
|
||||
--
|
||||
-- Anything that ships a systemd user unit is started as a unit rather than as
|
||||
-- a compositor child. That is not cosmetic: under uwsm, units get the correct
|
||||
-- activation environment (see uwsm/env), they restart on failure, and they
|
||||
-- shut down in order. Panama's `change-settings` script enables them once;
|
||||
-- the explicit starts here make a fresh clone work before that has run.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
hl.on("hyprland.start", function()
|
||||
-- Publish the session environment to systemd and D-Bus so user units and
|
||||
-- the portals can see WAYLAND_DISPLAY. Cheap, and essential without uwsm.
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=Hyprland")
|
||||
hl.exec_cmd("systemctl --user start hyprland-session.target")
|
||||
|
||||
-- Units: polkit prompts, wallpaper, launcher daemon, idle/lock.
|
||||
hl.exec_cmd("systemctl --user start hyprpolkitagent.service hyprpaper.service vicinae.service hypridle.service")
|
||||
|
||||
-- The shell: bar, dock, overview, quick settings, notifications, capture.
|
||||
-- No systemd unit ships with quickshell, so it runs as a compositor child.
|
||||
hl.exec_cmd("quickshell --daemonize")
|
||||
|
||||
-- Removable-media automounting. GNOME did this invisibly via gvfs+udisks;
|
||||
-- outside GNOME something has to ask udisks to mount. No tray icon: the
|
||||
-- Quickshell bar already has a tray, and udiskie's own icon would be
|
||||
-- redundant. -f opens Nautilus when you click the mount notification.
|
||||
hl.exec_cmd("udiskie --automount --notify --no-tray --file-manager nautilus")
|
||||
|
||||
-- Keyring unlock, for Nextcloud and Bitwarden credential storage.
|
||||
hl.exec_cmd("/usr/bin/gnome-keyring-daemon --start --components=secrets,ssh,pkcs11")
|
||||
|
||||
-- Tray applications carried over from ~/.config/autostart. These need the
|
||||
-- Quickshell tray (an SNI host) to be up, hence starting after it.
|
||||
--
|
||||
-- Commands are copied verbatim from the GNOME .desktop files rather than
|
||||
-- guessed: Bitwarden is a flatpak with no `bitwarden` binary on PATH, and
|
||||
-- its autostart entry launches a specific script with --autostart.
|
||||
--
|
||||
-- RustDesk is deliberately absent: it ships an enabled *system* service
|
||||
-- (`rustdesk --service`) that spawns --server and --tray for the session on
|
||||
-- its own. Starting it here as well would give you two trays.
|
||||
hl.exec_cmd("nextcloud --background")
|
||||
hl.exec_cmd("flatpak run --command=bitwarden.sh com.bitwarden.desktop --autostart")
|
||||
end)
|
||||
|
||||
hl.on("hyprland.shutdown", function()
|
||||
hl.exec_cmd("systemctl --user stop hyprland-session.target")
|
||||
end)
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,86 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Environment
|
||||
--
|
||||
-- hl.env() sets variables before the display server initialises, which is what
|
||||
-- makes it valid for AQ_* and cursor variables.
|
||||
--
|
||||
-- NOTE for uwsm sessions: if you log in via "Hyprland (uwsm-managed)", uwsm
|
||||
-- prefers these live in ~/.config/uwsm/env and ~/.config/uwsm/env-hyprland
|
||||
-- instead. They still work here; see the session notes in autostart.lua.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = require("prefs")
|
||||
|
||||
-- ── GPU selection ───────────────────────────────────────────────────────────
|
||||
-- This box has a discrete RX 7800 XT (0000:03:00.0) and a Granite Ridge iGPU
|
||||
-- (0000:12:00.0). The monitor hangs off the dGPU, so the dGPU must render.
|
||||
--
|
||||
-- /dev/dri/cardN numbering is not stable across boots, and the by-path names
|
||||
-- contain colons -- which are AQ_DRM_DEVICES' own separator, so they cannot be
|
||||
-- used directly. Panama installs a udev rule creating colon-free stable
|
||||
-- symlinks (/dev/dri/amd-dgpu, /dev/dri/amd-igpu); see
|
||||
-- config/copy/etc/udev/rules.d/99-panama-gpu.rules
|
||||
--
|
||||
-- First entry is the primary renderer. The iGPU stays in the list so anything
|
||||
-- plugged into its outputs still lights up.
|
||||
--
|
||||
-- Guarded on the symlink actually existing: this config is shared across
|
||||
-- machines, and pointing AQ_DRM_DEVICES at a device that isn't there stops
|
||||
-- Hyprland from starting at all. If the udev rule hasn't been installed (or
|
||||
-- this is a different box), we say nothing and let aquamarine choose.
|
||||
local function exists(path)
|
||||
local f = io.open(path, "r")
|
||||
if f then f:close() return true end
|
||||
return false
|
||||
end
|
||||
|
||||
if exists("/dev/dri/amd-dgpu") then
|
||||
local devices = "/dev/dri/amd-dgpu"
|
||||
if exists("/dev/dri/amd-igpu") then
|
||||
devices = devices .. ":/dev/dri/amd-igpu"
|
||||
end
|
||||
hl.env("AQ_DRM_DEVICES", devices)
|
||||
end
|
||||
|
||||
-- ── Cursor ──────────────────────────────────────────────────────────────────
|
||||
-- Theme carried over from GNOME.
|
||||
--
|
||||
-- Deliberately no HYPRCURSOR_THEME: oreo_blue_cursors is a plain XCursor theme
|
||||
-- (it has cursors/ + index.theme and no hyprcursors/ directory or manifest.hl),
|
||||
-- so setting it would point hyprcursor at nothing. Hyprland falls back to the
|
||||
-- XCursor path, which is what we want.
|
||||
hl.env("XCURSOR_THEME", prefs.get("cursorTheme", "oreo_blue_cursors"))
|
||||
hl.env("XCURSOR_SIZE", "24")
|
||||
|
||||
-- ── Toolkits ────────────────────────────────────────────────────────────────
|
||||
-- Wayland first, X11 only as a fallback for apps that genuinely need it.
|
||||
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
|
||||
|
||||
-- "gtk3" makes Qt derive its palette from the live GTK3 theme via the qgtk3
|
||||
-- platform plugin, so Qt apps match adw-gtk3-dark exactly rather than
|
||||
-- approximating it -- and it gives them GTK file/font/colour dialogs. It also
|
||||
-- covers Qt5 and Qt6 with one value.
|
||||
--
|
||||
-- Note this is a single value, NOT a fallback list: Qt splits on ":" and uses
|
||||
-- only the first token, so the commonly-copied "qt5ct:qt6ct" silently means
|
||||
-- just "qt5ct".
|
||||
hl.env("QT_QPA_PLATFORMTHEME", "gtk3")
|
||||
hl.env("QT_WAYLAND_DISABLE_WINDOWDECORATION", "1")
|
||||
hl.env("QT_AUTO_SCREEN_SCALE_FACTOR", "1")
|
||||
hl.env("GDK_BACKEND", "wayland,x11")
|
||||
hl.env("SDL_VIDEODRIVER", "wayland")
|
||||
hl.env("CLUTTER_BACKEND", "wayland")
|
||||
hl.env("MOZ_ENABLE_WAYLAND", "1")
|
||||
|
||||
-- Electron apps (Slack, Obsidian, Claude, Codex, BlueBubbles, Podman Desktop)
|
||||
-- pick Wayland from this rather than needing per-app flags.
|
||||
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "auto")
|
||||
|
||||
-- ── Desktop identity ────────────────────────────────────────────────────────
|
||||
-- Portals and some GNOME apps key off these. uwsm sets them itself, so these
|
||||
-- are only load-bearing for the plain (non-uwsm) session.
|
||||
hl.env("XDG_CURRENT_DESKTOP", "Hyprland")
|
||||
hl.env("XDG_SESSION_TYPE", "wayland")
|
||||
hl.env("XDG_SESSION_DESKTOP", "Hyprland")
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,39 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# hypridle — idle behaviour
|
||||
#
|
||||
# Mirrors the GNOME power profile this replaces, exactly:
|
||||
# org.gnome.desktop.session idle-delay = 300 (screen off at 5min)
|
||||
# org.gnome.desktop.screensaver lock-delay = 300 (lock 5min after that)
|
||||
# org.gnome.settings-daemon.plugins.power
|
||||
# sleep-inactive-ac-type = nothing (never auto-suspend)
|
||||
# power-button-action = nothing
|
||||
#
|
||||
# So: screen off at 5 minutes, lock at 10, never suspend. This is a desktop.
|
||||
#
|
||||
# hyprlang syntax, not Lua — hypridle is a separate project from Hyprland.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
general {
|
||||
# `pidof` guard prevents stacking lockers if this fires twice.
|
||||
lock_cmd = pidof hyprlock || hyprlock
|
||||
before_sleep_cmd = loginctl lock-session
|
||||
after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms({ action = "on" })'
|
||||
|
||||
# 2 = auto. Holds off sleep until the locker is actually up.
|
||||
inhibit_sleep = 2
|
||||
}
|
||||
|
||||
# 5 minutes -> screen off.
|
||||
listener {
|
||||
timeout = 300
|
||||
on-timeout = hyprctl dispatch 'hl.dsp.dpms({ action = "off" })'
|
||||
on-resume = hyprctl dispatch 'hl.dsp.dpms({ action = "on" })'
|
||||
}
|
||||
|
||||
# 10 minutes -> lock. Matches GNOME's lock-delay of 300s after blanking.
|
||||
listener {
|
||||
timeout = 600
|
||||
on-timeout = loginctl lock-session
|
||||
}
|
||||
|
||||
# Deliberately no suspend listener: sleep-inactive-ac-type was 'nothing'.
|
||||
@@ -0,0 +1,31 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Panama :: Hyprland
|
||||
--
|
||||
-- Hyprland 0.55 replaced hyprlang (hyprland.conf) with Lua. The .conf format
|
||||
-- still loads as a legacy fallback in 0.56 but is removed in 0.57, so this
|
||||
-- config is Lua-native. Reference for every option below:
|
||||
-- /usr/share/hypr/stubs/hl.meta.lua <- authoritative for THIS build
|
||||
-- /usr/share/hypr/hyprland.lua <- upstream example
|
||||
--
|
||||
-- Entry point only. Each require() is its own error scope, so a mistake in one
|
||||
-- module doesn't take the whole config down with it.
|
||||
--
|
||||
-- Reload: hyprctl reload
|
||||
-- Check: Hyprland --verify-config
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- Shared preferences first: looks/input/monitors read their defaults through it.
|
||||
-- It never raises, so a missing or malformed settings file costs customisations
|
||||
-- and nothing else.
|
||||
require("prefs")
|
||||
|
||||
require("env")
|
||||
require("monitors")
|
||||
require("looks")
|
||||
require("input")
|
||||
require("rules")
|
||||
require("keybinds")
|
||||
require("autostart")
|
||||
|
||||
-- Per-machine overrides, loaded last so it wins. Ships effectively empty.
|
||||
require("overrides")
|
||||
@@ -0,0 +1,114 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# hyprlock — lock screen
|
||||
#
|
||||
# Tokyo Night Moon, matching quickshell/config/Theme.qml:
|
||||
# accent #82aaff fg #c8d3f5 dim #828bb8 bg #222436 red #ff757f
|
||||
#
|
||||
# hyprlang syntax, not Lua — hyprlock is a separate project from Hyprland.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
general {
|
||||
hide_cursor = true
|
||||
|
||||
# 2 = auto. Matters at the 1.5x fractional scale this display runs.
|
||||
fractional_scaling = 2
|
||||
|
||||
# 0 = GPU-accelerated screencopy. 1 is a CPU fallback and is very slow at
|
||||
# 4500x3000.
|
||||
screencopy_mode = 0
|
||||
|
||||
fail_timeout = 2000
|
||||
}
|
||||
|
||||
auth {
|
||||
pam:enabled = true
|
||||
pam:module = hyprlock
|
||||
}
|
||||
|
||||
# ── Background ──────────────────────────────────────────────────────────────
|
||||
# `path = screenshot` grabs the desktop at lock time and blurs it in place,
|
||||
# which is what GNOME does.
|
||||
#
|
||||
# Caveat: this uses screencopy. If the desktop is ever switched to full-time
|
||||
# HDR (see hypr/overrides.lua), this background may come back black — that is
|
||||
# the same upstream screencopy bug, not a hyprlock misconfiguration.
|
||||
background {
|
||||
monitor =
|
||||
path = screenshot
|
||||
|
||||
blur_passes = 3
|
||||
blur_size = 8
|
||||
noise = 0.0117
|
||||
contrast = 0.9
|
||||
brightness = 0.8
|
||||
vibrancy = 0.17
|
||||
vibrancy_darkness = 0.05
|
||||
|
||||
# Shown if the screenshot is unavailable.
|
||||
color = rgba(34, 36, 54, 1.0)
|
||||
|
||||
zindex = -1
|
||||
}
|
||||
|
||||
# ── Clock ───────────────────────────────────────────────────────────────────
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:1000] date +"%-I:%M"
|
||||
color = rgba(200, 211, 245, 1.0)
|
||||
font_size = 120
|
||||
font_family = Adwaita Sans Light
|
||||
position = 0, 260
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
label {
|
||||
monitor =
|
||||
text = cmd[update:60000] date +"%A, %B %-d"
|
||||
color = rgba(130, 139, 184, 1.0)
|
||||
font_size = 24
|
||||
font_family = Adwaita Sans
|
||||
position = 0, 160
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
|
||||
# ── Password field ──────────────────────────────────────────────────────────
|
||||
input-field {
|
||||
monitor =
|
||||
size = 340, 52
|
||||
position = 0, -40
|
||||
halign = center
|
||||
valign = center
|
||||
|
||||
outline_thickness = 2
|
||||
rounding = 26
|
||||
|
||||
outer_color = rgba(130, 170, 255, 0.9)
|
||||
inner_color = rgba(46, 47, 61, 0.85)
|
||||
font_color = rgba(200, 211, 245, 1.0)
|
||||
check_color = rgba(130, 170, 255, 1.0)
|
||||
fail_color = rgba(255, 117, 127, 1.0)
|
||||
|
||||
dots_size = 0.25
|
||||
dots_spacing = 0.3
|
||||
dots_center = true
|
||||
|
||||
placeholder_text = <span foreground="##828bb8"><i>Password</i></span>
|
||||
fail_text = <span foreground="##ff757f"><i>$FAIL ($ATTEMPTS)</i></span>
|
||||
|
||||
fade_on_empty = false
|
||||
hide_input = false
|
||||
}
|
||||
|
||||
# ── User ────────────────────────────────────────────────────────────────────
|
||||
label {
|
||||
monitor =
|
||||
text = $USER
|
||||
color = rgba(200, 211, 245, 0.9)
|
||||
font_size = 16
|
||||
font_family = Adwaita Sans
|
||||
position = 0, -110
|
||||
halign = center
|
||||
valign = center
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# hyprpaper — static wallpaper
|
||||
#
|
||||
# NOTE: hyprpaper, hypridle, hyprlock and hyprtoolkit are separate projects and
|
||||
# still use hyprlang. Only Hyprland itself moved to Lua. Don't "fix" these.
|
||||
#
|
||||
# SYNTAX NOTE: hyprpaper 0.8 was rewritten on hyprtoolkit/hyprwire and replaced
|
||||
# the old flat syntax with blocks. The pre-0.8 form
|
||||
#
|
||||
# preload = /path/to.jpg
|
||||
# wallpaper = DP-2,/path/to.jpg
|
||||
#
|
||||
# is silently ignored — it parses, then logs "Monitor DP-2 has no target: no wp
|
||||
# will be created" and shows nothing. Almost every guide online still uses it.
|
||||
#
|
||||
# Static only, by choice: animated wallpapers repaint continuously and cost
|
||||
# frame time for nothing.
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
wallpaper {
|
||||
monitor = DP-2
|
||||
path = ~/Pictures/Wallpapers/faroe_islands.jpg
|
||||
# cover = fill the output, cropping overflow. Matches the "zoom" picture
|
||||
# option this setup used under GNOME.
|
||||
fit_mode = cover
|
||||
}
|
||||
|
||||
# Fallback for any output without its own block above.
|
||||
wallpaper {
|
||||
monitor =
|
||||
path = ~/Pictures/Wallpapers/faroe_islands.jpg
|
||||
fit_mode = cover
|
||||
}
|
||||
|
||||
ipc = on
|
||||
splash = false
|
||||
@@ -0,0 +1,34 @@
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# hyprtoolkit — shared theme for the first-party Hypr GUI apps
|
||||
#
|
||||
# One file themes all of these, which is why they're preferred over the usual
|
||||
# grab-bag of GTK/Qt utilities:
|
||||
# hyprshutdown session exit dialog (bound to the power menu)
|
||||
# hyprpwcenter PipeWire / audio routing GUI (replaces a pavucontrol/TUI)
|
||||
# hyprsysteminfo system info panel
|
||||
# hyprland-guiutils hyprland-dialog / hyprland-run
|
||||
# hyprpolkitagent authentication prompts (via hyprland-qt-support)
|
||||
#
|
||||
# Colours are Tokyo Night Moon, matching quickshell/config/Theme.qml and
|
||||
# hypr/looks.lua. Format is hyprlang; path is fixed at
|
||||
# $XDG_CONFIG_HOME/hypr/hyprtoolkit.conf (confirmed in libhyprtoolkit).
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
background = 0xFF1E2030 # bg_dark — window background
|
||||
base = 0xFF222436 # bg — raised surfaces
|
||||
alternate_base = 0xFF2F334D # bg_highlight — rows, hover
|
||||
text = 0xFFC8D3F5 # fg
|
||||
bright_text = 0xFFFFFFFF
|
||||
link_text = 0xFF82AAFF # blue
|
||||
|
||||
accent = 0xFF82AAFF # blue — same accent as everything else
|
||||
accent_secondary = 0xFFC099FF # magenta
|
||||
|
||||
font_family = Adwaita Sans
|
||||
font_family_monospace = VictorMono Nerd Font
|
||||
|
||||
icon_theme = Adwaita
|
||||
|
||||
# Match the rounding used by the Quickshell popovers and the Hyprland windows.
|
||||
rounding_large = 18
|
||||
rounding_small = 8
|
||||
@@ -0,0 +1,50 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Input
|
||||
--
|
||||
-- Deliberately GNOME-like: click-to-focus, no focus-follows-mouse, no mouse
|
||||
-- acceleration changes. The goal is that muscle memory transfers untouched.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = require("prefs")
|
||||
|
||||
hl.config({
|
||||
input = {
|
||||
kb_layout = prefs.get("keyboardLayout", "us"),
|
||||
kb_variant = prefs.get("keyboardVariant", ""),
|
||||
kb_model = "",
|
||||
kb_options = prefs.get("keyboardOptions", "caps:escape_shifted_capslock"),
|
||||
kb_rules = "",
|
||||
|
||||
numlock_by_default = prefs.get("numlockByDefault", true),
|
||||
|
||||
-- GNOME's defaults are 500ms delay / 33Hz repeat.
|
||||
repeat_delay = prefs.get("keyRepeatDelay", 500),
|
||||
repeat_rate = prefs.get("keyRepeatRate", 33),
|
||||
|
||||
-- 1 = FOLLOW. The window under the pointer takes focus. This comment
|
||||
-- previously claimed 1 was "click to focus, GNOME's behaviour", which
|
||||
-- is the opposite of what Hyprland does -- `hyprctl descriptions` gives
|
||||
-- map: [{"separate":3},{"detached":2},{"follow":1},{"disabled":0}],
|
||||
-- so click-to-focus is 0. Changing the shipped value is a behaviour
|
||||
-- decision rather than a correction, so the value is left alone and
|
||||
-- only the description is fixed; Settings exposes all four.
|
||||
follow_mouse = prefs.getInt("followMouse", 1),
|
||||
|
||||
-- Softens follow_mouse: with this off, focus changes only when the
|
||||
-- pointer crosses a window boundary, not on every movement inside one.
|
||||
-- Still focus-follows-pointer, just less twitchy.
|
||||
mouse_refocus = false,
|
||||
|
||||
-- Flat pointer response, no acceleration. Matters for gaming.
|
||||
sensitivity = prefs.get("pointerSensitivity", 0),
|
||||
accel_profile = "flat",
|
||||
|
||||
-- Clicking a floating window raises and focuses it.
|
||||
float_switch_override_focus = 2,
|
||||
},
|
||||
})
|
||||
|
||||
-- Desktop machine: no touchpad, no gestures worth wiring. If a laptop ever
|
||||
-- runs this config, add touchpad settings in overrides.lua.
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,284 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Keybindings
|
||||
--
|
||||
-- Ported 1:1 from the GNOME + Forge setup this replaces. Where Hyprland has no
|
||||
-- equivalent, the deviation is called out in a comment rather than silently
|
||||
-- dropped.
|
||||
--
|
||||
-- The mental model, unchanged from Forge:
|
||||
-- SUPER -> act on windows (focus / move / swap / resize)
|
||||
-- ALT -> act on workspaces
|
||||
-- SUPER + CTRL -> change layout structure (split, float, swap)
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = require("prefs")
|
||||
|
||||
local mod = "SUPER"
|
||||
|
||||
-- Programs, matched to the GNOME custom keybindings and media-key settings.
|
||||
local terminal = "kitty"
|
||||
local editor = "kitty nvim ."
|
||||
local browser = "helium-browser-bin"
|
||||
local files = "nautilus --new-window"
|
||||
local calculator = "gnome-calculator"
|
||||
local mail = "flatpak run org.mozilla.thunderbird_esr"
|
||||
-- Panama owns the Hyprland and shell controls. GNOME Settings remains installed
|
||||
-- and searchable in Vicinae for its hardware/account panels.
|
||||
local settings = "qs ipc call settings toggle"
|
||||
local sysmonitor = "flatpak run io.missioncenter.MissionCenter"
|
||||
|
||||
-- Vicinae is the Raycast-style launcher. `vicinae toggle` shows/hides the
|
||||
-- window against the already-running server (started in autostart.lua).
|
||||
local launcher = "vicinae toggle"
|
||||
local osd = function(action)
|
||||
return "$HOME/.config/quickshell/scripts/panama-osd " .. action
|
||||
end
|
||||
|
||||
-- Quickshell IPC targets. See quickshell/shell.qml for the handlers.
|
||||
local qs = function(target, fn) return "qs ipc call " .. target .. " " .. fn end
|
||||
|
||||
-- ── Applications ────────────────────────────────────────────────────────────
|
||||
-- ── User rebinding ──────────────────────────────────────────────────────────
|
||||
-- Every bind below goes through `bind` rather than `hl.bind` directly, so a
|
||||
-- chord can be replaced from Panama Settings without this file changing.
|
||||
--
|
||||
-- Overrides are keyed by the bind's SHIPPED chord, and ONLY the chord is taken
|
||||
-- from settings -- the action is always the Lua value written here. A stored
|
||||
-- override can therefore move a shortcut but can never make one do something
|
||||
-- else, which is the property that makes reading them from a JSON file the
|
||||
-- user can edit safe.
|
||||
--
|
||||
-- Keyed by chord rather than by description because chords are unique and
|
||||
-- descriptions are not: "Calculator" is both SUPER+C and the XF86Calculator
|
||||
-- hardware key, and keying by description moved both of them onto the same new
|
||||
-- chord, silently costing the hardware key.
|
||||
--
|
||||
-- An override whose chord is not a plausible chord is ignored and the shipped
|
||||
-- one is used, so a hand-edited settings file cannot cost you a keymap.
|
||||
|
||||
local overrides = prefs.get("keybindOverrides", {})
|
||||
|
||||
local function valid_chord(chord)
|
||||
if type(chord) ~= "string" or chord == "" or #chord > 64 then
|
||||
return false
|
||||
end
|
||||
-- "SUPER + SHIFT + K", "XF86AudioPlay", "Print", "SUPER + mouse:272"
|
||||
return chord:match("^[%w_+%s:]+$") ~= nil
|
||||
end
|
||||
|
||||
local function bind(chord, action, opts)
|
||||
local override = overrides[chord]
|
||||
if valid_chord(override) then
|
||||
chord = override
|
||||
end
|
||||
return hl.bind(chord, action, opts)
|
||||
end
|
||||
|
||||
bind(mod .. " + T", hl.dsp.exec_cmd(terminal), { description = "Terminal" })
|
||||
bind(mod .. " + N", hl.dsp.exec_cmd(editor), { description = "Neovim" })
|
||||
bind(mod .. " + W", hl.dsp.exec_cmd(browser), { description = "Browser" })
|
||||
bind(mod .. " + F", hl.dsp.exec_cmd(files), { description = "Files" })
|
||||
bind(mod .. " + C", hl.dsp.exec_cmd(calculator), { description = "Calculator" })
|
||||
bind(mod .. " + E", hl.dsp.exec_cmd(mail), { description = "Mail" })
|
||||
bind(mod .. " + I", hl.dsp.exec_cmd(settings), { description = "Settings" })
|
||||
bind("CTRL + SHIFT + Escape", hl.dsp.exec_cmd(sysmonitor), { description = "System monitor" })
|
||||
|
||||
-- ── Launcher ────────────────────────────────────────────────────────────────
|
||||
-- All three keys open the same launcher, on purpose: SUPER+A and SUPER+R were
|
||||
-- the GNOME app-grid and run-dialog shortcuts, and SUPER+SPACE is here as a
|
||||
-- third option to settle on. Vicinae covers apps, calculator, files, clipboard,
|
||||
-- emoji and window switching, so a separate run dialog and app grid are gone.
|
||||
bind(mod .. " + A", hl.dsp.exec_cmd(launcher), { description = "Launcher" })
|
||||
bind(mod .. " + R", hl.dsp.exec_cmd(launcher), { description = "Launcher" })
|
||||
bind(mod .. " + Space", hl.dsp.exec_cmd(launcher), { description = "Launcher" })
|
||||
|
||||
-- Emergency fallback launcher. Vicinae runs as a systemd user service and the
|
||||
-- bar is Quickshell; if either fails to come up, this is how you start an
|
||||
-- application without dropping to a TTY. Depends on nothing but wofi itself.
|
||||
bind(mod .. " + SHIFT + R", hl.dsp.exec_cmd("wofi"), { description = "Fallback launcher" })
|
||||
|
||||
-- Clipboard history and emoji, straight into the relevant launcher view.
|
||||
-- Deeplink form is the one from vicinae's own Hyprland quickstart.
|
||||
bind(mod .. " + V", hl.dsp.exec_cmd("vicinae vicinae://launch/clipboard/history"),
|
||||
{ description = "Clipboard history" })
|
||||
bind(mod .. " + Period", hl.dsp.exec_cmd("vicinae vicinae://launch/emoji/search"),
|
||||
{ description = "Emoji picker" })
|
||||
|
||||
-- ── Shell surfaces (Quickshell) ─────────────────────────────────────────────
|
||||
-- SUPER+S was GNOME's quick settings; kept.
|
||||
bind(mod .. " + S", hl.dsp.exec_cmd(qs("quicksettings", "toggle")), { description = "Quick settings" })
|
||||
|
||||
-- Start a 45-minute focus session on the current workspace, or reveal its
|
||||
-- Signal Glass controls if one is already running.
|
||||
bind(mod .. " + SHIFT + F", hl.dsp.exec_cmd(qs("focus", "reveal")), { description = "Focus session" })
|
||||
|
||||
-- Workspace overview. GNOME put this on a bare SUPER tap; tap-detection on a
|
||||
-- modifier misfires when you're fast with SUPER+key combos, so it lives on a
|
||||
-- real chord instead. SUPER+grave was Forge's "cycle windows of same app",
|
||||
-- which Hyprland has no equivalent for.
|
||||
bind(mod .. " + grave", hl.dsp.exec_cmd(qs("overview", "toggle")), { description = "Overview" })
|
||||
|
||||
-- Notification centre.
|
||||
bind(mod .. " + B", hl.dsp.exec_cmd(qs("notifications", "toggle")), { description = "Notifications" })
|
||||
|
||||
-- Screenshot / screen record. One key, then pick screen / window / region and
|
||||
-- whether to capture or record -- reproducing GNOME's Print-screen UI.
|
||||
bind("Print", hl.dsp.exec_cmd(qs("capture", "open")), { description = "Screenshot / record" })
|
||||
-- The GNOME direct-capture variants, kept as shortcuts past the picker.
|
||||
bind("SHIFT + Print", hl.dsp.exec_cmd(qs("capture", "screenNow")), { description = "Screenshot: whole screen" })
|
||||
bind("ALT + Print", hl.dsp.exec_cmd(qs("capture", "windowNow")), { description = "Screenshot: window" })
|
||||
|
||||
-- Local OCR and QR/barcode recognition through the same region picker. This
|
||||
-- opens directly in Selection + Read mode; Print still exposes every mode.
|
||||
bind(mod .. " + SHIFT + S", hl.dsp.exec_cmd(qs("screen-intelligence", "open")),
|
||||
{ description = "Screen Intelligence" })
|
||||
|
||||
-- Colour picker: copies the hex under the cursor to the clipboard.
|
||||
bind(mod .. " + SHIFT + P", hl.dsp.exec_cmd("hyprpicker -a -f hex"), { description = "Colour picker" })
|
||||
|
||||
-- ── Window management ───────────────────────────────────────────────────────
|
||||
bind(mod .. " + Q", hl.dsp.window.close(), { description = "Close window" })
|
||||
bind(mod .. " + U", hl.dsp.window.fullscreen({ mode = "fullscreen" }), { description = "Fullscreen" })
|
||||
|
||||
-- Forge: window-toggle-float / window-toggle-always-float.
|
||||
-- "Always float" has no Hyprland equivalent (it wrote a persistent rule); pin
|
||||
-- is the nearest useful thing -- the window floats above every workspace.
|
||||
bind(mod .. " + CTRL + C", hl.dsp.window.float({ action = "toggle" }), { description = "Toggle float" })
|
||||
bind(mod .. " + CTRL + SHIFT + C", hl.dsp.window.pin({ action = "toggle" }), { description = "Pin window" })
|
||||
|
||||
-- Forge: con-split-layout-toggle / con-split-horizontal / con-split-vertical.
|
||||
bind(mod .. " + CTRL + G", hl.dsp.layout("togglesplit"), { description = "Toggle split direction" })
|
||||
bind(mod .. " + CTRL + Z", hl.dsp.layout("preselect r"), { description = "Next window splits right" })
|
||||
bind(mod .. " + CTRL + V", hl.dsp.layout("preselect d"), { description = "Next window splits down" })
|
||||
|
||||
-- Forge: window-shrink / window-expand / window-reset-sizes.
|
||||
bind(mod .. " + bracketleft", hl.dsp.layout("splitratio -0.05"), { repeating = true, description = "Shrink" })
|
||||
bind(mod .. " + bracketright", hl.dsp.layout("splitratio +0.05"), { repeating = true, description = "Expand" })
|
||||
bind(mod .. " + equal", hl.dsp.layout("splitratio exact 0.5"), { description = "Reset split" })
|
||||
|
||||
-- Focus (Forge: window-focus-*). Both vim keys and arrows, as in Forge.
|
||||
bind(mod .. " + H", hl.dsp.focus({ direction = "l" }), { description = "Focus left" })
|
||||
bind(mod .. " + J", hl.dsp.focus({ direction = "d" }), { description = "Focus down" })
|
||||
bind(mod .. " + K", hl.dsp.focus({ direction = "u" }), { description = "Focus up" })
|
||||
bind(mod .. " + L", hl.dsp.focus({ direction = "r" }), { description = "Focus right" })
|
||||
bind(mod .. " + left", hl.dsp.focus({ direction = "l" }), { description = "Focus left" })
|
||||
bind(mod .. " + down", hl.dsp.focus({ direction = "d" }), { description = "Focus down" })
|
||||
bind(mod .. " + up", hl.dsp.focus({ direction = "u" }), { description = "Focus up" })
|
||||
bind(mod .. " + right", hl.dsp.focus({ direction = "r" }), { description = "Focus right" })
|
||||
|
||||
-- Move (Forge: window-move-*).
|
||||
bind(mod .. " + SHIFT + H", hl.dsp.window.move({ direction = "l" }), { description = "Move window left" })
|
||||
bind(mod .. " + SHIFT + J", hl.dsp.window.move({ direction = "d" }), { description = "Move window down" })
|
||||
bind(mod .. " + SHIFT + K", hl.dsp.window.move({ direction = "u" }), { description = "Move window up" })
|
||||
bind(mod .. " + SHIFT + L", hl.dsp.window.move({ direction = "r" }), { description = "Move window right" })
|
||||
|
||||
-- Swap (Forge: window-swap-*).
|
||||
bind(mod .. " + CTRL + H", hl.dsp.window.swap({ direction = "l" }), { description = "Swap left" })
|
||||
bind(mod .. " + CTRL + J", hl.dsp.window.swap({ direction = "d" }), { description = "Swap down" })
|
||||
bind(mod .. " + CTRL + K", hl.dsp.window.swap({ direction = "u" }), { description = "Swap up" })
|
||||
bind(mod .. " + CTRL + L", hl.dsp.window.swap({ direction = "r" }), { description = "Swap right" })
|
||||
|
||||
-- Resize (Forge: window-resize-<edge>-<increase|decrease>).
|
||||
--
|
||||
-- Forge resized one named edge at a time. Hyprland resizes the active window
|
||||
-- along an axis and lets the layout decide which edge actually moves, so the
|
||||
-- eight Forge keys collapse onto four behaviours. The pairing is kept
|
||||
-- consistent with the original: Y/B/O/M are horizontal, I/P/U/N are vertical,
|
||||
-- and "increase" always grows while "decrease" always shrinks.
|
||||
local step = 60
|
||||
bind(mod .. " + SHIFT + Y", hl.dsp.window.resize({ x = step, y = 0, relative = true }), { repeating = true, description = "Wider" })
|
||||
bind(mod .. " + SHIFT + O", hl.dsp.window.resize({ x = step, y = 0, relative = true }), { repeating = true, description = "Wider" })
|
||||
bind(mod .. " + SHIFT + B", hl.dsp.window.resize({ x = -step, y = 0, relative = true }), { repeating = true, description = "Narrower" })
|
||||
bind(mod .. " + SHIFT + M", hl.dsp.window.resize({ x = -step, y = 0, relative = true }), { repeating = true, description = "Narrower" })
|
||||
bind(mod .. " + SHIFT + I", hl.dsp.window.resize({ x = 0, y = step, relative = true }), { repeating = true, description = "Taller" })
|
||||
bind(mod .. " + SHIFT + U", hl.dsp.window.resize({ x = 0, y = step, relative = true }), { repeating = true, description = "Taller" })
|
||||
bind(mod .. " + SHIFT + P", hl.dsp.window.resize({ x = 0, y = -step, relative = true }), { repeating = true, description = "Shorter" })
|
||||
bind(mod .. " + SHIFT + N", hl.dsp.window.resize({ x = 0, y = -step, relative = true }), { repeating = true, description = "Shorter" })
|
||||
|
||||
-- Window cycling (GNOME: cycle-windows on SUPER+Tab).
|
||||
bind(mod .. " + Tab", hl.dsp.window.cycle_next({ next = true }), { description = "Next window" })
|
||||
bind(mod .. " + SHIFT + Tab", hl.dsp.window.cycle_next({ next = false }), { description = "Previous window" })
|
||||
-- Jump back to the previously focused window.
|
||||
bind(mod .. " + SHIFT + grave", hl.dsp.focus({ last = true }), { description = "Last window" })
|
||||
|
||||
-- Mouse: drag to move, right-drag to resize.
|
||||
bind(mod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true, description = "Move window with pointer" })
|
||||
bind(mod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true, description = "Resize window with pointer" })
|
||||
|
||||
-- ── Workspaces ──────────────────────────────────────────────────────────────
|
||||
-- ALT is the workspace modifier, matching the GNOME setup.
|
||||
--
|
||||
-- Plain relative selectors ("+1" / "-1") reproduce GNOME's dynamic workspaces:
|
||||
-- moving right past the last workspace creates a new one, and moving left from
|
||||
-- the first clamps instead of wrapping.
|
||||
bind("ALT + H", hl.dsp.focus({ workspace = "-1" }), { description = "Workspace left" })
|
||||
bind("ALT + L", hl.dsp.focus({ workspace = "+1" }), { description = "Workspace right" })
|
||||
bind("ALT + SHIFT + H", hl.dsp.window.move({ workspace = "-1" }), { description = "Move window to workspace left" })
|
||||
bind("ALT + SHIFT + L", hl.dsp.window.move({ workspace = "+1" }), { description = "Move window to workspace right" })
|
||||
|
||||
-- GNOME also had these on CTRL+ALT+Up/Down.
|
||||
bind("CTRL + ALT + up", hl.dsp.focus({ workspace = "-1" }), { description = "Workspace left" })
|
||||
bind("CTRL + ALT + down", hl.dsp.focus({ workspace = "+1" }), { description = "Workspace right" })
|
||||
|
||||
-- Direct jump. ALT+0 is workspace 10.
|
||||
for i = 1, 10 do
|
||||
local key = i % 10
|
||||
bind("ALT + " .. key, hl.dsp.focus({ workspace = i }), { description = "Workspace " .. i })
|
||||
bind("ALT + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }), { description = "Move window to workspace " .. i })
|
||||
end
|
||||
|
||||
-- Scroll the mouse wheel over the desktop with SUPER held to change workspace.
|
||||
-- (Scrolling the workspace indicator in the bar does the same; that's handled
|
||||
-- in quickshell/modules/bar/Workspaces.qml.)
|
||||
bind(mod .. " + mouse_down", hl.dsp.focus({ workspace = "+1" }), { description = "Workspace right" })
|
||||
bind(mod .. " + mouse_up", hl.dsp.focus({ workspace = "-1" }), { description = "Workspace left" })
|
||||
|
||||
-- Minimise, as far as Hyprland has one.
|
||||
--
|
||||
-- Hyprland has no minimise: it receives the request (the binary has
|
||||
-- setSetMinimized handlers for xdg, XWayland and foreign-toplevel) but exposes
|
||||
-- no dispatcher, no config option and not even an event to hook, so titlebar
|
||||
-- minimise buttons are inert and cannot be made to work. A tiling WM has no
|
||||
-- iconified state and no taskbar to restore from.
|
||||
--
|
||||
-- The scratchpad is the honest equivalent: the window goes away, and the same
|
||||
-- key brings it back. Bound to X to match the muscle memory it replaces.
|
||||
bind(mod .. " + X", hl.dsp.workspace.toggle_special("scratch"), { description = "Toggle scratchpad (restore minimised)" })
|
||||
bind(mod .. " + SHIFT + X", hl.dsp.window.move({ workspace = "special:scratch" }), { description = "Minimise to scratchpad" })
|
||||
|
||||
-- ── Session ─────────────────────────────────────────────────────────────────
|
||||
-- GNOME's lock was SUPER+L, which is "focus right" here, so lock moves to
|
||||
-- CTRL+ALT+L -- the other binding most people already have in muscle memory.
|
||||
bind("CTRL + ALT + L", hl.dsp.exec_cmd("loginctl lock-session"), { description = "Lock" })
|
||||
bind("CTRL + ALT + Delete", hl.dsp.exec_cmd(qs("powermenu", "toggle")), { description = "Power menu" })
|
||||
|
||||
-- ── Media and volume ────────────────────────────────────────────────────────
|
||||
-- locked = true keeps these working on the lock screen, as they do in GNOME.
|
||||
-- 6% steps match the GNOME volume-step setting.
|
||||
bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd(osd("volume up 6")), { locked = true, repeating = true , description = "Volume up" })
|
||||
bind("XF86AudioLowerVolume", hl.dsp.exec_cmd(osd("volume down 6")), { locked = true, repeating = true , description = "Volume down" })
|
||||
bind("XF86AudioMute", hl.dsp.exec_cmd(osd("volume toggle")), { locked = true , description = "Mute" })
|
||||
bind("XF86AudioMicMute", hl.dsp.exec_cmd(osd("microphone toggle")), { locked = true , description = "Mute microphone" })
|
||||
|
||||
-- Fine-grained steps, matching GNOME's shift/alt volume modifiers.
|
||||
bind("SHIFT + XF86AudioRaiseVolume", hl.dsp.exec_cmd(osd("volume up 1")), { locked = true, repeating = true , description = "Volume up (fine)" })
|
||||
bind("SHIFT + XF86AudioLowerVolume", hl.dsp.exec_cmd(osd("volume down 1")), { locked = true, repeating = true , description = "Volume down (fine)" })
|
||||
|
||||
bind("XF86AudioPlay", hl.dsp.exec_cmd(osd("media play-pause")), { locked = true , description = "Play or pause" })
|
||||
bind("XF86AudioPause", hl.dsp.exec_cmd(osd("media play-pause")), { locked = true , description = "Play or pause" })
|
||||
bind("XF86AudioNext", hl.dsp.exec_cmd(osd("media next")), { locked = true , description = "Next track" })
|
||||
bind("XF86AudioPrev", hl.dsp.exec_cmd(osd("media previous")), { locked = true , description = "Previous track" })
|
||||
bind("XF86AudioStop", hl.dsp.exec_cmd(osd("media stop")), { locked = true , description = "Stop playback" })
|
||||
|
||||
bind("XF86MonBrightnessUp", hl.dsp.exec_cmd(osd("brightness up 5")), { locked = true, repeating = true , description = "Brightness up" })
|
||||
bind("XF86MonBrightnessDown", hl.dsp.exec_cmd(osd("brightness down 5")), { locked = true, repeating = true , description = "Brightness down" })
|
||||
|
||||
-- Hardware keys GNOME mapped that have obvious equivalents.
|
||||
bind("XF86Tools", hl.dsp.exec_cmd(settings), { description = "Settings" })
|
||||
bind("XF86Calculator", hl.dsp.exec_cmd(calculator), { description = "Calculator" })
|
||||
bind("XF86Explorer", hl.dsp.exec_cmd(files), { description = "Files" })
|
||||
bind("XF86WWW", hl.dsp.exec_cmd(browser), { description = "Browser" })
|
||||
bind("XF86Mail", hl.dsp.exec_cmd(mail), { description = "Mail" })
|
||||
bind("XF86Search", hl.dsp.exec_cmd(launcher), { description = "Launcher" })
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,258 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Look and feel -- Tokyo Night Moon
|
||||
--
|
||||
-- Colours here must stay in sync with quickshell/config/Theme.qml.
|
||||
-- accent #82aaff borders / focus
|
||||
-- bg #222436 base
|
||||
--
|
||||
-- Performance note: every animation below is event-driven. Nothing uses the
|
||||
-- "loop" style on borderangle/shadowangle/glowangle -- that style forces
|
||||
-- Hyprland to render at full refresh rate forever, even when nothing is
|
||||
-- visible, which is exactly the kind of idle GPU burn to avoid on a 4500x3000
|
||||
-- display.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = require("prefs")
|
||||
|
||||
hl.config({
|
||||
general = {
|
||||
gaps_in = prefs.get("gapsIn", 5),
|
||||
gaps_out = prefs.get("gapsOut", 10),
|
||||
|
||||
border_size = prefs.get("borderSize", 2),
|
||||
|
||||
col = {
|
||||
-- The prism: blue leads, orchid follows, on a diagonal so the pair
|
||||
-- is visible on both a tall and a wide window. Same two colours as
|
||||
-- the shell's hairline (quickshell/widgets/PrismEdge.qml) and the
|
||||
-- tmux theme this palette came from.
|
||||
active_border = { colors = { "rgba(82aaffee)", "rgba(b172b0ee)" }, angle = 115 },
|
||||
-- Unfocused windows get no colour at all. The gradient only means
|
||||
-- something if exactly one window on screen is wearing it.
|
||||
-- Follows the colour scheme: a dark neutral is invisible against a
|
||||
-- light desktop. services/ColorScheme.qml applies changes live;
|
||||
-- this is the value a fresh session starts from.
|
||||
inactive_border = prefs.get("colorScheme", "dark") == "light"
|
||||
and "rgba(a8aecb99)" or "rgba(3b426199)",
|
||||
},
|
||||
|
||||
resize_on_border = prefs.get("resizeOnBorder", true),
|
||||
extend_border_grab_area = prefs.getInt("borderGrabArea", 15),
|
||||
hover_icon_on_border = prefs.get("hoverIconOnBorder", true),
|
||||
|
||||
-- Enables the per-window "immediate" rule used for games in rules.lua.
|
||||
-- Harmless on its own; tearing only happens where a rule opts in.
|
||||
allow_tearing = true,
|
||||
|
||||
layout = "dwindle",
|
||||
|
||||
snap = {
|
||||
enabled = true,
|
||||
window_gap = prefs.getInt("snapWindowGap", 10),
|
||||
monitor_gap = prefs.getInt("snapMonitorGap", 10),
|
||||
respect_gaps = prefs.get("snapRespectGaps", false),
|
||||
},
|
||||
},
|
||||
|
||||
decoration = {
|
||||
-- 18 to match the shell's popover radius, so a window and a panel sitting
|
||||
-- next to each other read as the same object family.
|
||||
rounding = prefs.get("windowRounding", 18),
|
||||
rounding_power = prefs.get("roundingPower", 2),
|
||||
|
||||
active_opacity = prefs.get("activeOpacity", 1.0),
|
||||
fullscreen_opacity = prefs.get("fullscreenOpacity", 1.0),
|
||||
inactive_opacity = prefs.get("inactiveOpacity", 1.0),
|
||||
|
||||
blur = {
|
||||
enabled = prefs.get("blurEnabled", true),
|
||||
size = prefs.get("blurSize", 8),
|
||||
passes = prefs.get("blurPasses", 3),
|
||||
|
||||
-- Required for blur to be affordable. Never turn this off.
|
||||
new_optimizations = true,
|
||||
-- Floating windows skip blurring what's behind tiled ones. Big win.
|
||||
xray = true,
|
||||
|
||||
ignore_opacity = true,
|
||||
noise = 0.0117,
|
||||
contrast = 0.9,
|
||||
brightness = 0.85,
|
||||
vibrancy = 0.17,
|
||||
vibrancy_darkness = 0.05,
|
||||
|
||||
popups = true,
|
||||
popups_ignorealpha = 0.2,
|
||||
|
||||
-- Blurring the special workspace is expensive and rarely seen.
|
||||
special = false,
|
||||
},
|
||||
|
||||
shadow = {
|
||||
enabled = prefs.get("shadowEnabled", true),
|
||||
range = prefs.get("shadowRange", 20),
|
||||
render_power = prefs.getInt("shadowRenderPower", 3),
|
||||
sharp = prefs.get("shadowSharp", false),
|
||||
color = "rgba(15161eee)",
|
||||
-- Deliberately not a setting: a two-axis offset needs a control we
|
||||
-- do not have, and a slider bound to half a value is worse than
|
||||
-- leaving it alone. SystemSettings understands the vec2 shape
|
||||
-- already, so adding it later is only a matter of the widget.
|
||||
offset = { 0, 4 },
|
||||
scale = 1.0,
|
||||
},
|
||||
|
||||
-- New in 0.56. Kept deliberately faint: in this direction the gradient
|
||||
-- border is the signature, and a strong halo would compete with it.
|
||||
-- This is just enough to lift the focused window off the wallpaper.
|
||||
glow = {
|
||||
enabled = prefs.get("glowEnabled", true),
|
||||
range = prefs.get("glowRange", 8),
|
||||
render_power = 2,
|
||||
color = "rgba(82aaff33)",
|
||||
color_inactive = "rgba(00000000)",
|
||||
},
|
||||
|
||||
-- Off: costs real frame time and reads as smeary on a 60Hz panel.
|
||||
motion_blur = { enabled = false },
|
||||
},
|
||||
|
||||
animations = { enabled = prefs.get("animationsEnabled", true) },
|
||||
|
||||
dwindle = {
|
||||
-- Keep the split orientation a window was created with. Closest match
|
||||
-- to how the Forge extension behaved on GNOME.
|
||||
preserve_split = prefs.get("preserveSplit", true),
|
||||
smart_resizing = true,
|
||||
},
|
||||
|
||||
-- Only in effect when the tiling layout is "master". Panama ships dwindle,
|
||||
-- but Settings offers master as a choice, and a layout you can select and
|
||||
-- cannot configure is barely a choice at all.
|
||||
master = {
|
||||
mfact = prefs.get("masterFactor", 0.55),
|
||||
orientation = prefs.get("masterOrientation", "left"),
|
||||
new_status = prefs.get("masterNewStatus", "slave"),
|
||||
new_on_top = prefs.get("masterNewOnTop", false),
|
||||
},
|
||||
|
||||
misc = {
|
||||
force_default_wallpaper = 0,
|
||||
-- Stored as "show the logo / show the splash" and written as Hyprland's
|
||||
-- `disable_*`, matching the `invert` flag on these entries in
|
||||
-- PreferenceSchema so both sides agree about which way round they are.
|
||||
disable_hyprland_logo = not prefs.get("hyprlandLogo", false),
|
||||
disable_splash_rendering = not prefs.get("hyprlandSplash", false),
|
||||
|
||||
-- Keep native Wayland selection paste and GTK's matching preference
|
||||
-- in lockstep. DesktopStyle applies the GTK half only after this value
|
||||
-- has been read back and stored by SystemSettings.
|
||||
middle_click_paste = prefs.get("middleClickPaste", true),
|
||||
|
||||
-- Same setting as Theme.fontFamily in the shell. If only the QML side
|
||||
-- followed the preference, the compositor and the shell would disagree
|
||||
-- about the interface font and nobody would notice until a tooltip
|
||||
-- rendered in a different typeface.
|
||||
font_family = prefs.get("interfaceFont", "Adwaita Sans"),
|
||||
|
||||
-- Variable refresh rate. 3 = enable only for fullscreen windows whose
|
||||
-- content type is "video" or "game" -- the tag rules.lua applies to
|
||||
-- games. Keeps VRR off the desktop, where it causes visible flicker.
|
||||
vrr = prefs.getInt("vrrPolicy", 3),
|
||||
|
||||
-- Blur behind the lock screen.
|
||||
session_lock_blur = true,
|
||||
|
||||
-- Let a crashed lock screen be recovered rather than stranding you.
|
||||
allow_session_lock_restore = true,
|
||||
|
||||
-- Don't let apps steal focus by shouting; matches GNOME's behaviour.
|
||||
focus_on_activate = false,
|
||||
},
|
||||
|
||||
render = {
|
||||
-- Colour management is stable and on by default in 0.56.
|
||||
cm_enabled = true,
|
||||
|
||||
-- 1 = automatically flip the monitor into HDR for fullscreen content
|
||||
-- that asks for it, and back out afterwards. This is how games get HDR
|
||||
-- without the desktop paying the screencopy cost. See monitors.lua.
|
||||
cm_auto_hdr = prefs.getInt("autoHdr", 1),
|
||||
|
||||
-- 2 = direct scanout only for windows tagged content = "game"
|
||||
-- (set by the rules in rules.lua). Bypasses compositing for real
|
||||
-- fullscreen games.
|
||||
direct_scanout = prefs.getInt("directScanoutPolicy", 2),
|
||||
},
|
||||
|
||||
cursor = {
|
||||
-- 2 = auto. Disables the hardware cursor when tearing, which is what
|
||||
-- keeps the cursor from ghosting in games.
|
||||
no_hardware_cursors = 2,
|
||||
|
||||
-- Don't let the cursor break VRR on content tagged as a game.
|
||||
no_break_fs_vrr = 2,
|
||||
min_refresh_rate = 48,
|
||||
|
||||
-- Pull the cursor theme from gsettings so GTK apps and Hyprland agree.
|
||||
sync_gsettings_theme = true,
|
||||
|
||||
-- Fade the cursor out after 4s of no movement, like GNOME does.
|
||||
inactive_timeout = prefs.get("cursorInactiveTimeout", 4),
|
||||
},
|
||||
|
||||
ecosystem = {
|
||||
no_update_news = not prefs.get("hyprlandUpdateNews", false),
|
||||
no_donation_nag = not prefs.get("hyprlandDonationNag", false),
|
||||
},
|
||||
|
||||
xwayland = {
|
||||
-- Render X11 apps at 1:1 device pixels so they're sharp rather than
|
||||
-- bilinearly upscaled. See the note in README.md about the unavoidable
|
||||
-- sizing tradeoff at 1.5x.
|
||||
force_zero_scaling = true,
|
||||
},
|
||||
|
||||
quirks = {
|
||||
-- 2 = report HDR as preferred to gamescope only. Stops gamescope from
|
||||
-- white-screening when it expects HDR to already be live.
|
||||
prefer_hdr = 2,
|
||||
},
|
||||
})
|
||||
|
||||
-- ── Motion ──────────────────────────────────────────────────────────────────
|
||||
-- speed is in deciseconds: speed = 4 means 400ms. Higher = slower.
|
||||
|
||||
hl.curve("easeOutQuint", { type = "bezier", points = { { 0.23, 1 }, { 0.32, 1 } } })
|
||||
hl.curve("almostLinear", { type = "bezier", points = { { 0.5, 0.5 }, { 0.75, 1 } } })
|
||||
hl.curve("quick", { type = "bezier", points = { { 0.15, 0 }, { 0.1, 1 } } })
|
||||
hl.curve("easy", { type = "spring", mass = 1, stiffness = 238.1191, dampening = 24.21279 })
|
||||
-- A touch bouncier than "easy"; used for windows appearing.
|
||||
hl.curve("snappy", { type = "spring", mass = 1, stiffness = 320, dampening = 22 })
|
||||
|
||||
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
|
||||
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
|
||||
|
||||
-- The focus sweep: when a window takes focus, its gradient border rotates into
|
||||
-- place once. Style "once" is load-bearing -- the alternative, "loop", makes
|
||||
-- Hyprland render at full refresh rate forever, even when no border is visible.
|
||||
-- That is exactly the idle GPU cost this config exists to avoid, so: never loop.
|
||||
hl.animation({ leaf = "borderangle", enabled = true, speed = 6, bezier = "easeOutQuint", style = "once" })
|
||||
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" })
|
||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, spring = "snappy", style = "popin 90%" })
|
||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "almostLinear", style = "popin 90%" })
|
||||
|
||||
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
|
||||
|
||||
-- Layer surfaces = the Quickshell bar, dock, popovers and overlays.
|
||||
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "layersIn", enabled = true, speed = 3, bezier = "easeOutQuint", style = "fade" })
|
||||
hl.animation({ leaf = "layersOut", enabled = true, speed = 2, bezier = "almostLinear", style = "fade" })
|
||||
|
||||
-- Workspace switching: horizontal slide, matching Alt+H / Alt+L direction.
|
||||
hl.animation({ leaf = "workspaces", enabled = true, speed = 2.5, bezier = "easeOutQuint", style = "slidefade 15%" })
|
||||
|
||||
hl.animation({ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "quick" })
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,138 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Monitors
|
||||
--
|
||||
-- Kuycon P20 on DP-2: 4500x3000 @ 60Hz, 1.5x fractional scale.
|
||||
-- 4500/1.5 = 3000 and 3000/1.5 = 2000, both integers, so this is a "clean"
|
||||
-- fractional scale and Hyprland will not complain.
|
||||
--
|
||||
-- On HDR --------------------------------------------------------------------
|
||||
-- GNOME runs this panel in bt2100/HDR. Hyprland CAN do that (cm = "hdr"), but
|
||||
-- on 0.56.x it currently breaks screencopy: grim screenshots come back empty,
|
||||
-- and OBS/Sunshine capture and hyprlock's blurred background go with them.
|
||||
-- Root cause is still open upstream. Since screen capture matters more here
|
||||
-- than an HDR desktop, the desktop runs SDR at 10-bit and HDR is handed to
|
||||
-- fullscreen games only, via render.cm_auto_hdr in looks.lua.
|
||||
--
|
||||
-- To try full-time HDR anyway, set cm = "hdr" below (or override it in
|
||||
-- overrides.lua) and read the notes in that file first.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = require("prefs")
|
||||
|
||||
-- Per-output overrides written by Panama Settings, keyed by output name:
|
||||
-- { ["DP-2"] = { mode = "3840x2160@60", scale = 2, transform = 0 } }
|
||||
--
|
||||
-- Only mode, scale, and transform are read. Colour management and bit depth
|
||||
-- stay here, because those are the settings with a documented reason attached
|
||||
-- (see the header) rather than preferences, and a settings page has no way to
|
||||
-- explain the screencopy tradeoff at the moment you would be changing it.
|
||||
local displays = prefs.get("displays", {})
|
||||
if type(displays) ~= "table" then
|
||||
displays = {}
|
||||
end
|
||||
|
||||
local function mode_dimensions(mode)
|
||||
if type(mode) ~= "string" then
|
||||
return nil, nil
|
||||
end
|
||||
local width, height, refresh = mode:match("^(%d+)x(%d+)@(%d+%.%d+)$")
|
||||
if width == nil then
|
||||
width, height, refresh = mode:match("^(%d+)x(%d+)@(%d+)$")
|
||||
end
|
||||
width, height, refresh = tonumber(width), tonumber(height), tonumber(refresh)
|
||||
if width == nil or height == nil or refresh == nil
|
||||
or width <= 0 or height <= 0 or refresh <= 0 then
|
||||
return nil, nil
|
||||
end
|
||||
return width, height
|
||||
end
|
||||
|
||||
local function valid_mode(mode)
|
||||
local width = mode_dimensions(mode)
|
||||
return width ~= nil
|
||||
end
|
||||
|
||||
local function valid_scale(mode, scale)
|
||||
local width, height = mode_dimensions(mode)
|
||||
if width == nil or type(scale) ~= "number" or scale ~= scale
|
||||
or scale <= 0 or scale > 4 then
|
||||
return false
|
||||
end
|
||||
local logical_width = width / scale
|
||||
local logical_height = height / scale
|
||||
return math.abs(logical_width - math.floor(logical_width + 0.5)) < 0.0001
|
||||
and math.abs(logical_height - math.floor(logical_height + 0.5)) < 0.0001
|
||||
end
|
||||
|
||||
local function valid_transform(transform)
|
||||
return type(transform) == "number"
|
||||
and transform == math.floor(transform)
|
||||
and transform >= 0
|
||||
and transform <= 3
|
||||
end
|
||||
|
||||
local function display_entry(output)
|
||||
if type(output) ~= "string" or output == ""
|
||||
or output:match("^[%w_.-]+$") == nil then
|
||||
return nil
|
||||
end
|
||||
local entry = displays[output]
|
||||
if type(entry) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
if not valid_mode(entry.mode)
|
||||
or not valid_scale(entry.mode, entry.scale)
|
||||
or not valid_transform(entry.transform) then
|
||||
return nil
|
||||
end
|
||||
return entry
|
||||
end
|
||||
|
||||
local shipped_mode = "4500x3000@60"
|
||||
local shipped_scale = 1.5
|
||||
local shipped_transform = 0
|
||||
local dp2 = display_entry("DP-2")
|
||||
|
||||
hl.monitor({
|
||||
output = "DP-2",
|
||||
mode = dp2 and dp2.mode or shipped_mode,
|
||||
position = "0x0",
|
||||
scale = dp2 and dp2.scale or shipped_scale,
|
||||
transform = dp2 and dp2.transform or shipped_transform,
|
||||
|
||||
-- 10-bit output. 4500x3000@60 at 10bpc is ~24 Gbps, right at the edge of
|
||||
-- DP 1.4 HBR3, so this relies on DSC. If the display fails to light up or
|
||||
-- falls back to a lower mode, drop this line first.
|
||||
bitdepth = 10,
|
||||
|
||||
-- "auto" = sRGB at 8bpc, wide gamut at 10bpc. Not HDR; see header.
|
||||
cm = "auto",
|
||||
})
|
||||
|
||||
-- Other connected outputs use the same validated per-output store. They keep
|
||||
-- automatic placement and the compositor's normal colour policy; DP-2 alone
|
||||
-- carries the panel-specific 10-bit policy documented above.
|
||||
for output, _ in pairs(displays) do
|
||||
if output ~= "DP-2" then
|
||||
local entry = display_entry(output)
|
||||
if entry ~= nil then
|
||||
hl.monitor({
|
||||
output = output,
|
||||
mode = entry.mode,
|
||||
position = "auto",
|
||||
scale = entry.scale,
|
||||
transform = entry.transform,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Any monitor not named above: sane defaults rather than nothing.
|
||||
hl.monitor({
|
||||
output = "",
|
||||
mode = "preferred",
|
||||
position = "auto",
|
||||
scale = "auto",
|
||||
})
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,33 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Per-machine overrides
|
||||
--
|
||||
-- Loaded last, so anything here beats the modules above. Keep machine-specific
|
||||
-- settings in this file rather than editing the others -- it keeps the rest of
|
||||
-- the config portable between boxes.
|
||||
--
|
||||
-- Full-time HDR ---------------------------------------------------------------
|
||||
-- The desktop ships SDR at 10-bit with HDR handed to fullscreen games (see
|
||||
-- monitors.lua for why). To run the whole desktop in HDR, uncomment this:
|
||||
--
|
||||
-- hl.monitor({
|
||||
-- output = "DP-2",
|
||||
-- mode = "4500x3000@60",
|
||||
-- position = "0x0",
|
||||
-- scale = 1.5,
|
||||
-- bitdepth = 10,
|
||||
-- cm = "hdr",
|
||||
-- sdrbrightness = 1.2, -- how bright SDR content appears, 1.0 - 2.0
|
||||
-- sdrsaturation = 0.98,
|
||||
-- })
|
||||
--
|
||||
-- Then expect to also need some of these, in this order:
|
||||
-- hl.config({ render = { keep_unmodified_copy = 1 } }) -- if captures go transparent
|
||||
-- hl.config({ render = { use_shader_blur_blend = true } }) -- if that kills blur
|
||||
-- hl.config({ render = { direct_scanout = 0 } }) -- if screenshots come back empty
|
||||
-- hl.config({ cursor = { no_hardware_cursors = 1 } }) -- if the cursor looks oversaturated
|
||||
--
|
||||
-- If screenshots, OBS or Sunshine break and stay broken, that is the known
|
||||
-- upstream issue -- go back to cm = "auto".
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
return true
|
||||
@@ -0,0 +1,252 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Shared preferences
|
||||
--
|
||||
-- Reads the same file Panama Settings writes:
|
||||
-- $XDG_CONFIG_HOME/panama/settings.json (default ~/.config/panama/settings.json)
|
||||
--
|
||||
-- This is what makes the desktop one product rather than two that happen to
|
||||
-- agree. The relationship is:
|
||||
--
|
||||
-- * the Lua config is the DEFAULT -- every prefs.get() call passes the shipped
|
||||
-- value as its fallback, so this config still works standalone with no JSON
|
||||
-- file at all;
|
||||
-- * the JSON file is the TRUTH -- both Hyprland and Quickshell read it;
|
||||
-- * Panama Settings is the EDITOR -- it writes the file and applies the change
|
||||
-- live through `hyprctl eval`, so nothing needs a reload and the two sides
|
||||
-- cannot drift apart.
|
||||
--
|
||||
-- Nothing here may raise. A missing, empty, truncated, or actively malformed
|
||||
-- file must cost the user nothing worse than their customisations; it must
|
||||
-- never cost them a working compositor. Every failure path returns the caller's
|
||||
-- fallback.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
local prefs = {}
|
||||
|
||||
-- ── A small JSON reader ─────────────────────────────────────────────────────
|
||||
-- Hyprland's Lua has no JSON support and pulling in a rock for a flat object of
|
||||
-- scalars is not worth the dependency. Handles the whole format apart from
|
||||
-- non-ASCII \u escapes, which are replaced rather than decoded -- no setting is
|
||||
-- a non-ASCII string, and mangling one is preferable to failing the parse.
|
||||
|
||||
local function decode(text)
|
||||
local pos = 1
|
||||
|
||||
local function skipSpace()
|
||||
pos = text:find("[^ \t\r\n]", pos) or #text + 1
|
||||
end
|
||||
|
||||
local parseValue
|
||||
|
||||
local function parseString()
|
||||
pos = pos + 1 -- opening quote
|
||||
local parts = {}
|
||||
while true do
|
||||
local char = text:sub(pos, pos)
|
||||
if char == "" then
|
||||
error("unterminated string")
|
||||
elseif char == '"' then
|
||||
pos = pos + 1
|
||||
break
|
||||
elseif char == "\\" then
|
||||
local escape = text:sub(pos + 1, pos + 1)
|
||||
local simple = {
|
||||
n = "\n", t = "\t", r = "\r", b = "\b", f = "\f",
|
||||
['"'] = '"', ["\\"] = "\\", ["/"] = "/",
|
||||
}
|
||||
if simple[escape] then
|
||||
parts[#parts + 1] = simple[escape]
|
||||
pos = pos + 2
|
||||
elseif escape == "u" then
|
||||
local code = tonumber(text:sub(pos + 2, pos + 5), 16)
|
||||
parts[#parts + 1] = (code and code < 128) and string.char(code) or "?"
|
||||
pos = pos + 6
|
||||
else
|
||||
error("invalid escape")
|
||||
end
|
||||
else
|
||||
parts[#parts + 1] = char
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
return table.concat(parts)
|
||||
end
|
||||
|
||||
local function parseNumber()
|
||||
local literal = text:match("^-?%d+%.?%d*[eE]?[-+]?%d*", pos)
|
||||
if not literal or literal == "" then
|
||||
error("invalid number")
|
||||
end
|
||||
pos = pos + #literal
|
||||
local value = tonumber(literal)
|
||||
if not value then
|
||||
error("invalid number")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
local function parseObject()
|
||||
pos = pos + 1 -- opening brace
|
||||
local out = {}
|
||||
skipSpace()
|
||||
if text:sub(pos, pos) == "}" then
|
||||
pos = pos + 1
|
||||
return out
|
||||
end
|
||||
while true do
|
||||
skipSpace()
|
||||
if text:sub(pos, pos) ~= '"' then
|
||||
error("expected key")
|
||||
end
|
||||
local key = parseString()
|
||||
skipSpace()
|
||||
if text:sub(pos, pos) ~= ":" then
|
||||
error("expected colon")
|
||||
end
|
||||
pos = pos + 1
|
||||
out[key] = parseValue()
|
||||
skipSpace()
|
||||
local char = text:sub(pos, pos)
|
||||
pos = pos + 1
|
||||
if char == "}" then
|
||||
return out
|
||||
elseif char ~= "," then
|
||||
error("expected comma or closing brace")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function parseArray()
|
||||
pos = pos + 1 -- opening bracket
|
||||
local out = {}
|
||||
skipSpace()
|
||||
if text:sub(pos, pos) == "]" then
|
||||
pos = pos + 1
|
||||
return out
|
||||
end
|
||||
while true do
|
||||
out[#out + 1] = parseValue()
|
||||
skipSpace()
|
||||
local char = text:sub(pos, pos)
|
||||
pos = pos + 1
|
||||
if char == "]" then
|
||||
return out
|
||||
elseif char ~= "," then
|
||||
error("expected comma or closing bracket")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
parseValue = function()
|
||||
skipSpace()
|
||||
local char = text:sub(pos, pos)
|
||||
if char == "{" then
|
||||
return parseObject()
|
||||
elseif char == "[" then
|
||||
return parseArray()
|
||||
elseif char == '"' then
|
||||
return parseString()
|
||||
elseif text:sub(pos, pos + 3) == "true" then
|
||||
pos = pos + 4
|
||||
return true
|
||||
elseif text:sub(pos, pos + 4) == "false" then
|
||||
pos = pos + 5
|
||||
return false
|
||||
elseif text:sub(pos, pos + 3) == "null" then
|
||||
pos = pos + 4
|
||||
return nil
|
||||
elseif char == "" then
|
||||
error("unexpected end of input")
|
||||
else
|
||||
return parseNumber()
|
||||
end
|
||||
end
|
||||
|
||||
local value = parseValue()
|
||||
if type(value) ~= "table" then
|
||||
error("top level value is not an object")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- ── Loading ─────────────────────────────────────────────────────────────────
|
||||
|
||||
local function settingsPath()
|
||||
local configHome = os.getenv("XDG_CONFIG_HOME")
|
||||
if configHome == nil or configHome == "" then
|
||||
local home = os.getenv("HOME")
|
||||
if home == nil or home == "" then
|
||||
return nil
|
||||
end
|
||||
configHome = home .. "/.config"
|
||||
end
|
||||
return configHome .. "/panama/settings.json"
|
||||
end
|
||||
|
||||
local function read()
|
||||
local path = settingsPath()
|
||||
if not path then
|
||||
return {}
|
||||
end
|
||||
local file = io.open(path, "r")
|
||||
if not file then
|
||||
return {} -- no file yet is the normal first-run case, not an error
|
||||
end
|
||||
local text = file:read("*a")
|
||||
file:close()
|
||||
if not text or text:match("^%s*$") then
|
||||
return {}
|
||||
end
|
||||
local ok, parsed = pcall(decode, text)
|
||||
if ok and type(parsed) == "table" then
|
||||
return parsed
|
||||
end
|
||||
return {}
|
||||
end
|
||||
|
||||
-- Loaded once at config time. A pcall around the whole thing so that even an
|
||||
-- unanticipated failure in the reader degrades to shipped defaults.
|
||||
local values = {}
|
||||
do
|
||||
local ok, parsed = pcall(read)
|
||||
if ok and type(parsed) == "table" then
|
||||
values = parsed
|
||||
end
|
||||
end
|
||||
|
||||
-- ── Public interface ────────────────────────────────────────────────────────
|
||||
|
||||
-- Returns the stored value for `key`, or `fallback` when it is absent or is not
|
||||
-- the same type as the fallback. The type guard matters: a stale or hand-edited
|
||||
-- file that puts a string where Hyprland needs a number would otherwise abort
|
||||
-- the config, taking down far more than the one setting that was wrong.
|
||||
function prefs.get(key, fallback)
|
||||
local value = values[key]
|
||||
if value == nil then
|
||||
return fallback
|
||||
end
|
||||
if type(value) ~= type(fallback) then
|
||||
return fallback
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
-- Hyprland has no boolean-to-integer coercion for options that take 0/1, and
|
||||
-- several of them read more naturally as a toggle in the settings UI.
|
||||
function prefs.getInt(key, fallback)
|
||||
local value = values[key]
|
||||
if type(value) == "boolean" then
|
||||
return value and 1 or 0
|
||||
end
|
||||
if type(value) ~= "number" then
|
||||
return fallback
|
||||
end
|
||||
return math.floor(value + 0.5)
|
||||
end
|
||||
|
||||
-- True when a settings file was actually read. Useful from overrides.lua.
|
||||
function prefs.loaded()
|
||||
return next(values) ~= nil
|
||||
end
|
||||
|
||||
return prefs
|
||||
@@ -0,0 +1,207 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
-- Window, workspace and layer rules
|
||||
--
|
||||
-- Rules evaluate top to bottom, last match wins -- but every NAMED rule is
|
||||
-- evaluated before every anonymous one, so a named rule can never override an
|
||||
-- anonymous rule further down. Names are used sparingly here for that reason.
|
||||
--
|
||||
-- Matching is Google RE2 (no backtracking). Prefix a pattern with "negative:"
|
||||
-- to invert it.
|
||||
-- ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
-- ── Upstream sanity rules ───────────────────────────────────────────────────
|
||||
hl.window_rule({
|
||||
name = "suppress-maximize-events",
|
||||
match = { class = ".*" },
|
||||
suppress_event = "maximize",
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
name = "fix-xwayland-drags",
|
||||
match = {
|
||||
class = "^$",
|
||||
title = "^$",
|
||||
xwayland = true,
|
||||
float = true,
|
||||
fullscreen = false,
|
||||
pin = false,
|
||||
},
|
||||
no_focus = true,
|
||||
})
|
||||
|
||||
-- ── Floating apps ───────────────────────────────────────────────────────────
|
||||
-- Carried over from the Forge windows.json override list, minus entries for
|
||||
-- apps that are no longer installed.
|
||||
hl.window_rule({
|
||||
match = {
|
||||
class = "^(jetbrains-toolbox|zoom|Cider|Hidamari|com\\.mattjakeman\\.ExtensionManager)$",
|
||||
},
|
||||
float = true,
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
match = { class = "^([Bb]itwarden|com\\.bitwarden\\.desktop)$" },
|
||||
float = true,
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
match = { class = "^(com\\.nextcloud\\.desktopclient\\.nextcloud|Nextcloud)$" },
|
||||
float = true,
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
match = { class = "^(org\\.gnome\\.Calculator|gnome-calculator)$" },
|
||||
float = true,
|
||||
size = { 400, 600 },
|
||||
center = true,
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
match = { class = "^(mpv|io\\.mpv\\.Mpv)$" },
|
||||
float = true,
|
||||
-- Video should never be blurred or dimmed behind another window.
|
||||
no_blur = true,
|
||||
no_dim = true,
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
match = { class = "^(com\\.spotify\\.Client|Spotify)$" },
|
||||
float = true,
|
||||
})
|
||||
|
||||
-- Settings-style utility windows and pickers: float and centre, like GNOME did.
|
||||
hl.window_rule({
|
||||
match = { class = "^(pavucontrol|org\\.pulseaudio\\.pavucontrol|nm-connection-editor|blueman-manager|org\\.gnome\\.Settings)$" },
|
||||
float = true,
|
||||
size = { "monitor_w * 0.45", "monitor_h * 0.6" },
|
||||
center = true,
|
||||
})
|
||||
|
||||
-- Portal dialogs (file chooser, screen share picker) should always float.
|
||||
hl.window_rule({
|
||||
match = { class = "^(xdg-desktop-portal-gtk|org\\.freedesktop\\.impl\\.portal\\.desktop\\.gtk|hyprland-share-picker)$" },
|
||||
float = true,
|
||||
center = true,
|
||||
})
|
||||
|
||||
hl.window_rule({
|
||||
match = { title = "^(Open File|Save File|Save As|Open Folder|Select a File|Choose Files)$" },
|
||||
float = true,
|
||||
center = true,
|
||||
})
|
||||
|
||||
-- Polkit prompt.
|
||||
hl.window_rule({
|
||||
match = { class = "^(hyprpolkitagent|polkit-gnome-authentication-agent-1)$" },
|
||||
float = true,
|
||||
center = true,
|
||||
})
|
||||
|
||||
-- ── Gaming ──────────────────────────────────────────────────────────────────
|
||||
-- content = "game" is the keystone: misc.vrr = 3, render.direct_scanout = 2 and
|
||||
-- cursor.no_break_fs_vrr = 2 all key off it. Blur, animation, shadow and dim are
|
||||
-- all disabled so the compositor gets out of the way entirely.
|
||||
hl.window_rule({
|
||||
match = { class = "^(steam_app_\\d+|gamescope|lutris|net\\.lutris\\.Lutris|com\\.heroicgameslauncher\\.hgl|Minecraft.*|moonlight|com\\.moonlight_stream\\.Moonlight)$" },
|
||||
content = "game",
|
||||
immediate = true,
|
||||
no_blur = true,
|
||||
no_anim = true,
|
||||
no_shadow = true,
|
||||
no_dim = true,
|
||||
})
|
||||
|
||||
-- Steam itself is a normal window, but its transient popups are a mess.
|
||||
hl.window_rule({
|
||||
match = { class = "^steam$", title = "^(Friends List|Steam Settings|Special Offer.*)$" },
|
||||
float = true,
|
||||
})
|
||||
|
||||
-- Fullscreen video in a browser also benefits from no blur / no dim.
|
||||
hl.window_rule({
|
||||
match = { fullscreen = true },
|
||||
no_blur = true,
|
||||
no_dim = true,
|
||||
})
|
||||
|
||||
-- ── Workspace rules ─────────────────────────────────────────────────────────
|
||||
-- Deliberately NO "smart gaps".
|
||||
--
|
||||
-- The usual trick is to strip gaps, border and rounding when a workspace holds
|
||||
-- a single tiled window (`workspace = "w[tv1]"` + matching window rules). It
|
||||
-- was here and it was wrong for this setup: the common case is one window on a
|
||||
-- workspace, so the desktop spent most of its time square-cornered and
|
||||
-- edge-to-edge -- the opposite of the intended look. Gaps and rounding are
|
||||
-- constant now, however many windows are open.
|
||||
|
||||
-- ── Layer rules (Quickshell surfaces) ───────────────────────────────────────
|
||||
-- Namespaces are set in QML via WlrLayershell.namespace; keep these regexes and
|
||||
-- those strings in sync. `hyprctl layers` lists what is actually live.
|
||||
--
|
||||
-- The bar intentionally has no rule: it is a transparent edge-to-edge input
|
||||
-- surface, not glass. Its individual controls paint their own hover feedback.
|
||||
-- Keeping a full-width blur rule here would spend compositor work on pixels
|
||||
-- the bar does not draw.
|
||||
|
||||
hl.layer_rule({
|
||||
name = "qs-dock",
|
||||
match = { namespace = "^qs-dock$" },
|
||||
blur = true,
|
||||
ignore_alpha = 0.3,
|
||||
})
|
||||
|
||||
-- Popovers: calendar, quick settings, notification centre, tray menus.
|
||||
hl.layer_rule({
|
||||
name = "qs-popover",
|
||||
match = { namespace = "^qs-popover" },
|
||||
blur = true,
|
||||
blur_popups = true,
|
||||
ignore_alpha = 0.2,
|
||||
})
|
||||
|
||||
-- Overview, capture and local screen-reading UI dim the desktop behind them.
|
||||
hl.layer_rule({
|
||||
name = "qs-overlay",
|
||||
match = { namespace = "^qs-(overview|capture|screen-intelligence)$" },
|
||||
blur = true,
|
||||
ignore_alpha = 0.4,
|
||||
dim_around = true,
|
||||
no_screen_share = true,
|
||||
})
|
||||
|
||||
-- Notification toasts. Blurred like every other shell surface -- without this
|
||||
-- the cards are a near-transparent fill sitting directly on the wallpaper and
|
||||
-- read as washed out rather than as glass.
|
||||
--
|
||||
-- no_anim because the toasts animate themselves in QML, and no_screen_share so
|
||||
-- they don't leak into OBS or Sunshine streams.
|
||||
hl.layer_rule({
|
||||
name = "qs-notifications",
|
||||
match = { namespace = "^qs-notifications$" },
|
||||
blur = true,
|
||||
ignore_alpha = 0.15,
|
||||
no_anim = true,
|
||||
no_screen_share = true,
|
||||
})
|
||||
|
||||
-- Signal Glass provides its own event-driven motion, so the compositor only
|
||||
-- supplies the material blur and privacy behavior.
|
||||
hl.layer_rule({
|
||||
name = "qs-signal-glass",
|
||||
match = { namespace = "^qs-signal-glass$" },
|
||||
blur = true,
|
||||
ignore_alpha = 0.15,
|
||||
no_anim = true,
|
||||
no_screen_share = true,
|
||||
})
|
||||
|
||||
-- The launcher blurs and dims whatever is behind it.
|
||||
hl.layer_rule({
|
||||
name = "launcher",
|
||||
match = { namespace = "^(vicinae|wofi)$" },
|
||||
blur = true,
|
||||
ignore_alpha = 0.5,
|
||||
dim_around = true,
|
||||
})
|
||||
|
||||
return true
|
||||
@@ -183,7 +183,6 @@ font_size 14.0
|
||||
|
||||
#: Cursor customization {{{
|
||||
|
||||
cursor #c8d3f5
|
||||
|
||||
#: Default cursor color. If set to the special value none the cursor
|
||||
#: will be rendered with a "reverse video" effect. It's color will be
|
||||
@@ -193,7 +192,6 @@ cursor #c8d3f5
|
||||
#: precedence. Also, the cursor colors are modified if the cell
|
||||
#: background and foreground colors have very low contrast.
|
||||
|
||||
cursor_text_color #111325
|
||||
|
||||
#: The color of text under the cursor. If you want it rendered with
|
||||
#: the background color of the cell underneath instead, use the
|
||||
@@ -312,7 +310,6 @@ cursor_shape block
|
||||
#: robustly with the ever-changing sea of bugs that is Cocoa is too
|
||||
#: much effort.
|
||||
|
||||
url_color #4fd6be
|
||||
# url_style curly
|
||||
|
||||
#: The color and style for highlighting URLs on mouse-over. url_style
|
||||
@@ -734,12 +731,10 @@ window_border_width 1pt
|
||||
#: placed centrally. A value of top-left means the padding will be
|
||||
#: only at the bottom and right edges.
|
||||
|
||||
active_border_color #82aaff
|
||||
|
||||
#: The color for the border of the active window. Set this to none to
|
||||
#: not draw borders around the active window.
|
||||
|
||||
inactive_border_color #2f334d
|
||||
|
||||
#: The color for the border of inactive windows.
|
||||
|
||||
@@ -753,7 +748,7 @@ inactive_border_color #2f334d
|
||||
#: Fade the text in inactive windows by the specified amount (a number
|
||||
#: between zero and one, with zero being fully faded).
|
||||
|
||||
hide_window_decorations yes
|
||||
hide_window_decorations no
|
||||
|
||||
#: Hide the window decorations (title-bar and window borders) with
|
||||
#: yes. On macOS, titlebar-only can be used to only hide the titlebar.
|
||||
@@ -960,16 +955,11 @@ tab_powerline_style round
|
||||
#: Template to use for active tabs. If not specified falls back to
|
||||
#: tab_title_template.
|
||||
|
||||
active_tab_foreground #1e2030
|
||||
active_tab_background #82aaff
|
||||
# active_tab_font_style bold-italic
|
||||
inactive_tab_foreground #c8d3f5
|
||||
inactive_tab_background #42465a
|
||||
# inactive_tab_font_style normal
|
||||
|
||||
#: Tab bar colors and styles.
|
||||
|
||||
tab_bar_background #222436
|
||||
|
||||
#: Background color for the tab bar. Defaults to using the terminal
|
||||
#: background color.
|
||||
@@ -983,8 +973,8 @@ tab_bar_margin_color none
|
||||
|
||||
#: Color scheme {{{
|
||||
|
||||
foreground #c8d3f5
|
||||
background #222436
|
||||
|
||||
include current-theme.conf
|
||||
|
||||
#: The foreground and background colors.
|
||||
|
||||
@@ -1043,8 +1033,6 @@ dim_opacity 1
|
||||
#: How much to dim text that has the DIM/FAINT attribute set. One
|
||||
#: means no dimming and zero means fully dimmed (i.e. invisible).
|
||||
|
||||
selection_foreground #2d3f76
|
||||
selection_background #c8d3f5
|
||||
|
||||
#: The foreground and background colors for text selected with the
|
||||
#: mouse. Setting both of these to none will cause a "reverse video"
|
||||
@@ -1060,47 +1048,29 @@ selection_background #c8d3f5
|
||||
#: dull and bright version, for the first 16 colors. You can set the
|
||||
#: remaining 240 colors as color16 to color255.
|
||||
|
||||
color0 #1b1d2b
|
||||
color8 #444a73
|
||||
|
||||
#: black
|
||||
|
||||
color1 #ff757f
|
||||
color9 #ff757f
|
||||
|
||||
#: red
|
||||
|
||||
color2 #a5e8b5
|
||||
color10 #a5e8b5
|
||||
|
||||
#: green
|
||||
color3 #ffc777
|
||||
color11 #ffc777
|
||||
|
||||
#: yellow
|
||||
|
||||
color4 #82aaff
|
||||
color12 #82aaff
|
||||
|
||||
#: blue
|
||||
|
||||
color5 #c099ff
|
||||
color13 #c099ff
|
||||
|
||||
#: magenta
|
||||
|
||||
color6 #86e1fc
|
||||
color14 #86e1fc
|
||||
|
||||
#: cyan
|
||||
|
||||
color7 #828bb8
|
||||
color15 #c8d3f5
|
||||
|
||||
#: white
|
||||
|
||||
color16 #ff966c
|
||||
color17 #c53b53
|
||||
|
||||
# mark1_foreground black
|
||||
|
||||
@@ -1504,7 +1474,7 @@ wayland_titlebar_color #222436
|
||||
#: configuration will change this value only for newly created OS
|
||||
#: windows.
|
||||
|
||||
linux_display_server Wayland
|
||||
# linux_display_server x11
|
||||
|
||||
#: Choose between Wayland and X11 backends. By default, an appropriate
|
||||
#: backend based on the system state is chosen automatically. Set it
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Open plain file paths with neovim in a new kitty tab
|
||||
protocol file
|
||||
mime text/*
|
||||
action launch --type=tab nvim -- "$FILE_PATH"
|
||||
|
||||
# Also handle paths with line/column numbers if supported by the source
|
||||
protocol file
|
||||
fragment_matches ^(\d+)(?::(\d+))?$
|
||||
action launch --type=tab nvim "+call cursor($1, ${2:-1})" -- "$FILE_PATH"
|
||||
@@ -0,0 +1,38 @@
|
||||
# Tokyo Night Day — the palette's own light variant.
|
||||
#
|
||||
# The same hues as Moon at a different lightness, so a terminal in light mode
|
||||
# still belongs to this desktop rather than looking like a different machine.
|
||||
# Kept in the same order as tokyonight-moon.conf so the two can be diffed.
|
||||
|
||||
cursor #3760bf
|
||||
cursor_text_color #e1e2e7
|
||||
url_color #118c74
|
||||
active_border_color #2e7de9
|
||||
inactive_border_color #c4c8da
|
||||
active_tab_foreground #e1e2e7
|
||||
active_tab_background #2e7de9
|
||||
inactive_tab_foreground #3760bf
|
||||
inactive_tab_background #c4c8da
|
||||
tab_bar_background #e1e2e7
|
||||
foreground #3760bf
|
||||
background #e1e2e7
|
||||
selection_foreground #e1e2e7
|
||||
selection_background #99a7df
|
||||
color0 #e9e9ed
|
||||
color8 #a1a6c5
|
||||
color1 #f52a65
|
||||
color9 #f52a65
|
||||
color2 #587539
|
||||
color10 #587539
|
||||
color3 #8c6c3e
|
||||
color11 #8c6c3e
|
||||
color4 #2e7de9
|
||||
color12 #2e7de9
|
||||
color5 #9854f1
|
||||
color13 #9854f1
|
||||
color6 #007197
|
||||
color14 #007197
|
||||
color7 #6172b0
|
||||
color15 #3760bf
|
||||
color16 #b15c00
|
||||
color17 #c64343
|
||||
@@ -0,0 +1,42 @@
|
||||
# Tokyo Night Moon — the dark theme Panama ships.
|
||||
#
|
||||
# Extracted from kitty.conf so the two schemes can be swapped. kitty.conf
|
||||
# includes current-theme.conf, which Panama generates from the colour scheme
|
||||
# setting; that generated file is gitignored because it is machine state.
|
||||
#
|
||||
# Live changes go through `kitty @ set-colors`, which is why kitty.conf enables
|
||||
# remote control. Without it a scheme change would only reach terminals opened
|
||||
# afterwards.
|
||||
|
||||
cursor #c8d3f5
|
||||
cursor_text_color #111325
|
||||
url_color #4fd6be
|
||||
active_border_color #82aaff
|
||||
inactive_border_color #2f334d
|
||||
active_tab_foreground #1e2030
|
||||
active_tab_background #82aaff
|
||||
inactive_tab_foreground #c8d3f5
|
||||
inactive_tab_background #42465a
|
||||
tab_bar_background #222436
|
||||
foreground #c8d3f5
|
||||
background #222436
|
||||
selection_foreground #2d3f76
|
||||
selection_background #c8d3f5
|
||||
color0 #1b1d2b
|
||||
color8 #444a73
|
||||
color1 #ff757f
|
||||
color9 #ff757f
|
||||
color2 #a5e8b5
|
||||
color10 #a5e8b5
|
||||
color3 #ffc777
|
||||
color11 #ffc777
|
||||
color4 #82aaff
|
||||
color12 #82aaff
|
||||
color5 #c099ff
|
||||
color13 #c099ff
|
||||
color6 #86e1fc
|
||||
color14 #86e1fc
|
||||
color7 #828bb8
|
||||
color15 #c8d3f5
|
||||
color16 #ff966c
|
||||
color17 #c53b53
|
||||
@@ -0,0 +1,8 @@
|
||||
tt.*
|
||||
.tests
|
||||
doc/tags
|
||||
debug
|
||||
.repro
|
||||
foo.*
|
||||
*.log
|
||||
data
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"runtime.version": "LuaJIT",
|
||||
"runtime.path": [
|
||||
"lua/?.lua",
|
||||
"lua/?/init.lua"
|
||||
],
|
||||
"diagnostics.globals": ["vim"],
|
||||
"workspace.checkThirdParty": false,
|
||||
"workspace.library": [ "$VIMRUNTIME" ],
|
||||
"workspace.maxPreload": 100000,
|
||||
"workspace.preloadFileSize": 10000,
|
||||
"telemetry.enable": false
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"neodev": {
|
||||
"library": {
|
||||
"enabled": true,
|
||||
"plugins": true
|
||||
}
|
||||
},
|
||||
"neoconf": {
|
||||
"plugins": {
|
||||
"lua_ls": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,4 @@
|
||||
# 💤 LazyVim
|
||||
|
||||
A starter template for [LazyVim](https://github.com/LazyVim/LazyVim).
|
||||
Refer to the [documentation](https://lazyvim.github.io/installation) to get started.
|
||||
@@ -1 +1 @@
|
||||
require'config'
|
||||
require("config.lazy")
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
{
|
||||
"LuaSnip": { "branch": "master", "commit": "458560534a73f7f8d7a11a146c801db00b081df0" },
|
||||
"avante.nvim": { "branch": "main", "commit": "80f7079556c6acf3d3effa13c22f0e4fd00bcffd" },
|
||||
"barbar.nvim": { "branch": "master", "commit": "53b5a2f34b68875898f0531032fbf090e3952ad7" },
|
||||
"cloak.nvim": { "branch": "main", "commit": "648aca6d33ec011dc3166e7af3b38820d01a71e4" },
|
||||
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
|
||||
"cmp-cmdline": { "branch": "main", "commit": "d126061b624e0af6c3a556428712dd4d4194ec6d" },
|
||||
"cmp-copilot": { "branch": "main", "commit": "1f3f31c54bd71e41ed157430702bc2837ea582ab" },
|
||||
"cmp-dotenv": { "branch": "main", "commit": "4dd53aab60982f1f75848aec5e6214986263325e" },
|
||||
"cmp-emoji": { "branch": "main", "commit": "e8398e2adf512a03bb4e1728ca017ffeac670a9f" },
|
||||
"cmp-git": { "branch": "main", "commit": "b24309c386c9666c549a1abaedd4956541676d06" },
|
||||
"cmp-luasnip-choice": { "branch": "master", "commit": "4f49232e51c9df379b9daf43f25f7ee6320450f0" },
|
||||
"cmp-nerdfont": { "branch": "main", "commit": "e97482344ebed29093015a18c155057adf5c842b" },
|
||||
"cmp-npm": { "branch": "main", "commit": "2337f109f51a09297596dd6b538b70ccba92b4e4" },
|
||||
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
|
||||
"cmp-nvim-lsp-document-symbol": { "branch": "main", "commit": "f94f7ba948e32cd302caba1c2ca3f7c697fb4fcf" },
|
||||
"cmp-nvim-lsp-signature-help": { "branch": "main", "commit": "fd3e882e56956675c620898bf1ffcf4fcbe7ec84" },
|
||||
"cmp-nvim-lua": { "branch": "main", "commit": "e3a22cb071eb9d6508a156306b102c45cd2d573d" },
|
||||
"cmp-path": { "branch": "main", "commit": "c642487086dbd9a93160e1679a1327be111cbc25" },
|
||||
"cmp-tw2css": { "branch": "main", "commit": "1abe0eebcb57fcbd5538d054f0db61f4e4a1302b" },
|
||||
"cmp_luasnip": { "branch": "master", "commit": "98d9cb5c2c38532bd9bdb481067b20fea8f32e90" },
|
||||
"conform.nvim": { "branch": "master", "commit": "9b8fa5e0b78168f68bee9bf886dc20f287c61e02" },
|
||||
"copilot.vim": { "branch": "release", "commit": "f89e977c87180519ba3b942200e3d05b17b1e2fc" },
|
||||
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
|
||||
"fidget.nvim": { "branch": "main", "commit": "e32b672d8fd343f9d6a76944fedb8c61d7d8111a" },
|
||||
"friendly-snippets": { "branch": "main", "commit": "572f5660cf05f8cd8834e096d7b4c921ba18e175" },
|
||||
"gitsigns.nvim": { "branch": "main", "commit": "5813e4878748805f1518cee7abb50fd7205a3a48" },
|
||||
"image.nvim": { "branch": "master", "commit": "446a8a5cc7a3eae3185ee0c697732c32a5547a0b" },
|
||||
"img-clip.nvim": { "branch": "main", "commit": "93fcf50924108262caec66b900077987dfcb7c7f" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
|
||||
"lspkind.nvim": { "branch": "master", "commit": "3ddd1b4edefa425fda5a9f95a4f25578727c0bb3" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "3b3571b4dadbcb464804466e9872e7246c316af7" },
|
||||
"mason.nvim": { "branch": "main", "commit": "57e5a8addb8c71fb063ee4acda466c7cf6ad2800" },
|
||||
"mcphub.nvim": { "branch": "main", "commit": "5193329d510a68f1f5bf189960642c925c177a3a" },
|
||||
"mini.icons": { "branch": "main", "commit": "ff2e4f1d29f659cc2bad0f9256f2f6195c6b2428" },
|
||||
"neo-tree.nvim": { "branch": "v3.x", "commit": "f3df514fff2bdd4318127c40470984137f87b62e" },
|
||||
"nerdcommenter": { "branch": "master", "commit": "a462bbda1e26f44fb3d3eb9d9d1c6a07aa98e665" },
|
||||
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
|
||||
"nvim-autopairs": { "branch": "master", "commit": "c2a0dd0d931d0fb07665e1fedb1ea688da3b80b4" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "d97d85e01339f01b842e6ec1502f639b080cb0fc" },
|
||||
"nvim-lsp-file-operations": { "branch": "master", "commit": "9744b738183a5adca0f916527922078a965515ed" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "c4f67bf85b01a57e3c130352c0a0e453ab8cd5b9" },
|
||||
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" },
|
||||
"nvim-treesitter-context": { "branch": "master", "commit": "64dd4cf3f6fd0ab17622c5ce15c91fc539c3f24a" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "c4ca798ab95b316a768d51eaaaee48f64a4a46bc" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "8dcb311b0c92d460fac00eac706abd43d94d68af" },
|
||||
"nvim-window-picker": { "branch": "main", "commit": "6382540b2ae5de6c793d4aa2e3fe6dbb518505ec" },
|
||||
"playground": { "branch": "master", "commit": "ba48c6a62a280eefb7c85725b0915e021a1a0749" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
|
||||
"render-markdown.nvim": { "branch": "main", "commit": "07d088bf8bdadd159eb807b90eaee86a4778383f" },
|
||||
"snacks.nvim": { "branch": "main", "commit": "fe7cfe9800a182274d0f868a74b7263b8c0c020b" },
|
||||
"supermaven-nvim": { "branch": "main", "commit": "07d20fce48a5629686aefb0a7cd4b25e33947d50" },
|
||||
"telescope.nvim": { "branch": "master", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" },
|
||||
"tokyonight.nvim": { "branch": "main", "commit": "5da1b76e64daf4c5d410f06bcb6b9cb640da7dfd" },
|
||||
"trouble.nvim": { "branch": "main", "commit": "bd67efe408d4816e25e8491cc5ad4088e708a69a" },
|
||||
"undotree": { "branch": "master", "commit": "0f1c9816975b5d7f87d5003a19c53c6fd2ff6f7f" }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extras": [
|
||||
|
||||
],
|
||||
"install_version": 8,
|
||||
"news": {
|
||||
"NEWS.md": "11866"
|
||||
},
|
||||
"version": 8
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
local augroup = vim.api.nvim_create_augroup
|
||||
local gib_group = augroup('Gib', {})
|
||||
local autocmd = vim.api.nvim_create_autocmd
|
||||
local yank_group = augroup('HighlightYank', {})
|
||||
|
||||
function R(name)
|
||||
require("plenary.reload").reload_module(name)
|
||||
end
|
||||
|
||||
autocmd('TextYankPost', {
|
||||
group = yank_group,
|
||||
pattern = '*',
|
||||
callback = function()
|
||||
vim.highlight.on_yank({
|
||||
higroup = 'IncSearch',
|
||||
timeout = 40,
|
||||
})
|
||||
end,
|
||||
})
|
||||
|
||||
autocmd({"BufWritePre"}, {
|
||||
group = gib_group,
|
||||
pattern = "*",
|
||||
command = [[%s/\s\+$//e]],
|
||||
})
|
||||
|
||||
autocmd({'BufWritePost'}, {
|
||||
pattern = 'init.lua',
|
||||
command = 'source <afile>',
|
||||
})
|
||||
|
||||
autocmd('LspAttach', {
|
||||
group = gib_group,
|
||||
callback = function(e)
|
||||
local opts = { buffer = e.buf }
|
||||
vim.keymap.set('n', '<leader>kf', function() vim.lsp.buf.format() end, opts)
|
||||
vim.keymap.set("n", "<leader>gd", function() vim.lsp.buf.definition() end, opts)
|
||||
vim.keymap.set("n", "<leader>kw", function() vim.lsp.buf.workspace_symbol() end, opts)
|
||||
vim.keymap.set("n", "<leader>kd", function() vim.diagnostic.open_float() end, opts)
|
||||
vim.keymap.set("n", "<leader>vca", function() vim.lsp.buf.code_action() end, opts)
|
||||
vim.keymap.set("n", "<leader>re", function() vim.lsp.buf.references() end, opts)
|
||||
vim.keymap.set("n", "<leader>rn", function() vim.lsp.buf.rename() end, opts)
|
||||
vim.keymap.set("i", "<C-h>", function() vim.lsp.buf.signature_help() end, opts)
|
||||
end
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Autocmds are automatically loaded on the VeryLazy event
|
||||
-- Default autocmds that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/autocmds.lua
|
||||
|
||||
-- Strip trailing whitespace on save
|
||||
vim.api.nvim_create_autocmd("BufWritePre", {
|
||||
pattern = "*",
|
||||
callback = function()
|
||||
local view = vim.fn.winsaveview()
|
||||
vim.cmd([[keeppatterns %s/\s\+$//e]])
|
||||
vim.fn.winrestview(view)
|
||||
end,
|
||||
})
|
||||
|
||||
-- Follow the desktop's light/dark setting while running, not only at startup.
|
||||
require("config.panama").watch()
|
||||
@@ -1,4 +0,0 @@
|
||||
require'config.options'
|
||||
require'config.keymaps'
|
||||
require'config.lazy'
|
||||
require'config.autocmd'
|
||||
@@ -1,41 +1,155 @@
|
||||
vim.g.mapleader = ' '
|
||||
vim.g.maplocalleader = "\\"
|
||||
vim.keymap.set('n', '<leader><leader>s', ':update<CR> :source<CR>')
|
||||
vim.keymap.set('n', '<leader><leader>x', ':!chmod +x %<CR>', { silent = true })
|
||||
vim.keymap.set({'n', 'v', 'x'}, '<leader>h', '<C-w>h')
|
||||
vim.keymap.set({'n', 'v', 'x'}, '<leader>j', '<C-w>j')
|
||||
vim.keymap.set({'n', 'v', 'x'}, '<leader>k', '<C-w>k')
|
||||
vim.keymap.set({'n', 'v', 'x'}, '<leader>l', '<C-w>l')
|
||||
vim.keymap.set('n', '<leader>w', ':write<CR>')
|
||||
vim.keymap.set('n', '<leader>q', ':quit<CR>')
|
||||
-- Move the selected lines up or down one line and reselect
|
||||
vim.keymap.set('v', 'J', ':m \'>+1<CR>gv=gv')
|
||||
vim.keymap.set('v', 'K', ':m \'<-2<CR>gv=gv')
|
||||
-- Join the current line with the line below and reposition the cursor
|
||||
vim.keymap.set('n', 'J', 'mzJ`z')
|
||||
-- Scroll down or up and reposition the cursor
|
||||
vim.keymap.set('n', '<C-d>', '<C-d>zz')
|
||||
vim.keymap.set('n', '<C-u>', '<C-u>zz')
|
||||
-- Search forward and reposition the cursor
|
||||
vim.keymap.set('n', 'n', 'nzzzv')
|
||||
-- Search backward and reposition the cursor
|
||||
vim.keymap.set('n', 'N', 'Nzzzv')
|
||||
-- Paste the selection from the system clipboard
|
||||
vim.keymap.set({'n', 'v', 'x'}, '<leader>p', [["+p]])
|
||||
vim.keymap.set({'n', 'v', 'x'}, '<leader>P', [["+P]])
|
||||
-- Move selected text to the black hole register & replace with copied text.
|
||||
vim.keymap.set('x', '<leader>v', [["_dP]])
|
||||
-- Yank (copy) the selection to the system clipboard
|
||||
vim.keymap.set({'n', 'v'}, '<leader>y', [["+y]])
|
||||
-- Yank (copy) the entire buffer to the system clipboard
|
||||
vim.keymap.set('n', '<leader>yy', [["+Y]])
|
||||
-- Yank all the text in the file to the system clipboard
|
||||
vim.keymap.set('n', '<leader>YY', ':%y+<CR>')
|
||||
-- Delete the selection without yanking (copying) it
|
||||
vim.keymap.set({'n', 'v'}, '<leader>d', [["_d]])
|
||||
-- Delete the line without yanking (copying) it
|
||||
vim.keymap.set('n', '<leader>dd', [["_dd]])
|
||||
-- Map Q in Normal mode to do nothing (nop)
|
||||
vim.keymap.set('n', 'Q', '<nop>')
|
||||
-- Perform a search and replace operation using the word under the cursor
|
||||
vim.keymap.set('n', '<leader>sr', [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]])
|
||||
-- Keymaps are automatically loaded on the VeryLazy event
|
||||
-- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua
|
||||
|
||||
-- Remove LazyVim's C-h/j/k/l window navigation (using <leader>h/j/k/l instead)
|
||||
pcall(vim.keymap.del, "n", "<C-h>")
|
||||
pcall(vim.keymap.del, "n", "<C-j>")
|
||||
pcall(vim.keymap.del, "n", "<C-k>")
|
||||
pcall(vim.keymap.del, "n", "<C-l>")
|
||||
pcall(vim.keymap.del, "t", "<C-h>")
|
||||
pcall(vim.keymap.del, "t", "<C-j>")
|
||||
pcall(vim.keymap.del, "t", "<C-k>")
|
||||
pcall(vim.keymap.del, "t", "<C-l>")
|
||||
-- Remove LazyVim's :Lazy shortcut — <leader>l is used for window navigation
|
||||
pcall(vim.keymap.del, "n", "<leader>l")
|
||||
|
||||
-- Window navigation
|
||||
vim.keymap.set(
|
||||
{ "n", "v", "x" },
|
||||
"<leader>h",
|
||||
"<C-w>h",
|
||||
{ desc = "Go to left window", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ "n", "v", "x" },
|
||||
"<leader>j",
|
||||
"<C-w>j",
|
||||
{ desc = "Go to lower window", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ "n", "v", "x" },
|
||||
"<leader>k",
|
||||
"<C-w>k",
|
||||
{ desc = "Go to upper window", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ "n", "v", "x" },
|
||||
"<leader>l",
|
||||
"<C-w>l",
|
||||
{ desc = "Go to right window", silent = true }
|
||||
)
|
||||
|
||||
-- Buffer tab navigation
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<C-h>",
|
||||
"<cmd>BufferLineCyclePrev<cr>",
|
||||
{ desc = "Previous buffer", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<C-l>",
|
||||
"<cmd>BufferLineCycleNext<cr>",
|
||||
{ desc = "Next buffer", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<C-j>",
|
||||
"<cmd>BufferLineMovePrev<cr>",
|
||||
{ desc = "Move buffer left", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<C-k>",
|
||||
"<cmd>BufferLineMoveNext<cr>",
|
||||
{ desc = "Move buffer right", silent = true }
|
||||
)
|
||||
vim.keymap.set("n", "<C-q>", function()
|
||||
Snacks.bufdelete()
|
||||
end, { desc = "Close buffer", silent = true })
|
||||
vim.keymap.set("n", "<C-a>", function()
|
||||
Snacks.bufdelete.other()
|
||||
end, { desc = "Close other buffers", silent = true })
|
||||
|
||||
-- Disable ex mode
|
||||
vim.keymap.set("n", "Q", "<nop>")
|
||||
|
||||
-- Move selected lines up/down
|
||||
vim.keymap.set(
|
||||
"v",
|
||||
"J",
|
||||
":m '>+1<CR>gv=gv",
|
||||
{ desc = "Move selection down", silent = true }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"v",
|
||||
"K",
|
||||
":m '<-2<CR>gv=gv",
|
||||
{ desc = "Move selection up", silent = true }
|
||||
)
|
||||
|
||||
-- Join line without moving cursor
|
||||
vim.keymap.set("n", "J", "mzJ`z", { desc = "Join line, keep cursor" })
|
||||
|
||||
-- Scroll and center
|
||||
vim.keymap.set("n", "<C-d>", "<C-d>zz", { desc = "Scroll down and center" })
|
||||
vim.keymap.set("n", "<C-u>", "<C-u>zz", { desc = "Scroll up and center" })
|
||||
|
||||
-- Search and center
|
||||
vim.keymap.set("n", "n", "nzzzv", { desc = "Next search result" })
|
||||
vim.keymap.set("n", "N", "Nzzzv", { desc = "Prev search result" })
|
||||
|
||||
-- Yank to system clipboard
|
||||
vim.keymap.set({ "n", "v" }, "<leader>y", '"+y', { desc = "Yank to clipboard" })
|
||||
vim.keymap.set("n", "<leader>Y", '"+Y', { desc = "Yank line to clipboard" })
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>YY",
|
||||
":%y+<CR>",
|
||||
{ desc = "Yank buffer to clipboard" }
|
||||
)
|
||||
|
||||
-- Delete to black hole register (no yank side effect)
|
||||
vim.keymap.set(
|
||||
{ "n", "v" },
|
||||
"<leader>d",
|
||||
'"_d',
|
||||
{ desc = "Delete to black hole" }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>dd",
|
||||
'"_dd',
|
||||
{ desc = "Delete line to black hole" }
|
||||
)
|
||||
|
||||
-- Paste from system clipboard
|
||||
vim.keymap.set(
|
||||
{ "n", "v", "x" },
|
||||
"<leader>p",
|
||||
'"+p',
|
||||
{ desc = "Paste from clipboard (after)" }
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ "n", "v", "x" },
|
||||
"<leader>P",
|
||||
'"+P',
|
||||
{ desc = "Paste from clipboard (before)" }
|
||||
)
|
||||
|
||||
-- Paste over selection without overwriting the yank register
|
||||
vim.keymap.set("x", "<leader>v", '"_dP', { desc = "Paste over selection" })
|
||||
|
||||
-- Utility
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>so",
|
||||
":update<CR>:source<CR>",
|
||||
{ desc = "Save and source file" }
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader><leader>x",
|
||||
":!chmod +x %<CR>",
|
||||
{ desc = "Make file executable" }
|
||||
)
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
|
||||
local out = vim.fn.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"--branch=stable",
|
||||
lazyrepo,
|
||||
lazypath
|
||||
})
|
||||
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
|
||||
if vim.v.shell_error ~= 0 then
|
||||
vim.api.nvim_echo({
|
||||
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
|
||||
@@ -21,8 +14,47 @@ if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
|
||||
require'lazy'.setup({
|
||||
spec = { { import = 'plugins' } },
|
||||
install = { colorscheme = { 'tokyonight-moon' } },
|
||||
checker = { enabled = false },
|
||||
require("lazy").setup({
|
||||
spec = {
|
||||
-- add LazyVim and import its plugins
|
||||
{ "LazyVim/LazyVim", import = "lazyvim.plugins" },
|
||||
-- LazyVim extras (must come before user plugins)
|
||||
{ import = "lazyvim.plugins.extras.lang.typescript" },
|
||||
{ import = "lazyvim.plugins.extras.formatting.prettier" },
|
||||
{ import = "lazyvim.plugins.extras.linting.eslint" },
|
||||
{ import = "lazyvim.plugins.extras.editor.snacks_explorer" },
|
||||
{ import = "lazyvim.plugins.extras.editor.snacks_picker" },
|
||||
{ import = "lazyvim.plugins.extras.ai.supermaven" },
|
||||
-- import/override with your plugins
|
||||
{ import = "plugins" },
|
||||
},
|
||||
defaults = {
|
||||
-- By default, only LazyVim plugins will be lazy-loaded. Your custom plugins will load during startup.
|
||||
-- If you know what you're doing, you can set this to `true` to have all your custom plugins lazy-loaded by default.
|
||||
lazy = false,
|
||||
-- It's recommended to leave version=false for now, since a lot the plugin that support versioning,
|
||||
-- have outdated releases, which may break your Neovim install.
|
||||
version = false, -- always use the latest git commit
|
||||
-- version = "*", -- try installing the latest stable version for plugins that support semver
|
||||
},
|
||||
install = { colorscheme = { "tokyonight", "habamax" } },
|
||||
checker = {
|
||||
enabled = true, -- check for plugin updates periodically
|
||||
notify = false, -- notify on update
|
||||
}, -- automatically check for plugin updates
|
||||
performance = {
|
||||
rtp = {
|
||||
-- disable some rtp plugins
|
||||
disabled_plugins = {
|
||||
"gzip",
|
||||
-- "matchit",
|
||||
-- "matchparen",
|
||||
-- "netrwPlugin",
|
||||
"tarPlugin",
|
||||
"tohtml",
|
||||
"tutor",
|
||||
"zipPlugin",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
vim.o.number = true
|
||||
vim.o.relativenumber = true
|
||||
vim.o.winborder = 'rounded'
|
||||
vim.o.tabstop = 2
|
||||
vim.o.softtabstop = 2
|
||||
vim.o.shiftwidth = 2
|
||||
vim.o.expandtab = true
|
||||
vim.o.smartindent = true
|
||||
vim.o.swapfile = false
|
||||
vim.o.undodir = os.getenv('HOME') .. '/.vim/undodir'
|
||||
vim.o.undofile = true
|
||||
vim.o.hlsearch = true
|
||||
vim.o.incsearch = true
|
||||
vim.o.termguicolors = true
|
||||
vim.o.scrolloff = 4
|
||||
vim.o.signcolumn = 'yes'
|
||||
vim.o.updatetime = 50
|
||||
vim.opt.isfname:append('@-@')
|
||||
vim.cmd([[set list]])
|
||||
vim.cmd([[set listchars=trail:⋅,nbsp:⋅,tab:\ \ ]])
|
||||
vim.cmd(':hi statusline guibg=NONE')
|
||||
-- Options are automatically loaded before lazy.nvim startup
|
||||
-- Default options that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/options.lua
|
||||
|
||||
-- LazyVim's default syncs clipboard only outside SSH:
|
||||
-- vim.opt.clipboard = vim.env.SSH_CONNECTION and "" or "unnamedplus"
|
||||
-- Disabled entirely because wl-clipboard triggers GNOME window spawning on every yank.
|
||||
-- Use <leader>y/<leader>p for explicit clipboard access.
|
||||
vim.opt.clipboard = ""
|
||||
vim.opt.winborder = "rounded"
|
||||
vim.opt.updatetime = 50
|
||||
vim.opt.undodir = os.getenv("HOME") .. "/.vim/undodir"
|
||||
vim.opt.listchars = { trail = "⋅", nbsp = "⋅", tab = " " }
|
||||
|
||||
vim.g.ai_cmp = true
|
||||
vim.g.markdown_recommended_style = 0
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
-- Panama desktop integration.
|
||||
--
|
||||
-- Neovim is the one application here that neither reads the desktop portal nor
|
||||
-- has a control socket open by default, so it reads the shared settings file
|
||||
-- directly -- the same ~/.config/panama/settings.json that the shell and the
|
||||
-- Hyprland config read.
|
||||
--
|
||||
-- Nothing here may raise. A missing or malformed settings file must cost the
|
||||
-- user their colour scheme preference and nothing else; editing text is more
|
||||
-- important than matching the desktop.
|
||||
|
||||
local M = {}
|
||||
|
||||
local function settings_path()
|
||||
local config_home = os.getenv("XDG_CONFIG_HOME")
|
||||
if config_home == nil or config_home == "" then
|
||||
local home = os.getenv("HOME")
|
||||
if home == nil or home == "" then
|
||||
return nil
|
||||
end
|
||||
config_home = home .. "/.config"
|
||||
end
|
||||
return config_home .. "/panama/settings.json"
|
||||
end
|
||||
|
||||
-- "dark" or "light". Defaults to dark, which is what Panama ships.
|
||||
function M.color_scheme()
|
||||
local path = settings_path()
|
||||
if not path then
|
||||
return "dark"
|
||||
end
|
||||
|
||||
local ok, contents = pcall(function()
|
||||
local file = io.open(path, "r")
|
||||
if not file then
|
||||
return nil
|
||||
end
|
||||
local text = file:read("*a")
|
||||
file:close()
|
||||
return text
|
||||
end)
|
||||
if not ok or not contents or contents == "" then
|
||||
return "dark"
|
||||
end
|
||||
|
||||
local decoded_ok, decoded = pcall(vim.json.decode, contents)
|
||||
if not decoded_ok or type(decoded) ~= "table" then
|
||||
return "dark"
|
||||
end
|
||||
|
||||
return decoded.colorScheme == "light" and "light" or "dark"
|
||||
end
|
||||
|
||||
function M.is_light()
|
||||
return M.color_scheme() == "light"
|
||||
end
|
||||
|
||||
-- tokyonight ships a light variant in the same family, so light mode stays the
|
||||
-- same identity rather than becoming a different editor theme.
|
||||
function M.tokyonight_style()
|
||||
return M.is_light() and "day" or "moon"
|
||||
end
|
||||
|
||||
-- Re-apply the scheme when the window regains focus.
|
||||
--
|
||||
-- Neovim reads the setting once at startup and has no control socket open by
|
||||
-- default, so an editor already running when the desktop scheme flips would
|
||||
-- otherwise stay on the old palette until it was restarted. FocusGained is
|
||||
-- cheap, happens exactly when you would notice the mismatch, and does nothing
|
||||
-- at all unless the scheme actually changed.
|
||||
function M.watch()
|
||||
local applied = M.color_scheme()
|
||||
|
||||
vim.api.nvim_create_autocmd("FocusGained", {
|
||||
group = vim.api.nvim_create_augroup("PanamaColorScheme", { clear = true }),
|
||||
callback = function()
|
||||
local current = M.color_scheme()
|
||||
if current == applied then
|
||||
return
|
||||
end
|
||||
applied = current
|
||||
vim.o.background = current
|
||||
pcall(vim.cmd.colorscheme, current == "light" and "tokyonight-day" or "tokyonight-moon")
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -1,239 +0,0 @@
|
||||
return {
|
||||
'yetone/avante.nvim',
|
||||
-- if you want to build from source then do `make BUILD_FROM_SOURCE=true`
|
||||
-- ⚠️ must add this setting! ! !
|
||||
build = vim.fn.has('win32') ~= 0
|
||||
and 'powershell -ExecutionPolicy Bypass -File Build.ps1 -BuildFromSource false'
|
||||
or 'make',
|
||||
event = 'VeryLazy',
|
||||
version = false, -- Never set this value to '*'! Never!
|
||||
---@module 'avante'
|
||||
---@type avante.Config
|
||||
opts = {
|
||||
instructions_file = 'avante.md',
|
||||
provider = 'openai',
|
||||
auto_suggestions_provider = 'openai',
|
||||
providers = {
|
||||
claude = {
|
||||
endpoint = 'https://api.anthropic.com',
|
||||
model = 'claude-sonnet-4-20250514',
|
||||
timeout = 30000,
|
||||
extra_request_body = {
|
||||
temperature = 0.75,
|
||||
max_tokens = 4096,
|
||||
},
|
||||
},
|
||||
openai = {
|
||||
endpoint = 'https://api.openai.com/v1',
|
||||
model = 'gpt-5',
|
||||
extra_request_body = {
|
||||
temperature = 1,
|
||||
max_completion_tokens = 8192,
|
||||
},
|
||||
},
|
||||
},
|
||||
rag_service = {
|
||||
enabled = false,
|
||||
host_path = '/home/gib/Documents/Code',
|
||||
runner = 'docker',
|
||||
llm = {
|
||||
provider = 'openai',
|
||||
endpoint = 'https://api.openai.com/v1',
|
||||
api_key = 'OPENAI_API_KEY',
|
||||
model = 'gpt-5',
|
||||
extra = {
|
||||
temperature = 1,
|
||||
max_tokens = 512,
|
||||
},
|
||||
},
|
||||
embed = {
|
||||
provider = 'openai',
|
||||
endpoint = 'https://api.openai.com/v1',
|
||||
api_key = 'OPENAI_API_KEY',
|
||||
model = 'text-embedding-3-large',
|
||||
extra = nil,
|
||||
},
|
||||
docker_extra_args = '',
|
||||
},
|
||||
web_search_engine = {
|
||||
provider = 'searxng',
|
||||
proxy = nil,
|
||||
},
|
||||
system_prompt = function()
|
||||
local hub = require('mcphub').get_hub_instance()
|
||||
return hub and hub:get_active_servers_prompt() or ''
|
||||
end,
|
||||
custom_tools = function()
|
||||
return {
|
||||
require('mcphub.extensions.avante').mcp_tool(),
|
||||
}
|
||||
end,
|
||||
behaviour = {
|
||||
auto_suggestions = false,
|
||||
auto_set_highlight_group = true,
|
||||
auto_set_keymaps = true,
|
||||
auto_apply_diff_after_generation = false,
|
||||
support_paste_from_clipboard = false,
|
||||
minimize_diff = true,
|
||||
enable_token_counting = true,
|
||||
auto_approve_tool_permissions = true,
|
||||
},
|
||||
mappings = {
|
||||
--- @class AvanteConflictMappings
|
||||
diff = {
|
||||
ours = 'co',
|
||||
theirs = 'ct',
|
||||
all_theirs = 'ca',
|
||||
both = 'cb',
|
||||
cursor = 'cc',
|
||||
next = ']x',
|
||||
prev = '[x',
|
||||
},
|
||||
suggestion = {
|
||||
accept = '<M-l>',
|
||||
next = '<M-]>',
|
||||
prev = '<M-[>',
|
||||
dismiss = '<C-]>',
|
||||
},
|
||||
jump = {
|
||||
next = ']]',
|
||||
prev = '[[',
|
||||
},
|
||||
submit = {
|
||||
normal = '<CR>',
|
||||
insert = '<C-s>',
|
||||
},
|
||||
cancel = {
|
||||
normal = { '<C-c>', '<Esc>', 'q' },
|
||||
insert = { '<C-c>' },
|
||||
},
|
||||
sidebar = {
|
||||
apply_all = 'A',
|
||||
apply_cursor = 'a',
|
||||
retry_user_request = 'r',
|
||||
edit_user_request = 'e',
|
||||
switch_windows = '<Tab>',
|
||||
reverse_switch_windows = '<S-Tab>',
|
||||
remove_file = 'd',
|
||||
add_file = '@',
|
||||
close = { '<Esc>', 'q' },
|
||||
close_from_input = nil,
|
||||
},
|
||||
},
|
||||
hints = { enabled = true },
|
||||
windows = {
|
||||
---@type 'right' | 'left' | 'top' | 'bottom'
|
||||
position = 'right',
|
||||
wrap = true,
|
||||
width = 35,
|
||||
sidebar_header = {
|
||||
enabled = true,
|
||||
align = 'center',
|
||||
rounded = true,
|
||||
},
|
||||
input = {
|
||||
prefix = '> ',
|
||||
height = 8,
|
||||
},
|
||||
edit = {
|
||||
border = 'rounded',
|
||||
start_insert = true,
|
||||
},
|
||||
ask = {
|
||||
floating = false,
|
||||
start_insert = true,
|
||||
border = 'rounded',
|
||||
---@type 'ours' | 'theirs'
|
||||
focus_on_apply = 'ours',
|
||||
},
|
||||
},
|
||||
highlights = {
|
||||
diff = {
|
||||
current = 'DiffText',
|
||||
incoming = 'DiffAdd',
|
||||
},
|
||||
},
|
||||
--- @class AvanteConflictUserConfig
|
||||
diff = {
|
||||
autojump = true,
|
||||
---@type string | fun(): any
|
||||
list_opener = 'copen',
|
||||
override_timeoutlen = 500,
|
||||
},
|
||||
suggestion = {
|
||||
debounce = 1000,
|
||||
throttle = 1000,
|
||||
},
|
||||
},
|
||||
dependencies = {
|
||||
{ 'nvim-lua/plenary.nvim' },
|
||||
{ 'MunifTanjim/nui.nvim' },
|
||||
{ 'hrsh7th/nvim-cmp' },
|
||||
{ 'nvim-treesitter/nvim-treesitter' },
|
||||
{ 'stevearc/dressing.nvim' },
|
||||
{ 'nvim-tree/nvim-web-devicons' },
|
||||
{ 'folke/snacks.nvim' },
|
||||
{
|
||||
'ravitemer/mcphub.nvim',
|
||||
build = "npm install -g mcp-hub@latest",
|
||||
config = function()
|
||||
require'mcphub'.setup({
|
||||
config = vim.fn.expand('/home/gib/.config/mcphub/servers.json'),
|
||||
port = 37373,
|
||||
shutdown_delay = 60 * 10 * 000,
|
||||
use_bundled_binary = false,
|
||||
mcp_request_timeout = 60000,
|
||||
auto_approve = true,
|
||||
auto_toggle_mcp_servers = true,
|
||||
extensions = {
|
||||
avante = {
|
||||
make_slash_commands = true,
|
||||
},
|
||||
},
|
||||
native_servers = {},
|
||||
ui = {
|
||||
window = {
|
||||
width = 0.8,
|
||||
height = 0.8,
|
||||
align = 'center',
|
||||
relative = 'editor',
|
||||
zindex = 50,
|
||||
border = 'rounded',
|
||||
},
|
||||
wo = {
|
||||
winhl = 'Normal:MCPHubNormal,FloatBorder:MCPHubBorder',
|
||||
},
|
||||
},
|
||||
log = {
|
||||
level = vim.log.levels.WARN,
|
||||
to_file = false,
|
||||
file_path = nil,
|
||||
prefix = 'MCPHub',
|
||||
},
|
||||
})
|
||||
end
|
||||
},
|
||||
{
|
||||
-- support for image pasting
|
||||
'HakonHarnes/img-clip.nvim',
|
||||
event = 'VeryLazy',
|
||||
opts = {
|
||||
-- recommended settings
|
||||
default = {
|
||||
embed_image_as_base64 = false,
|
||||
prompt_for_file_name = false,
|
||||
drag_and_drop = {
|
||||
insert_mode = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'MeanderingProgrammer/render-markdown.nvim',
|
||||
opts = {
|
||||
file_types = { 'markdown', 'Avante' },
|
||||
},
|
||||
ft = { 'markdown', 'Avante' },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
return {
|
||||
'romgrk/barbar.nvim',
|
||||
dependencies = {
|
||||
'lewis6991/gitsigns.nvim',
|
||||
'nvim-tree/nvim-web-devicons',
|
||||
},
|
||||
init = function() vim.g.barbar_auto_setup = false end,
|
||||
opts = {
|
||||
animation = true,
|
||||
insert_at_start = true,
|
||||
},
|
||||
keys = {
|
||||
{ "<C-h>", ":BufferPrevious<CR>", mode = "n", silent = true, desc = "Go to previous tab" },
|
||||
{ "<C-l>", ":BufferNext<CR>", mode = "n", silent = true, desc = "Go to next tab" },
|
||||
{ "<C-j>", ":BufferMovePrevious<CR>", mode = "n", silent = true, desc = "Move tab to previous position" },
|
||||
{ "<C-k>", ":BufferMoveNext<CR>", mode = "n", silent = true, desc = "Move tab to next position" },
|
||||
{ "<C-q>", ":BufferClose<CR>", mode = "n", silent = true, desc = "Close current tab" },
|
||||
{ "<C-a>", ":BufferCloseAllButCurrent<CR>", mode = "n", silent = true, desc = "Close all tabs except current" },
|
||||
},
|
||||
version = '^1.0.0', -- optional: only update when a new 1.x version is released
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
return {
|
||||
'laytan/cloak.nvim',
|
||||
config = function()
|
||||
require'cloak'.setup({
|
||||
enabled = false,
|
||||
cloak_character = "•",
|
||||
highlight_group = "Comment",
|
||||
patterns = {
|
||||
{
|
||||
file_pattern = {
|
||||
'.env*',
|
||||
'wrangler.toml',
|
||||
'.dev.vars',
|
||||
},
|
||||
cloak_pattern = "=.+"
|
||||
},
|
||||
},
|
||||
})
|
||||
end,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
return {
|
||||
'folke/tokyonight.nvim',
|
||||
lazy = false,
|
||||
priority = 1000,
|
||||
opts = {
|
||||
style = 'moon',
|
||||
transparent = true,
|
||||
on_colors = function(colors)
|
||||
colors.comment = '#a0a7c5'
|
||||
colors.fg_gutter = '#787f93'
|
||||
colors.terminal_black = '#828bb8'
|
||||
end,
|
||||
},
|
||||
config = function(_,opts)
|
||||
require'tokyonight'.setup(opts)
|
||||
vim.cmd.colorscheme('tokyonight-moon')
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
-- Tokyo Night, following the desktop's colour scheme.
|
||||
--
|
||||
-- Moon when Panama is dark, Day when it is light. Same theme family either way,
|
||||
-- so the editor keeps the identity the rest of the desktop has rather than
|
||||
-- becoming a different-looking application when the scheme flips.
|
||||
--
|
||||
-- The readability fixes below are deliberately dark-only. They were written
|
||||
-- against Moon's palette -- a pale comment colour, a mid-grey gutter -- and
|
||||
-- applying them to Day would put light grey text on a light background, which
|
||||
-- is exactly the legibility problem they exist to solve, inverted.
|
||||
|
||||
local panama = require("config.panama")
|
||||
|
||||
return {
|
||||
{
|
||||
"folke/tokyonight.nvim",
|
||||
opts = function()
|
||||
local light = panama.is_light()
|
||||
|
||||
return {
|
||||
style = panama.tokyonight_style(),
|
||||
light_style = "day",
|
||||
transparent = true,
|
||||
|
||||
on_colors = function(colors)
|
||||
if light then
|
||||
-- Day's defaults are tuned for a light ground and mostly need no
|
||||
-- help. Comments are the exception: the shipped #848cb5 measures
|
||||
-- 2.54:1 against the #e1e2e7 background, well under the 3:1 floor
|
||||
-- for secondary text. This is 3.25:1 -- readable, and still clearly
|
||||
-- dimmer than Normal's 4.52:1 so it does not compete with code.
|
||||
colors.comment = "#7079a8"
|
||||
return
|
||||
end
|
||||
|
||||
colors.comment = "#a0a7c5"
|
||||
colors.fg_gutter = "#787f93"
|
||||
colors.terminal_black = "#828bb8"
|
||||
end,
|
||||
|
||||
on_highlights = function(highlights, colors)
|
||||
-- Inline code in markdown is invisible at both lightnesses without
|
||||
-- an explicit background, because the theme leaves it unset.
|
||||
highlights["@markup.raw.markdown_inline"] = {
|
||||
bg = light and colors.bg_highlight or colors.terminal_black,
|
||||
fg = colors.fg,
|
||||
}
|
||||
highlights["RenderMarkdownCodeInline"] = {
|
||||
bg = light and colors.bg_highlight or colors.terminal_black,
|
||||
fg = colors.fg,
|
||||
}
|
||||
|
||||
-- LspReference* readability: DiagnosticUnnecessary dims fg for unused
|
||||
-- imports, making text nearly invisible against LspReferenceText's
|
||||
-- background.
|
||||
local reference = {
|
||||
bg = light and colors.bg_visual or colors.fg_gutter,
|
||||
fg = colors.fg,
|
||||
}
|
||||
highlights["LspReferenceText"] = reference
|
||||
highlights["LspReferenceRead"] = reference
|
||||
highlights["LspReferenceWrite"] = reference
|
||||
end,
|
||||
}
|
||||
end,
|
||||
},
|
||||
{
|
||||
"LazyVim/LazyVim",
|
||||
opts = {
|
||||
colorscheme = "tokyonight",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
return {
|
||||
'3rd/image.nvim',
|
||||
dependencies = {
|
||||
'nvim-treesitter/nvim-treesitter',
|
||||
},
|
||||
config = function()
|
||||
require'image'.setup({
|
||||
backend = "kitty",
|
||||
integrations = {
|
||||
markdown = {
|
||||
enabled = true,
|
||||
clear_in_insert_mode = false,
|
||||
download_remote_images = true,
|
||||
only_render_image_at_cursor = false,
|
||||
filetypes = { "markdown", "vimwiki" },
|
||||
},
|
||||
neorg = {
|
||||
enabled = true,
|
||||
clear_in_insert_mode = false,
|
||||
download_remote_images = true,
|
||||
only_render_image_at_cursor = false,
|
||||
filetypes = { "norg" },
|
||||
},
|
||||
},
|
||||
max_width = nil,
|
||||
max_height = nil,
|
||||
max_width_window_percentage = nil,
|
||||
max_height_window_percentage = 50,
|
||||
kitty_method = "normal",
|
||||
})
|
||||
end,
|
||||
}
|
||||
@@ -1,371 +1,36 @@
|
||||
return {
|
||||
'mason-org/mason-lspconfig.nvim',
|
||||
dependencies = {
|
||||
{ 'neovim/nvim-lspconfig', },
|
||||
{
|
||||
'mason-org/mason.nvim',
|
||||
"neovim/nvim-lspconfig",
|
||||
opts = {
|
||||
ui = {
|
||||
icons = {
|
||||
package_installed = "✓",
|
||||
package_pending = "➜",
|
||||
package_uninstalled = "✗"
|
||||
}
|
||||
}
|
||||
servers = {
|
||||
-- CSS / HTML / Tailwind
|
||||
cssls = {},
|
||||
html = {},
|
||||
tailwindcss = {},
|
||||
-- Shell / Config
|
||||
bashls = {},
|
||||
yamlls = {},
|
||||
-- Docker
|
||||
dockerls = {},
|
||||
docker_compose_language_service = {},
|
||||
-- Systems languages
|
||||
gopls = {},
|
||||
rust_analyzer = {},
|
||||
-- Web frameworks
|
||||
svelte = {},
|
||||
graphql = {},
|
||||
-- Database / ORM
|
||||
prismals = {},
|
||||
sqlls = {},
|
||||
-- PHP
|
||||
intelephense = {},
|
||||
-- Python
|
||||
pyright = {},
|
||||
-- Disabled: ltex grammar/spell LSP runs a ~1GB JVM and auto-attaches to
|
||||
-- markdown/text/html/gitcommit/etc. via mason-lspconfig automatic_enable.
|
||||
ltex = { enabled = false },
|
||||
ltex_plus = { enabled = false },
|
||||
},
|
||||
},
|
||||
{
|
||||
'hrsh7th/nvim-cmp',
|
||||
event = { 'InsertEnter', 'CmdlineEnter' },
|
||||
config = function()
|
||||
local cmp = require'cmp'
|
||||
local lspkind = require'lspkind'
|
||||
local cmp_select = { behavior = cmp.SelectBehavior.Select }
|
||||
local luasnip = require'luasnip'
|
||||
cmp.setup({
|
||||
snippet = {
|
||||
expand = function(args)
|
||||
luasnip.lsp_expand(args.body)
|
||||
end,
|
||||
},
|
||||
window = {
|
||||
completion = cmp.config.window.bordered(),
|
||||
documentation = cmp.config.window.bordered(),
|
||||
},
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'nvim_lsp' },
|
||||
{ name = 'luasnip' },
|
||||
}, {
|
||||
{ name = 'buffer' },
|
||||
{ name = 'luasnip_choice' },
|
||||
{ name = 'copilot' },
|
||||
{ name = 'supermaven' },
|
||||
{ name = 'nvim_lsp_document_symbol' },
|
||||
{ name = 'nvim_lsp_signature_help' },
|
||||
{ name = 'nvim_lua' },
|
||||
{ name = 'nerdfont' },
|
||||
{ name = 'emoji' },
|
||||
{ name = 'npm' },
|
||||
{ name = 'cmp-tw2css' },
|
||||
{ name = 'dotenv' },
|
||||
{ name = 'render-markdown' },
|
||||
}),
|
||||
formatting = {
|
||||
format = lspkind.cmp_format({
|
||||
mode = 'symbol',
|
||||
maxwidth = {
|
||||
menu = 50,
|
||||
abbr = 50,
|
||||
},
|
||||
symbol_map = {
|
||||
Supermaven = '',
|
||||
},
|
||||
ellipsis_char = '...',
|
||||
show_labelDetails = true,
|
||||
before = function(entry, vim_item)
|
||||
return vim_item
|
||||
end,
|
||||
}),
|
||||
},
|
||||
mapping = cmp.mapping.preset.insert({
|
||||
['<C-p>'] = cmp.mapping.select_prev_item(cmp_select),
|
||||
['<C-n>'] = cmp.mapping.select_next_item(cmp_select),
|
||||
['<C-y>'] = cmp.mapping.confirm({ select = true }),
|
||||
['<C-b>'] = cmp.mapping.scroll_docs(-4),
|
||||
['<C-f>'] = cmp.mapping.scroll_docs(4),
|
||||
['<C-e>'] = cmp.mapping.abort(),
|
||||
}),
|
||||
--mapping = cmp.mapping.preset.insert({
|
||||
--['<Tab>'] = cmp.mapping(function(fallback)
|
||||
--if cmp.visible() then
|
||||
--if cmp.get_selected_entry() then
|
||||
--cmp.confirm({ select = false })
|
||||
--else
|
||||
--cmp.select_next_item()
|
||||
--end
|
||||
--else
|
||||
--fallback()
|
||||
--end
|
||||
--end, { 'i', 's' }),
|
||||
|
||||
--['<C-y>'] = cmp.mapping.confirm({ select = true }),
|
||||
--['<C-Space>'] = cmp.mapping.complete(),
|
||||
--['<C-b>'] = cmp.mapping.scroll_docs(-4),
|
||||
--['<C-f>'] = cmp.mapping.scroll_docs(4),
|
||||
--['<C-e>'] = cmp.mapping.abort(),
|
||||
--['<C-p>'] = cmp.mapping.select_prev_item(cmp_select),
|
||||
--['<C-n>'] = cmp.mapping.select_next_item(cmp_select),
|
||||
--['<C-d>'] = cmp.mapping.scroll_docs(4),
|
||||
--['<C-u>'] = cmp.mapping.scroll_docs(-4),
|
||||
--}),
|
||||
preselect = cmp.PreselectMode.Item,
|
||||
completion = {
|
||||
completeopt = 'menu,menuone,noinsert',
|
||||
},
|
||||
})
|
||||
cmp.setup.filetype('gitcommit', {
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'git' },
|
||||
}, {
|
||||
{ name = 'buffer' },
|
||||
})
|
||||
})
|
||||
cmp.setup.cmdline({ '/', '?' }, {
|
||||
mapping = cmp.mapping.preset.cmdline(),
|
||||
sources = {
|
||||
{ name = 'buffer' },
|
||||
}
|
||||
})
|
||||
cmp.setup.cmdline(':', {
|
||||
mapping = cmp.mapping.preset.cmdline(),
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'path' },
|
||||
}, {
|
||||
{ name = 'cmdline' },
|
||||
}),
|
||||
matching = { disallow_symbol_nonprefix_matching = false }
|
||||
})
|
||||
end,
|
||||
},
|
||||
{ 'hrsh7th/cmp-nvim-lsp' },
|
||||
{ 'nvim-lua/plenary.nvim' },
|
||||
{ 'hrsh7th/cmp-nvim-lsp-document-symbol' },
|
||||
{ 'hrsh7th/cmp-nvim-lua' },
|
||||
{ 'hrsh7th/cmp-nvim-lsp-signature-help' },
|
||||
{ 'hrsh7th/cmp-path' },
|
||||
{ 'hrsh7th/cmp-copilot' },
|
||||
{ 'hrsh7th/cmp-buffer' },
|
||||
{ 'hrsh7th/cmp-emoji' },
|
||||
{ 'chrisgrieser/cmp-nerdfont' },
|
||||
{ 'jcha0713/cmp-tw2css' },
|
||||
{ 'SergioRibera/cmp-dotenv' },
|
||||
{ 'saadparwaiz1/cmp_luasnip' },
|
||||
{ 'onsails/lspkind.nvim' },
|
||||
{ 'rafamadriz/friendly-snippets' },
|
||||
{
|
||||
'L3MON4D3/cmp-luasnip-choice',
|
||||
config = function()
|
||||
require'cmp_luasnip_choice'.setup()
|
||||
end,
|
||||
},
|
||||
{
|
||||
'petertriho/cmp-git',
|
||||
config = function()
|
||||
require'cmp_git'.setup({})
|
||||
end
|
||||
},
|
||||
{
|
||||
'j-hui/fidget.nvim',
|
||||
config = function()
|
||||
require'fidget'.setup({})
|
||||
end,
|
||||
},
|
||||
{
|
||||
'David-Kunz/cmp-npm',
|
||||
config = function()
|
||||
require'cmp-npm'.setup({})
|
||||
end
|
||||
},
|
||||
{
|
||||
'stevearc/conform.nvim',
|
||||
config = function()
|
||||
require'conform'.setup({
|
||||
formatters_by_ft = {
|
||||
cs = { 'csharpier', 'lsp' },
|
||||
javascript = { 'prettierd', 'prettier' },
|
||||
javascriptreact = { 'prettierd', 'prettier' },
|
||||
typescript = { 'prettierd', 'prettier' },
|
||||
typescriptreact = { 'prettierd', 'prettier' },
|
||||
json = { 'prettierd', 'prettier' },
|
||||
html = { 'prettierd', 'prettier' },
|
||||
css = { 'prettierd', 'prettier' },
|
||||
lua = { 'stylua' },
|
||||
},
|
||||
})
|
||||
vim.api.nvim_create_user_command(
|
||||
'Format',
|
||||
function(args)
|
||||
local range
|
||||
if args.count ~= -1 then
|
||||
local start_line = vim.fn.line("'<")
|
||||
local end_line = vim.fn.line("'>")
|
||||
range = { start = { start_line, 0 }, ["end"] = { end_line, 0 } }
|
||||
end
|
||||
require'conform'.format({
|
||||
async = true,
|
||||
lsp_fallback = true,
|
||||
range = range,
|
||||
})
|
||||
end, { range = true, desc = "Format current buffer or range" })
|
||||
end,
|
||||
keys = {
|
||||
{
|
||||
'<leader>fk',
|
||||
':Format<CR>',
|
||||
mode = {'n', 'v'},
|
||||
silent = true,
|
||||
desc = "Format with conform"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'L3MON4D3/LuaSnip',
|
||||
version = 'v2.*',
|
||||
build = 'make install_jsregexp'
|
||||
},
|
||||
{
|
||||
'github/copilot.vim',
|
||||
},
|
||||
{
|
||||
'supermaven-inc/supermaven-nvim',
|
||||
config = function()
|
||||
require'supermaven-nvim'.setup({
|
||||
keymaps = {
|
||||
accept_suggestion = '<Tab>',
|
||||
clear_suggestion = '<C-]>',
|
||||
accept_word = '<C-.>',
|
||||
},
|
||||
disable_inline_completion = true,
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
'windwp/nvim-autopairs',
|
||||
event = 'InsertEnter',
|
||||
config = true
|
||||
},
|
||||
{
|
||||
'windwp/nvim-ts-autotag',
|
||||
config = function()
|
||||
require'nvim-ts-autotag'.setup({})
|
||||
end,
|
||||
},
|
||||
--{
|
||||
--"garyhurtz/cmp_kitty",
|
||||
--dependencies = {
|
||||
--{ "hrsh7th/nvim-cmp" },
|
||||
--},
|
||||
--init = function()
|
||||
--require('cmp_kitty'):setup()
|
||||
--end
|
||||
--},
|
||||
{ 'hrsh7th/cmp-cmdline' },
|
||||
},
|
||||
config = function()
|
||||
vim.diagnostic.config({
|
||||
float = {
|
||||
focusable = false,
|
||||
style = 'minimal',
|
||||
border = 'rounded',
|
||||
source = true,
|
||||
header = '',
|
||||
prefix = '',
|
||||
},
|
||||
})
|
||||
require'mason'.setup({})
|
||||
local cmp = require'cmp'
|
||||
local cmp_lsp = require'cmp_nvim_lsp'
|
||||
local lspconfig = require'lspconfig'
|
||||
local capabilities = vim.tbl_deep_extend(
|
||||
'force',
|
||||
{},
|
||||
vim.lsp.protocol.make_client_capabilities(),
|
||||
cmp_lsp.default_capabilities()
|
||||
)
|
||||
local cmp_autopairs = require'nvim-autopairs.completion.cmp'
|
||||
cmp.event:on('confirm_done', cmp_autopairs.on_confirm_done())
|
||||
require 'mason-lspconfig'.setup({
|
||||
ensure_installed = {
|
||||
'angularls',
|
||||
'bacon_ls',
|
||||
'bashls',
|
||||
'clangd',
|
||||
'cssls',
|
||||
'css_variables',
|
||||
'cssmodules_ls',
|
||||
'docker_compose_language_service',
|
||||
'docker_language_server',
|
||||
'dockerls',
|
||||
'eslint',
|
||||
'gopls',
|
||||
'graphql',
|
||||
'html',
|
||||
'htmx',
|
||||
'intelephense',
|
||||
'jsonls',
|
||||
'kotlin_lsp',
|
||||
'ltex',
|
||||
'ltex_plus',
|
||||
'lua_ls',
|
||||
'markdown_oxide',
|
||||
'phpactor',
|
||||
'postgres_lsp',
|
||||
'prismals',
|
||||
'pylyzer',
|
||||
'pyright',
|
||||
'remark_ls',
|
||||
'rust_analyzer',
|
||||
'sqlls',
|
||||
'svelte',
|
||||
'systemd_ls',
|
||||
'tailwindcss',
|
||||
'tinymist',
|
||||
'ts_ls',
|
||||
'yamlls',
|
||||
},
|
||||
handlers = {
|
||||
function(server_name)
|
||||
lspconfig[server_name].setup({
|
||||
capabilities = capabilities
|
||||
})
|
||||
end,
|
||||
zls = function()
|
||||
lspconfig.zls.setup({
|
||||
root_dir = lspconfig.util.root_pattern(".git", "build.zig", "zls.json"),
|
||||
settings = {
|
||||
zls = {
|
||||
enable_inlay_hints = true,
|
||||
enable_snippets = true,
|
||||
warn_style = true,
|
||||
},
|
||||
},
|
||||
})
|
||||
vim.g.zig_fmt_parse_errors = 0
|
||||
vim.g.zig_fmt_autosave = 0
|
||||
end,
|
||||
["lua_ls"] = function()
|
||||
lspconfig.lua_ls.setup({
|
||||
capabilities = capabilities,
|
||||
settings = {
|
||||
Lua = {
|
||||
runtime = {
|
||||
version = 'LuaJIT',
|
||||
},
|
||||
diagnostics = {
|
||||
globals = { 'vim' },
|
||||
},
|
||||
workspace = {
|
||||
library = vim.api.nvim_get_runtime_file("", true),
|
||||
checkThirdParty = false,
|
||||
-- These are the key settings to prevent the infinite processing
|
||||
maxPreload = 100000,
|
||||
preloadFileSize = 10000,
|
||||
},
|
||||
telemetry = {
|
||||
enable = false,
|
||||
},
|
||||
format = {
|
||||
enable = true,
|
||||
defaultConfig = {
|
||||
indent_style = "space",
|
||||
indent_size = "2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
end,
|
||||
}
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
return {
|
||||
'nvim-lualine/lualine.nvim',
|
||||
config = function()
|
||||
require'lualine'.setup({
|
||||
options = {
|
||||
icons_enabled = true,
|
||||
theme = 'tokyonight-moon',
|
||||
component_separators = { left = '', right = ''},
|
||||
section_separators = { left = '', right = ''},
|
||||
disabled_filetypes = {
|
||||
statusline = {},
|
||||
winbar = {},
|
||||
},
|
||||
ignore_focus = {},
|
||||
always_divide_middle = true,
|
||||
always_show_tabline = true,
|
||||
globalstatus = false,
|
||||
refresh = {
|
||||
statusline = 1000,
|
||||
tabline = 1000,
|
||||
winbar = 1000,
|
||||
refresh_time = 16,
|
||||
events = {
|
||||
'WinEnter',
|
||||
'BufEnter',
|
||||
'BufWritePost',
|
||||
'SessionLoadPost',
|
||||
'FileChangedShellPost',
|
||||
'VimResized',
|
||||
'Filetype',
|
||||
'CursorMoved',
|
||||
'CursorMovedI',
|
||||
'ModeChanged',
|
||||
},
|
||||
}
|
||||
},
|
||||
sections = {
|
||||
lualine_a = {'mode'},
|
||||
lualine_b = {'branch', 'diff', 'diagnostics'},
|
||||
lualine_c = {'filename'},
|
||||
lualine_x = {'encoding', 'fileformat', 'filetype'},
|
||||
lualine_y = {'progress'},
|
||||
lualine_z = {'location'}
|
||||
},
|
||||
inactive_sections = {
|
||||
lualine_a = {},
|
||||
lualine_b = {},
|
||||
lualine_c = {'filename'},
|
||||
lualine_x = {'location'},
|
||||
lualine_y = {},
|
||||
lualine_z = {}
|
||||
},
|
||||
tabline = {},
|
||||
winbar = {},
|
||||
inactive_winbar = {},
|
||||
extensions = {}
|
||||
})
|
||||
end,
|
||||
dependencies = {
|
||||
{ 'nvim-tree/nvim-web-devicons' },
|
||||
{ 'folke/tokyonight.nvim' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
return {
|
||||
-- Mask .env values in the buffer
|
||||
{
|
||||
"laytan/cloak.nvim",
|
||||
event = "BufReadPre",
|
||||
opts = {
|
||||
enabled = false,
|
||||
cloak_character = "*",
|
||||
patterns = {
|
||||
{
|
||||
file_pattern = ".env*",
|
||||
cloak_pattern = "=.+",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
-- Terminal image rendering via Kitty backend
|
||||
{
|
||||
"3rd/image.nvim",
|
||||
opts = {
|
||||
backend = "kitty",
|
||||
integrations = {
|
||||
markdown = {
|
||||
enabled = true,
|
||||
clear_in_insert_mode = false,
|
||||
download_remote_images = true,
|
||||
only_render_image_at_cursor = false,
|
||||
filetypes = { "markdown", "vimwiki" },
|
||||
},
|
||||
typst = { enabled = true, filetypes = { "typst" } },
|
||||
},
|
||||
max_height_window_percentage = 50,
|
||||
window_overlap_clear_enabled = false,
|
||||
window_overlap_clear_ft_ignore = { "cmp_menu", "cmp_docs", "" },
|
||||
},
|
||||
},
|
||||
|
||||
-- Paste images into markdown/typst from clipboard or drag-and-drop
|
||||
{
|
||||
"HakonHarnes/img-clip.nvim",
|
||||
event = "BufEnter",
|
||||
opts = {
|
||||
default = {
|
||||
embed_image_as_base64 = false,
|
||||
prompt_for_file_name = false,
|
||||
drag_and_drop = {
|
||||
insert_mode = true,
|
||||
},
|
||||
use_absolute_path = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
-- Render markdown formatting in-buffer
|
||||
{
|
||||
"MeanderingProgrammer/render-markdown.nvim",
|
||||
opts = {},
|
||||
},
|
||||
|
||||
-- Live preview for Typst documents
|
||||
{
|
||||
"chomosuke/typst-preview.nvim",
|
||||
lazy = false,
|
||||
version = "1.*",
|
||||
build = function()
|
||||
require("typst-preview").update()
|
||||
end,
|
||||
},
|
||||
|
||||
-- LeetCode integration
|
||||
{
|
||||
"kawre/leetcode.nvim",
|
||||
cmd = "Leet",
|
||||
opts = {
|
||||
lang = "typescript",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
return {
|
||||
'nvim-neo-tree/neo-tree.nvim',
|
||||
config = function()
|
||||
vim.diagnostic.config({
|
||||
signs = {
|
||||
text = {
|
||||
[vim.diagnostic.severity.ERROR] = '',
|
||||
[vim.diagnostic.severity.WARN] = '',
|
||||
[vim.diagnostic.severity.INFO] = '',
|
||||
[vim.diagnostic.severity.HINT] = '',
|
||||
},
|
||||
}
|
||||
})
|
||||
require'neo-tree'.setup({
|
||||
close_if_last_window = true,
|
||||
enable_git_status = true,
|
||||
enable_diagnostics = true,
|
||||
open_files_do_not_replace_types = { 'terminal', 'trouble', 'qf' },
|
||||
open_files_using_relative_paths = false,
|
||||
sort_case_insensitive = true,
|
||||
sort_function = nil,
|
||||
default_component_configs = {
|
||||
container = {
|
||||
enable_character_fade = true,
|
||||
},
|
||||
indent = {
|
||||
indent_size = 2,
|
||||
padding = 1,
|
||||
with_markers = true,
|
||||
indent_marker = '│',
|
||||
last_indent_marker = '└',
|
||||
highlight = 'NeoTreeIndentMarker',
|
||||
with_expanders = nil,
|
||||
expander_collapsed = '',
|
||||
expander_expanded = '',
|
||||
expander_highlight = 'NeoTreeExpander',
|
||||
},
|
||||
icon = {
|
||||
folder_closed = '',
|
||||
folder_open = '',
|
||||
folder_empty = '',
|
||||
provider = function(icon, node, state)
|
||||
if node.type == 'file' or node.type == 'terminal' then
|
||||
local success, web_devicons = pcall(require, 'nvim-web-devicons')
|
||||
local name = node.type == 'terminal' and 'terminal' or node.name
|
||||
if success then
|
||||
local devicon, hl = web_devicons.get_icon(name)
|
||||
icon.text = devicon or icon.text
|
||||
icon.highlight = hl or icon.highlight
|
||||
end
|
||||
end
|
||||
end,
|
||||
default = '*',
|
||||
highlight = 'NeoTreeFileIcon',
|
||||
},
|
||||
modified = {
|
||||
symbol = '[+]',
|
||||
highlight = 'NeoTreeModified',
|
||||
},
|
||||
name = {
|
||||
trailing_slash = false,
|
||||
use_git_status_colors = true,
|
||||
highlight = 'NeoTreeFileName',
|
||||
},
|
||||
git_status = {
|
||||
symbols = {
|
||||
added = '',
|
||||
modified = '',
|
||||
deleted = '✖',
|
||||
renamed = '',
|
||||
untracked = '',
|
||||
ignored = '',
|
||||
unstaged = '',
|
||||
staged = '',
|
||||
conflict = '',
|
||||
},
|
||||
},
|
||||
file_size = {
|
||||
enabled = true,
|
||||
width = 12,
|
||||
required_width = 64,
|
||||
},
|
||||
type = {
|
||||
enabled = true,
|
||||
width = 10,
|
||||
required_width = 122,
|
||||
},
|
||||
last_modified = {
|
||||
enabled = true,
|
||||
width = 20,
|
||||
required_width = 88,
|
||||
},
|
||||
created = {
|
||||
enabled = true,
|
||||
width = 20,
|
||||
required_width = 110,
|
||||
},
|
||||
symlink_target = {
|
||||
enabled = false,
|
||||
},
|
||||
},
|
||||
window = {
|
||||
position = 'left',
|
||||
width = 30,
|
||||
mapping_options = {
|
||||
noremap = true,
|
||||
nowait = true,
|
||||
},
|
||||
mappings = {
|
||||
['<space>'] = {
|
||||
'toggle_node',
|
||||
nowait = false,
|
||||
},
|
||||
['<2-LeftMouse>'] = 'open',
|
||||
['<cr>'] = 'open',
|
||||
['<esc>'] = 'cancel',
|
||||
['P'] = { 'toggle_preview', config = { use_float = true, use_image_nvim = true } },
|
||||
['l'] = 'focus_preview',
|
||||
['S'] = 'open_split',
|
||||
['s'] = 'open_vsplit',
|
||||
['t'] = 'open_tabnew',
|
||||
['w'] = 'open_with_window_picker',
|
||||
['C'] = 'close_node',
|
||||
['z'] = 'close_all_nodes',
|
||||
['a'] = {
|
||||
'add',
|
||||
config = {
|
||||
show_path = 'relative',
|
||||
},
|
||||
},
|
||||
['A'] = 'add_directory',
|
||||
['d'] = 'delete',
|
||||
['r'] = 'rename',
|
||||
['b'] = 'rename_basename',
|
||||
['y'] = 'copy_to_clipboard',
|
||||
['x'] = 'cut_to_clipboard',
|
||||
['p'] = 'paste_from_clipboard',
|
||||
['c'] = { 'copy', config = { show_path = 'relative' } },
|
||||
['m'] = { 'move', config = { show_path = 'relative' } },
|
||||
['q'] = 'close_window',
|
||||
['R'] = 'refresh',
|
||||
['?'] = 'show_help',
|
||||
['<'] = 'prev_source',
|
||||
['>'] = 'next_source',
|
||||
['i'] = 'show_file_details',
|
||||
},
|
||||
},
|
||||
filesystem = {
|
||||
filtered_items = {
|
||||
visible = false,
|
||||
hide_dotfiles = true,
|
||||
hide_gitignored = true,
|
||||
hide_hidden = true,
|
||||
hide_by_name = {
|
||||
'node_modules',
|
||||
'.next',
|
||||
'.vscode',
|
||||
'.idea',
|
||||
},
|
||||
hide_by_pattern = {
|
||||
'*.meta',
|
||||
'*/src/*/tsconfig.json',
|
||||
},
|
||||
always_show = {
|
||||
'.gitignored',
|
||||
},
|
||||
always_show_by_pattern = { -- uses glob style patterns
|
||||
'.env*',
|
||||
},
|
||||
never_show = {
|
||||
'.DS_Store',
|
||||
'thumbs.db'
|
||||
},
|
||||
never_show_by_pattern = {},
|
||||
},
|
||||
follow_current_file = {
|
||||
enabled = false,
|
||||
leave_dirs_open = false,
|
||||
},
|
||||
group_empty_dirs = false,
|
||||
hijack_netrw_behavior = 'open_default',
|
||||
use_libuv_file_watcher = false,
|
||||
commands = {
|
||||
avante_add_files = function(state)
|
||||
local node = state.tree:get_node()
|
||||
local filepath = node:get_id()
|
||||
local relative_path = require('avante.utils').relative_path(filepath)
|
||||
local sidebar = require('avante').get()
|
||||
local open = sidebar:is_open()
|
||||
if not open then
|
||||
require('avante.api').ask()
|
||||
sidebar = require('avante').get()
|
||||
end
|
||||
sidebar.file_selector:add_selected_file(relative_path)
|
||||
if not open then
|
||||
sidebar.file_selector:remove_selected_file('neo-tree filesystem [1]')
|
||||
end
|
||||
end,
|
||||
},
|
||||
window = {
|
||||
mappings = {
|
||||
['<bs>'] = 'navigate_up',
|
||||
['.'] = 'set_root',
|
||||
['H'] = 'toggle_hidden',
|
||||
['/'] = 'fuzzy_finder',
|
||||
['D'] = 'fuzzy_finder_directory',
|
||||
['#'] = 'fuzzy_sorter',
|
||||
-- ['D'] = 'fuzzy_sorter_directory',
|
||||
['f'] = 'filter_on_submit',
|
||||
['<c-x>'] = 'clear_filter',
|
||||
['[g'] = 'prev_git_modified',
|
||||
[']g'] = 'next_git_modified',
|
||||
['o'] = {
|
||||
'show_help',
|
||||
nowait = false,
|
||||
config = { title = 'Order by', prefix_key = 'o' },
|
||||
},
|
||||
['oa'] = 'avante_add_files',
|
||||
['oc'] = { 'order_by_created', nowait = false },
|
||||
['od'] = { 'order_by_diagnostics', nowait = false },
|
||||
['og'] = { 'order_by_git_status', nowait = false },
|
||||
['om'] = { 'order_by_modified', nowait = false },
|
||||
['on'] = { 'order_by_name', nowait = false },
|
||||
['os'] = { 'order_by_size', nowait = false },
|
||||
['ot'] = { 'order_by_type', nowait = false },
|
||||
},
|
||||
fuzzy_finder_mappings = {
|
||||
['<down>'] = 'move_cursor_down',
|
||||
['<C-n>'] = 'move_cursor_down',
|
||||
['<up>'] = 'move_cursor_up',
|
||||
['<C-p>'] = 'move_cursor_up',
|
||||
['<esc>'] = 'close',
|
||||
},
|
||||
},
|
||||
},
|
||||
buffers = {
|
||||
follow_current_file = {
|
||||
enabled = true,
|
||||
leave_dirs_open = false,
|
||||
},
|
||||
group_empty_dirs = true,
|
||||
show_unloaded = true,
|
||||
window = {
|
||||
mappings = {
|
||||
['d'] = 'buffer_delete',
|
||||
['bd'] = 'buffer_delete',
|
||||
['<bs>'] = 'navigate_up',
|
||||
['.'] = 'set_root',
|
||||
['o'] = {
|
||||
'show_help',
|
||||
nowait = false,
|
||||
config = { title = 'Order by', prefix_key = 'o' },
|
||||
},
|
||||
['oc'] = { 'order_by_created', nowait = false },
|
||||
['od'] = { 'order_by_diagnostics', nowait = false },
|
||||
['om'] = { 'order_by_modified', nowait = false },
|
||||
['on'] = { 'order_by_name', nowait = false },
|
||||
['os'] = { 'order_by_size', nowait = false },
|
||||
['ot'] = { 'order_by_type', nowait = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
git_status = {
|
||||
window = {
|
||||
position = 'float',
|
||||
mappings = {
|
||||
['A'] = 'git_add_all',
|
||||
['gu'] = 'git_unstage_file',
|
||||
['gU'] = 'git_undo_last_commit',
|
||||
['ga'] = 'git_add_file',
|
||||
['gr'] = 'git_revert_file',
|
||||
['gc'] = 'git_commit',
|
||||
['gp'] = 'git_push',
|
||||
['gg'] = 'git_commit_and_push',
|
||||
['o'] = {
|
||||
'show_help',
|
||||
nowait = false,
|
||||
config = { title = 'Order by', prefix_key = 'o' },
|
||||
},
|
||||
['oc'] = { 'order_by_created', nowait = false },
|
||||
['od'] = { 'order_by_diagnostics', nowait = false },
|
||||
['om'] = { 'order_by_modified', nowait = false },
|
||||
['on'] = { 'order_by_name', nowait = false },
|
||||
['os'] = { 'order_by_size', nowait = false },
|
||||
['ot'] = { 'order_by_type', nowait = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
end,
|
||||
keys = {
|
||||
{
|
||||
'<leader>t',
|
||||
':Neotree toggle<CR>',
|
||||
mode = { 'n', 'v', 'x'},
|
||||
silent = true,
|
||||
desc = 'Toggle NeoTree',
|
||||
},
|
||||
{
|
||||
'<leader>T',
|
||||
':Neotree focus<CR>',
|
||||
mode = { 'n', 'v', 'x' },
|
||||
silent = true, desc = 'Focus NeoTree',
|
||||
},
|
||||
},
|
||||
branch = 'v3.x',
|
||||
dependencies = {
|
||||
{ 'nvim-lua/plenary.nvim', },
|
||||
{ 'MunifTanjim/nui.nvim', },
|
||||
{ 'nvim-tree/nvim-web-devicons', },
|
||||
{
|
||||
'antosha417/nvim-lsp-file-operations',
|
||||
config = function()
|
||||
require'lsp-file-operations'.setup({})
|
||||
end,
|
||||
},
|
||||
{ '3rd/image.nvim', },
|
||||
{
|
||||
's1n7ax/nvim-window-picker',
|
||||
version = '2.*',
|
||||
config = function()
|
||||
require'window-picker'.setup({})
|
||||
end,
|
||||
},
|
||||
},
|
||||
lazy = false,
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
return {
|
||||
'preservim/nerdcommenter',
|
||||
keys = {
|
||||
{
|
||||
'<leader>c',
|
||||
'<Plug>NERDCommenterToggle',
|
||||
mode = { 'n', 'v', 'x' },
|
||||
silent = true,
|
||||
desc = 'Toggle commenting line',
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,40 @@
|
||||
return {
|
||||
'folke/snacks.nvim',
|
||||
dependencies = {
|
||||
{ 'echasnovski/mini.icons', },
|
||||
{
|
||||
"folke/snacks.nvim",
|
||||
keys = {
|
||||
{ "<leader>t", function() Snacks.explorer({ cwd = LazyVim.root() }) end, desc = "Explorer (Root Dir)" },
|
||||
{ "<leader>T", function() Snacks.explorer() end, desc = "Explorer (cwd)" },
|
||||
},
|
||||
opts = {
|
||||
picker = {
|
||||
sources = {
|
||||
explorer = {
|
||||
hidden = false, -- dotfiles hidden by default; H to toggle
|
||||
ignored = false, -- gitignored files hidden by default; I to toggle
|
||||
follow_file = true, -- auto-reveal current buffer in tree
|
||||
git_status = true,
|
||||
git_untracked = true,
|
||||
watch = true, -- live file watcher
|
||||
exclude = { "node_modules", ".next", ".vscode", ".idea", ".DS_Store", "thumbs.db" },
|
||||
include = { ".env*" }, -- always show .env files regardless of hidden/ignored
|
||||
layout = {
|
||||
layout = {
|
||||
width = 30,
|
||||
min_width = 30,
|
||||
},
|
||||
},
|
||||
win = {
|
||||
list = {
|
||||
keys = {
|
||||
["S"] = "split", -- horizontal split (like NeoTree S)
|
||||
["s"] = "vsplit", -- vertical split (like NeoTree s)
|
||||
["t"] = "tab", -- new tab (like NeoTree t)
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
config = function()
|
||||
require'snacks'.setup({
|
||||
animate = { enabled = true },
|
||||
bigfile = { enabled = true },
|
||||
image = { enabled = true },
|
||||
indent = { enabled = true },
|
||||
scroll = { enabled = true },
|
||||
})
|
||||
end
|
||||
}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
return {
|
||||
'nvim-telescope/telescope.nvim',
|
||||
tag = '0.1.8',
|
||||
dependencies = {
|
||||
{ 'nvim-lua/plenary.nvim', },
|
||||
},
|
||||
config = function()
|
||||
require'telescope'.setup({
|
||||
defaults = {
|
||||
file_ignore_patterns = {
|
||||
'node_modules',
|
||||
'.git',
|
||||
'.next',
|
||||
'.cache',
|
||||
'.DS_Store',
|
||||
'.venv',
|
||||
'.conda',
|
||||
},
|
||||
},
|
||||
})
|
||||
local builtin = require'telescope.builtin'
|
||||
vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Find files' })
|
||||
vim.keymap.set('n', '<leader>fg', builtin.git_files, { desc = 'Find git files' })
|
||||
vim.keymap.set(
|
||||
'n',
|
||||
'<leader>fs',
|
||||
function()
|
||||
builtin.grep_string({ search = vim.fn.input('Grep > ')})
|
||||
end,
|
||||
{ desc = 'Find string' }
|
||||
)
|
||||
end,
|
||||
}
|
||||
@@ -1,92 +1,31 @@
|
||||
return {
|
||||
'nvim-treesitter/nvim-treesitter',
|
||||
branch = 'master',
|
||||
lazy = false,
|
||||
build = ':TSUpdate',
|
||||
dependencies = {
|
||||
{ 'nvim-treesitter/playground', },
|
||||
{
|
||||
'nvim-treesitter/nvim-treesitter-context',
|
||||
config = function()
|
||||
require'treesitter-context'.setup({
|
||||
enable = true,
|
||||
multiwindow = false,
|
||||
max_lines = 10,
|
||||
min_window_height = 0,
|
||||
line_numbers = true,
|
||||
multiline_threshold = 20,
|
||||
trim_scope = 'outer',
|
||||
mode = 'topline',
|
||||
separator = nil,
|
||||
zindex = 20,
|
||||
on_attach = nil,
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
opts = function(_, opts)
|
||||
vim.list_extend(opts.ensure_installed, {
|
||||
"cmake",
|
||||
"cpp",
|
||||
"dockerfile",
|
||||
"git_config",
|
||||
"git_rebase",
|
||||
"gitattributes",
|
||||
"gitcommit",
|
||||
"gitignore",
|
||||
"java",
|
||||
"jsdoc",
|
||||
"jsonc",
|
||||
"kotlin",
|
||||
"latex",
|
||||
"make",
|
||||
"php",
|
||||
"scala",
|
||||
"sql",
|
||||
"svelte",
|
||||
"swift",
|
||||
"toml",
|
||||
"xml",
|
||||
"zig",
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
config = function()
|
||||
require'nvim-treesitter.install'.update({ with_sync = true })
|
||||
require'nvim-treesitter.configs'.setup({
|
||||
ensure_installed = {
|
||||
'bash',
|
||||
'c',
|
||||
'cmake',
|
||||
'cpp',
|
||||
'css',
|
||||
'diff',
|
||||
'dockerfile',
|
||||
'git_config',
|
||||
'git_rebase',
|
||||
'gitattributes',
|
||||
'gitcommit',
|
||||
'gitignore',
|
||||
'html',
|
||||
'java',
|
||||
'javascript',
|
||||
'jsdoc',
|
||||
'json',
|
||||
'jsonc',
|
||||
'kotlin',
|
||||
'latex',
|
||||
'lua',
|
||||
'luadoc',
|
||||
'luap',
|
||||
'make',
|
||||
'markdown',
|
||||
'markdown_inline',
|
||||
'php',
|
||||
'printf',
|
||||
'python',
|
||||
'query',
|
||||
'regex',
|
||||
'rust',
|
||||
'scala',
|
||||
'sql',
|
||||
'svelte',
|
||||
'swift',
|
||||
'toml',
|
||||
'tsx',
|
||||
'typescript',
|
||||
'vim',
|
||||
'vimdoc',
|
||||
'xml',
|
||||
'yaml',
|
||||
'zig'
|
||||
},
|
||||
sync_install = false,
|
||||
auto_install = true,
|
||||
indent = { enable = true },
|
||||
highlight = {
|
||||
enable = true,
|
||||
disable = function(lang, buf)
|
||||
local max_filesize = 500 * 1024 -- 100 KB
|
||||
local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf))
|
||||
if ok and stats and stats.size > max_filesize then
|
||||
return true
|
||||
end
|
||||
end,
|
||||
additional_vim_regex_highlighting = false,
|
||||
},
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
return {
|
||||
'folke/trouble.nvim',
|
||||
config = function()
|
||||
require'trouble'.setup({
|
||||
vim.api.nvim_create_user_command('Trouble', function()
|
||||
require'trouble'.open()
|
||||
end, { desc = "Open Trouble" }),
|
||||
})
|
||||
end,
|
||||
keys = {
|
||||
{
|
||||
'<leader>x',
|
||||
':Trouble diagnostics toggle<CR>',
|
||||
mode = 'n',
|
||||
silent = true,
|
||||
desc = 'Toggle Trouble',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
return {
|
||||
-- Remove S-h/S-l buffer navigation — conflicts with desktop environment window manager
|
||||
-- Buffer navigation is on C-h/l (set in keymaps.lua)
|
||||
{
|
||||
"akinsho/bufferline.nvim",
|
||||
keys = {
|
||||
{ "<S-h>", false },
|
||||
{ "<S-l>", false },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
return {
|
||||
'mbbill/undotree',
|
||||
keys = {
|
||||
{
|
||||
'<leader>u',
|
||||
':UndotreeToggle<CR>',
|
||||
mode = { 'n' },
|
||||
silent = true,
|
||||
desc = 'Toggle undo tree',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
indent_type = "Spaces"
|
||||
indent_width = 2
|
||||
column_width = 80
|
||||
@@ -0,0 +1,177 @@
|
||||
pragma Singleton
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Durable user choices, derived entirely from config/PreferenceSchema.qml.
|
||||
//
|
||||
// This object owns reading, validating, and writing. It knows nothing about
|
||||
// which settings exist -- that is the schema's job -- so adding a setting never
|
||||
// requires touching this file. That is the point: the previous implementation
|
||||
// restated every key four times (a property alias, a JSON adapter property, a
|
||||
// change handler, and a line in reset), and each omission failed silently.
|
||||
//
|
||||
// Reads go through get(), writes through set(). Typed accessors for the values
|
||||
// the shell reads on every frame live in Settings.qml, which remains the stable
|
||||
// public surface.
|
||||
//
|
||||
// The store lives at ~/.config/panama/settings.json rather than inside
|
||||
// Quickshell's per-shell state directory, because the Hyprland Lua config reads
|
||||
// the same file (see config/dot/hypr/prefs.lua) and because a user should be
|
||||
// able to back it up, diff it, or keep it in a dotfiles repo.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property string path: (Quickshell.env("XDG_CONFIG_HOME") || `${Quickshell.env("HOME")}/.config`) + "/panama/settings.json"
|
||||
|
||||
// Bumped on every accepted change. get() reads it so bindings built on get()
|
||||
// have something to invalidate; a bare function call would otherwise capture
|
||||
// no dependency and every reader would silently go stale.
|
||||
property int revision: 0
|
||||
|
||||
// Everything currently on disk, including keys this build does not know
|
||||
// about. Unknown keys are carried through untouched so that rolling back to
|
||||
// an older Panama does not discard a newer version's settings.
|
||||
property var values: ({})
|
||||
|
||||
property bool loaded: false
|
||||
|
||||
function get(key: string): var {
|
||||
root.revision;
|
||||
const stored = root.values[key];
|
||||
if (stored === undefined)
|
||||
return PreferenceSchema.defaultFor(key);
|
||||
const coerced = PreferenceSchema.coerce(key, stored);
|
||||
return coerced === undefined ? PreferenceSchema.defaultFor(key) : coerced;
|
||||
}
|
||||
|
||||
// Returns false when the key is unknown or the value cannot be represented,
|
||||
// so a caller can surface the rejection instead of assuming it took.
|
||||
function set(key: string, value: var): bool {
|
||||
if (!PreferenceSchema.has(key))
|
||||
return false;
|
||||
const coerced = PreferenceSchema.coerce(key, value);
|
||||
if (coerced === undefined)
|
||||
return false;
|
||||
if (root.values[key] === coerced)
|
||||
return true;
|
||||
|
||||
// Reassign rather than mutate: QML does not notify on in-place changes
|
||||
// to a var property's contents.
|
||||
const next = Object.assign({}, root.values);
|
||||
next[key] = coerced;
|
||||
root.values = next;
|
||||
root.revision++;
|
||||
persistTimer.restart();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Restores every schema default in one write. Complete by construction --
|
||||
// there is no hand-maintained list to fall out of sync with the schema.
|
||||
function resetDesktopDefaults(): void {
|
||||
const next = Object.assign({}, root.values, PreferenceSchema.defaults());
|
||||
root.values = next;
|
||||
root.revision++;
|
||||
persistTimer.restart();
|
||||
}
|
||||
|
||||
// Re-read the file from disk. Used after something outside the shell has
|
||||
// rewritten it -- restoring a snapshot, or a hand edit -- so the running
|
||||
// desktop reflects the new contents without waiting for the next change.
|
||||
function reload(): void {
|
||||
preferencesFile.reload();
|
||||
root.load();
|
||||
}
|
||||
|
||||
function load(): void {
|
||||
let parsed = {};
|
||||
try {
|
||||
const text = preferencesFile.text();
|
||||
if (text && text.trim().length > 0)
|
||||
parsed = JSON.parse(text);
|
||||
} catch (error) {
|
||||
// A corrupt file must not cost the user a working desktop. Fall
|
||||
// back to shipped defaults and let the next write replace it.
|
||||
parsed = {};
|
||||
}
|
||||
const raw = (parsed && typeof parsed === "object") ? parsed : {};
|
||||
|
||||
// Upgrade before anything reads a value. A stored key the current
|
||||
// schema no longer recognises is carried through untouched and silently
|
||||
// stops taking effect, so the conversion has to happen here rather than
|
||||
// being noticed later by whoever owns that setting.
|
||||
const result = Migrations.apply(raw);
|
||||
root.values = result.values;
|
||||
|
||||
if (result.migrated)
|
||||
console.info("Settings migrated from version", result.from, "to", result.to + ":",
|
||||
result.applied.join("; "));
|
||||
|
||||
// Write whenever the version moved, which includes stamping a file
|
||||
// written before versioning existed. Left unwritten, the stamp lives
|
||||
// only in memory and is redone on every launch, and a migration that is
|
||||
// not idempotent would compound.
|
||||
if (result.changed)
|
||||
persistTimer.restart();
|
||||
|
||||
root.revision++;
|
||||
root.loaded = true;
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: preferencesFile
|
||||
|
||||
path: root.path
|
||||
blockLoading: true
|
||||
printErrors: false
|
||||
atomicWrites: true
|
||||
|
||||
onLoaded: root.load()
|
||||
// No file yet is the normal first-run case, not an error.
|
||||
onLoadFailed: root.load()
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
migration.adopt();
|
||||
root.load();
|
||||
}
|
||||
|
||||
// Coalesce a burst of changes into one atomic write. Writing on every
|
||||
// assignment races a second assignment and can reload the older value.
|
||||
Timer {
|
||||
id: persistTimer
|
||||
interval: 0
|
||||
onTriggered: preferencesFile.setText(JSON.stringify(root.values, null, 2) + "\n")
|
||||
}
|
||||
|
||||
// One-time move from the pre-Stage-1 location inside Quickshell's state
|
||||
// directory. Reads the old file only when the new one does not exist yet, so
|
||||
// it can never overwrite newer settings, and never deletes the original.
|
||||
QtObject {
|
||||
id: migration
|
||||
|
||||
function adopt(): void {
|
||||
if (preferencesFile.text())
|
||||
return;
|
||||
try {
|
||||
const legacy = legacyFile.text();
|
||||
if (legacy && legacy.trim().length > 0)
|
||||
preferencesFile.setText(legacy);
|
||||
} catch (error) {
|
||||
// Nothing to migrate.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: legacyFile
|
||||
|
||||
path: Quickshell.stateDir + "/panama-settings.json"
|
||||
blockLoading: true
|
||||
printErrors: false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property alias initialized: values.initialized
|
||||
property alias favorites: values.favorites
|
||||
property string saveError: ""
|
||||
|
||||
FileView {
|
||||
id: preferencesFile
|
||||
|
||||
path: Quickshell.stateDir + "/panama-home.json"
|
||||
blockLoading: true
|
||||
printErrors: false
|
||||
atomicWrites: true
|
||||
onSaved: root.saveError = ""
|
||||
onSaveFailed: error => root.saveError = "Could not save Home favourites."
|
||||
|
||||
JsonAdapter {
|
||||
id: values
|
||||
|
||||
property bool initialized: false
|
||||
property var favorites: []
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: values
|
||||
function onInitializedChanged(): void { persistTimer.restart(); }
|
||||
function onFavoritesChanged(): void { persistTimer.restart(); }
|
||||
}
|
||||
|
||||
Component.onCompleted: preferencesFile.reload()
|
||||
|
||||
Timer {
|
||||
id: persistTimer
|
||||
interval: 180
|
||||
repeat: false
|
||||
onTriggered: preferencesFile.writeAdapter()
|
||||
}
|
||||
|
||||
function cloneFavorites(): var {
|
||||
var clone = [];
|
||||
for (var index = 0; index < values.favorites.length; index++) {
|
||||
var favorite = values.favorites[index];
|
||||
clone.push({
|
||||
id: favorite.id,
|
||||
alias: favorite.alias
|
||||
});
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
function isValidEntityId(entityId: var): bool {
|
||||
return typeof entityId === "string" && /^light\.[a-z0-9_]+$/.test(entityId);
|
||||
}
|
||||
|
||||
function initialize(legacyIds: var): void {
|
||||
if (values.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
var seededFavorites = [];
|
||||
var seen = {};
|
||||
if (legacyIds && typeof legacyIds.length === "number") {
|
||||
for (var index = 0; index < legacyIds.length; index++) {
|
||||
var entityId = legacyIds[index];
|
||||
if (!isValidEntityId(entityId) || seen[entityId]) {
|
||||
continue;
|
||||
}
|
||||
seen[entityId] = true;
|
||||
seededFavorites.push({ id: entityId, alias: "" });
|
||||
}
|
||||
}
|
||||
|
||||
values.favorites = seededFavorites;
|
||||
values.initialized = true;
|
||||
}
|
||||
|
||||
function resetHomeDefaults(): void {
|
||||
persistTimer.stop();
|
||||
values.favorites = [];
|
||||
values.initialized = false;
|
||||
root.saveError = "";
|
||||
preferencesFile.writeAdapter();
|
||||
}
|
||||
|
||||
function isSelected(entityId: string): bool {
|
||||
for (var index = 0; index < values.favorites.length; index++) {
|
||||
if (values.favorites[index].id === entityId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function aliasFor(entityId: string, sourceName: string): string {
|
||||
for (var index = 0; index < values.favorites.length; index++) {
|
||||
var favorite = values.favorites[index];
|
||||
if (favorite.id === entityId && favorite.alias !== "") {
|
||||
return favorite.alias;
|
||||
}
|
||||
}
|
||||
return sourceName;
|
||||
}
|
||||
|
||||
function add(entityId: string): void {
|
||||
if (!isValidEntityId(entityId) || isSelected(entityId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var nextFavorites = cloneFavorites();
|
||||
nextFavorites.push({ id: entityId, alias: "" });
|
||||
values.favorites = nextFavorites;
|
||||
}
|
||||
|
||||
function remove(entityId: string): void {
|
||||
var nextFavorites = [];
|
||||
var removed = false;
|
||||
for (var index = 0; index < values.favorites.length; index++) {
|
||||
var favorite = values.favorites[index];
|
||||
if (favorite.id === entityId) {
|
||||
removed = true;
|
||||
continue;
|
||||
}
|
||||
nextFavorites.push({ id: favorite.id, alias: favorite.alias });
|
||||
}
|
||||
if (removed) {
|
||||
values.favorites = nextFavorites;
|
||||
}
|
||||
}
|
||||
|
||||
function setAlias(entityId: string, alias: string): void {
|
||||
var nextFavorites = cloneFavorites();
|
||||
var updated = false;
|
||||
var trimmedAlias = String(alias).trim();
|
||||
for (var index = 0; index < nextFavorites.length; index++) {
|
||||
if (nextFavorites[index].id === entityId) {
|
||||
nextFavorites[index] = { id: entityId, alias: trimmedAlias };
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (updated) {
|
||||
values.favorites = nextFavorites;
|
||||
}
|
||||
}
|
||||
|
||||
function move(entityId: string, targetIndex: int): void {
|
||||
var nextFavorites = cloneFavorites();
|
||||
var currentIndex = -1;
|
||||
for (var index = 0; index < nextFavorites.length; index++) {
|
||||
if (nextFavorites[index].id === entityId) {
|
||||
currentIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentIndex === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var favorite = nextFavorites.splice(currentIndex, 1)[0];
|
||||
var clampedIndex = Math.max(0, Math.min(targetIndex, nextFavorites.length));
|
||||
nextFavorites.splice(clampedIndex, 0, favorite);
|
||||
values.favorites = nextFavorites;
|
||||
}
|
||||
|
||||
function retrySave(): void {
|
||||
preferencesFile.writeAdapter();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
pragma Singleton
|
||||
|
||||
// Versioned upgrades for the settings file.
|
||||
//
|
||||
// The schema is the single source of truth for what a setting IS, but it cannot
|
||||
// describe what a setting USED to be. Renaming a key, changing its units, or
|
||||
// splitting one setting into two all leave a stored value that the new schema
|
||||
// does not recognise -- and an unrecognised key is silently carried through
|
||||
// untouched, so the user's choice simply stops taking effect with nothing to
|
||||
// say why. That is the failure this exists to prevent.
|
||||
//
|
||||
// HOW IT WORKS
|
||||
//
|
||||
// settings.json carries a schemaVersion. On load, every migration with a
|
||||
// version ABOVE the stored one runs in order, then the file is stamped with
|
||||
// `current`. A file with no schemaVersion at all is a file written before this
|
||||
// existed; it is stamped at `baseline` and NOT migrated, because those
|
||||
// migrations were never written for it.
|
||||
//
|
||||
// WRITING ONE
|
||||
//
|
||||
// { version: 2, describe: "rename dockDelay to dockHideDelayMs",
|
||||
// migrate: values => { ... return values; } }
|
||||
//
|
||||
// Rules that make this safe to run against a real user's file:
|
||||
//
|
||||
// * migrate() receives the whole values object and returns it. Mutating and
|
||||
// returning the same object is fine.
|
||||
// * NEVER delete a key you are not replacing. Unknown keys are deliberately
|
||||
// preserved so that rolling back to an older Panama does not discard a
|
||||
// newer version's settings, and a migration is the one place that promise
|
||||
// could quietly be broken.
|
||||
// * A migration must tolerate its input being absent or the wrong type. It
|
||||
// runs against files written by every previous version, including ones
|
||||
// that were hand-edited.
|
||||
// * Migrations never run twice: the stored version only moves forward.
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
// What a file written today is stamped with. Bump this when adding a
|
||||
// migration, to the version of the migration you added.
|
||||
readonly property int current: 1
|
||||
|
||||
// Files predating versioning are stamped here without being migrated.
|
||||
readonly property int baseline: 1
|
||||
|
||||
readonly property string versionKey: "schemaVersion"
|
||||
|
||||
// Ordered by version. Empty is the correct state until the first breaking
|
||||
// schema change -- this exists so that change is a routine edit rather than
|
||||
// an emergency.
|
||||
readonly property var steps: []
|
||||
|
||||
function storedVersion(values: var): int {
|
||||
const raw = values ? values[root.versionKey] : undefined;
|
||||
return (typeof raw === "number" && isFinite(raw)) ? Math.floor(raw) : 0;
|
||||
}
|
||||
|
||||
// Returns { values, migrated, changed, from, to, applied }.
|
||||
//
|
||||
// `applied` names each step that ran, so the caller can log something
|
||||
// meaningful rather than "settings changed somehow". `changed` is the one
|
||||
// the caller should write on: stamping a pre-versioning file changes it
|
||||
// without running any step, and left unwritten the stamp would live only in
|
||||
// memory and be redone on every launch.
|
||||
function apply(values: var): var {
|
||||
return root.applyWith(values, root.steps, root.current, root.baseline);
|
||||
}
|
||||
|
||||
// The same logic with the step list injected, so the machinery can be
|
||||
// tested against fixture migrations. The real list is empty until the first
|
||||
// breaking schema change, and a mechanism that has never run against a
|
||||
// failing step is not one to find out about during an upgrade.
|
||||
function applyWith(values: var, steps: var, current: int, baseline: int): var {
|
||||
const safe = (values && typeof values === "object") ? values : {};
|
||||
const from = root.storedVersion(safe);
|
||||
|
||||
// No version: written before versioning existed. Stamp it and stop.
|
||||
// Running the migration list against it would apply upgrades designed
|
||||
// for schemas this file never had.
|
||||
if (from === 0) {
|
||||
safe[root.versionKey] = baseline;
|
||||
return { values: safe, migrated: false, changed: true, from: 0, to: baseline, applied: [] };
|
||||
}
|
||||
|
||||
// A file from a NEWER Panama. Left completely alone: downgrading its
|
||||
// keys is not something this can do correctly, and unknown keys are
|
||||
// already preserved, so the older build simply ignores what it does not
|
||||
// understand.
|
||||
if (from > current)
|
||||
return { values: safe, migrated: false, changed: false, from: from, to: from, applied: [] };
|
||||
|
||||
const applied = [];
|
||||
let working = safe;
|
||||
for (const step of steps) {
|
||||
if (step.version <= from || step.version > current)
|
||||
continue;
|
||||
try {
|
||||
const result = step.migrate(working);
|
||||
if (result && typeof result === "object")
|
||||
working = result;
|
||||
applied.push(step.version + ": " + step.describe);
|
||||
} catch (error) {
|
||||
// One bad migration must not cost the user every setting. Stop
|
||||
// at the last good version so the next launch retries from
|
||||
// here rather than skipping the failed step forever.
|
||||
console.warn("Migrations: step", step.version, "failed:", error);
|
||||
working[root.versionKey] = step.version - 1;
|
||||
return { values: working, migrated: applied.length > 0, changed: applied.length > 0,
|
||||
from: from, to: step.version - 1, applied: applied };
|
||||
}
|
||||
}
|
||||
|
||||
working[root.versionKey] = current;
|
||||
return { values: working, migrated: applied.length > 0, changed: from !== current,
|
||||
from: from, to: current, applied: applied };
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
pragma Singleton
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// User-facing knobs. Everything here is something you might reasonably want to
|
||||
// change without understanding the rest of the shell.
|
||||
//
|
||||
// Theme.qml holds *how it looks*; this holds *what it does*.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// ── Clock ───────────────────────────────────────────────────────────────
|
||||
// Carried over from GNOME: 12-hour, weekday + date shown, seconds on.
|
||||
readonly property bool use24Hour: DesktopPreferences.get("use24Hour")
|
||||
readonly property bool showSeconds: DesktopPreferences.get("showSeconds")
|
||||
readonly property bool showWeekday: DesktopPreferences.get("showWeekday")
|
||||
|
||||
// ── Weather ─────────────────────────────────────────────────────────────
|
||||
// Coordinates taken from the GNOME night-light setting, which had already
|
||||
// resolved the location. Uses Open-Meteo, which needs no API key.
|
||||
readonly property real latitude: DesktopPreferences.get("weatherLatitude")
|
||||
readonly property real longitude: DesktopPreferences.get("weatherLongitude")
|
||||
// Open-Meteo returns coordinates but no friendly place name. Keep the
|
||||
// label deliberately general rather than exposing precise coordinates in
|
||||
// the UI or guessing at a city from them.
|
||||
readonly property string weatherLocation: DesktopPreferences.get("weatherLocation")
|
||||
readonly property string temperatureUnit: DesktopPreferences.get("temperatureUnit")
|
||||
readonly property int weatherRefreshMinutes: DesktopPreferences.get("weatherRefreshMinutes")
|
||||
|
||||
// ── Vitals ──────────────────────────────────────────────────────────────
|
||||
// The GNOME Vitals extension showed processor usage, memory usage and GPU
|
||||
// usage, in that order. Same here.
|
||||
readonly property int vitalsIntervalMs: DesktopPreferences.get("vitalsIntervalMs")
|
||||
readonly property bool showCpu: DesktopPreferences.get("showCpu")
|
||||
readonly property bool showMemory: DesktopPreferences.get("showMemory")
|
||||
readonly property bool showGpu: DesktopPreferences.get("showGpu")
|
||||
|
||||
// amdgpu exposes utilisation here. Verified present on this machine; the
|
||||
// widget hides itself if the path is missing rather than showing zeros.
|
||||
readonly property string gpuBusyPath: DesktopPreferences.get("gpuBusyPath")
|
||||
|
||||
// ── Night light ─────────────────────────────────────────────────────────
|
||||
// Matches the (disabled) GNOME schedule: 3500K from 17:00 to 10:00.
|
||||
readonly property int nightLightTemperature: DesktopPreferences.get("nightLightTemperature")
|
||||
readonly property real nightLightFrom: DesktopPreferences.get("nightLightFrom")
|
||||
readonly property real nightLightTo: DesktopPreferences.get("nightLightTo")
|
||||
readonly property bool nightLightEnabledByDefault: DesktopPreferences.get("nightLightEnabled")
|
||||
|
||||
// ── Notifications ───────────────────────────────────────────────────────
|
||||
readonly property int notificationTimeoutMs: DesktopPreferences.get("notificationTimeoutMs")
|
||||
readonly property int notificationTimeoutCriticalMs: DesktopPreferences.get("notificationTimeoutCriticalMs") // 0 = never auto-expire
|
||||
readonly property int notificationHistoryLimit: DesktopPreferences.get("notificationHistoryLimit")
|
||||
readonly property int maxVisibleToasts: DesktopPreferences.get("maxVisibleToasts")
|
||||
|
||||
// ── Focus ──────────────────────────────────────────────────────────────
|
||||
// One deliberate default rather than a preset picker: quick settings and
|
||||
// the keyboard shortcut should start a useful session in one action.
|
||||
readonly property int focusDurationMinutes: DesktopPreferences.get("focusDurationMinutes")
|
||||
|
||||
// ── Dock ────────────────────────────────────────────────────────────────
|
||||
// Pinned apps, in order, taken from the GNOME dash favourites.
|
||||
readonly property var dockPinned: DesktopPreferences.get("dockPinned")
|
||||
|
||||
// Dash-to-Dock was set to intellihide against all windows: the dock hides
|
||||
// when any window would overlap it, and comes back on hover.
|
||||
readonly property bool dockAutohide: DesktopPreferences.get("dockAutohide")
|
||||
|
||||
// 0: reveal the instant the pointer reaches the bottom edge. A reveal delay
|
||||
// is indistinguishable from lag, because the user has already committed to
|
||||
// the gesture by the time the strip is hit.
|
||||
readonly property int dockRevealDelayMs: DesktopPreferences.get("dockRevealDelayMs")
|
||||
|
||||
// Hiding keeps a delay, so brushing past the bottom edge or crossing the
|
||||
// gap between two icons doesn't make the dock flicker.
|
||||
readonly property int dockHideDelayMs: DesktopPreferences.get("dockHideDelayMs")
|
||||
|
||||
// ── Capture ─────────────────────────────────────────────────────────────
|
||||
readonly property string screenshotDir: DesktopPreferences.get("screenshotDir")
|
||||
readonly property string recordingDir: DesktopPreferences.get("recordingDir")
|
||||
// Passed to wf-recorder. Uses VAAPI on the AMD card so recording does not
|
||||
// cost CPU while gaming.
|
||||
readonly property string recorderArgs: DesktopPreferences.get("recorderArgs")
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
pragma Singleton
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Tokyo Night Moon — the single source of truth for every colour, radius,
|
||||
// duration and font in the shell. Nothing below this file should hardcode a
|
||||
// colour; if you find yourself writing "#" outside this file, add a token here.
|
||||
//
|
||||
// The bar keeps GNOME's 36px information density but sits directly on the
|
||||
// desktop, edge to edge. The dock retains the former glass vocabulary: bottom,
|
||||
// 48px icons and dot indicators.
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
import Quickshell
|
||||
// QtQuick is required even though nothing visual is declared here: `color` is a
|
||||
// QtQuick value type, and Qt.rgba() lives in its JS namespace.
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
// ── Colour scheme ───────────────────────────────────────────────────────
|
||||
// Tokyo Night ships an official light variant (Day), so light mode is that
|
||||
// rather than a palette invented to merely not be dark. The two share the
|
||||
// same hues at different lightness, which is what lets the Prism identity
|
||||
// survive the switch: blue still leads into orchid, it is simply a darker
|
||||
// blue on a lighter ground.
|
||||
//
|
||||
// Every token below is a binding on this, so flipping it repaints the whole
|
||||
// shell without anything needing to know it happened.
|
||||
readonly property bool dark: DesktopPreferences.get("colorScheme") !== "light"
|
||||
|
||||
// ── Palette ─────────────────────────────────────────────────────────────
|
||||
// Canonical Tokyo Night Moon. `accent` matches the GNOME accent exactly.
|
||||
readonly property color bg: root.dark ? "#222436" : "#e1e2e7"
|
||||
readonly property color bgDark: root.dark ? "#1e2030" : "#d3d5de"
|
||||
readonly property color bgHighlight: root.dark ? "#2f334d" : "#c4c8da"
|
||||
readonly property color bgPanel: root.dark ? "#2e2f3d" : "#d9dae3" // dock glass surface
|
||||
readonly property color bgPopover: root.dark ? "#21212f" : "#eaeaee" // Openbar submenu background
|
||||
|
||||
readonly property color fg: root.dark ? "#c8d3f5" : "#3760bf"
|
||||
readonly property color fgDim: root.dark ? "#828bb8" : "#6172b0"
|
||||
readonly property color fgMuted: root.dark ? "#636da6" : "#848cb5"
|
||||
readonly property color gutter: root.dark ? "#3b4261" : "#a8aecb"
|
||||
|
||||
// The pair. `accent` is the primary and carries every state meaning
|
||||
// (focused, active, on). `accentSecondary` is the orchid from the tmux
|
||||
// theme — it never appears alone, only as the far end of a gradient. That
|
||||
// restraint is the whole point: the two colours meeting is the signature,
|
||||
// so the pink stops being special the moment it's used as a flat fill.
|
||||
readonly property color accent: root.dark ? "#82aaff" : "#2e7de9" // blue
|
||||
readonly property color accentSecondary: root.dark ? "#b172b0" : "#9854f1" // orchid, from tmux
|
||||
readonly property color accentAlt: root.dark ? "#65bcff" : "#007197" // blue1, a lighter blue
|
||||
readonly property color cyan: root.dark ? "#86e1fc" : "#007197"
|
||||
readonly property color teal: root.dark ? "#4fd6be" : "#118c74"
|
||||
readonly property color green: root.dark ? "#c3e88d" : "#587539"
|
||||
readonly property color yellow: root.dark ? "#ffc777" : "#8c6c3e"
|
||||
readonly property color orange: root.dark ? "#ff966c" : "#b15c00"
|
||||
readonly property color red: root.dark ? "#ff757f" : "#f52a65"
|
||||
readonly property color redDeep: root.dark ? "#c53b53" : "#c64343"
|
||||
readonly property color magenta: root.dark ? "#c099ff" : "#9854f1"
|
||||
readonly property color pink: root.dark ? "#fca7ea" : "#d20065"
|
||||
|
||||
// Semantic aliases — prefer these in widgets so intent survives a repaint.
|
||||
readonly property color ok: green
|
||||
readonly property color warn: yellow
|
||||
readonly property color danger: red
|
||||
readonly property color urgent: orange
|
||||
|
||||
// ── Surface alphas ──────────────────────────────────────────────────────
|
||||
// The dock sits on compositor blur, so its fill is deliberately very
|
||||
// translucent. Popovers are near-opaque because they carry text that must
|
||||
// stay legible. The bar itself has no surface or alpha token.
|
||||
// Light surfaces need more opacity for the same sense of a solid panel: the
|
||||
// same 0.34 that reads as glass over a dark desktop reads as haze over a
|
||||
// light one, and text on it stops being legible.
|
||||
readonly property real dockAlpha: root.dark ? 0.34 : 0.62
|
||||
readonly property real popoverAlpha: root.dark ? 0.92 : 0.97
|
||||
readonly property real overlayAlpha: root.dark ? 0.55 : 0.40
|
||||
readonly property real hoverAlpha: root.dark ? 0.14 : 0.10
|
||||
readonly property real activeAlpha: root.dark ? 0.24 : 0.18
|
||||
|
||||
// ── Geometry ────────────────────────────────────────────────────────────
|
||||
readonly property int barHeight: 36
|
||||
readonly property int barGap: 6 // breathing room below the bar for popovers
|
||||
readonly property int barSideMargin: 10 // inset for floating popovers
|
||||
|
||||
readonly property int dockIconSize: 48
|
||||
readonly property int dockPadding: 8
|
||||
readonly property int dockGap: 8
|
||||
readonly property int dockRadius: 20
|
||||
|
||||
readonly property int popoverRadius: 18
|
||||
readonly property int popoverPadding: 14
|
||||
readonly property int popoverWidth: 380
|
||||
readonly property int controlCenterWidth: 430
|
||||
readonly property int controlCenterTopGap: 2
|
||||
|
||||
readonly property int cardRadius: 12
|
||||
readonly property int pillRadius: 999
|
||||
readonly property int itemSpacing: 8
|
||||
readonly property int sectionSpacing: 14
|
||||
|
||||
// ── Type ────────────────────────────────────────────────────────────────
|
||||
// Adwaita Sans for everything the user reads as text. No exceptions: a
|
||||
// monospaced clock or percentage reads as a terminal readout pasted into a
|
||||
// UI, which is the opposite of the intent here.
|
||||
readonly property string fontFamily: DesktopPreferences.get("interfaceFont")
|
||||
|
||||
// Nerd Font, used ONLY to draw icon glyphs — never for text. It is the
|
||||
// pragmatic alternative to freedesktop symbolic icons, which ship with a
|
||||
// hardcoded dark fill Qt will not recolour (see widgets/ThemedIcon.qml).
|
||||
readonly property string fontMono: DesktopPreferences.get("iconFont")
|
||||
|
||||
// Apply to any text whose digits change in place — clocks, percentages,
|
||||
// elapsed timers, dimension readouts. Tabular figures all share one
|
||||
// advance width, so the text stops twitching as the numbers tick without
|
||||
// needing a monospaced face.
|
||||
//
|
||||
// font.family: Theme.fontFamily
|
||||
// font.features: Theme.tabularFigures
|
||||
readonly property var tabularFigures: ({
|
||||
"tnum": 1
|
||||
})
|
||||
// The four sizes are a scale, not four independent numbers: they move
|
||||
// together so the relationship between body, caption, heading and title
|
||||
// survives a change to the base rather than drifting apart.
|
||||
readonly property int fontSize: DesktopPreferences.get("interfaceFontSize")
|
||||
readonly property int fontSizeSmall: Math.max(9, root.fontSize - 2)
|
||||
readonly property int fontSizeLarge: root.fontSize + 3
|
||||
readonly property int fontSizeTitle: root.fontSize + 7
|
||||
|
||||
// ── Motion ──────────────────────────────────────────────────────────────
|
||||
// Event-driven only. Nothing in this shell animates while idle — no pulse,
|
||||
// no shimmer, no spinners. These durations are used for open/close/hover.
|
||||
readonly property int durFast: 120
|
||||
readonly property int durNormal: 200
|
||||
readonly property int durSlow: 320
|
||||
|
||||
// The dock revealing is the one animation that answers a live pointer
|
||||
// movement, so it gets its own (much shorter) duration. Anything slower
|
||||
// reads as the desktop lagging behind the cursor rather than as motion.
|
||||
readonly property int durDockReveal: 90
|
||||
|
||||
// Matches the "snappy" spring curve defined in hypr/looks.lua.
|
||||
readonly property list<real> easeStandard: [0.05, 0.9, 0.1, 1.0]
|
||||
readonly property list<real> easeOvershoot: [0.34, 1.56, 0.64, 1.0]
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────
|
||||
// Qt.alpha() does not exist; this is the idiomatic way to re-alpha a token.
|
||||
function alpha(c: color, a: real): color {
|
||||
return Qt.rgba(c.r, c.g, c.b, a);
|
||||
}
|
||||
|
||||
// Blend two tokens — used for hover/pressed states so we never invent a
|
||||
// colour that isn't derived from the palette.
|
||||
function mix(a: color, b: color, ratio: real): color {
|
||||
return Qt.rgba(a.r + (b.r - a.r) * ratio, a.g + (b.g - a.g) * ratio, a.b + (b.b - a.b) * ratio, a.a + (b.a - a.a) * ratio);
|
||||
}
|
||||
|
||||
// ── The prism ───────────────────────────────────────────────────────────
|
||||
// Where the two accents meet. Used for the hairline along the top edge of
|
||||
// every glass surface (widgets/PrismEdge.qml), the focused window border,
|
||||
// the active workspace pill and slider fills.
|
||||
//
|
||||
// Where the gradient runs left-to-right the blue leads, because the eye
|
||||
// reads left first and blue is the colour that carries meaning.
|
||||
readonly property real prismStart: 0.22
|
||||
readonly property real prismEnd: 0.78
|
||||
|
||||
// Opacity of the hairline itself. Deliberately below 1: at full strength it
|
||||
// reads as a drawn line rather than as light catching an edge.
|
||||
readonly property real prismEdgeAlpha: 0.85
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
module qs.config
|
||||
singleton DesktopPreferences 1.0 DesktopPreferences.qml
|
||||
singleton PreferenceSchema 1.0 PreferenceSchema.qml
|
||||
singleton HomePreferences 1.0 HomePreferences.qml
|
||||
singleton Settings 1.0 Settings.qml
|
||||
singleton Theme 1.0 Theme.qml
|
||||
singleton Migrations 1.0 Migrations.qml
|
||||
@@ -0,0 +1,28 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.services
|
||||
|
||||
ShellRoot {
|
||||
IpcHandler {
|
||||
target: "connectivity-test"
|
||||
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
wifiDevice: Connectivity.wifiDevice ? Connectivity.wifiDevice.name : "",
|
||||
wiredDevice: Connectivity.wiredDevice ? Connectivity.wiredDevice.name : "",
|
||||
wiredConnected: !!(Connectivity.wiredDevice && Connectivity.wiredDevice.connected),
|
||||
networks: Connectivity.networks.length,
|
||||
activeSsid: Connectivity.activeNetwork ? Connectivity.activeNetwork.name : "",
|
||||
activeStrength: Connectivity.activeNetwork ? Connectivity.activeNetwork.signalStrength : -1,
|
||||
activeLabel: Connectivity.activeNetwork ? Connectivity.signalLabel(Connectivity.activeNetwork.signalStrength) : "",
|
||||
adapter: Connectivity.adapter ? true : false,
|
||||
btDevices: Connectivity.bluetoothDevices.length
|
||||
});
|
||||
}
|
||||
|
||||
function labelFor(strength: real): string { return Connectivity.signalLabel(strength); }
|
||||
function setActive(on: bool): void { Connectivity.active = on; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
ShellRoot {
|
||||
IpcHandler {
|
||||
target: "displays-test"
|
||||
|
||||
function status(): string {
|
||||
const monitor = Displays.monitors.length > 0 ? Displays.monitors[0] : null;
|
||||
return JSON.stringify({
|
||||
count: Displays.monitors.length,
|
||||
name: monitor ? monitor.name : "",
|
||||
width: monitor ? monitor.width : 0,
|
||||
height: monitor ? monitor.height : 0,
|
||||
refresh: monitor ? monitor.refreshRate : 0,
|
||||
mode: monitor ? monitor.mode : "",
|
||||
scale: monitor ? monitor.scale : 0,
|
||||
transform: monitor ? monitor.transform : -1,
|
||||
modes: monitor ? monitor.modes.length : 0,
|
||||
awaiting: Displays.awaitingConfirmation,
|
||||
canConfirm: Displays.canConfirm,
|
||||
secondsLeft: Displays.secondsLeft,
|
||||
lastError: Displays.lastError,
|
||||
overridden: monitor ? Displays.isOverridden(monitor.name) : false
|
||||
});
|
||||
}
|
||||
|
||||
function applyScale(scale: real): bool {
|
||||
const monitor = Displays.monitors[0];
|
||||
if (!monitor) return false;
|
||||
const mode = monitor.mode;
|
||||
return Displays.apply(monitor.name, mode, scale, monitor.transform);
|
||||
}
|
||||
|
||||
function refreshIdentityFixture(): string {
|
||||
const modes = Displays.normaliseModes([
|
||||
"[email protected]",
|
||||
"[email protected]"
|
||||
]);
|
||||
const monitor = { width: 1920, height: 1080, refreshRate: 59.94 };
|
||||
return JSON.stringify({
|
||||
count: modes.length,
|
||||
modes: modes.map(mode => mode.mode),
|
||||
selected: modes.filter(mode => Displays.modeIsCurrent(monitor, mode)).map(mode => mode.mode)
|
||||
});
|
||||
}
|
||||
|
||||
function applyBad(kind: string): bool {
|
||||
const monitor = Displays.monitors[0];
|
||||
if (!monitor) return false;
|
||||
const mode = monitor.mode;
|
||||
if (kind === "mode") return Displays.apply(monitor.name, "9999x9999@240", monitor.scale, monitor.transform);
|
||||
if (kind === "scale") return Displays.apply(monitor.name, mode, 1.37, monitor.transform);
|
||||
if (kind === "dirtyScale") {
|
||||
const dirty = Displays.scales.find(scale => !Displays.isScaleClean(mode, scale));
|
||||
return dirty === undefined ? false : Displays.apply(monitor.name, mode, dirty, monitor.transform);
|
||||
}
|
||||
if (kind === "transform") return Displays.apply(monitor.name, mode, monitor.scale, 9);
|
||||
if (kind === "output") return Displays.apply("NOPE-1", mode, monitor.scale, monitor.transform);
|
||||
return false;
|
||||
}
|
||||
|
||||
function confirmChange(): bool { return Displays.confirm(); }
|
||||
function revertChange(): void { Displays.revert(); }
|
||||
function forget(): void {
|
||||
const monitor = Displays.monitors[0];
|
||||
if (monitor) Displays.forget(monitor.name);
|
||||
}
|
||||
function refresh(): void { Displays.refresh(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
ShellRoot {
|
||||
// DesktopEntries populates asynchronously. Reading `applications.values`
|
||||
// here rather than calling byId() blind is the documented way to wait for
|
||||
// it -- and is exactly the trap noted in modules/settings/README.md.
|
||||
readonly property var ids: {
|
||||
const out = {};
|
||||
for (const entry of DesktopEntries.applications.values)
|
||||
out[entry.id] = entry;
|
||||
return out;
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "dock-pin-test"
|
||||
|
||||
function count(): int { return DesktopEntries.applications.values.length; }
|
||||
|
||||
function resolve(): string {
|
||||
const pinned = DesktopPreferences.get("dockPinned");
|
||||
const missing = [];
|
||||
let first = null;
|
||||
for (const id of pinned) {
|
||||
const entry = DesktopEntries.byId(id);
|
||||
if (!entry) missing.push(id);
|
||||
if (first === null)
|
||||
first = { id: id, found: !!entry, name: entry ? entry.name : "", icon: entry ? entry.icon : "" };
|
||||
}
|
||||
return JSON.stringify({ total: pinned.length, first: first, missing: missing });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.services
|
||||
|
||||
ShellRoot {
|
||||
// The production singleton still performs its delayed startup scan. The
|
||||
// harness turns it off before its 2200 ms deadline so each fixture drives
|
||||
// only the state transition it is asserting.
|
||||
Component.onCompleted: Health.startupScanEnabled = false
|
||||
|
||||
IpcHandler {
|
||||
target: "health-test"
|
||||
|
||||
function accept(text: string, generation: int): bool { return Health.consumeSnapshot(text, generation); }
|
||||
function queue(): void { Health.refresh(); Health.refresh(); }
|
||||
function status(): string { return JSON.stringify(Health.diagnostics()); }
|
||||
function repair(id: string): bool { return Health.repair(id, false); }
|
||||
function report(): string { return JSON.stringify(Health.snapshot, null, 2); }
|
||||
function copy(): bool { return Health.copyReport(); }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.modules.quicksettings
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
property int confirmedValue: 30
|
||||
property int commitCount: 0
|
||||
property int lastCommit: -1
|
||||
|
||||
HomeBrightnessSlider {
|
||||
id: slider
|
||||
width: 200
|
||||
value: root.confirmedValue
|
||||
accessibleName: "Desk lamp brightness"
|
||||
onCommitted: value => {
|
||||
root.commitCount += 1;
|
||||
root.lastCommit = value;
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
target: "home-brightness-slider-test"
|
||||
|
||||
function reset(value: int): void {
|
||||
root.confirmedValue = value;
|
||||
root.commitCount = 0;
|
||||
root.lastCommit = -1;
|
||||
slider.cancelPointerInteraction();
|
||||
}
|
||||
function external(value: int): void { root.confirmedValue = value; }
|
||||
function press(position: int): void { slider.beginPointerInteraction(position); }
|
||||
function move(position: int): void { slider.movePointerInteraction(position); }
|
||||
function release(): void { slider.releasePointerInteraction(); }
|
||||
function cancel(): void { slider.cancelPointerInteraction(); }
|
||||
function wheel(delta: int): void { slider.commitWheel(delta); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
confirmedValue: root.confirmedValue,
|
||||
previewValue: slider.previewValue,
|
||||
interactionActive: slider.interactionActive,
|
||||
commitCount: root.commitCount,
|
||||
lastCommit: root.lastCommit,
|
||||
accessibleRoleIsSlider: slider.Accessible.role === Accessible.Slider,
|
||||
accessibleName: slider.Accessible.name,
|
||||
accessibleDescription: slider.Accessible.description,
|
||||
accessibleFocusable: slider.Accessible.focusable
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.config
|
||||
|
||||
ShellRoot {
|
||||
IpcHandler {
|
||||
target: "home-pref-test"
|
||||
|
||||
function initialize(idsJson: string): void { HomePreferences.initialize(JSON.parse(idsJson)); }
|
||||
function add(id: string): void { HomePreferences.add(id); }
|
||||
function alias(id: string, value: string): void { HomePreferences.setAlias(id, value); }
|
||||
function move(id: string, index: int): void { HomePreferences.move(id, index); }
|
||||
function remove(id: string): void { HomePreferences.remove(id); }
|
||||
function reset(): void { HomePreferences.resetHomeDefaults(); }
|
||||
function status(): string {
|
||||
return JSON.stringify({
|
||||
initialized: HomePreferences.initialized,
|
||||
favorites: HomePreferences.favorites,
|
||||
saveError: HomePreferences.saveError,
|
||||
stateDir: Quickshell.stateDir
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
ShellRoot {
|
||||
IpcHandler {
|
||||
target: "keybinds-test"
|
||||
|
||||
function rebind(current: string, next: string): bool {
|
||||
return Keybinds.rebind(current, next);
|
||||
}
|
||||
|
||||
function resetBind(current: string): bool { return Keybinds.resetBind(current); }
|
||||
function resetAll(): void { Keybinds.resetAll(); }
|
||||
|
||||
function seedResetCollision(shipped: string, current: string, occupantShipped: string): void {
|
||||
const overrides = {};
|
||||
overrides[shipped] = current;
|
||||
overrides[occupantShipped] = shipped;
|
||||
DesktopPreferences.set("keybindOverrides", overrides);
|
||||
}
|
||||
|
||||
function chordFor(description: string): string {
|
||||
const found = Keybinds.binds.find(bind => bind.description === description);
|
||||
return found ? found.luaChord : "";
|
||||
}
|
||||
|
||||
function overrideState(): string {
|
||||
return JSON.stringify({
|
||||
overrides: Keybinds.overrides,
|
||||
count: Object.keys(Keybinds.overrides).length,
|
||||
lastError: Keybinds.lastError
|
||||
});
|
||||
}
|
||||
|
||||
function status(): string {
|
||||
const grouped = Keybinds.grouped();
|
||||
let groupedCount = 0;
|
||||
for (const group of grouped)
|
||||
groupedCount += group.binds.length;
|
||||
|
||||
const superT = Keybinds.binds.find(bind => bind.description === "Terminal");
|
||||
|
||||
return JSON.stringify({
|
||||
loaded: Keybinds.loaded,
|
||||
count: Keybinds.binds.length,
|
||||
groupedCount: groupedCount,
|
||||
groups: grouped.map(group => group.name),
|
||||
sample: superT ? superT.chord : "",
|
||||
emptyDescriptions: Keybinds.binds.filter(bind => !bind.description).length,
|
||||
lastError: Keybinds.lastError
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Exercises Migrations.applyWith against fixture steps and prints a verdict per
|
||||
// case. Run by tests/quickshell/migrations-contract.sh.
|
||||
//
|
||||
// Fixture steps rather than the real list: the real one is empty until the
|
||||
// first breaking schema change, and a mechanism that has never been run against
|
||||
// a failing step is not one to discover the behaviour of during an upgrade.
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
ShellRoot {
|
||||
Component.onCompleted: {
|
||||
const steps = [
|
||||
{ version: 2, describe: "add b", migrate: v => { v.b = (v.a ?? 0) + 1; return v; } },
|
||||
{ version: 3, describe: "add c", migrate: v => { v.c = "three"; return v; } },
|
||||
{ version: 4, describe: "explode", migrate: v => { throw new Error("boom"); } }
|
||||
];
|
||||
const results = {};
|
||||
|
||||
// No version at all: a file written before versioning. Stamped, never
|
||||
// migrated.
|
||||
let r = Migrations.applyWith({ a: 1 }, steps, 3, 1);
|
||||
results.unversioned = { v: r.values.schemaVersion, migrated: r.migrated,
|
||||
changed: r.changed, untouched: r.values.b === undefined };
|
||||
|
||||
// Older file: every step above its version runs, in order.
|
||||
r = Migrations.applyWith({ a: 1, schemaVersion: 1 }, steps, 3, 1);
|
||||
results.upgrade = { v: r.values.schemaVersion, b: r.values.b, c: r.values.c,
|
||||
count: r.applied.length, migrated: r.migrated };
|
||||
|
||||
// Already current: nothing runs.
|
||||
r = Migrations.applyWith({ schemaVersion: 3, keep: "me" }, steps, 3, 1);
|
||||
results.current = { v: r.values.schemaVersion, migrated: r.migrated,
|
||||
kept: r.values.keep === "me", b: r.values.b === undefined };
|
||||
|
||||
// From the future: left completely alone, including its unknown keys.
|
||||
r = Migrations.applyWith({ schemaVersion: 9, futureKey: "x" }, steps, 3, 1);
|
||||
results.future = { v: r.values.schemaVersion, migrated: r.migrated,
|
||||
changed: r.changed, kept: r.values.futureKey === "x" };
|
||||
|
||||
// A failing step stops at the last good version rather than losing the
|
||||
// file or skipping past the failure forever.
|
||||
r = Migrations.applyWith({ schemaVersion: 1, a: 5 }, steps, 4, 1);
|
||||
results.failure = { v: r.values.schemaVersion, b: r.values.b, c: r.values.c,
|
||||
kept: r.values.a === 5, count: r.applied.length };
|
||||
|
||||
// Unknown keys survive a migration: rolling back to an older Panama
|
||||
// must not discard a newer version's settings.
|
||||
r = Migrations.applyWith({ schemaVersion: 1, unknownFromFuture: true }, steps, 3, 1);
|
||||
results.preserved = { kept: r.values.unknownFromFuture === true };
|
||||
|
||||
console.info("PANAMA-MIGRATIONS " + JSON.stringify(results));
|
||||
Qt.callLater(() => Qt.quit());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Persistent, bar-native privacy state. Signal Glass handles transitions; this
|
||||
// remains until the underlying capture activity actually ends.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.widgets
|
||||
|
||||
Pill {
|
||||
id: root
|
||||
|
||||
visible: PrivacyState.anyActive
|
||||
horizontalPadding: 8
|
||||
onActivated: ShellState.toggle("activity")
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (PrivacyState.recordingActive)
|
||||
return "\u{F044A}";
|
||||
if (PrivacyState.screenSharingActive)
|
||||
return "\u{F0379}";
|
||||
if (PrivacyState.cameraActive)
|
||||
return "\u{F0100}";
|
||||
return "\u{F036C}";
|
||||
}
|
||||
color: PrivacyState.recordingActive ? Theme.danger : Theme.warn
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 14
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: PrivacyState.activeKinds.length > 1
|
||||
text: String(PrivacyState.activeKinds.length)
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Compact explanation and control surface for persistent privacy indicators.
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
visible: ShellState.activityOpen && PrivacyState.anyActive
|
||||
color: "transparent"
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: Theme.barHeight + Theme.barGap * 2
|
||||
margins.right: Theme.barSideMargin
|
||||
exclusiveZone: 0
|
||||
implicitWidth: 350
|
||||
implicitHeight: surface.implicitHeight
|
||||
|
||||
WlrLayershell.namespace: "qs-popover-activity"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
readonly property var activities: {
|
||||
const result = [];
|
||||
if (PrivacyState.recordingActive)
|
||||
result.push({ kind: "recording", glyph: "\u{F044A}", label: "Screen recording", detail: "Panama · " + root.elapsed(), tone: "danger", stoppable: true });
|
||||
if (PrivacyState.screenSharingActive)
|
||||
result.push({ kind: "screen", glyph: "\u{F0379}", label: "Screen sharing", detail: PrivacyState.screenSharingApp || "Managed by the application", tone: "warn", stoppable: false });
|
||||
if (PrivacyState.cameraActive)
|
||||
result.push({ kind: "camera", glyph: "\u{F0100}", label: "Camera", detail: PrivacyState.cameraApp || "Managed by the application", tone: "warn", stoppable: false });
|
||||
if (PrivacyState.microphoneActive)
|
||||
result.push({ kind: "microphone", glyph: "\u{F036C}", label: "Microphone", detail: PrivacyState.microphoneApp || "Managed by the application", tone: "warn", stoppable: false });
|
||||
return result;
|
||||
}
|
||||
|
||||
function elapsed(): string {
|
||||
const total = Capture.recordingSeconds;
|
||||
const seconds = String(total % 60).padStart(2, "0");
|
||||
const minutes = Math.floor(total / 60) % 60;
|
||||
const hours = Math.floor(total / 3600);
|
||||
return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (root.visible)
|
||||
openAnimation.restart();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PrivacyState
|
||||
function onAnyActiveChanged(): void {
|
||||
if (!PrivacyState.anyActive && ShellState.activityOpen)
|
||||
ShellState.close();
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandFocusGrab {
|
||||
windows: [root]
|
||||
active: root.visible
|
||||
onCleared: ShellState.close()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: surface
|
||||
implicitHeight: content.implicitHeight + Theme.popoverPadding * 2
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
radius: Theme.popoverRadius
|
||||
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.fg, 0.08)
|
||||
|
||||
PrismEdge {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 1
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
inset: parent.radius
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
id: openScale
|
||||
origin.x: surface.width
|
||||
origin.y: 0
|
||||
xScale: 1
|
||||
yScale: 1
|
||||
}
|
||||
|
||||
Column {
|
||||
id: content
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.popoverPadding
|
||||
spacing: 8
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "Privacy & activity"
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "Active access stays visible until it ends."
|
||||
color: Theme.fgDim
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Theme.alpha(Theme.fg, 0.08)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.activities
|
||||
|
||||
Rectangle {
|
||||
id: activityRow
|
||||
required property var modelData
|
||||
width: content.width
|
||||
height: 50
|
||||
radius: Theme.cardRadius
|
||||
border.width: 0
|
||||
color: Theme.alpha(Theme.fg, 0.045)
|
||||
|
||||
Text {
|
||||
id: activityIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: activityRow.modelData.glyph
|
||||
color: activityRow.modelData.tone === "danger" ? Theme.danger : Theme.warn
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 17
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: activityIcon.right
|
||||
anchors.leftMargin: 11
|
||||
anchors.right: stopButton.visible ? stopButton.left : parent.right
|
||||
anchors.rightMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: activityRow.modelData.label
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: activityRow.modelData.detail
|
||||
color: Theme.fgDim
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: stopButton
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: activityRow.modelData.stoppable
|
||||
width: 54
|
||||
height: 28
|
||||
radius: Theme.pillRadius
|
||||
border.width: 0
|
||||
color: stopMouse.containsMouse ? Theme.alpha(Theme.danger, 0.26) : Theme.alpha(Theme.danger, 0.15)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Stop"
|
||||
color: Theme.danger
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: stopMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: Capture.stopRecording()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: openAnimation
|
||||
target: openScale
|
||||
property: "yScale"
|
||||
from: 0.96
|
||||
to: 1
|
||||
duration: Theme.durNormal
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// The top bar: edge-to-edge system chrome with no surface of its own. Content
|
||||
// floats directly over the desktop, while individual controls still provide
|
||||
// hover and press feedback. The entire bar is a hit target so the top-left
|
||||
// corner remains a dependable workspace-scroll gesture.
|
||||
//
|
||||
// Instantiate one per screen:
|
||||
//
|
||||
// Variants {
|
||||
// model: Quickshell.screens
|
||||
// Bar {}
|
||||
// }
|
||||
//
|
||||
// Layout mirrors GNOME's: workspaces + vitals left, clock + weather centre,
|
||||
// media + tray + status cluster right.
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.modules.clipboard
|
||||
import qs.modules.focus
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
// Set by Variants. Left as a plain property (not `required`) so the bar can
|
||||
// also be instantiated standalone on the default screen while testing.
|
||||
property var modelData: null
|
||||
|
||||
// Raised when the status cluster is clicked; the shell wires this to the
|
||||
// quick settings panel, which this module does not own.
|
||||
signal requestQuickSettings
|
||||
|
||||
// Bound by the shell when an idle inhibitor is held, so the cluster can
|
||||
// show the caffeine glyph.
|
||||
property alias idleInhibited: statusCluster.idleInhibited
|
||||
|
||||
screen: root.modelData
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
// PanelWindow defaults to an opaque white background.
|
||||
color: "transparent"
|
||||
|
||||
implicitHeight: Theme.barHeight
|
||||
|
||||
exclusiveZone: Theme.barHeight
|
||||
|
||||
// Stable namespace for compositor inspection and shell diagnostics.
|
||||
WlrLayershell.namespace: "qs-bar"
|
||||
WlrLayershell.layer: WlrLayer.Top
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
// The full-width region is intentional: unlike a floating island, a menu
|
||||
// bar owns the screen edge. In particular, Workspaces starts at x=0 and
|
||||
// spans the full height so scrolling at the literal corner always works.
|
||||
mask: Region {
|
||||
item: barContent
|
||||
}
|
||||
|
||||
Item {
|
||||
id: barContent
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
// ── Left ────────────────────────────────────────────────────────────
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.itemSpacing
|
||||
|
||||
Workspaces {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
screen: root.screen
|
||||
}
|
||||
|
||||
VitalsWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// ── Centre ──────────────────────────────────────────────────────────
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Clock {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
WeatherWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
// ── Right ───────────────────────────────────────────────────────────
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.itemSpacing
|
||||
|
||||
CalendarIndicator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
HealthIndicator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
ActivityIndicator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
FocusIndicator {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
MediaWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
ClipboardWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Tray {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StatusCluster {
|
||||
id: statusCluster
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onRequestQuickSettings: root.requestQuickSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Month-stepping arrow in CalendarPopup's header. Round hover target, sized
|
||||
// like GNOME's calendar arrows rather than a bare glyph.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal activated
|
||||
|
||||
required property string glyph
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
radius: width / 2
|
||||
border.width: 0
|
||||
color: mouse.containsMouse ? Theme.alpha(Theme.fg, Theme.hoverAlpha) : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.durFast
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.glyph
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: Theme.fontSize
|
||||
color: Theme.fgDim
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.activated()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// A deliberately quiet appointment reminder. It only enters the bar during
|
||||
// the narrow window where an upcoming event is immediately useful.
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.widgets
|
||||
|
||||
Pill {
|
||||
id: root
|
||||
|
||||
visible: CalendarAgenda.capsuleVisible
|
||||
opacity: visible ? 1 : 0
|
||||
horizontalPadding: 8
|
||||
onActivated: ShellState.openDateMenu("agenda")
|
||||
|
||||
ToolTip.visible: root.hovered && root.visible
|
||||
ToolTip.delay: 500
|
||||
ToolTip.text: CalendarAgenda.nextEvent?.summary ?? "Upcoming event"
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
duration: 120
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "\u{F00ED}"
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 13
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: CalendarAgenda.capsuleText
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user