feat(hypr): update monitor configuration and lid behavior
This commit is contained in:
@@ -7,5 +7,7 @@ source = ~/.config/hypr/bindings/tiling.conf
|
|||||||
source = ~/.config/hypr/bindings/hyprshot.conf
|
source = ~/.config/hypr/bindings/hyprshot.conf
|
||||||
source = ~/.config/hypr/bindings/randrwall.conf
|
source = ~/.config/hypr/bindings/randrwall.conf
|
||||||
source = ~/.config/hypr/bindings/media.conf
|
source = ~/.config/hypr/bindings/media.conf
|
||||||
|
source = ~/.config/hypr/bindings/lock.conf
|
||||||
|
source = ~/.config/hypr/bindings/programs.conf
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
hypr/bindings/lock.conf
Normal file
1
hypr/bindings/lock.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bind = CTRL ALT, Q, exec, hyprlock
|
||||||
6
hypr/bindings/programs.conf
Normal file
6
hypr/bindings/programs.conf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
bind = $mainMod, RETURN, exec, $terminal
|
||||||
|
bind = $mainMod, W, killactive,
|
||||||
|
bind = $mainMod, M, exit,
|
||||||
|
bind = $mainMod, E, exec, $fileManager
|
||||||
|
bind = $mainMod, B, exec, $browser
|
||||||
|
bind = ALT, space, exec, $menu
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
|
$randrwall = "~/.config/hypr/scripts/randrwall.py"
|
||||||
$randrwall = "/home/stereov/Developer/antistereov/randrwall/randrwall.py"
|
|
||||||
|
|
||||||
bind = SUPER SHIFT, W, exec, $randrwall random
|
bind = SUPER SHIFT, W, exec, $randrwall random
|
||||||
bind = SUPER SHIFT, F, exec, $randrwall favorite
|
bind = SUPER SHIFT, F, exec, $randrwall favorite
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
$mainMod = SUPER # Sets "Windows" key as main modifier
|
$mainMod = SUPER # Sets "Windows" key as main modifier
|
||||||
|
|
||||||
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
|
# Example binds, see https://wiki.hyprland.org/Configuring/Binds/ for more
|
||||||
|
|
||||||
bind = $mainMod, RETURN, exec, $terminal
|
|
||||||
bind = $mainMod, W, killactive,
|
|
||||||
bind = $mainMod, M, exit,
|
|
||||||
bind = $mainMod, E, exec, $fileManager
|
|
||||||
bind = $mainMod, B, exec, $browser
|
|
||||||
bind = $mainMod, F, togglefloating,
|
bind = $mainMod, F, togglefloating,
|
||||||
bind = ALT, space, exec, $menu
|
|
||||||
bind = $mainMod, P, pseudo, # dwindle
|
bind = $mainMod, P, pseudo, # dwindle
|
||||||
bind = $mainMod SHIFT, J, togglesplit, # dwindle
|
bind = $mainMod SHIFT, J, togglesplit, # dwindle
|
||||||
|
|
||||||
@@ -66,7 +59,3 @@ bind = $mainMod, mouse_up, workspace, e-1
|
|||||||
bindm = $mainMod, mouse:272, movewindow
|
bindm = $mainMod, mouse:272, movewindow
|
||||||
bindm = $mainMod, mouse:273, resizewindow
|
bindm = $mainMod, mouse:273, resizewindow
|
||||||
|
|
||||||
bind = CTRL ALT, Q, exec, hyprlock
|
|
||||||
bindl = , switch:Lid Switch, exec, hyprlock
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
# See https://wiki.hyprland.org/Configuring/Monitors/
|
# See https://wiki.hyprland.org/Configuring/Monitors/
|
||||||
monitor=,preferred, auto, auto
|
monitor=,preferred, auto, auto
|
||||||
monitor=eDP-1, 2256x1504, auto, 1.175
|
monitor=eDP-1, 2256x1504, 0x0, 1.175
|
||||||
monitor=DP-10, 3440x1440, -3440x0, 1
|
monitor=DP-10, 3840x2160, -640x-1800, 1.2
|
||||||
|
|
||||||
|
bindl = , switch:on:Lid Switch, exec, ~/.config/hypr/scripts/lid.sh off
|
||||||
|
bindl = , switch:off:Lid Switch, exec, ~/.config/hypr/scripts/lid.sh on
|
||||||
|
|
||||||
|
|||||||
40
hypr/scripts/lid.sh
Executable file
40
hypr/scripts/lid.sh
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ACTION="${1:-}" # "on" oder "off"
|
||||||
|
LAPTOP="eDP-1"
|
||||||
|
LAPTOP_MODE="2256x1504"
|
||||||
|
LAPTOP_SCALE="1.175"
|
||||||
|
|
||||||
|
has_external() {
|
||||||
|
hyprctl monitors | awk '/^Monitor /{print $2}' | grep -vq "^${LAPTOP}$"
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_laptop() {
|
||||||
|
hyprctl keyword monitor "${LAPTOP}, ${LAPTOP_MODE}, 0x0, ${LAPTOP_SCALE}"
|
||||||
|
hyprctl dispatch dpms on "${LAPTOP}" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_laptop() {
|
||||||
|
hyprctl keyword monitor "${LAPTOP}, disable"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$ACTION" in
|
||||||
|
off)
|
||||||
|
if has_external; then
|
||||||
|
disable_laptop
|
||||||
|
else
|
||||||
|
disable_laptop
|
||||||
|
hyprlock
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
on)
|
||||||
|
enable_laptop
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "usage: $0 {on|off}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
309
hypr/scripts/randrwall.py
Executable file
309
hypr/scripts/randrwall.py
Executable file
@@ -0,0 +1,309 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from os import environ
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
HOME = Path.home()
|
||||||
|
|
||||||
|
BASE_DIR = HOME / "Pictures" / "Wallpapers"
|
||||||
|
FAV_DIR = BASE_DIR / "favorites"
|
||||||
|
CACHE_DIR = BASE_DIR / "cache"
|
||||||
|
CURRENT = BASE_DIR / "current"
|
||||||
|
STATE_FILE = HOME / ".cache" / "randrwall_state.json"
|
||||||
|
|
||||||
|
DEFAULT_TAGS = [
|
||||||
|
"space",
|
||||||
|
"nebula",
|
||||||
|
"cyberpunk",
|
||||||
|
"minimal",
|
||||||
|
"architecture",
|
||||||
|
"landscape",
|
||||||
|
"fantasy",
|
||||||
|
"digital art",
|
||||||
|
]
|
||||||
|
ATLEAST = "3840x2160"
|
||||||
|
RATIOS = "16x9,21x9"
|
||||||
|
SORTING = "random"
|
||||||
|
|
||||||
|
CATEGORIES = "110"
|
||||||
|
PURITY = "100"
|
||||||
|
|
||||||
|
|
||||||
|
def load_env_file(path: Path) -> None:
|
||||||
|
if not path.exists():
|
||||||
|
return
|
||||||
|
for line in path.read_text().splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if "=" not in line:
|
||||||
|
continue
|
||||||
|
k, v = line.split("=", 1)
|
||||||
|
environ.setdefault(k.strip(), v.strip())
|
||||||
|
|
||||||
|
|
||||||
|
load_env_file(Path() / ".env")
|
||||||
|
API_KEY = environ.get("WALLHAVEN_API_KEY")
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd: list[str]) -> None:
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_dirs() -> None:
|
||||||
|
FAV_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
def read_state() -> dict:
|
||||||
|
if STATE_FILE.exists():
|
||||||
|
try:
|
||||||
|
return json.loads(STATE_FILE.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def write_state(state: dict) -> None:
|
||||||
|
STATE_FILE.write_text(json.dumps(state, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def is_image(path: Path) -> bool:
|
||||||
|
return path.suffix.lower() in {".jpg", ".jpeg", ".png", ".webp"}
|
||||||
|
|
||||||
|
|
||||||
|
def swww_set(path: Path) -> None:
|
||||||
|
run(
|
||||||
|
[
|
||||||
|
"swww",
|
||||||
|
"img",
|
||||||
|
str(path),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def pick_random_from(dir_path: Path) -> Path | None:
|
||||||
|
if not dir_path.exists():
|
||||||
|
return None
|
||||||
|
|
||||||
|
imgs = [p for p in dir_path.iterdir() if p.is_file() and is_image(p)]
|
||||||
|
if not imgs:
|
||||||
|
return None
|
||||||
|
return random.choice(imgs)
|
||||||
|
|
||||||
|
|
||||||
|
UA = "randrwall/0.1 (+https://gitea.stereov.io/antistereov/randrwall)"
|
||||||
|
|
||||||
|
HEADERS = {
|
||||||
|
"User-Agent": UA,
|
||||||
|
"Referer": "https://wallhaven.cc/",
|
||||||
|
"Accept": "*/*",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def http_get_json(url: str, timeout: int = 15) -> dict:
|
||||||
|
req = urllib.request.Request(url, headers=HEADERS)
|
||||||
|
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||||
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def wallhaven_search_random(tags: list[str]) -> str:
|
||||||
|
tag = random.choice(tags)
|
||||||
|
q = quote(tag)
|
||||||
|
|
||||||
|
params = (
|
||||||
|
f"q={q}"
|
||||||
|
f"&categories={CATEGORIES}"
|
||||||
|
f"$purity={PURITY}"
|
||||||
|
f"&atleast={ATLEAST}"
|
||||||
|
f"&ratios={quote(RATIOS)}"
|
||||||
|
f"&sorting={SORTING}"
|
||||||
|
"&order=desc"
|
||||||
|
"&page=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
if API_KEY:
|
||||||
|
print("Using API key")
|
||||||
|
params += f"&apiKey={API_KEY}"
|
||||||
|
|
||||||
|
url = f"https://wallhaven.cc/api/v1/search?{params}"
|
||||||
|
|
||||||
|
data = http_get_json(url)
|
||||||
|
|
||||||
|
arr = data.get("data") or []
|
||||||
|
if not arr:
|
||||||
|
raise RuntimeError(f"Wallhaven: no results for tag='{tag}'")
|
||||||
|
|
||||||
|
item = random.choice(arr)
|
||||||
|
path = item.get("path")
|
||||||
|
if not path:
|
||||||
|
raise RuntimeError("Wallhaven: response does not contain 'path'")
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def prune_cache(max_files: int = 10) -> None:
|
||||||
|
if max_files <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
files = [p for p in CACHE_DIR.iterdir() if p.is_file() and is_image(p)]
|
||||||
|
files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
|
||||||
|
|
||||||
|
for p in files[max_files:]:
|
||||||
|
try:
|
||||||
|
p.unlink()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def download_to_cache(url: str) -> Path:
|
||||||
|
filename = url.split("/")[-1]
|
||||||
|
out = CACHE_DIR / filename
|
||||||
|
if out.exists() and out.stat().st_size > 0:
|
||||||
|
return out
|
||||||
|
|
||||||
|
tmp_path = CACHE_DIR / (filename + ".part")
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, headers=HEADERS, method="GET")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||||
|
if resp.status != 200:
|
||||||
|
raise RuntimeError(f"Download failed: HTTP {resp.status}")
|
||||||
|
with open(tmp_path, "wb") as f:
|
||||||
|
shutil.copyfileobj(resp, f)
|
||||||
|
|
||||||
|
tmp_path.replace(out)
|
||||||
|
prune_cache()
|
||||||
|
return out
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
if tmp_path.exists():
|
||||||
|
tmp_path.unlink()
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def copy_to_current(img: Path) -> None:
|
||||||
|
shutil.copy2(img, CURRENT)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_random(args: argparse.Namespace) -> None:
|
||||||
|
ensure_dirs()
|
||||||
|
state = read_state()
|
||||||
|
|
||||||
|
use_fav = random.random() < args.fav_weight
|
||||||
|
fav_pick = pick_random_from(FAV_DIR) if use_fav else None
|
||||||
|
|
||||||
|
if fav_pick is not None:
|
||||||
|
swww_set(fav_pick)
|
||||||
|
state["current"] = str(fav_pick)
|
||||||
|
state["source"] = "favorites"
|
||||||
|
write_state(state)
|
||||||
|
print(f"Set (favorites): {fav_pick}")
|
||||||
|
copy_to_current(fav_pick)
|
||||||
|
return
|
||||||
|
|
||||||
|
url = wallhaven_search_random(args.tags)
|
||||||
|
img = download_to_cache(url)
|
||||||
|
swww_set(img)
|
||||||
|
copy_to_current(img)
|
||||||
|
|
||||||
|
state["current"] = str(img)
|
||||||
|
state["source"] = "wallhaven"
|
||||||
|
state["wallhaven_url"] = url
|
||||||
|
write_state(state)
|
||||||
|
print(f"Set (download): {img}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_favorite(args: argparse.Namespace) -> None:
|
||||||
|
ensure_dirs()
|
||||||
|
state = read_state()
|
||||||
|
cur = state.get("current")
|
||||||
|
|
||||||
|
if not cur:
|
||||||
|
print(
|
||||||
|
"No current wallpaper found in state. Please set one first via 'random'",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
src = Path(cur)
|
||||||
|
if not src.exists():
|
||||||
|
print(f"Current wallpaper does not exist anymore: {src}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
dest = FAV_DIR / src.name
|
||||||
|
if dest.exists():
|
||||||
|
print(f"Already in favorites: {dest}")
|
||||||
|
return
|
||||||
|
|
||||||
|
shutil.copy2(src, dest)
|
||||||
|
print(f"Saved to favorites: {dest}")
|
||||||
|
|
||||||
|
|
||||||
|
def reload(args: argparse.Namespace) -> None:
|
||||||
|
ensure_dirs()
|
||||||
|
state = read_state()
|
||||||
|
cur = state.get("current")
|
||||||
|
|
||||||
|
if not cur:
|
||||||
|
print(
|
||||||
|
"No current wallpaper found in state. Please set one first via 'random'",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
src = Path(cur)
|
||||||
|
if not src.exists():
|
||||||
|
print(f"Current wallpaper does not exist anymore: {src}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
swww_set(cur)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Hyprland wallpaper helper (swww + wallhaven + favorites"
|
||||||
|
)
|
||||||
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
p_random = sub.add_parser(
|
||||||
|
"random", help="Set random wallpaper (favorites or download)"
|
||||||
|
)
|
||||||
|
p_random.add_argument(
|
||||||
|
"--fav-weight",
|
||||||
|
type=float,
|
||||||
|
default=0.3,
|
||||||
|
help="Probability to pick from favorites (0..1). Default: 0.3",
|
||||||
|
)
|
||||||
|
p_random.add_argument(
|
||||||
|
"--tags",
|
||||||
|
nargs="+",
|
||||||
|
default=DEFAULT_TAGS,
|
||||||
|
help=f"Wall haven tags list. Default: {DEFAULT_TAGS}",
|
||||||
|
)
|
||||||
|
p_random.set_defaults(func=cmd_random)
|
||||||
|
|
||||||
|
p_fav = sub.add_parser("favorite", help="Save current wallpaper to favorites")
|
||||||
|
p_fav.set_defaults(func=cmd_favorite)
|
||||||
|
|
||||||
|
p_fav = sub.add_parser("reload", help="Reload current wallpaper")
|
||||||
|
p_fav.set_defaults(func=reload)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
args.func(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user