Initial commit: Omeron modular Hyprland setup framework
- Modular installer with gum-based TUI - Fresh-install detection with auto GPU driver selection - Preflight module for system detection (Intel/AMD/NVIDIA) - Core modules: packages, dotfiles, services, SDDM - Optional software installer (Obsidian, Neovim, VS Code, etc.) - Homelab config module with dynamic AGS integration - Two complete themes: Forest Neon and Rose Night - 19 Hyprland control scripts + 4 AGS widgets - Idempotent dotfile deployment with automatic backup - YAML-based configuration, extensible module system - Full logging to ~/.local/share/omeron/
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# Omeron specific
|
||||||
|
*.before-theme-switcher
|
||||||
219
README.md
Normal file
219
README.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
# ᎾᎷᎬᏒᎾᏁ
|
||||||
|
|
||||||
|
**Modular System Setup Framework** für Arch/Hyprland
|
||||||
|
|
||||||
|
Omeron ist ein modulares, interaktives Setup-Framework, das Dotfiles-Konsolidierung,
|
||||||
|
Paketverwaltung und Systemkonfiguration in einem sauberen, wartbaren Projekt vereint.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Fresh-Install Detection** — Erkennt automatisch ein neues System und installiert Hyprland, GPU-Treiber + alle Dependencies
|
||||||
|
- **GPU-Erkennung** — Intel/AMD/NVIDIA via PCI Vendor-ID, installiert passende Treiber
|
||||||
|
- **Modularer Installer** — Jede Komponente ist ein eigenständiges Modul
|
||||||
|
- **Interaktive TUI** — Basierend auf `gum`, mit fallback auf einfaches CLI
|
||||||
|
- **Idempotent** — Beliebig oft ausführbar, Backup vor jeder Änderung
|
||||||
|
- **Dotfiles-Management** — Automatische Sicherung und Deployment
|
||||||
|
- **Homelab-Integration** — Unraid-Server-Konfiguration für das AGS Control Center
|
||||||
|
- **Theme-Engine** — Zwei vollständige Themes (Forest Neon, Rose Night)
|
||||||
|
- **Optionale Software** — Interaktive Auswahl via TUI
|
||||||
|
- **Logging** — Alle Schritte werden protokolliert
|
||||||
|
- **Erweiterbar** — Einfaches Plugin-System für eigene Module
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projektstruktur
|
||||||
|
|
||||||
|
```
|
||||||
|
Omeron/
|
||||||
|
├── install.sh # Main Installer (Einstiegspunkt)
|
||||||
|
├── README.md
|
||||||
|
├── config/
|
||||||
|
│ ├── omeron.yaml # Installer-Konfiguration
|
||||||
|
│ └── homelab.yaml # Homelab Standard-Konfiguration
|
||||||
|
├── lib/
|
||||||
|
│ ├── log.sh # Logging-Framework
|
||||||
|
│ ├── tui.sh # TUI-Wrapper (gum/basic)
|
||||||
|
│ ├── config.sh # YAML/Shell-Konfiguration
|
||||||
|
│ ├── utils.sh # Utility-Funktionen
|
||||||
|
│ └── modules.sh # Modul-Manager
|
||||||
|
├── modules/
|
||||||
|
│ ├── core/ # Core-Module (obligatorisch)
|
||||||
|
│ │ ├── preflight.sh # System-Detection (GPU, Fresh-Install, AUR-Helper)
|
||||||
|
│ │ ├── packages.sh # System-Pakete inkl. GPU-Treiber installieren
|
||||||
|
│ │ ├── dotfiles.sh # Dotfiles deployen
|
||||||
|
│ │ ├── services.sh # Systemd-Services aktivieren
|
||||||
|
│ │ └── sddm.sh # SDDM-Theme installieren
|
||||||
|
│ ├── optional/
|
||||||
|
│ │ ├── install.sh # Optionale Software-Auswahl
|
||||||
|
│ │ └── packages/ # Einzelpaket-Installer
|
||||||
|
│ ├── homelab/
|
||||||
|
│ │ └── setup.sh # Homelab-Konfiguration
|
||||||
|
│ └── post/
|
||||||
|
│ └── apply-theme.sh # Theme nach Installation anwenden
|
||||||
|
├── dotfiles/ # Konsolidierte Dotfiles
|
||||||
|
│ ├── hypr/ # Hyprland + AGS Widgets
|
||||||
|
│ ├── waybar/ # Waybar Status Bar
|
||||||
|
│ ├── wofi/ # Application Launcher
|
||||||
|
│ ├── swaync/ # Notification Center
|
||||||
|
│ ├── kitty/ # Terminal
|
||||||
|
│ ├── gtk-3.0/ # GTK3-Theme
|
||||||
|
│ ├── gtk-4.0/ # GTK4-Theme
|
||||||
|
│ ├── qt5ct/ # Qt5-Theme
|
||||||
|
│ ├── qt6ct/ # Qt6-Theme
|
||||||
|
│ ├── starship.toml # Shell Prompt
|
||||||
|
│ └── wallpapers/ # Mitgelieferte Wallpaper
|
||||||
|
└── templates/
|
||||||
|
└── homelab/ # Config-Templates
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Voraussetzungen
|
||||||
|
|
||||||
|
- **Arch Linux / CachyOS** (oder andere Arch-Derivate)
|
||||||
|
- `bash` >= 4.0
|
||||||
|
- `gum` (optional, aber empfohlen) — `sudo pacman -S gum`
|
||||||
|
- `git`
|
||||||
|
|
||||||
|
### Schnellstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <your-repo-url>/Omeron.git
|
||||||
|
cd Omeron
|
||||||
|
./install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Optionen
|
||||||
|
|
||||||
|
### Autodetect (Fresh Install)
|
||||||
|
|
||||||
|
Wird `./install.sh` auf einem frischen Arch-System ohne Hyprland ausgeführt, erkennt der Installer dies automatisch und installiert:
|
||||||
|
|
||||||
|
- **Hyprland** + alle Komponenten (Hyprlock, Hyprpaper, Hypridle)
|
||||||
|
- **GPU-Treiber** (Intel/AMD/NVIDIA automatisch erkannt)
|
||||||
|
- **Audio** (PipeWire + WirePlumber)
|
||||||
|
- **Netzwerk** (NetworkManager + Bluetooth)
|
||||||
|
- **Fonts & Themes** (Nerd Fonts, Papirus Icons)
|
||||||
|
- **Dotfiles** inkl. Theme und Services
|
||||||
|
|
||||||
|
Ohne `--fresh` und ohne `--modules` startet der interaktive Modus mit Step-by-Step Auswahl.
|
||||||
|
|
||||||
|
### Optionen
|
||||||
|
|
||||||
|
| Flag | Beschreibung |
|
||||||
|
|------|-------------|
|
||||||
|
| `--fresh` | Full System Setup (Hyprland + GPU + alle Dependencies) |
|
||||||
|
| `--modules m1,m2` | Nur bestimmte Module ausführen |
|
||||||
|
| `--skip m1,m2` | Bestimmte Module überspringen |
|
||||||
|
| `--skip-packages` | Paketinstallation überspringen |
|
||||||
|
| `--with-sddm` | SDDM-Theme inkludieren |
|
||||||
|
| `--list-modules` | Verfügbare Module anzeigen |
|
||||||
|
| `--help` | Hilfe anzeigen |
|
||||||
|
|
||||||
|
### Beispiele
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Automatisch: erkennt Fresh-Install oder interaktiv
|
||||||
|
./install.sh
|
||||||
|
|
||||||
|
# Fresh Install auf neuem System (oder forcieren)
|
||||||
|
./install.sh --fresh
|
||||||
|
|
||||||
|
# Nur Dotfiles und Theme deployen
|
||||||
|
./install.sh --modules core/dotfiles,post/apply-theme
|
||||||
|
|
||||||
|
# Komplette Installation ohne Pakete
|
||||||
|
./install.sh --skip-packages
|
||||||
|
|
||||||
|
# Mit SDDM-Theme
|
||||||
|
./install.sh --with-sddm
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module Erstellen
|
||||||
|
|
||||||
|
Jedes Modul ist eine Bash-Datei in `modules/` mit folgenden Funktionen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "My Module - does something\n"; }
|
||||||
|
module_required() { return 1; } # 0 = immer ausführen
|
||||||
|
module_should_skip() { return 1; } # 0 = überspringen
|
||||||
|
module_prereqs() { require mycmd mypackage; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "My Module"
|
||||||
|
# Your code here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Homelab-Konfiguration
|
||||||
|
|
||||||
|
Der Installer fragt beim Homelab-Modul ab:
|
||||||
|
|
||||||
|
- **Server-Adresse** (IP oder Domain)
|
||||||
|
- **SSH-Benutzername**
|
||||||
|
|
||||||
|
Gespeichert in `~/.config/homelab/config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
address: "192.168.1.100"
|
||||||
|
username: "root"
|
||||||
|
port: 22
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Themes
|
||||||
|
|
||||||
|
| Theme | Akzent | Hintergrund | Wallpaper |
|
||||||
|
|-------|--------|-------------|-----------|
|
||||||
|
| **Forest Neon** | `#00ff9c` (grün) | `#14141e` | `forest.jpg` |
|
||||||
|
| **Rose Night** | `#f38ba8` (pink) | `#18141f` | `rose-pink.jpg` |
|
||||||
|
|
||||||
|
Theme-Wechsel via:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
~/.config/hypr/Scripts/theme-menu.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Verzeichnis-Layout
|
||||||
|
|
||||||
|
| Pfad | Zweck |
|
||||||
|
|------|-------|
|
||||||
|
| `Omeron/.gitignore` | Git-Ignore-Regeln |
|
||||||
|
| `Omeron/install.sh` | Main Installer (Einstiegspunkt) |
|
||||||
|
| `Omeron/config/omeron.yaml` | Installer-Konfiguration |
|
||||||
|
| `Omeron/lib/` | Framework-Bibliotheken (Log, TUI, Config, Utils, Module) |
|
||||||
|
| `Omeron/modules/core/` | Core-Module (Preflight, Packages, Dotfiles, Services, SDDM) |
|
||||||
|
| `Omeron/modules/optional/` | Optionale Software-Auswahl |
|
||||||
|
| `Omeron/modules/homelab/` | Homelab-Konfiguration |
|
||||||
|
| `Omeron/dotfiles/` | Alle Konfigurationsdateien |
|
||||||
|
| `~/.config/hypr/` | Hyprland-Konfiguration + Scripts |
|
||||||
|
| `~/.config/hypr/Scripts/` | Alle Steuerungsskripte (19 Stück) |
|
||||||
|
| `~/.config/hypr/ags/` | AGS Widgets (Panel, Switcher, Package Manager, Homelab) |
|
||||||
|
| `~/.config/hypr/Themes/` | Theme-Definitionen |
|
||||||
|
| `~/.config/waybar/` | Status Bar |
|
||||||
|
| `~/.config/wofi/` | Application Launcher |
|
||||||
|
| `~/.config/swaync/` | Notification Center |
|
||||||
|
| `~/.config/starship.toml` | Shell Prompt |
|
||||||
|
| `~/.dotfiles-backup/` | Automatische Backups |
|
||||||
|
| `~/.config/homelab/config.yaml` | Homelab-Server-Konfiguration |
|
||||||
|
| `~/.local/share/omeron/` | Installer-Logs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lizenz
|
||||||
|
|
||||||
|
MIT
|
||||||
18
config/homelab.yaml
Normal file
18
config/homelab.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Homelab Configuration
|
||||||
|
# Server connection details for the Homelab Control Center
|
||||||
|
|
||||||
|
server:
|
||||||
|
address: ""
|
||||||
|
username: "root"
|
||||||
|
port: 22
|
||||||
|
|
||||||
|
control_center:
|
||||||
|
refresh_interval: 5
|
||||||
|
theme: "dark"
|
||||||
|
|
||||||
|
features:
|
||||||
|
docker: true
|
||||||
|
services: true
|
||||||
|
storage: true
|
||||||
|
network: true
|
||||||
|
monitoring: true
|
||||||
130
config/omeron.yaml
Normal file
130
config/omeron.yaml
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Omeron System Setup Framework
|
||||||
|
# Main Configuration
|
||||||
|
|
||||||
|
installer:
|
||||||
|
title: "Omeron System Setup"
|
||||||
|
style: "gum"
|
||||||
|
log_level: "INFO"
|
||||||
|
backup_dir: "${HOME}/.dotfiles-backup"
|
||||||
|
|
||||||
|
fresh_install_defaults: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- id: "packages"
|
||||||
|
name: "System Packages"
|
||||||
|
description: "Install core system packages"
|
||||||
|
module: "core/packages"
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
|
||||||
|
- id: "dotfiles"
|
||||||
|
name: "Dotfiles"
|
||||||
|
description: "Deploy configuration files"
|
||||||
|
module: "core/dotfiles"
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
|
||||||
|
- id: "services"
|
||||||
|
name: "System Services"
|
||||||
|
description: "Enable and start system services"
|
||||||
|
module: "core/services"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
|
||||||
|
- id: "sddm"
|
||||||
|
name: "SDDM Theme"
|
||||||
|
description: "Install SDDM login theme"
|
||||||
|
module: "core/sddm"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
|
||||||
|
- id: "homelab"
|
||||||
|
name: "Homelab Configuration"
|
||||||
|
description: "Configure Homelab server access"
|
||||||
|
module: "homelab/setup"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
|
||||||
|
- id: "optional"
|
||||||
|
name: "Optional Software"
|
||||||
|
description: "Select and install optional packages"
|
||||||
|
module: "optional/install"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
|
||||||
|
- id: "theme"
|
||||||
|
name: "Apply Theme"
|
||||||
|
description: "Apply default theme"
|
||||||
|
module: "post/apply-theme"
|
||||||
|
required: false
|
||||||
|
default: true
|
||||||
|
|
||||||
|
detection:
|
||||||
|
gpu_auto: true
|
||||||
|
fresh_install_auto: true
|
||||||
|
|
||||||
|
packages:
|
||||||
|
core:
|
||||||
|
- hyprland
|
||||||
|
- hyprpaper
|
||||||
|
- hyprlock
|
||||||
|
- waybar
|
||||||
|
- wofi
|
||||||
|
- swaync
|
||||||
|
- kitty
|
||||||
|
- nautilus
|
||||||
|
- brightnessctl
|
||||||
|
- playerctl
|
||||||
|
- wireplumber
|
||||||
|
- pipewire
|
||||||
|
- pipewire-pulse
|
||||||
|
- networkmanager
|
||||||
|
- bluez
|
||||||
|
- bluez-utils
|
||||||
|
- hyprshot
|
||||||
|
- grim
|
||||||
|
- slurp
|
||||||
|
- swappy
|
||||||
|
- wl-clipboard
|
||||||
|
- libnotify
|
||||||
|
- sshpass
|
||||||
|
- papirus-icon-theme
|
||||||
|
- qt5ct
|
||||||
|
- qt6ct
|
||||||
|
- starship
|
||||||
|
- python-gobject
|
||||||
|
- gtk3
|
||||||
|
- gtk4
|
||||||
|
- noto-fonts
|
||||||
|
- noto-fonts-emoji
|
||||||
|
- ttf-jetbrains-mono-nerd
|
||||||
|
|
||||||
|
aur: []
|
||||||
|
|
||||||
|
services:
|
||||||
|
- networkmanager
|
||||||
|
- bluetooth
|
||||||
|
|
||||||
|
theme:
|
||||||
|
default: "forest-neon"
|
||||||
|
themes_dir: "${HOME}/.config/hypr/Themes"
|
||||||
|
|
||||||
|
dotfiles:
|
||||||
|
items:
|
||||||
|
- hypr
|
||||||
|
- waybar
|
||||||
|
- wofi
|
||||||
|
- swaync
|
||||||
|
- kitty
|
||||||
|
- gtk-3.0
|
||||||
|
- gtk-4.0
|
||||||
|
- qt5ct
|
||||||
|
- qt6ct
|
||||||
|
|
||||||
|
extra:
|
||||||
|
- source: "starship.toml"
|
||||||
|
target: "${HOME}/.config/starship.toml"
|
||||||
|
|
||||||
|
wallpapers:
|
||||||
|
source: "wallpapers"
|
||||||
|
target: "${HOME}/Bilder/Wallpaper"
|
||||||
7
dotfiles/gtk-3.0/bookmarks
Normal file
7
dotfiles/gtk-3.0/bookmarks
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
file:///home/pascal/Schreibtisch Schreibtisch
|
||||||
|
file:///home/pascal/Dokumente Dokumente
|
||||||
|
file:///home/pascal/Downloads Downloads
|
||||||
|
file:///home/pascal/Videos Videos
|
||||||
|
file:///home/pascal/Musik Musik
|
||||||
|
file:///home/pascal/Bilder Bilder
|
||||||
|
file:///home/pascal/Projekte Projekte
|
||||||
18
dotfiles/gtk-3.0/gtk.css
Normal file
18
dotfiles/gtk-3.0/gtk.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@define-color accent_color #00ff9c;
|
||||||
|
@define-color accent_bg_color #00ff9c;
|
||||||
|
@define-color accent_fg_color #000000;
|
||||||
|
@define-color window_bg_color #14141e;
|
||||||
|
@define-color window_fg_color #cdd6f4;
|
||||||
|
@define-color view_bg_color #14141e;
|
||||||
|
@define-color view_fg_color #cdd6f4;
|
||||||
|
@define-color card_bg_color #282837;
|
||||||
|
@define-color card_fg_color #cdd6f4;
|
||||||
|
@define-color popover_bg_color #282837;
|
||||||
|
@define-color popover_fg_color #cdd6f4;
|
||||||
|
@define-color headerbar_bg_color #282837;
|
||||||
|
@define-color headerbar_fg_color #cdd6f4;
|
||||||
|
@define-color sidebar_bg_color #14141e;
|
||||||
|
@define-color sidebar_fg_color #cdd6f4;
|
||||||
|
@define-color destructive_color #f38ba8;
|
||||||
|
@define-color warning_color #f9e2af;
|
||||||
|
@define-color success_color #a6e3a1;
|
||||||
4
dotfiles/gtk-3.0/settings.ini
Normal file
4
dotfiles/gtk-3.0/settings.ini
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[Settings]
|
||||||
|
gtk-theme-name=Adwaita
|
||||||
|
gtk-icon-theme-name=Papirus-ForestNeon
|
||||||
|
gtk-application-prefer-dark-theme=1
|
||||||
18
dotfiles/gtk-4.0/gtk.css
Normal file
18
dotfiles/gtk-4.0/gtk.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@define-color accent_color #00ff9c;
|
||||||
|
@define-color accent_bg_color #00ff9c;
|
||||||
|
@define-color accent_fg_color #000000;
|
||||||
|
@define-color window_bg_color #14141e;
|
||||||
|
@define-color window_fg_color #cdd6f4;
|
||||||
|
@define-color view_bg_color #14141e;
|
||||||
|
@define-color view_fg_color #cdd6f4;
|
||||||
|
@define-color card_bg_color #282837;
|
||||||
|
@define-color card_fg_color #cdd6f4;
|
||||||
|
@define-color popover_bg_color #282837;
|
||||||
|
@define-color popover_fg_color #cdd6f4;
|
||||||
|
@define-color headerbar_bg_color #282837;
|
||||||
|
@define-color headerbar_fg_color #cdd6f4;
|
||||||
|
@define-color sidebar_bg_color #14141e;
|
||||||
|
@define-color sidebar_fg_color #cdd6f4;
|
||||||
|
@define-color destructive_color #f38ba8;
|
||||||
|
@define-color warning_color #f9e2af;
|
||||||
|
@define-color success_color #a6e3a1;
|
||||||
4
dotfiles/gtk-4.0/settings.ini
Normal file
4
dotfiles/gtk-4.0/settings.ini
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[Settings]
|
||||||
|
gtk-theme-name=Adwaita
|
||||||
|
gtk-icon-theme-name=Papirus-ForestNeon
|
||||||
|
gtk-application-prefer-dark-theme=1
|
||||||
91
dotfiles/hypr/Scripts/ags-package-runner.py
Executable file
91
dotfiles/hypr/Scripts/ags-package-runner.py
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import pty
|
||||||
|
import select
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def emit(event_type, **payload):
|
||||||
|
print(json.dumps({"type": event_type, **payload}), flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_message(pid, fd, raw_line):
|
||||||
|
try:
|
||||||
|
message = json.loads(raw_line.decode("utf-8", "replace"))
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return
|
||||||
|
|
||||||
|
if message.get("type") == "input":
|
||||||
|
os.write(fd, str(message.get("data", "")).encode("utf-8", "replace"))
|
||||||
|
elif message.get("type") == "signal":
|
||||||
|
os.kill(pid, int(message.get("signal", 15)))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if "--" not in sys.argv:
|
||||||
|
emit("exit", code=2, signaled=False)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
command = sys.argv[sys.argv.index("--") + 1 :]
|
||||||
|
if not command:
|
||||||
|
emit("exit", code=2, signaled=False)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
pid, fd = pty.fork()
|
||||||
|
if pid == 0:
|
||||||
|
os.execvp(command[0], command)
|
||||||
|
|
||||||
|
emit("start", pid=pid)
|
||||||
|
stdin_fd = sys.stdin.fileno()
|
||||||
|
open_fds = [fd, stdin_fd]
|
||||||
|
stdin_buffer = b""
|
||||||
|
|
||||||
|
while open_fds:
|
||||||
|
readable, _, _ = select.select(open_fds, [], [], 0.2)
|
||||||
|
|
||||||
|
if fd in readable:
|
||||||
|
try:
|
||||||
|
data = os.read(fd, 4096)
|
||||||
|
except OSError:
|
||||||
|
data = b""
|
||||||
|
|
||||||
|
if data:
|
||||||
|
emit("out", data=data.decode("utf-8", "replace"))
|
||||||
|
else:
|
||||||
|
open_fds.remove(fd)
|
||||||
|
|
||||||
|
if stdin_fd in readable:
|
||||||
|
try:
|
||||||
|
chunk = os.read(stdin_fd, 4096)
|
||||||
|
except OSError:
|
||||||
|
chunk = b""
|
||||||
|
|
||||||
|
if not chunk:
|
||||||
|
open_fds.remove(stdin_fd)
|
||||||
|
continue
|
||||||
|
|
||||||
|
stdin_buffer += chunk
|
||||||
|
while b"\n" in stdin_buffer:
|
||||||
|
line, stdin_buffer = stdin_buffer.split(b"\n", 1)
|
||||||
|
handle_message(pid, fd, line)
|
||||||
|
|
||||||
|
try:
|
||||||
|
finished_pid, status = os.waitpid(pid, os.WNOHANG)
|
||||||
|
except ChildProcessError:
|
||||||
|
break
|
||||||
|
|
||||||
|
if finished_pid == pid:
|
||||||
|
if os.WIFSIGNALED(status):
|
||||||
|
emit("exit", code=os.WTERMSIG(status), signaled=True)
|
||||||
|
return 128 + os.WTERMSIG(status)
|
||||||
|
|
||||||
|
code = os.WEXITSTATUS(status)
|
||||||
|
emit("exit", code=code, signaled=False)
|
||||||
|
return code
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
22
dotfiles/hypr/Scripts/ags-switcher.sh
Executable file
22
dotfiles/hypr/Scripts/ags-switcher.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
MODE="${1:-wallpaper}"
|
||||||
|
export HYPR_DIR
|
||||||
|
export HYPR_SWITCHER_THEME_DIR="$HYPR_DIR/Themes"
|
||||||
|
export HYPR_SWITCHER_WALLPAPER_DIR="${WALLPAPER_DIR:-$HOME/Bilder/Wallpaper}"
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send "AGS Switcher" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v ags >/dev/null 2>&1; then
|
||||||
|
notify "ags ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$HYPR_DIR"
|
||||||
|
ags quit --instance hypr-switcher >/dev/null 2>&1 || true
|
||||||
|
exec ags run "$HYPR_DIR/ags/switcher.tsx" "$MODE"
|
||||||
20
dotfiles/hypr/Scripts/appearance-menu.sh
Executable file
20
dotfiles/hypr/Scripts/appearance-menu.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Theme wechseln" \
|
||||||
|
" Wallpaper wechseln" |
|
||||||
|
wofi --dmenu --prompt " Aussehen" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Theme wechseln"*)
|
||||||
|
"$SCRIPT_DIR/ags-switcher.sh" theme
|
||||||
|
;;
|
||||||
|
*"Wallpaper wechseln"*)
|
||||||
|
"$SCRIPT_DIR/ags-switcher.sh" wallpaper
|
||||||
|
;;
|
||||||
|
esac
|
||||||
74
dotfiles/hypr/Scripts/audio-menu.sh
Executable file
74
dotfiles/hypr/Scripts/audio-menu.sh
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Audio" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_wpctl() {
|
||||||
|
if ! command -v wpctl >/dev/null 2>&1; then
|
||||||
|
notify "wpctl ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_sink() {
|
||||||
|
wpctl status |
|
||||||
|
awk '
|
||||||
|
/Sinks:/ {in_sinks=1; next}
|
||||||
|
/Sources:/ {in_sinks=0}
|
||||||
|
in_sinks && /\*/ {gsub(/^[[:space:]]*[│├└─* ]*/, ""); print " " $0}
|
||||||
|
in_sinks && /^[[:space:]]*[│├└─ ]*[0-9]+\./ {gsub(/^[[:space:]]*[│├└─ ]*/, ""); print " " $0}
|
||||||
|
' |
|
||||||
|
wofi --dmenu --prompt " Audioausgabe" --insensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
require_wpctl
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Lauter" \
|
||||||
|
" Leiser" \
|
||||||
|
" Stumm schalten" \
|
||||||
|
" Ausgabe wechseln" \
|
||||||
|
" Mikrofon stumm" \
|
||||||
|
" Play/Pause" \
|
||||||
|
" Naechster Titel" \
|
||||||
|
" Vorheriger Titel" \
|
||||||
|
" Status" |
|
||||||
|
wofi --dmenu --prompt " Audio" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Lauter"*)
|
||||||
|
wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
|
||||||
|
;;
|
||||||
|
*"Leiser"*)
|
||||||
|
wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||||
|
;;
|
||||||
|
*"Stumm schalten"*)
|
||||||
|
wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||||
|
;;
|
||||||
|
*"Ausgabe wechseln"*)
|
||||||
|
selection="$(choose_sink)"
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
sink_id="$(printf '%s\n' "$selection" | sed -n 's/.* \([0-9][0-9]*\)\..*/\1/p')"
|
||||||
|
[ -n "$sink_id" ] || exit 0
|
||||||
|
wpctl set-default "$sink_id" && notify "Audioausgabe gewechselt."
|
||||||
|
;;
|
||||||
|
*"Mikrofon stumm"*)
|
||||||
|
wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
|
||||||
|
;;
|
||||||
|
*"Play/Pause"*)
|
||||||
|
playerctl play-pause
|
||||||
|
;;
|
||||||
|
*"Naechster Titel"*)
|
||||||
|
playerctl next
|
||||||
|
;;
|
||||||
|
*"Vorheriger Titel"*)
|
||||||
|
playerctl previous
|
||||||
|
;;
|
||||||
|
*"Status"*)
|
||||||
|
wpctl status | wofi --dmenu --prompt " Audiostatus"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
133
dotfiles/hypr/Scripts/bluetooth-menu.sh
Executable file
133
dotfiles/hypr/Scripts/bluetooth-menu.sh
Executable file
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Bluetooth" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_bluetoothctl() {
|
||||||
|
if ! command -v bluetoothctl >/dev/null 2>&1; then
|
||||||
|
notify "bluetoothctl ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wofi_pick() {
|
||||||
|
wofi --dmenu --prompt "$1" --insensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter_powered() {
|
||||||
|
{ bluetoothctl show 2>/dev/null || true; } | awk -F': ' '/Powered/ {print $2; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
device_name() {
|
||||||
|
local mac="$1"
|
||||||
|
bluetoothctl info "$mac" 2>/dev/null | awk -F': ' '/Name/ {print $2; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_device() {
|
||||||
|
local prompt="$1"
|
||||||
|
bluetoothctl devices |
|
||||||
|
awk '{mac=$2; $1=""; $2=""; sub(/^ */, ""); printf " %s [%s]\n", $0, mac}' |
|
||||||
|
wofi_pick "$prompt"
|
||||||
|
}
|
||||||
|
|
||||||
|
selected_mac() {
|
||||||
|
sed -n 's/.*\[\([0-9A-Fa-f:]\{17\}\)\].*/\1/p'
|
||||||
|
}
|
||||||
|
|
||||||
|
scan_devices() {
|
||||||
|
notify "Scan laeuft fuer 8 Sekunden."
|
||||||
|
timeout 8 bluetoothctl scan on >/dev/null 2>&1 || true
|
||||||
|
bluetoothctl scan off >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
local selection mac name
|
||||||
|
selection="$(pick_device " Geraet verbinden")"
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
mac="$(printf '%s\n' "$selection" | selected_mac)"
|
||||||
|
name="$(device_name "$mac")"
|
||||||
|
[ -n "$name" ] || name="$mac"
|
||||||
|
|
||||||
|
bluetoothctl pair "$mac" >/dev/null 2>&1 || true
|
||||||
|
bluetoothctl trust "$mac" >/dev/null 2>&1 || true
|
||||||
|
bluetoothctl connect "$mac" && notify "Verbunden mit $name."
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_saved_device() {
|
||||||
|
local selection mac name
|
||||||
|
selection="$(pick_device " Gekoppeltes Geraet verbinden")"
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
mac="$(printf '%s\n' "$selection" | selected_mac)"
|
||||||
|
name="$(device_name "$mac")"
|
||||||
|
[ -n "$name" ] || name="$mac"
|
||||||
|
bluetoothctl connect "$mac" && notify "Verbunden mit $name."
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect_device() {
|
||||||
|
local selection mac name
|
||||||
|
selection="$(
|
||||||
|
bluetoothctl devices Connected |
|
||||||
|
awk '{mac=$2; $1=""; $2=""; sub(/^ */, ""); printf " %s [%s]\n", $0, mac}' |
|
||||||
|
wofi_pick " Bluetooth trennen"
|
||||||
|
)"
|
||||||
|
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
mac="$(printf '%s\n' "$selection" | selected_mac)"
|
||||||
|
name="$(device_name "$mac")"
|
||||||
|
[ -n "$name" ] || name="$mac"
|
||||||
|
bluetoothctl disconnect "$mac" && notify "$name getrennt."
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_device() {
|
||||||
|
local selection mac name
|
||||||
|
selection="$(pick_device " Geraet entfernen")"
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
mac="$(printf '%s\n' "$selection" | selected_mac)"
|
||||||
|
name="$(device_name "$mac")"
|
||||||
|
[ -n "$name" ] || name="$mac"
|
||||||
|
bluetoothctl remove "$mac" && notify "$name entfernt."
|
||||||
|
}
|
||||||
|
|
||||||
|
require_bluetoothctl
|
||||||
|
|
||||||
|
power="$(adapter_powered)"
|
||||||
|
[ -n "$power" ] || power="unknown"
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Geraet suchen und verbinden" \
|
||||||
|
" Bluetooth ein/aus" \
|
||||||
|
" Gekoppelte Geraete" \
|
||||||
|
" Verbundenes Geraet trennen" \
|
||||||
|
" Geraet entfernen" \
|
||||||
|
" Status: $power" |
|
||||||
|
wofi_pick " Bluetooth"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Geraet suchen"*)
|
||||||
|
bluetoothctl power on >/dev/null
|
||||||
|
bluetoothctl agent on >/dev/null 2>&1 || true
|
||||||
|
bluetoothctl default-agent >/dev/null 2>&1 || true
|
||||||
|
scan_devices
|
||||||
|
;;
|
||||||
|
*"Bluetooth ein/aus"*)
|
||||||
|
if [ "$power" = "yes" ]; then
|
||||||
|
bluetoothctl power off && notify "Bluetooth ausgeschaltet."
|
||||||
|
else
|
||||||
|
bluetoothctl power on && notify "Bluetooth eingeschaltet."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Gekoppelte Geraete"*)
|
||||||
|
connect_saved_device
|
||||||
|
;;
|
||||||
|
*"Verbundenes Geraet trennen"*)
|
||||||
|
disconnect_device
|
||||||
|
;;
|
||||||
|
*"Geraet entfernen"*)
|
||||||
|
remove_device
|
||||||
|
;;
|
||||||
|
*"Status:"*)
|
||||||
|
bluetoothctl show | wofi --dmenu --prompt " Bluetoothstatus"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
405
dotfiles/hypr/Scripts/dev-menu.sh
Executable file
405
dotfiles/hypr/Scripts/dev-menu.sh
Executable file
@@ -0,0 +1,405 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
PROJECT_ROOTS=(
|
||||||
|
"$HOME/Projekte"
|
||||||
|
"$HOME/Projects"
|
||||||
|
"$HOME/Code"
|
||||||
|
"$HOME/Developer"
|
||||||
|
"$HOME/dev"
|
||||||
|
"$HOME/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Dev Menue" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
pick() {
|
||||||
|
wofi --dmenu --prompt "$1" --insensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal_hold() {
|
||||||
|
local title="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if command -v kitty >/dev/null 2>&1; then
|
||||||
|
kitty --title "$title" sh -lc "$*; printf '\n'; read -r -p 'Enter zum Schliessen... ' _"
|
||||||
|
else
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
shell_quote() {
|
||||||
|
printf '%q' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
code_cmd() {
|
||||||
|
if command -v code >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' code
|
||||||
|
elif command -v codium >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' codium
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_available() {
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
notify "Docker ist nicht installiert."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
notify "Docker laeuft nicht oder ist nicht erreichbar."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
has_compose() {
|
||||||
|
local project="$1"
|
||||||
|
|
||||||
|
[[ -f "$project/compose.yml" ||
|
||||||
|
-f "$project/compose.yaml" ||
|
||||||
|
-f "$project/docker-compose.yml" ||
|
||||||
|
-f "$project/docker-compose.yaml" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
project_label() {
|
||||||
|
local project="$1"
|
||||||
|
local marker=""
|
||||||
|
|
||||||
|
if [[ -d "$project/.git" ]]; then
|
||||||
|
marker+=" "
|
||||||
|
fi
|
||||||
|
|
||||||
|
if has_compose "$project"; then
|
||||||
|
marker+=" "
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s%s' "$marker" "${project#$HOME/}"
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_project() {
|
||||||
|
local roots=()
|
||||||
|
local projects=()
|
||||||
|
local labels=()
|
||||||
|
local root project choice
|
||||||
|
|
||||||
|
for root in "${PROJECT_ROOTS[@]}"; do
|
||||||
|
[[ -d "$root" ]] && roots+=("$root")
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((${#roots[@]} == 0)); then
|
||||||
|
notify "Keine Projektordner gefunden."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t projects < <(
|
||||||
|
{
|
||||||
|
find "${roots[@]}" -maxdepth 4 -type d -name .git -printf '%h\n' 2>/dev/null
|
||||||
|
find "${roots[@]}" -maxdepth 3 -type d \( -name node_modules -o -name vendor \) -prune -o \
|
||||||
|
-type f \( -name compose.yml -o -name compose.yaml -o -name docker-compose.yml -o -name docker-compose.yaml -o -name package.json -o -name Cargo.toml -o -name pyproject.toml -o -name go.mod \) \
|
||||||
|
-printf '%h\n' 2>/dev/null
|
||||||
|
} |
|
||||||
|
sort -u
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((${#projects[@]} == 0)); then
|
||||||
|
notify "Keine Projekte gefunden."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for project in "${projects[@]}"; do
|
||||||
|
labels+=("$(project_label "$project")")
|
||||||
|
done
|
||||||
|
|
||||||
|
choice="$(printf '%s\n' "${labels[@]}" | pick "📁 Projekt")"
|
||||||
|
[[ -z "${choice:-}" ]] && return 1
|
||||||
|
|
||||||
|
for i in "${!labels[@]}"; do
|
||||||
|
if [[ "$choice" == "${labels[$i]}" ]]; then
|
||||||
|
printf '%s\n' "${projects[$i]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
open_project_browser() {
|
||||||
|
local project="$1"
|
||||||
|
local choice url
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
"🌐 http://localhost:3000" \
|
||||||
|
"🌐 http://localhost:5173" \
|
||||||
|
"🌐 http://localhost:8000" \
|
||||||
|
"🌐 http://localhost:8080" \
|
||||||
|
"🌐 http://localhost:5000" \
|
||||||
|
"🌐 http://localhost:4200" \
|
||||||
|
"📂 Projektordner im Browser" |
|
||||||
|
pick "🌐 Projekt im Browser"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Projektordner"*)
|
||||||
|
xdg-open "$project" >/dev/null 2>&1 &
|
||||||
|
;;
|
||||||
|
*"http"*)
|
||||||
|
url="${choice#* }"
|
||||||
|
xdg-open "$url" >/dev/null 2>&1 &
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
project_menu() {
|
||||||
|
local project project_q choice editor
|
||||||
|
|
||||||
|
project="$(pick_project)" || return 0
|
||||||
|
project_q="$(shell_quote "$project")"
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
"📂 Projekt oeffnen (Code)" \
|
||||||
|
"🌐 Projekt im Browser oeffnen" \
|
||||||
|
"🐳 Docker Compose starten" \
|
||||||
|
"🛑 Docker stoppen" \
|
||||||
|
"🔄 Container neu starten" \
|
||||||
|
"📜 Logs anzeigen" \
|
||||||
|
"📦 Git Repo Status anzeigen" |
|
||||||
|
pick "📁 $(basename "$project")"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Projekt oeffnen"*)
|
||||||
|
if editor="$(code_cmd)"; then
|
||||||
|
"$editor" "$project" >/dev/null 2>&1 &
|
||||||
|
else
|
||||||
|
notify "VS Code/Codium ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Browser"*)
|
||||||
|
open_project_browser "$project"
|
||||||
|
;;
|
||||||
|
*"Docker Compose starten"*)
|
||||||
|
docker_available || return 0
|
||||||
|
if has_compose "$project"; then
|
||||||
|
terminal_hold "Docker Compose: $(basename "$project")" "cd $project_q && docker compose up -d && docker compose ps"
|
||||||
|
else
|
||||||
|
notify "Kein Docker-Compose File im Projekt."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Docker stoppen"*)
|
||||||
|
docker_available || return 0
|
||||||
|
if has_compose "$project"; then
|
||||||
|
terminal_hold "Docker Stop: $(basename "$project")" "cd $project_q && docker compose stop && docker compose ps"
|
||||||
|
else
|
||||||
|
notify "Kein Docker-Compose File im Projekt."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Container neu starten"*)
|
||||||
|
docker_available || return 0
|
||||||
|
if has_compose "$project"; then
|
||||||
|
terminal_hold "Docker Restart: $(basename "$project")" "cd $project_q && docker compose restart && docker compose ps"
|
||||||
|
else
|
||||||
|
notify "Kein Docker-Compose File im Projekt."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Logs anzeigen"*)
|
||||||
|
docker_available || return 0
|
||||||
|
if has_compose "$project"; then
|
||||||
|
if command -v kitty >/dev/null 2>&1; then
|
||||||
|
kitty --title "Logs: $(basename "$project")" sh -lc "cd $project_q && docker compose logs -f --tail=200"
|
||||||
|
else
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
notify "Kein Docker-Compose File im Projekt."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Git Repo Status"*)
|
||||||
|
if [[ -d "$project/.git" ]]; then
|
||||||
|
terminal_hold "Git Status: $(basename "$project")" "cd $project_q && git status --short --branch"
|
||||||
|
else
|
||||||
|
notify "Kein Git Repository."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
pick_container() {
|
||||||
|
local containers=()
|
||||||
|
local labels=()
|
||||||
|
local line name status choice
|
||||||
|
|
||||||
|
docker_available || return 1
|
||||||
|
|
||||||
|
mapfile -t containers < <(docker ps -a --format '{{.Names}}|{{.Status}}' | sort)
|
||||||
|
if ((${#containers[@]} == 0)); then
|
||||||
|
notify "Keine Docker Container gefunden."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for line in "${containers[@]}"; do
|
||||||
|
name="${line%%|*}"
|
||||||
|
status="${line#*|}"
|
||||||
|
labels+=("🐳 $name · $status")
|
||||||
|
done
|
||||||
|
|
||||||
|
choice="$(printf '%s\n' "${labels[@]}" | pick "🐳 Container")"
|
||||||
|
[[ -z "${choice:-}" ]] && return 1
|
||||||
|
|
||||||
|
for i in "${!labels[@]}"; do
|
||||||
|
if [[ "$choice" == "${labels[$i]}" ]]; then
|
||||||
|
printf '%s\n' "${containers[$i]%%|*}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
docker_menu() {
|
||||||
|
local choice container running_ids all_ids
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
"▶️ Alle Container starten" \
|
||||||
|
"⏹️ Alle stoppen" \
|
||||||
|
"🔄 Einzelnen Container neu starten" \
|
||||||
|
"📊 docker stats" \
|
||||||
|
"📜 Logs anzeigen" \
|
||||||
|
"🧹 Cleanup dangling images" |
|
||||||
|
pick "🐳 Docker Control"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Alle Container starten"*)
|
||||||
|
docker_available || return 0
|
||||||
|
all_ids="$(docker ps -aq)"
|
||||||
|
if [[ -n "$all_ids" ]]; then
|
||||||
|
terminal_hold "Docker Start" "docker start $all_ids && docker ps"
|
||||||
|
else
|
||||||
|
notify "Keine Container gefunden."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Alle stoppen"*)
|
||||||
|
docker_available || return 0
|
||||||
|
running_ids="$(docker ps -q)"
|
||||||
|
if [[ -n "$running_ids" ]]; then
|
||||||
|
terminal_hold "Docker Stop" "docker stop $running_ids && docker ps -a"
|
||||||
|
else
|
||||||
|
notify "Keine laufenden Container."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Einzelnen Container neu starten"*)
|
||||||
|
container="$(pick_container)" || return 0
|
||||||
|
terminal_hold "Docker Restart: $container" "docker restart '$container' && docker ps -a --filter name='^/$container$'"
|
||||||
|
;;
|
||||||
|
*"docker stats"*)
|
||||||
|
docker_available || return 0
|
||||||
|
if command -v kitty >/dev/null 2>&1; then
|
||||||
|
kitty --title "docker stats" sh -lc "docker stats"
|
||||||
|
else
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Logs anzeigen"*)
|
||||||
|
container="$(pick_container)" || return 0
|
||||||
|
if command -v kitty >/dev/null 2>&1; then
|
||||||
|
kitty --title "Logs: $container" sh -lc "docker logs -f --tail=200 '$container'"
|
||||||
|
else
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Cleanup dangling images"*)
|
||||||
|
docker_available || return 0
|
||||||
|
terminal_hold "Docker Cleanup" "docker image prune -f"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_codex() {
|
||||||
|
if ! command -v kitty >/dev/null 2>&1; then
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v codex >/dev/null 2>&1; then
|
||||||
|
notify "codex ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
kitty --title "Codex" sh -lc 'cd "$HOME" && exec codex' >/dev/null 2>&1 &
|
||||||
|
}
|
||||||
|
|
||||||
|
launch_opencode() {
|
||||||
|
if ! command -v kitty >/dev/null 2>&1; then
|
||||||
|
notify "kitty ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v opencode >/dev/null 2>&1; then
|
||||||
|
notify "opencode ist nicht installiert."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
kitty --title "opencode" sh -lc 'cd "$HOME" && exec opencode' >/dev/null 2>&1 &
|
||||||
|
}
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
"📁 Projekt Management" \
|
||||||
|
" Homelab Controlcenter" \
|
||||||
|
"🐳 Docker Control" \
|
||||||
|
" Codex" \
|
||||||
|
" opencode" \
|
||||||
|
" Terminal" \
|
||||||
|
" Projektordner" \
|
||||||
|
" VS Code / Codium" \
|
||||||
|
" Git GUI" |
|
||||||
|
pick " Dev Menue"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Projekt Management"*)
|
||||||
|
project_menu
|
||||||
|
;;
|
||||||
|
*"Homelab Controlcenter"*)
|
||||||
|
"$SCRIPT_DIR/homelab-control.sh"
|
||||||
|
;;
|
||||||
|
*"Docker Control"*)
|
||||||
|
docker_menu
|
||||||
|
;;
|
||||||
|
*"Codex"*)
|
||||||
|
launch_codex
|
||||||
|
;;
|
||||||
|
*"opencode"*)
|
||||||
|
launch_opencode
|
||||||
|
;;
|
||||||
|
*"Terminal"*)
|
||||||
|
kitty
|
||||||
|
;;
|
||||||
|
*"Projektordner"*)
|
||||||
|
nautilus
|
||||||
|
;;
|
||||||
|
*"VS Code"*|*"Codium"*)
|
||||||
|
if editor="$(code_cmd)"; then
|
||||||
|
"$editor" >/dev/null 2>&1 &
|
||||||
|
else
|
||||||
|
notify "VS Code/Codium ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Git GUI"*)
|
||||||
|
if command -v gitg >/dev/null 2>&1; then
|
||||||
|
gitg
|
||||||
|
elif command -v git-cola >/dev/null 2>&1; then
|
||||||
|
git-cola
|
||||||
|
else
|
||||||
|
notify "gitg oder git-cola ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
145
dotfiles/hypr/Scripts/display-menu.sh
Executable file
145
dotfiles/hypr/Scripts/display-menu.sh
Executable file
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Display" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
wofi_pick() {
|
||||||
|
wofi --dmenu --prompt "$1" --insensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
connected_monitors() {
|
||||||
|
hyprctl monitors all 2>/dev/null | awk '
|
||||||
|
/^Monitor / {
|
||||||
|
name=$2
|
||||||
|
disabled=0
|
||||||
|
}
|
||||||
|
/disabled: true/ {
|
||||||
|
disabled=1
|
||||||
|
}
|
||||||
|
/^$/ && name != "" {
|
||||||
|
if (!disabled) print name
|
||||||
|
name=""
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
if (name != "" && !disabled) print name
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
all_monitors() {
|
||||||
|
hyprctl monitors all 2>/dev/null | awk '/^Monitor / {print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
first_monitor() {
|
||||||
|
connected_monitors | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
second_monitor() {
|
||||||
|
connected_monitors | sed -n '2p'
|
||||||
|
}
|
||||||
|
|
||||||
|
show_status() {
|
||||||
|
hyprctl monitors all | wofi --dmenu --prompt " Displaystatus"
|
||||||
|
}
|
||||||
|
|
||||||
|
choose_monitor() {
|
||||||
|
local monitors
|
||||||
|
monitors="$(all_monitors)"
|
||||||
|
|
||||||
|
if [ -z "$monitors" ]; then
|
||||||
|
notify "Keine Monitore ueber hyprctl gefunden."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "$monitors" | awk '{print " " $0}' | wofi_pick "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_monitor() {
|
||||||
|
local selection monitor
|
||||||
|
selection="$(choose_monitor " Monitor deaktivieren")"
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
monitor="${selection#* }"
|
||||||
|
hyprctl keyword monitor "$monitor,disable" && notify "$monitor deaktiviert."
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_preferred() {
|
||||||
|
local selection monitor
|
||||||
|
selection="$(choose_monitor " Monitor aktivieren")"
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
monitor="${selection#* }"
|
||||||
|
hyprctl keyword monitor "$monitor,preferred,auto,1" && notify "$monitor aktiviert."
|
||||||
|
}
|
||||||
|
|
||||||
|
extend_right() {
|
||||||
|
local primary secondary
|
||||||
|
primary="$(first_monitor)"
|
||||||
|
secondary="$(second_monitor)"
|
||||||
|
|
||||||
|
if [ -z "$primary" ] || [ -z "$secondary" ]; then
|
||||||
|
notify "Dafuer muessen mindestens zwei aktive Monitore vorhanden sein."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
hyprctl keyword monitor "$primary,preferred,0x0,1"
|
||||||
|
hyprctl keyword monitor "$secondary,preferred,auto-right,1"
|
||||||
|
notify "Displays erweitert."
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror_displays() {
|
||||||
|
local primary secondary
|
||||||
|
primary="$(first_monitor)"
|
||||||
|
secondary="$(second_monitor)"
|
||||||
|
|
||||||
|
if [ -z "$primary" ] || [ -z "$secondary" ]; then
|
||||||
|
notify "Dafuer muessen mindestens zwei aktive Monitore vorhanden sein."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
hyprctl keyword monitor "$primary,preferred,0x0,1"
|
||||||
|
hyprctl keyword monitor "$secondary,preferred,0x0,1,mirror,$primary"
|
||||||
|
notify "Displays gespiegelt."
|
||||||
|
}
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Status anzeigen" \
|
||||||
|
" Display-Konfig neu laden" \
|
||||||
|
" Monitor aktivieren" \
|
||||||
|
" Monitor deaktivieren" \
|
||||||
|
" Displays erweitern" \
|
||||||
|
" Displays spiegeln" \
|
||||||
|
" Grafisches Display-Tool" |
|
||||||
|
wofi_pick " Display"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Status anzeigen"*)
|
||||||
|
show_status
|
||||||
|
;;
|
||||||
|
*"Display-Konfig neu laden"*)
|
||||||
|
hyprctl reload
|
||||||
|
;;
|
||||||
|
*"Monitor aktivieren"*)
|
||||||
|
enable_preferred
|
||||||
|
;;
|
||||||
|
*"Monitor deaktivieren"*)
|
||||||
|
disable_monitor
|
||||||
|
;;
|
||||||
|
*"Displays erweitern"*)
|
||||||
|
extend_right
|
||||||
|
;;
|
||||||
|
*"Displays spiegeln"*)
|
||||||
|
mirror_displays
|
||||||
|
;;
|
||||||
|
*"Grafisches Display-Tool"*)
|
||||||
|
if command -v nwg-displays >/dev/null 2>&1; then
|
||||||
|
nwg-displays
|
||||||
|
elif command -v wdisplays >/dev/null 2>&1; then
|
||||||
|
wdisplays
|
||||||
|
else
|
||||||
|
notify "Installiere nwg-displays oder wdisplays fuer ein grafisches Display-Tool."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
28
dotfiles/hypr/Scripts/homelab-control.sh
Executable file
28
dotfiles/hypr/Scripts/homelab-control.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
HOMELAB_CONFIG="${HOMELAB_CONFIG:-$HOME/.config/homelab/config.yaml}"
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send "Homelab" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v ags >/dev/null 2>&1; then
|
||||||
|
notify "ags ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v sshpass >/dev/null 2>&1; then
|
||||||
|
notify "sshpass ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f "$HOMELAB_CONFIG" ]]; then
|
||||||
|
export HOMELAB_CONFIG
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$HYPR_DIR"
|
||||||
|
ags quit --instance homelab-control >/dev/null 2>&1 || true
|
||||||
|
exec ags run "$HYPR_DIR/ags/homelab.tsx"
|
||||||
49
dotfiles/hypr/Scripts/main-menu.sh
Executable file
49
dotfiles/hypr/Scripts/main-menu.sh
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
if pgrep -x wofi >/dev/null; then
|
||||||
|
pkill -x wofi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Apps" \
|
||||||
|
" Dev Menue" \
|
||||||
|
" Einstellungen" \
|
||||||
|
" Terminal" \
|
||||||
|
" Dateien" \
|
||||||
|
" Hyprland neu laden" \
|
||||||
|
" Session beenden" |
|
||||||
|
wofi --dmenu --prompt " Hauptmenue" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Apps"*)
|
||||||
|
wofi --show drun
|
||||||
|
;;
|
||||||
|
*"Dev Menue"*)
|
||||||
|
"$SCRIPT_DIR/dev-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Einstellungen"*)
|
||||||
|
"$SCRIPT_DIR/settings-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Terminal"*)
|
||||||
|
kitty
|
||||||
|
;;
|
||||||
|
*"Dateien"*)
|
||||||
|
nautilus
|
||||||
|
;;
|
||||||
|
*"Hyprland neu laden"*)
|
||||||
|
hyprctl reload
|
||||||
|
;;
|
||||||
|
*"Session beenden"*)
|
||||||
|
if command -v hyprshutdown >/dev/null 2>&1; then
|
||||||
|
hyprshutdown
|
||||||
|
else
|
||||||
|
hyprctl dispatch exit
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
121
dotfiles/hypr/Scripts/network-menu.sh
Executable file
121
dotfiles/hypr/Scripts/network-menu.sh
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Netzwerk" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_nmcli() {
|
||||||
|
if ! command -v nmcli >/dev/null 2>&1; then
|
||||||
|
notify "nmcli ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wofi_pick() {
|
||||||
|
wofi --dmenu --prompt "$1" --insensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
wifi_status() {
|
||||||
|
nmcli -t -f WIFI general | head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
active_connection() {
|
||||||
|
nmcli -t -f NAME,TYPE connection show --active | awk -F: '$2 ~ /wireless|ethernet/ {print $1; exit}'
|
||||||
|
}
|
||||||
|
|
||||||
|
connect_wifi() {
|
||||||
|
nmcli radio wifi on
|
||||||
|
nmcli device wifi rescan >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
local selection ssid security password
|
||||||
|
selection="$(
|
||||||
|
nmcli -t -f IN-USE,SSID,SECURITY,SIGNAL device wifi list |
|
||||||
|
awk -F: '
|
||||||
|
$2 != "" {
|
||||||
|
icon = $1 == "*" ? "" : ""
|
||||||
|
lock = $3 == "" ? "offen" : "gesichert"
|
||||||
|
printf "%s %s [%s, %s%%]\n", icon, $2, lock, $4
|
||||||
|
}
|
||||||
|
' |
|
||||||
|
wofi_pick " WLAN verbinden"
|
||||||
|
)"
|
||||||
|
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
ssid="$(printf '%s\n' "$selection" | sed -E 's/^[^ ]+ //; s/ \[.*$//')"
|
||||||
|
security="$(nmcli -t -f SSID,SECURITY device wifi list | awk -F: -v ssid="$ssid" '$1 == ssid {print $2; exit}')"
|
||||||
|
|
||||||
|
if [ -n "$security" ]; then
|
||||||
|
password="$(printf '' | wofi --dmenu --password --prompt " Passwort fuer $ssid")"
|
||||||
|
[ -n "$password" ] || exit 0
|
||||||
|
nmcli device wifi connect "$ssid" password "$password" && notify "Verbunden mit $ssid."
|
||||||
|
else
|
||||||
|
nmcli device wifi connect "$ssid" && notify "Verbunden mit $ssid."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
saved_connections() {
|
||||||
|
local selection name
|
||||||
|
selection="$(
|
||||||
|
nmcli -t -f NAME,TYPE connection show |
|
||||||
|
awk -F: '$2 ~ /wireless|ethernet/ {printf " %s\n", $1}' |
|
||||||
|
wofi_pick " Gespeicherte Verbindungen"
|
||||||
|
)"
|
||||||
|
|
||||||
|
[ -n "$selection" ] || exit 0
|
||||||
|
name="${selection#* }"
|
||||||
|
nmcli connection up "$name" && notify "Verbindung $name gestartet."
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect_network() {
|
||||||
|
local conn
|
||||||
|
conn="$(active_connection)"
|
||||||
|
|
||||||
|
if [ -z "$conn" ]; then
|
||||||
|
notify "Keine aktive Netzwerkverbindung gefunden."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
nmcli connection down "$conn" && notify "Verbindung $conn getrennt."
|
||||||
|
}
|
||||||
|
|
||||||
|
require_nmcli
|
||||||
|
|
||||||
|
current="$(active_connection)"
|
||||||
|
[ -n "$current" ] || current="Nicht verbunden"
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" WLAN verbinden" \
|
||||||
|
" WLAN ein/aus" \
|
||||||
|
" Gespeicherte Verbindungen" \
|
||||||
|
" Aktive Verbindung trennen" \
|
||||||
|
" Netzwerk neu scannen" \
|
||||||
|
" Status: $current" |
|
||||||
|
wofi_pick " Netzwerk"
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"WLAN verbinden"*)
|
||||||
|
connect_wifi
|
||||||
|
;;
|
||||||
|
*"WLAN ein/aus"*)
|
||||||
|
if [ "$(wifi_status)" = "enabled" ]; then
|
||||||
|
nmcli radio wifi off && notify "WLAN ausgeschaltet."
|
||||||
|
else
|
||||||
|
nmcli radio wifi on && notify "WLAN eingeschaltet."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Gespeicherte Verbindungen"*)
|
||||||
|
saved_connections
|
||||||
|
;;
|
||||||
|
*"Aktive Verbindung trennen"*)
|
||||||
|
disconnect_network
|
||||||
|
;;
|
||||||
|
*"Netzwerk neu scannen"*)
|
||||||
|
nmcli device wifi rescan && notify "WLAN-Scan gestartet."
|
||||||
|
;;
|
||||||
|
*"Status:"*)
|
||||||
|
nmcli general status | wofi --dmenu --prompt " Netzwerkstatus"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
18
dotfiles/hypr/Scripts/package-manager.sh
Executable file
18
dotfiles/hypr/Scripts/package-manager.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send "Pakete" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v ags >/dev/null 2>&1; then
|
||||||
|
notify "ags ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$HYPR_DIR"
|
||||||
|
ags quit --instance package-manager >/dev/null 2>&1 || true
|
||||||
|
exec ags run "$HYPR_DIR/ags/package-manager.tsx"
|
||||||
211
dotfiles/hypr/Scripts/power-menu.py
Executable file
211
dotfiles/hypr/Scripts/power-menu.py
Executable file
@@ -0,0 +1,211 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
gi.require_version("Gdk", "3.0")
|
||||||
|
from gi.repository import Gdk, Gtk # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
HYPR_DIR = Path.home() / ".config" / "hypr"
|
||||||
|
THEME_DIR = HYPR_DIR / "Themes"
|
||||||
|
CURRENT_WALLPAPER = HYPR_DIR / "current-wallpaper"
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_THEME = {
|
||||||
|
"NAME": "Power",
|
||||||
|
"ACCENT": "#f38ba8",
|
||||||
|
"ACCENT_2": "#cba6f7",
|
||||||
|
"BACKGROUND_HEX": "#18141f",
|
||||||
|
"PANEL_HEX": "#313244",
|
||||||
|
"FOREGROUND": "#f5e0dc",
|
||||||
|
"MUTED": "#a6adc8",
|
||||||
|
"SELECTED_TEXT": "#11111b",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_theme_file(path):
|
||||||
|
theme = {}
|
||||||
|
|
||||||
|
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, value = line.split("=", 1)
|
||||||
|
key = key.strip()
|
||||||
|
value = value.strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
parsed = shlex.split(value)
|
||||||
|
theme[key] = parsed[0] if parsed else ""
|
||||||
|
except ValueError:
|
||||||
|
theme[key] = value.strip("\"'")
|
||||||
|
|
||||||
|
return theme
|
||||||
|
|
||||||
|
|
||||||
|
def load_current_theme():
|
||||||
|
current_wallpaper = ""
|
||||||
|
if CURRENT_WALLPAPER.exists():
|
||||||
|
current_wallpaper = CURRENT_WALLPAPER.read_text(encoding="utf-8", errors="ignore").strip()
|
||||||
|
|
||||||
|
for theme_file in sorted(THEME_DIR.glob("*.theme")):
|
||||||
|
theme = parse_theme_file(theme_file)
|
||||||
|
if theme.get("WALLPAPER") == current_wallpaper:
|
||||||
|
return {**DEFAULT_THEME, **theme}
|
||||||
|
|
||||||
|
rose = THEME_DIR / "rose-night.theme"
|
||||||
|
if rose.exists():
|
||||||
|
return {**DEFAULT_THEME, **parse_theme_file(rose)}
|
||||||
|
|
||||||
|
return DEFAULT_THEME
|
||||||
|
|
||||||
|
|
||||||
|
def run(command):
|
||||||
|
subprocess.Popen(command, start_new_session=True)
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
|
||||||
|
class PowerMenu(Gtk.Window):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(title="Power")
|
||||||
|
|
||||||
|
self.theme = load_current_theme()
|
||||||
|
self.set_decorated(False)
|
||||||
|
self.set_resizable(False)
|
||||||
|
self.set_keep_above(True)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
||||||
|
self.set_skip_taskbar_hint(True)
|
||||||
|
self.set_skip_pager_hint(True)
|
||||||
|
self.set_app_paintable(True)
|
||||||
|
self.connect("key-press-event", self.on_key_press)
|
||||||
|
self.connect("focus-out-event", lambda *_: Gtk.main_quit())
|
||||||
|
|
||||||
|
overlay = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=18)
|
||||||
|
overlay.get_style_context().add_class("power-shell")
|
||||||
|
|
||||||
|
title = Gtk.Label(label=self.theme.get("NAME", "Power"))
|
||||||
|
title.get_style_context().add_class("power-title")
|
||||||
|
overlay.pack_start(title, False, False, 0)
|
||||||
|
|
||||||
|
grid = Gtk.Grid()
|
||||||
|
grid.set_column_spacing(14)
|
||||||
|
grid.set_row_spacing(14)
|
||||||
|
grid.set_halign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
actions = [
|
||||||
|
("", "Sperren", ["hyprlock"]),
|
||||||
|
("", "Ruhezustand", ["systemctl", "suspend"]),
|
||||||
|
("", "Abmelden", ["hyprctl", "dispatch", "exit"]),
|
||||||
|
("", "Neustart", ["systemctl", "reboot"]),
|
||||||
|
("⏻", "Ausschalten", ["systemctl", "poweroff"]),
|
||||||
|
("", "Abbrechen", None),
|
||||||
|
]
|
||||||
|
|
||||||
|
for index, (icon, label, command) in enumerate(actions):
|
||||||
|
button = self.make_button(icon, label, command)
|
||||||
|
grid.attach(button, index % 3, index // 3, 1, 1)
|
||||||
|
|
||||||
|
overlay.pack_start(grid, False, False, 0)
|
||||||
|
self.add(overlay)
|
||||||
|
self.apply_css()
|
||||||
|
|
||||||
|
def make_button(self, icon, label, command):
|
||||||
|
button = Gtk.Button()
|
||||||
|
button.get_style_context().add_class("power-button")
|
||||||
|
button.set_size_request(150, 118)
|
||||||
|
button.set_relief(Gtk.ReliefStyle.NONE)
|
||||||
|
|
||||||
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
|
||||||
|
box.set_halign(Gtk.Align.CENTER)
|
||||||
|
box.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
|
icon_label = Gtk.Label(label=icon)
|
||||||
|
icon_label.get_style_context().add_class("power-icon")
|
||||||
|
text_label = Gtk.Label(label=label)
|
||||||
|
text_label.get_style_context().add_class("power-label")
|
||||||
|
|
||||||
|
box.pack_start(icon_label, False, False, 0)
|
||||||
|
box.pack_start(text_label, False, False, 0)
|
||||||
|
button.add(box)
|
||||||
|
|
||||||
|
if command:
|
||||||
|
button.connect("clicked", lambda *_: run(command))
|
||||||
|
else:
|
||||||
|
button.connect("clicked", lambda *_: Gtk.main_quit())
|
||||||
|
|
||||||
|
return button
|
||||||
|
|
||||||
|
def apply_css(self):
|
||||||
|
css = f"""
|
||||||
|
window {{
|
||||||
|
background: transparent;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.power-shell {{
|
||||||
|
margin: 0;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 26px;
|
||||||
|
border: 1px solid {self.theme["ACCENT"]};
|
||||||
|
background: alpha({self.theme["BACKGROUND_HEX"]}, 0.94);
|
||||||
|
box-shadow: 0 22px 70px alpha(#000000, 0.55);
|
||||||
|
}}
|
||||||
|
|
||||||
|
.power-title {{
|
||||||
|
color: {self.theme["FOREGROUND"]};
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.power-button {{
|
||||||
|
color: {self.theme["FOREGROUND"]};
|
||||||
|
background: alpha({self.theme["PANEL_HEX"]}, 0.82);
|
||||||
|
border: 1px solid alpha({self.theme["ACCENT_2"]}, 0.42);
|
||||||
|
border-radius: 18px;
|
||||||
|
transition: 160ms ease;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.power-button:hover,
|
||||||
|
.power-button:focus {{
|
||||||
|
color: {self.theme["SELECTED_TEXT"]};
|
||||||
|
background: linear-gradient(135deg, {self.theme["ACCENT"]}, {self.theme["ACCENT_2"]});
|
||||||
|
border-color: {self.theme["ACCENT"]};
|
||||||
|
}}
|
||||||
|
|
||||||
|
.power-icon {{
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
|
||||||
|
font-size: 34px;
|
||||||
|
font-weight: 800;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.power-label {{
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 800;
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
provider = Gtk.CssProvider()
|
||||||
|
provider.load_from_data(css.encode("utf-8"))
|
||||||
|
Gtk.StyleContext.add_provider_for_screen(
|
||||||
|
Gdk.Screen.get_default(),
|
||||||
|
provider,
|
||||||
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_key_press(self, _widget, event):
|
||||||
|
if event.keyval in (Gdk.KEY_Escape, Gdk.KEY_q):
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
window = PowerMenu()
|
||||||
|
window.show_all()
|
||||||
|
Gtk.main()
|
||||||
128
dotfiles/hypr/Scripts/screenshot-menu.sh
Executable file
128
dotfiles/hypr/Scripts/screenshot-menu.sh
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
screenshot_dir="${XDG_PICTURES_DIR:-$HOME/Pictures}/Screenshots"
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Screenshot" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_cmd() {
|
||||||
|
local cmd="$1"
|
||||||
|
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
notify "$cmd ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
filename() {
|
||||||
|
date +%Y-%m-%d_%H-%M-%S.png
|
||||||
|
}
|
||||||
|
|
||||||
|
take_hyprshot() {
|
||||||
|
local mode="$1"
|
||||||
|
local name="$2"
|
||||||
|
|
||||||
|
require_cmd hyprshot
|
||||||
|
mkdir -p "$screenshot_dir"
|
||||||
|
hyprshot -m "$mode" -o "$screenshot_dir" -f "$name" -s
|
||||||
|
printf '%s/%s\n' "$screenshot_dir" "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
annotate_region() {
|
||||||
|
local name path
|
||||||
|
|
||||||
|
require_cmd satty
|
||||||
|
name="$(filename)"
|
||||||
|
path="$(take_hyprshot region "$name")"
|
||||||
|
satty --filename "$path" --output-filename "$path"
|
||||||
|
}
|
||||||
|
|
||||||
|
quick_region() {
|
||||||
|
local name
|
||||||
|
|
||||||
|
name="$(filename)"
|
||||||
|
take_hyprshot region "$name" >/dev/null
|
||||||
|
notify "Bereich gespeichert."
|
||||||
|
}
|
||||||
|
|
||||||
|
quick_window() {
|
||||||
|
local name
|
||||||
|
|
||||||
|
name="$(filename)"
|
||||||
|
take_hyprshot window "$name" >/dev/null
|
||||||
|
notify "Fenster gespeichert."
|
||||||
|
}
|
||||||
|
|
||||||
|
quick_output() {
|
||||||
|
local name
|
||||||
|
|
||||||
|
name="$(filename)"
|
||||||
|
take_hyprshot output "$name" >/dev/null
|
||||||
|
notify "Bildschirm gespeichert."
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_region() {
|
||||||
|
require_cmd hyprshot
|
||||||
|
hyprshot -m region --clipboard-only -s
|
||||||
|
notify "Bereich in die Zwischenablage kopiert."
|
||||||
|
}
|
||||||
|
|
||||||
|
show_menu() {
|
||||||
|
require_cmd wofi
|
||||||
|
|
||||||
|
local choice
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Bereich markieren" \
|
||||||
|
" Bereich speichern" \
|
||||||
|
" Fenster speichern" \
|
||||||
|
" Bildschirm speichern" \
|
||||||
|
" Bereich kopieren" |
|
||||||
|
wofi --dmenu --prompt " Screenshot" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Bereich markieren"*)
|
||||||
|
annotate_region
|
||||||
|
;;
|
||||||
|
*"Bereich speichern"*)
|
||||||
|
quick_region
|
||||||
|
;;
|
||||||
|
*"Fenster speichern"*)
|
||||||
|
quick_window
|
||||||
|
;;
|
||||||
|
*"Bildschirm speichern"*)
|
||||||
|
quick_output
|
||||||
|
;;
|
||||||
|
*"Bereich kopieren"*)
|
||||||
|
copy_region
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-menu}" in
|
||||||
|
annotate-region)
|
||||||
|
annotate_region
|
||||||
|
;;
|
||||||
|
region)
|
||||||
|
quick_region
|
||||||
|
;;
|
||||||
|
window)
|
||||||
|
quick_window
|
||||||
|
;;
|
||||||
|
output)
|
||||||
|
quick_output
|
||||||
|
;;
|
||||||
|
copy-region)
|
||||||
|
copy_region
|
||||||
|
;;
|
||||||
|
menu)
|
||||||
|
show_menu
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
notify "Unbekannter Screenshot-Modus: $1"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
40
dotfiles/hypr/Scripts/settings-menu.sh
Executable file
40
dotfiles/hypr/Scripts/settings-menu.sh
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Aussehen" \
|
||||||
|
" Audio" \
|
||||||
|
" Bluetooth" \
|
||||||
|
" Display" \
|
||||||
|
" Netzwerk" \
|
||||||
|
" Screenshot" \
|
||||||
|
" System" |
|
||||||
|
wofi --dmenu --prompt " Einstellungen" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Aussehen"*)
|
||||||
|
"$SCRIPT_DIR/appearance-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Audio"*)
|
||||||
|
"$SCRIPT_DIR/audio-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Netzwerk"*)
|
||||||
|
"$SCRIPT_DIR/network-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Bluetooth"*)
|
||||||
|
"$SCRIPT_DIR/bluetooth-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Display"*)
|
||||||
|
"$SCRIPT_DIR/display-menu.sh"
|
||||||
|
;;
|
||||||
|
*"Screenshot"*)
|
||||||
|
"$SCRIPT_DIR/screenshot-menu.sh"
|
||||||
|
;;
|
||||||
|
*"System"*)
|
||||||
|
"$SCRIPT_DIR/system-menu.sh"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
81
dotfiles/hypr/Scripts/system-menu.sh
Executable file
81
dotfiles/hypr/Scripts/system-menu.sh
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " System" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
start_detached() {
|
||||||
|
local command_name="$1"
|
||||||
|
local unit_name="${command_name//[^[:alnum:]_.-]/-}"
|
||||||
|
|
||||||
|
if command -v systemd-run >/dev/null 2>&1; then
|
||||||
|
systemd-run --user --unit="$unit_name" --collect "$@" >/dev/null 2>&1 && return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v setsid >/dev/null 2>&1; then
|
||||||
|
setsid -f "$@" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
"$@" >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_waybar() {
|
||||||
|
local i
|
||||||
|
|
||||||
|
pkill -x waybar >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
for i in {1..20}; do
|
||||||
|
pgrep -x waybar >/dev/null 2>&1 || break
|
||||||
|
sleep 0.05
|
||||||
|
done
|
||||||
|
|
||||||
|
pgrep -x waybar >/dev/null 2>&1 && pkill -9 -x waybar >/dev/null 2>&1 || true
|
||||||
|
start_detached waybar
|
||||||
|
}
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Hyprland neu laden" \
|
||||||
|
" Waybar neu starten" \
|
||||||
|
" Paket Installation / Updates" \
|
||||||
|
" Bildschirm heller" \
|
||||||
|
" Bildschirm dunkler" \
|
||||||
|
" Session beenden" |
|
||||||
|
wofi --dmenu --prompt " System" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$choice" in
|
||||||
|
*"Hyprland neu laden"*)
|
||||||
|
hyprctl reload
|
||||||
|
;;
|
||||||
|
*"Waybar neu starten"*)
|
||||||
|
restart_waybar
|
||||||
|
;;
|
||||||
|
*"Paket Installation / Updates"*)
|
||||||
|
"$SCRIPT_DIR/package-manager.sh"
|
||||||
|
;;
|
||||||
|
*"Bildschirm heller"*)
|
||||||
|
if command -v brightnessctl >/dev/null 2>&1; then
|
||||||
|
brightnessctl -e4 -n2 set 5%+
|
||||||
|
else
|
||||||
|
notify "brightnessctl ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Bildschirm dunkler"*)
|
||||||
|
if command -v brightnessctl >/dev/null 2>&1; then
|
||||||
|
brightnessctl -e4 -n2 set 5%-
|
||||||
|
else
|
||||||
|
notify "brightnessctl ist nicht installiert."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*"Session beenden"*)
|
||||||
|
if command -v hyprshutdown >/dev/null 2>&1; then
|
||||||
|
hyprshutdown
|
||||||
|
else
|
||||||
|
hyprctl dispatch exit
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
1204
dotfiles/hypr/Scripts/theme-menu.sh
Executable file
1204
dotfiles/hypr/Scripts/theme-menu.sh
Executable file
File diff suppressed because it is too large
Load Diff
7
dotfiles/hypr/Scripts/toggle-wofi.sh
Executable file
7
dotfiles/hypr/Scripts/toggle-wofi.sh
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
if pgrep -x wofi >/dev/null; then
|
||||||
|
pkill -x wofi
|
||||||
|
else
|
||||||
|
wofi --show drun
|
||||||
|
fi
|
||||||
197
dotfiles/hypr/Scripts/wallpaper-menu.sh
Executable file
197
dotfiles/hypr/Scripts/wallpaper-menu.sh
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
WALLPAPER_DIR="${WALLPAPER_DIR:-$HOME/Bilder/Wallpaper}"
|
||||||
|
HYPRPAPER_CONF="$HYPR_DIR/hyprpaper.conf"
|
||||||
|
CURRENT_WALLPAPER="$HYPR_DIR/current-wallpaper"
|
||||||
|
|
||||||
|
TRANSITION_TYPE="${TRANSITION_TYPE:-grow}"
|
||||||
|
TRANSITION_DURATION="${TRANSITION_DURATION:-1.0}"
|
||||||
|
TRANSITION_FPS="${TRANSITION_FPS:-60}"
|
||||||
|
TRANSITION_POS="${TRANSITION_POS:-center}"
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send " Wallpaper" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
start_detached() {
|
||||||
|
local command_name="$1"
|
||||||
|
local unit_name="${command_name//[^[:alnum:]_.-]/-}"
|
||||||
|
|
||||||
|
if command -v systemd-run >/dev/null 2>&1; then
|
||||||
|
systemd-run --user --unit="$unit_name" --collect "$@" >/dev/null 2>&1 && return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v setsid >/dev/null 2>&1; then
|
||||||
|
setsid -f "$@" >/dev/null 2>&1
|
||||||
|
else
|
||||||
|
"$@" >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_wallpaper() {
|
||||||
|
local wallpaper="$1"
|
||||||
|
|
||||||
|
cat >"$HYPRPAPER_CONF" <<EOF
|
||||||
|
wallpaper {
|
||||||
|
monitor =
|
||||||
|
path = $wallpaper
|
||||||
|
fit_mode = cover
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
printf '%s\n' "$wallpaper" >"$CURRENT_WALLPAPER"
|
||||||
|
|
||||||
|
if command -v awww >/dev/null 2>&1; then
|
||||||
|
if ! pgrep -x awww-daemon >/dev/null 2>&1; then
|
||||||
|
start_detached awww-daemon
|
||||||
|
sleep 0.2
|
||||||
|
fi
|
||||||
|
|
||||||
|
awww img "$wallpaper" \
|
||||||
|
--transition-type "$TRANSITION_TYPE" \
|
||||||
|
--transition-duration "$TRANSITION_DURATION" \
|
||||||
|
--transition-fps "$TRANSITION_FPS" \
|
||||||
|
--transition-pos "$TRANSITION_POS" >/dev/null 2>&1 || true
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v swww >/dev/null 2>&1; then
|
||||||
|
if ! pgrep -x swww-daemon >/dev/null 2>&1; then
|
||||||
|
start_detached swww-daemon
|
||||||
|
sleep 0.2
|
||||||
|
fi
|
||||||
|
|
||||||
|
swww img "$wallpaper" \
|
||||||
|
--transition-type "$TRANSITION_TYPE" \
|
||||||
|
--transition-duration "$TRANSITION_DURATION" \
|
||||||
|
--transition-fps "$TRANSITION_FPS" \
|
||||||
|
--transition-pos "$TRANSITION_POS" >/dev/null 2>&1 || true
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v hyprctl >/dev/null 2>&1; then
|
||||||
|
if ! pgrep -x hyprpaper >/dev/null 2>&1; then
|
||||||
|
start_detached hyprpaper
|
||||||
|
sleep 0.2
|
||||||
|
fi
|
||||||
|
|
||||||
|
hyprctl hyprpaper preload "$wallpaper" >/dev/null || true
|
||||||
|
hyprctl hyprpaper wallpaper ",$wallpaper" >/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
preview_wallpaper() {
|
||||||
|
local wallpaper="$1"
|
||||||
|
|
||||||
|
if command -v swayimg >/dev/null 2>&1; then
|
||||||
|
start_detached swayimg "$wallpaper"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v imv >/dev/null 2>&1; then
|
||||||
|
start_detached imv "$wallpaper"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v kitty >/dev/null 2>&1; then
|
||||||
|
start_detached kitty \
|
||||||
|
--class wallpaper-preview \
|
||||||
|
--title "Wallpaper Vorschau" \
|
||||||
|
sh -c 'clear; printf "%s\n\n" "$1"; kitty +kitten icat --fit both --align center "$1"; printf "\nEnter schliesst die Vorschau."; read -r _' \
|
||||||
|
sh "$wallpaper"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
notify "Kein Vorschauprogramm gefunden."
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--apply" ]]; then
|
||||||
|
wallpaper="${2:-}"
|
||||||
|
|
||||||
|
if [[ -z "$wallpaper" || ! -f "$wallpaper" ]]; then
|
||||||
|
notify "Wallpaper nicht gefunden."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
apply_wallpaper "$wallpaper"
|
||||||
|
notify "$(basename "$wallpaper") angewendet."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--preview" ]]; then
|
||||||
|
wallpaper="${2:-}"
|
||||||
|
|
||||||
|
if [[ -z "$wallpaper" || ! -f "$wallpaper" ]]; then
|
||||||
|
notify "Wallpaper nicht gefunden."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
preview_wallpaper "$wallpaper"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
pick_wallpaper() {
|
||||||
|
local entries=()
|
||||||
|
local wallpaper
|
||||||
|
|
||||||
|
mapfile -t wallpapers < <(
|
||||||
|
find "$WALLPAPER_DIR" -maxdepth 1 -type f \
|
||||||
|
\( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' -o -iname '*.gif' \) |
|
||||||
|
sort
|
||||||
|
)
|
||||||
|
|
||||||
|
if ((${#wallpapers[@]} == 0)); then
|
||||||
|
notify "Keine Bilder in $WALLPAPER_DIR gefunden."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
for wallpaper in "${wallpapers[@]}"; do
|
||||||
|
entries+=(" $(basename "$wallpaper")")
|
||||||
|
done
|
||||||
|
|
||||||
|
choice="$(
|
||||||
|
printf '%s\n' "${entries[@]}" |
|
||||||
|
wofi --dmenu --prompt " Wallpaper" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
[[ -z "${choice:-}" ]] && exit 0
|
||||||
|
|
||||||
|
for i in "${!entries[@]}"; do
|
||||||
|
if [[ "$choice" == "${entries[$i]}" ]]; then
|
||||||
|
printf '%s\n' "${wallpapers[$i]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
wallpaper="$(pick_wallpaper)"
|
||||||
|
[[ -z "${wallpaper:-}" ]] && exit 0
|
||||||
|
|
||||||
|
action="$(
|
||||||
|
printf '%s\n' \
|
||||||
|
" Vorschau" \
|
||||||
|
" Anwenden" \
|
||||||
|
" Zurueck" |
|
||||||
|
wofi --dmenu --prompt " $(basename "$wallpaper")" --insensitive
|
||||||
|
)"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
*"Vorschau"*)
|
||||||
|
preview_wallpaper "$wallpaper"
|
||||||
|
;;
|
||||||
|
*"Anwenden"*)
|
||||||
|
apply_wallpaper "$wallpaper"
|
||||||
|
notify "$(basename "$wallpaper") angewendet."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*"Zurueck"*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
26
dotfiles/hypr/Scripts/widget-panel.sh
Executable file
26
dotfiles/hypr/Scripts/widget-panel.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
HYPR_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
export HYPR_DIR
|
||||||
|
|
||||||
|
notify() {
|
||||||
|
notify-send "Widgetbereich" "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v ags >/dev/null 2>&1; then
|
||||||
|
notify "ags ist nicht installiert."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mapfile -t AGS_INSTANCES < <(ags list 2>/dev/null || true)
|
||||||
|
for INSTANCE in "${AGS_INSTANCES[@]}"; do
|
||||||
|
if [[ "$INSTANCE" == "widget-panel" ]]; then
|
||||||
|
ags toggle widget-panel --instance widget-panel >/dev/null 2>&1 || true
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$HYPR_DIR"
|
||||||
|
nohup ags run "$HYPR_DIR/ags/widget-panel.tsx" >/dev/null 2>&1 &
|
||||||
34
dotfiles/hypr/Themes/forest-neon.theme
Normal file
34
dotfiles/hypr/Themes/forest-neon.theme
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
NAME="Forest Neon"
|
||||||
|
ICON=""
|
||||||
|
WALLPAPER="/home/pascal/Bilder/Wallpaper/forest.jpg"
|
||||||
|
TRANSITION_TYPE="grow"
|
||||||
|
TRANSITION_DURATION="1.0"
|
||||||
|
TRANSITION_FPS="60"
|
||||||
|
TRANSITION_POS="center"
|
||||||
|
ACTIVE_BORDER="rgba(00ff9cee) rgba(00cc88ee) 45deg"
|
||||||
|
INACTIVE_BORDER="rgba(2a2a2aaa)"
|
||||||
|
ACCENT="#00ff9c"
|
||||||
|
ACCENT_2="#00cc88"
|
||||||
|
BACKGROUND="rgba(20, 20, 30, 0.95)"
|
||||||
|
BACKGROUND_SOFT="rgba(40, 40, 55, 0.8)"
|
||||||
|
BACKGROUND_HEX="#14141e"
|
||||||
|
PANEL_HEX="#282837"
|
||||||
|
FOREGROUND="#cdd6f4"
|
||||||
|
MUTED="#cccccc"
|
||||||
|
SELECTED_TEXT="#000000"
|
||||||
|
WAYBAR_PANEL="rgba(24, 24, 37, 0.72)"
|
||||||
|
WAYBAR_ISLAND="rgba(5, 5, 9, 0.88)"
|
||||||
|
WAYBAR_MUTED="#7f849c"
|
||||||
|
WAYBAR_SUCCESS="#a6e3a1"
|
||||||
|
WAYBAR_WARNING="#f9e2af"
|
||||||
|
WAYBAR_DANGER="#f38ba8"
|
||||||
|
WAYBAR_BLUE="#89b4fa"
|
||||||
|
WAYBAR_CYAN="#89dceb"
|
||||||
|
WAYBAR_ORANGE="#fab387"
|
||||||
|
WAYBAR_PURPLE="#cba6f7"
|
||||||
|
APP_THEME_MODE="prefer-dark"
|
||||||
|
GNOME_ACCENT_COLOR="green"
|
||||||
|
GTK_THEME_NAME="Adwaita"
|
||||||
|
ICON_THEME_NAME="Papirus-ForestNeon"
|
||||||
|
PAPIRUS_FOLDER_COLOR="green"
|
||||||
|
KDE_COLOR_SCHEME="ForestNeon"
|
||||||
34
dotfiles/hypr/Themes/rose-night.theme
Normal file
34
dotfiles/hypr/Themes/rose-night.theme
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
NAME="Rose Night"
|
||||||
|
ICON=""
|
||||||
|
WALLPAPER="/home/pascal/Bilder/Wallpaper/rose-pink.jpg"
|
||||||
|
TRANSITION_TYPE="wipe"
|
||||||
|
TRANSITION_DURATION="1.0"
|
||||||
|
TRANSITION_FPS="60"
|
||||||
|
TRANSITION_POS="center"
|
||||||
|
ACTIVE_BORDER="rgba(f38ba8ee) rgba(cba6f7ee) 45deg"
|
||||||
|
INACTIVE_BORDER="rgba(313244aa)"
|
||||||
|
ACCENT="#f38ba8"
|
||||||
|
ACCENT_2="#cba6f7"
|
||||||
|
BACKGROUND="rgba(24, 20, 31, 0.95)"
|
||||||
|
BACKGROUND_SOFT="rgba(49, 50, 68, 0.82)"
|
||||||
|
BACKGROUND_HEX="#18141f"
|
||||||
|
PANEL_HEX="#313244"
|
||||||
|
FOREGROUND="#f5e0dc"
|
||||||
|
MUTED="#cdd6f4"
|
||||||
|
SELECTED_TEXT="#11111b"
|
||||||
|
WAYBAR_PANEL="rgba(49, 50, 68, 0.76)"
|
||||||
|
WAYBAR_ISLAND="rgba(24, 20, 31, 0.90)"
|
||||||
|
WAYBAR_MUTED="#a6adc8"
|
||||||
|
WAYBAR_SUCCESS="#f5c2e7"
|
||||||
|
WAYBAR_WARNING="#f9e2af"
|
||||||
|
WAYBAR_DANGER="#f38ba8"
|
||||||
|
WAYBAR_BLUE="#cba6f7"
|
||||||
|
WAYBAR_CYAN="#f5c2e7"
|
||||||
|
WAYBAR_ORANGE="#fab387"
|
||||||
|
WAYBAR_PURPLE="#cba6f7"
|
||||||
|
APP_THEME_MODE="prefer-dark"
|
||||||
|
GNOME_ACCENT_COLOR="pink"
|
||||||
|
GTK_THEME_NAME="Adwaita"
|
||||||
|
ICON_THEME_NAME="Papirus-RoseNight"
|
||||||
|
PAPIRUS_FOLDER_COLOR="pink"
|
||||||
|
KDE_COLOR_SCHEME="RoseNight"
|
||||||
312
dotfiles/hypr/ags/homelab.css
Normal file
312
dotfiles/hypr/ags/homelab.css
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.homelab-window {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-panel,
|
||||||
|
.shell {
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.16);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(20, 20, 30, 0.97);
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-panel {
|
||||||
|
min-width: 520px;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shell {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
min-width: 218px;
|
||||||
|
padding: 14px 10px;
|
||||||
|
border-right: 1px solid rgba(205, 214, 244, 0.10);
|
||||||
|
background: rgba(14, 16, 24, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
padding: 8px 8px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-title {
|
||||||
|
color: #00ff9c;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-subtitle,
|
||||||
|
.subtitle,
|
||||||
|
.row-subtitle {
|
||||||
|
color: #a6adc8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button:hover,
|
||||||
|
.nav-button:focus {
|
||||||
|
background: rgba(0, 255, 156, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button.active {
|
||||||
|
background: rgba(0, 255, 156, 0.22);
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
min-width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-scroll {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
min-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 750;
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.14);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(40, 40, 55, 0.82);
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry:focus {
|
||||||
|
border-color: rgba(0, 255, 156, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid,
|
||||||
|
.live-strip,
|
||||||
|
.service-grid {
|
||||||
|
min-height: 92px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card,
|
||||||
|
.chart-card,
|
||||||
|
.service-card,
|
||||||
|
.table-row {
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(40, 40, 55, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
min-width: 220px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide-card {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-card {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
min-width: 260px;
|
||||||
|
min-height: 98px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card.cpu {
|
||||||
|
border-color: rgba(0, 255, 156, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card.memory {
|
||||||
|
border-color: rgba(137, 180, 250, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card.load {
|
||||||
|
border-color: rgba(249, 226, 175, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-card.docker,
|
||||||
|
.chart-card.network {
|
||||||
|
border-color: rgba(116, 199, 236, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
color: #00cc88;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 750;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-value {
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact,
|
||||||
|
.row-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percent-label,
|
||||||
|
.chart-value {
|
||||||
|
color: #f9e2af;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 750;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sparkline {
|
||||||
|
color: #00ff9c;
|
||||||
|
font-size: 25px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter {
|
||||||
|
min-height: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(205, 214, 244, 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meter-fill {
|
||||||
|
min-height: 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: linear-gradient(to right, #00ff9c, #89b4fa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerts {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert,
|
||||||
|
.pill {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(205, 214, 244, 0.10);
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.ok,
|
||||||
|
.pill.ok {
|
||||||
|
background: rgba(0, 255, 156, 0.16);
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert.warn,
|
||||||
|
.pill.warn {
|
||||||
|
background: rgba(249, 226, 175, 0.16);
|
||||||
|
color: #f9e2af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-row {
|
||||||
|
min-height: 72px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-main {
|
||||||
|
min-width: 260px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-stats {
|
||||||
|
min-width: 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-actions {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-title {
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-weight: 750;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-card {
|
||||||
|
min-width: 250px;
|
||||||
|
min-height: 112px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.mini-button,
|
||||||
|
.icon-button {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(40, 40, 55, 0.82);
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-button {
|
||||||
|
padding: 6px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover,
|
||||||
|
.button:focus,
|
||||||
|
.mini-button:hover,
|
||||||
|
.mini-button:focus,
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button:focus {
|
||||||
|
background: rgba(0, 255, 156, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary {
|
||||||
|
background: rgba(0, 255, 156, 0.24);
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
1156
dotfiles/hypr/ags/homelab.tsx
Normal file
1156
dotfiles/hypr/ags/homelab.tsx
Normal file
File diff suppressed because it is too large
Load Diff
180
dotfiles/hypr/ags/package-manager.css
Normal file
180
dotfiles/hypr/ags/package-manager.css
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-window {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-panel {
|
||||||
|
min-width: 940px;
|
||||||
|
min-height: 620px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.16);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: rgba(20, 20, 30, 0.97);
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
|
||||||
|
color: #cdd6f4;
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #00ff9c;
|
||||||
|
font-size: 21px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle,
|
||||||
|
.muted,
|
||||||
|
.package-meta {
|
||||||
|
color: #a6adc8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.14);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(40, 40, 55, 0.82);
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry:focus {
|
||||||
|
border-color: rgba(0, 255, 156, 0.78);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button,
|
||||||
|
.tool-button,
|
||||||
|
.icon-button {
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(40, 40, 55, 0.82);
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover,
|
||||||
|
.button:focus,
|
||||||
|
.tool-button:hover,
|
||||||
|
.tool-button:focus,
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button:focus {
|
||||||
|
background: rgba(0, 255, 156, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary,
|
||||||
|
.tool-button.active {
|
||||||
|
background: rgba(0, 255, 156, 0.24);
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 34px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
min-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-strip {
|
||||||
|
min-height: 30px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(40, 40, 55, 0.48);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-panel {
|
||||||
|
min-height: 220px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.12);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(10, 10, 16, 0.58);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-header,
|
||||||
|
.operation-input-row {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-title {
|
||||||
|
color: #00ff9c;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-scroll {
|
||||||
|
min-height: 142px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(4, 4, 8, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-output {
|
||||||
|
padding: 10px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.danger {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-scroll {
|
||||||
|
min-height: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-row {
|
||||||
|
min-height: 76px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid rgba(205, 214, 244, 0.10);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgba(40, 40, 55, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-row:hover {
|
||||||
|
border-color: rgba(0, 255, 156, 0.35);
|
||||||
|
background: rgba(40, 40, 55, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-name {
|
||||||
|
color: #cdd6f4;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.package-desc {
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-pill,
|
||||||
|
.installed-pill {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
background: rgba(116, 199, 236, 0.16);
|
||||||
|
color: #89dceb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installed-pill {
|
||||||
|
background: rgba(0, 255, 156, 0.16);
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
min-height: 180px;
|
||||||
|
color: #a6adc8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
456
dotfiles/hypr/ags/package-manager.tsx
Normal file
456
dotfiles/hypr/ags/package-manager.tsx
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
import app from "ags/gtk4/app";
|
||||||
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
|
import { execAsync, subprocess, Process } from "ags/process";
|
||||||
|
import GLib from "gi://GLib";
|
||||||
|
import css from "./package-manager.css";
|
||||||
|
|
||||||
|
const WINDOW_MARGIN_TOP = 48;
|
||||||
|
const ESC_KEYVAL = 65307;
|
||||||
|
const RUNNER = `${GLib.get_home_dir()}/.config/hypr/Scripts/ags-package-runner.py`;
|
||||||
|
|
||||||
|
type Helper = "pacman" | "paru";
|
||||||
|
|
||||||
|
type PackageResult = {
|
||||||
|
repo: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
installed: boolean;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let helper: Helper = "paru";
|
||||||
|
let query = "";
|
||||||
|
let results: PackageResult[] = [];
|
||||||
|
let busy = false;
|
||||||
|
let statusMessage = "Suchbegriff eingeben und Enter druecken.";
|
||||||
|
let errorMessage = "";
|
||||||
|
let activeProcess: Process | null = null;
|
||||||
|
let operationTitle = "";
|
||||||
|
let operationOutput = "";
|
||||||
|
let operationInput = "";
|
||||||
|
let secretInput = false;
|
||||||
|
let lastOutput = "";
|
||||||
|
|
||||||
|
function shQuote(value: string) {
|
||||||
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify(message: string) {
|
||||||
|
execAsync(["notify-send", "Pakete", message]).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runShell(command: string) {
|
||||||
|
return execAsync(["bash", "-lc", command]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBusy(value: boolean) {
|
||||||
|
busy = value;
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendOperationOutput(data: string) {
|
||||||
|
operationOutput = `${operationOutput}${data}`.slice(-30000);
|
||||||
|
lastOutput = data;
|
||||||
|
secretInput = /(\[sudo\].*password|passwort|password).*:/i.test(data);
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
function commandLabel(command: string[]) {
|
||||||
|
return command.map(shQuote).join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function helperAvailable(nextHelper: Helper) {
|
||||||
|
return runShell(`command -v ${nextHelper} >/dev/null 2>&1`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSearch(output: string) {
|
||||||
|
const parsed: PackageResult[] = [];
|
||||||
|
const lines = output.split("\n");
|
||||||
|
|
||||||
|
for (let index = 0; index < lines.length; index += 1) {
|
||||||
|
const header = lines[index];
|
||||||
|
const match = header.match(/^([^/\s]+)\/([^\s]+)\s+([^\s]+)(?:\s+\[(installed[^\]]*)\])?/);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const descriptionLines: string[] = [];
|
||||||
|
let next = index + 1;
|
||||||
|
while (next < lines.length && /^\s+/.test(lines[next])) {
|
||||||
|
descriptionLines.push(lines[next].trim());
|
||||||
|
next += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed.push({
|
||||||
|
repo: match[1],
|
||||||
|
name: match[2],
|
||||||
|
version: match[3],
|
||||||
|
installed: Boolean(match[4]),
|
||||||
|
description: descriptionLines.join(" ") || "Keine Beschreibung.",
|
||||||
|
});
|
||||||
|
|
||||||
|
index = next - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.slice(0, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchPackages() {
|
||||||
|
const term = query.trim();
|
||||||
|
if (!term) {
|
||||||
|
results = [];
|
||||||
|
statusMessage = "Suchbegriff fehlt.";
|
||||||
|
rebuild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setBusy(true);
|
||||||
|
errorMessage = "";
|
||||||
|
statusMessage = `Suche mit ${helper} nach "${term}"...`;
|
||||||
|
|
||||||
|
helperAvailable(helper)
|
||||||
|
.then(() => runShell(`${helper} -Ss ${shQuote(term)} 2>/dev/null || true`))
|
||||||
|
.then(output => {
|
||||||
|
results = parseSearch(output);
|
||||||
|
statusMessage = results.length
|
||||||
|
? `${results.length} Treffer fuer "${term}" mit ${helper}.`
|
||||||
|
: `Keine Treffer fuer "${term}".`;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
results = [];
|
||||||
|
errorMessage = `${helper} ist nicht verfuegbar oder die Suche ist fehlgeschlagen.`;
|
||||||
|
statusMessage = "Suche fehlgeschlagen.";
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setBusy(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startOperation(title: string, command: string[]) {
|
||||||
|
if (activeProcess) {
|
||||||
|
statusMessage = "Es laeuft bereits ein Paketprozess.";
|
||||||
|
rebuild();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
operationTitle = title;
|
||||||
|
operationOutput = `$ ${commandLabel(command)}\n\n`;
|
||||||
|
operationInput = "";
|
||||||
|
errorMessage = "";
|
||||||
|
statusMessage = `${title} laeuft in AGS.`;
|
||||||
|
busy = true;
|
||||||
|
|
||||||
|
const process = subprocess(
|
||||||
|
[RUNNER, "--", ...command],
|
||||||
|
line => {
|
||||||
|
try {
|
||||||
|
const event = JSON.parse(line);
|
||||||
|
if (event.type === "out") {
|
||||||
|
appendOperationOutput(event.data || "");
|
||||||
|
} else if (event.type === "exit") {
|
||||||
|
const suffix = event.signaled ? `Signal ${event.code}` : `Exit ${event.code}`;
|
||||||
|
appendOperationOutput(`\n[${suffix}]\n`);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
appendOperationOutput(`${line}\n`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
line => appendOperationOutput(`${line}\n`),
|
||||||
|
);
|
||||||
|
|
||||||
|
activeProcess = process;
|
||||||
|
process.connect("exit", (_, code: number, signaled: boolean) => {
|
||||||
|
activeProcess = null;
|
||||||
|
busy = false;
|
||||||
|
secretInput = false;
|
||||||
|
statusMessage = signaled
|
||||||
|
? `${title} wurde beendet.`
|
||||||
|
: code === 0
|
||||||
|
? `${title} abgeschlossen.`
|
||||||
|
: `${title} fehlgeschlagen (Exit ${code}).`;
|
||||||
|
notify(statusMessage);
|
||||||
|
rebuild();
|
||||||
|
});
|
||||||
|
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendOperationInput(value = operationInput, allowEmpty = false) {
|
||||||
|
if (!activeProcess || (!allowEmpty && value.length === 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeProcess.writeAsync(`${JSON.stringify({ type: "input", data: `${value}\n` })}\n`).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
errorMessage = "Eingabe konnte nicht gesendet werden.";
|
||||||
|
rebuild();
|
||||||
|
});
|
||||||
|
operationInput = "";
|
||||||
|
secretInput = false;
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
function yesAnswer() {
|
||||||
|
return /[\[(][YyJj]\/[Nn][\])]/.test(lastOutput) ? "" : "y";
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendYesAnswer() {
|
||||||
|
const answer = yesAnswer();
|
||||||
|
appendOperationOutput(answer ? `\n> ${answer}\n` : "\n> Enter\n");
|
||||||
|
sendOperationInput(answer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendNoAnswer() {
|
||||||
|
appendOperationOutput("\n> n\n");
|
||||||
|
sendOperationInput("n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelOperation() {
|
||||||
|
if (!activeProcess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeProcess.writeAsync(`${JSON.stringify({ type: "signal", signal: 15 })}\n`).catch(console.error);
|
||||||
|
statusMessage = "Abbruch angefordert.";
|
||||||
|
rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
function installPackage(pkg: PackageResult) {
|
||||||
|
const command = helper === "pacman"
|
||||||
|
? ["sudo", "pacman", "-S", pkg.name]
|
||||||
|
: ["paru", "-S", pkg.name];
|
||||||
|
|
||||||
|
notify(`Installation gestartet: ${pkg.name}`);
|
||||||
|
startOperation(`Installation: ${pkg.name}`, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystem() {
|
||||||
|
const command = helper === "pacman"
|
||||||
|
? ["sudo", "pacman", "-Syu"]
|
||||||
|
: ["paru", "-Syu"];
|
||||||
|
|
||||||
|
notify(`Update gestartet mit ${helper}.`);
|
||||||
|
startOperation(`Systemupdate mit ${helper}`, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
function HelperButton({ id, label }: { id: Helper; label: string }) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
class={helper === id ? "tool-button active" : "tool-button"}
|
||||||
|
onClicked={() => {
|
||||||
|
helper = id;
|
||||||
|
statusMessage = `Aktiver Helper: ${helper}`;
|
||||||
|
rebuild();
|
||||||
|
}}
|
||||||
|
label={label}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PackageRow({ pkg }: { pkg: PackageResult }) {
|
||||||
|
return (
|
||||||
|
<box class="package-row" spacing={10}>
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} spacing={4} hexpand>
|
||||||
|
<box spacing={8}>
|
||||||
|
<label class="package-name" xalign={0} label={pkg.name} />
|
||||||
|
<label class="repo-pill" label={pkg.repo} />
|
||||||
|
{pkg.installed ? <label class="installed-pill" label="installed" /> : <box />}
|
||||||
|
</box>
|
||||||
|
<label class="package-meta" xalign={0} label={pkg.version} />
|
||||||
|
<label class="package-desc" xalign={0} wrap label={pkg.description} />
|
||||||
|
</box>
|
||||||
|
<button class="button primary" sensitive={!busy} onClicked={() => installPackage(pkg)} label="Installieren" />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OperationPanel() {
|
||||||
|
if (!operationTitle && !operationOutput) {
|
||||||
|
return <box />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box class="operation-panel" orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||||
|
<box class="operation-header" spacing={8}>
|
||||||
|
<label class="operation-title" xalign={0} hexpand label={operationTitle || "Paketprozess"} />
|
||||||
|
<button class="button danger" sensitive={Boolean(activeProcess)} onClicked={cancelOperation} label="Abbrechen" />
|
||||||
|
</box>
|
||||||
|
<scrolledwindow
|
||||||
|
class="operation-scroll"
|
||||||
|
hexpand
|
||||||
|
vexpand
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
<label class="operation-output" xalign={0} yalign={0} wrap selectable label={operationOutput || "Warte auf Ausgabe..."} />
|
||||||
|
</scrolledwindow>
|
||||||
|
<box class="operation-input-row" spacing={8}>
|
||||||
|
<entry
|
||||||
|
hexpand
|
||||||
|
sensitive={Boolean(activeProcess)}
|
||||||
|
visibility={!secretInput}
|
||||||
|
text={operationInput}
|
||||||
|
placeholderText={secretInput ? "Passwort eingeben..." : "Antwort eingeben..."}
|
||||||
|
onChanged={entry => {
|
||||||
|
operationInput = entry.get_text();
|
||||||
|
}}
|
||||||
|
onActivate={entry => {
|
||||||
|
operationInput = entry.get_text();
|
||||||
|
sendOperationInput();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button class="button" sensitive={Boolean(activeProcess)} onClicked={sendYesAnswer} label="Ja" />
|
||||||
|
<button class="button" sensitive={Boolean(activeProcess)} onClicked={sendNoAnswer} label="Nein" />
|
||||||
|
<button class="button primary" sensitive={Boolean(activeProcess)} onClicked={() => sendOperationInput()} label="Senden" />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PackageManagerWindow() {
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name="package-manager"
|
||||||
|
namespace="package-manager"
|
||||||
|
class="package-window"
|
||||||
|
visible
|
||||||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||||||
|
anchor={Astal.WindowAnchor.TOP}
|
||||||
|
application={app}
|
||||||
|
>
|
||||||
|
<Gtk.EventControllerKey onKeyPressed={(_, keyval) => {
|
||||||
|
if (keyval === ESC_KEYVAL) {
|
||||||
|
app.quit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}} />
|
||||||
|
<box class="package-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14} marginTop={WINDOW_MARGIN_TOP}>
|
||||||
|
<box class="header">
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
||||||
|
<label class="title" xalign={0} label="Paket Installation / Updates" />
|
||||||
|
<label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation direkt in AGS." />
|
||||||
|
</box>
|
||||||
|
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<box class="toolbar" spacing={8}>
|
||||||
|
<entry
|
||||||
|
hexpand
|
||||||
|
placeholderText="Paket suchen, z.B. firefox, docker, obsidian..."
|
||||||
|
onChanged={entry => {
|
||||||
|
query = entry.get_text();
|
||||||
|
}}
|
||||||
|
onActivate={entry => {
|
||||||
|
query = entry.get_text();
|
||||||
|
searchPackages();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button class="button primary" sensitive={!busy} onClicked={searchPackages} label={busy ? "Suche..." : "Suchen"} />
|
||||||
|
</box>
|
||||||
|
<box class="toolbar" spacing={8}>
|
||||||
|
<HelperButton id="paru" label="paru" />
|
||||||
|
<HelperButton id="pacman" label="pacman" />
|
||||||
|
<button class="button" sensitive={!busy} onClicked={updateSystem} label="System updaten" />
|
||||||
|
</box>
|
||||||
|
<box class="status-strip">
|
||||||
|
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
||||||
|
</box>
|
||||||
|
<OperationPanel />
|
||||||
|
<scrolledwindow
|
||||||
|
class="results-scroll"
|
||||||
|
hexpand
|
||||||
|
vexpand
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
{results.length
|
||||||
|
? (
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||||
|
{results.map(pkg => <PackageRow pkg={pkg} />)}
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<box class="empty">
|
||||||
|
<label hexpand xalign={0.5} label="Noch keine Suchergebnisse." />
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</scrolledwindow>
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuild() {
|
||||||
|
const win = app.get_window("package-manager");
|
||||||
|
if (!win) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
win.set_child(
|
||||||
|
<box class="package-panel" orientation={Gtk.Orientation.VERTICAL} spacing={14} marginTop={WINDOW_MARGIN_TOP}>
|
||||||
|
<box class="header">
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
||||||
|
<label class="title" xalign={0} label="Paket Installation / Updates" />
|
||||||
|
<label class="subtitle" xalign={0} label="Pacman und AUR Suche, Installation direkt in AGS." />
|
||||||
|
</box>
|
||||||
|
<button class="icon-button close" tooltipText="Schliessen" onClicked={() => app.quit()}>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<box class="toolbar" spacing={8}>
|
||||||
|
<entry
|
||||||
|
hexpand
|
||||||
|
text={query}
|
||||||
|
placeholderText="Paket suchen, z.B. firefox, docker, obsidian..."
|
||||||
|
onChanged={entry => {
|
||||||
|
query = entry.get_text();
|
||||||
|
}}
|
||||||
|
onActivate={entry => {
|
||||||
|
query = entry.get_text();
|
||||||
|
searchPackages();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button class="button primary" sensitive={!busy} onClicked={searchPackages} label={busy ? "Suche..." : "Suchen"} />
|
||||||
|
</box>
|
||||||
|
<box class="toolbar" spacing={8}>
|
||||||
|
<HelperButton id="paru" label="paru" />
|
||||||
|
<HelperButton id="pacman" label="pacman" />
|
||||||
|
<button class="button" sensitive={!busy} onClicked={updateSystem} label="System updaten" />
|
||||||
|
</box>
|
||||||
|
<box class="status-strip">
|
||||||
|
<label class={errorMessage ? "error" : "muted"} xalign={0} hexpand label={errorMessage || statusMessage} />
|
||||||
|
</box>
|
||||||
|
<OperationPanel />
|
||||||
|
<scrolledwindow
|
||||||
|
class="results-scroll"
|
||||||
|
hexpand
|
||||||
|
vexpand
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
{results.length
|
||||||
|
? (
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||||
|
{results.map(pkg => <PackageRow pkg={pkg} />)}
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<box class="empty">
|
||||||
|
<label hexpand xalign={0.5} label="Noch keine Suchergebnisse." />
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</scrolledwindow>
|
||||||
|
</box> as Gtk.Widget
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.start({
|
||||||
|
css,
|
||||||
|
instanceName: "package-manager",
|
||||||
|
main() {
|
||||||
|
PackageManagerWindow();
|
||||||
|
},
|
||||||
|
});
|
||||||
115
dotfiles/hypr/ags/switcher.css
Normal file
115
dotfiles/hypr/ags/switcher.css
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher-window {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.switcher {
|
||||||
|
min-width: 680px;
|
||||||
|
padding: 18px;
|
||||||
|
border: 1px solid alpha(@ags_fg, 0.18);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: @ags_bg;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
|
||||||
|
color: @ags_fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-scroll {
|
||||||
|
min-height: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid alpha(@ags_fg, 0.10);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: alpha(@ags_panel, 0.54);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-row {
|
||||||
|
min-height: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:hover,
|
||||||
|
.item:focus {
|
||||||
|
border-color: alpha(@ags_accent, 0.72);
|
||||||
|
background: alpha(@ags_panel, 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.active {
|
||||||
|
border-color: alpha(@ags_accent_2, 0.85);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
min-width: 96px;
|
||||||
|
min-height: 56px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: alpha(@ags_panel, 0.80);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-empty {
|
||||||
|
color: @ags_accent_2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-subtitle {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatches {
|
||||||
|
min-width: 86px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swatch {
|
||||||
|
min-width: 18px;
|
||||||
|
min-height: 18px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 34px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: @ags_fg;
|
||||||
|
background: alpha(@ags_panel, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-button {
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button:focus {
|
||||||
|
background: alpha(@ags_accent, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: @ags_accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
min-height: 150px;
|
||||||
|
color: @ags_muted;
|
||||||
|
}
|
||||||
321
dotfiles/hypr/ags/switcher.tsx
Normal file
321
dotfiles/hypr/ags/switcher.tsx
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
import app from "ags/gtk4/app";
|
||||||
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
|
import { readFile } from "ags/file";
|
||||||
|
import { execAsync } from "ags/process";
|
||||||
|
import css from "./switcher.css";
|
||||||
|
import GLib from "gi://GLib";
|
||||||
|
|
||||||
|
type SwitcherItem = {
|
||||||
|
type: "theme" | "wallpaper";
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
icon?: string;
|
||||||
|
wallpaper: string;
|
||||||
|
active?: boolean;
|
||||||
|
accent?: string;
|
||||||
|
accent2?: string;
|
||||||
|
muted?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const HYPR_DIR = GLib.getenv("HYPR_DIR") || "/home/pascal/.config/hypr";
|
||||||
|
const SCRIPT_DIR = `${HYPR_DIR}/Scripts`;
|
||||||
|
const THEME_DIR = GLib.getenv("HYPR_SWITCHER_THEME_DIR") || `${HYPR_DIR}/Themes`;
|
||||||
|
const WALLPAPER_DIR = GLib.getenv("HYPR_SWITCHER_WALLPAPER_DIR")
|
||||||
|
|| GLib.getenv("WALLPAPER_DIR")
|
||||||
|
|| `${GLib.get_home_dir()}/Bilder/Wallpaper`;
|
||||||
|
const CURRENT_WALLPAPER = `${HYPR_DIR}/current-wallpaper`;
|
||||||
|
const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif"];
|
||||||
|
|
||||||
|
type UiTheme = {
|
||||||
|
accent: string;
|
||||||
|
accent2: string;
|
||||||
|
background: string;
|
||||||
|
backgroundSoft: string;
|
||||||
|
foreground: string;
|
||||||
|
muted: string;
|
||||||
|
panelHex: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function notify(message: string) {
|
||||||
|
execAsync(["notify-send", "AGS Switcher", message]).catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fileExists(path: string) {
|
||||||
|
return GLib.file_test(path, GLib.FileTest.EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readText(path: string) {
|
||||||
|
try {
|
||||||
|
return readFile(path);
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listFiles(dir: string, predicate: (path: string, name: string) => boolean) {
|
||||||
|
try {
|
||||||
|
const directory = GLib.Dir.open(dir, 0);
|
||||||
|
const files: string[] = [];
|
||||||
|
let name = directory.read_name();
|
||||||
|
|
||||||
|
while (name !== null) {
|
||||||
|
const path = `${dir}/${name}`;
|
||||||
|
if (predicate(path, name)) {
|
||||||
|
files.push(path);
|
||||||
|
}
|
||||||
|
name = directory.read_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.sort((a, b) => a.localeCompare(b));
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shellValue(contents: string, key: string) {
|
||||||
|
const regex = new RegExp(`^${key}=(["']?)(.*?)\\1$`, "m");
|
||||||
|
return contents.match(regex)?.[2] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeTheme(): UiTheme {
|
||||||
|
const activeWallpaper = currentWallpaper();
|
||||||
|
const fallback = {
|
||||||
|
accent: "#00ff9c",
|
||||||
|
accent2: "#00cc88",
|
||||||
|
background: "rgba(20, 20, 30, 0.95)",
|
||||||
|
backgroundSoft: "rgba(40, 40, 55, 0.8)",
|
||||||
|
foreground: "#cdd6f4",
|
||||||
|
muted: "#cccccc",
|
||||||
|
panelHex: "#282837",
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeFile = listFiles(THEME_DIR, (_path, name) => name.endsWith(".theme"))
|
||||||
|
.find(path => shellValue(readText(path), "WALLPAPER") === activeWallpaper);
|
||||||
|
|
||||||
|
if (!themeFile) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = readText(themeFile);
|
||||||
|
return {
|
||||||
|
accent: shellValue(contents, "ACCENT") || fallback.accent,
|
||||||
|
accent2: shellValue(contents, "ACCENT_2") || fallback.accent2,
|
||||||
|
background: shellValue(contents, "BACKGROUND") || fallback.background,
|
||||||
|
backgroundSoft: shellValue(contents, "BACKGROUND_SOFT") || fallback.backgroundSoft,
|
||||||
|
foreground: shellValue(contents, "FOREGROUND") || fallback.foreground,
|
||||||
|
muted: shellValue(contents, "MUTED") || fallback.muted,
|
||||||
|
panelHex: shellValue(contents, "PANEL_HEX") || fallback.panelHex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function themeCss(theme: UiTheme) {
|
||||||
|
return [
|
||||||
|
`@define-color ags_accent ${theme.accent};`,
|
||||||
|
`@define-color ags_accent_2 ${theme.accent2};`,
|
||||||
|
`@define-color ags_bg ${theme.background};`,
|
||||||
|
`@define-color ags_bg_soft ${theme.backgroundSoft};`,
|
||||||
|
`@define-color ags_fg ${theme.foreground};`,
|
||||||
|
`@define-color ags_muted ${theme.muted};`,
|
||||||
|
`@define-color ags_panel ${theme.panelHex};`,
|
||||||
|
css,
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function basename(path: string) {
|
||||||
|
return GLib.path_get_basename(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentWallpaper() {
|
||||||
|
return readText(CURRENT_WALLPAPER).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadThemes(): SwitcherItem[] {
|
||||||
|
return listFiles(THEME_DIR, (_path, name) => name.endsWith(".theme"))
|
||||||
|
.map(path => {
|
||||||
|
const contents = readText(path);
|
||||||
|
return {
|
||||||
|
type: "theme",
|
||||||
|
path,
|
||||||
|
name: shellValue(contents, "NAME") || basename(path).replace(/\.theme$/, ""),
|
||||||
|
icon: shellValue(contents, "ICON") || "",
|
||||||
|
wallpaper: shellValue(contents, "WALLPAPER"),
|
||||||
|
accent: shellValue(contents, "ACCENT") || "#f38ba8",
|
||||||
|
accent2: shellValue(contents, "ACCENT_2") || "#cba6f7",
|
||||||
|
muted: shellValue(contents, "MUTED") || "#cdd6f4",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadWallpapers(): SwitcherItem[] {
|
||||||
|
const active = currentWallpaper();
|
||||||
|
|
||||||
|
return listFiles(WALLPAPER_DIR, (_path, name) =>
|
||||||
|
IMAGE_EXTENSIONS.some(ext => name.toLowerCase().endsWith(ext)),
|
||||||
|
).map(path => ({
|
||||||
|
type: "wallpaper",
|
||||||
|
path,
|
||||||
|
name: basename(path),
|
||||||
|
wallpaper: path,
|
||||||
|
active: path === active,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyItem(item: SwitcherItem) {
|
||||||
|
const command = item.type === "theme"
|
||||||
|
? [`${SCRIPT_DIR}/theme-menu.sh`, "--apply", item.path]
|
||||||
|
: [`${SCRIPT_DIR}/wallpaper-menu.sh`, "--apply", item.path];
|
||||||
|
|
||||||
|
execAsync(command)
|
||||||
|
.then(() => app.quit())
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
notify(`${item.name} konnte nicht angewendet werden.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewWallpaper(item: SwitcherItem) {
|
||||||
|
execAsync([`${SCRIPT_DIR}/wallpaper-menu.sh`, "--preview", item.path]).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
notify(`${item.name} konnte nicht geoeffnet werden.`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function Preview({ item }: { item: SwitcherItem }) {
|
||||||
|
const hasImage = item.wallpaper && fileExists(item.wallpaper);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box class={hasImage ? "preview" : "preview preview-empty"}>
|
||||||
|
{hasImage
|
||||||
|
? <image class="preview-image" file={item.wallpaper} pixelSize={92} />
|
||||||
|
: <label label="" />}
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Swatches({ item }: { item: SwitcherItem }) {
|
||||||
|
return (
|
||||||
|
<box class="swatches" spacing={6}>
|
||||||
|
{[item.accent, item.accent2, item.muted].map(color => (
|
||||||
|
<box class="swatch" css={`background: ${color};`} />
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ItemButton({ item }: { item: SwitcherItem }) {
|
||||||
|
const content = (
|
||||||
|
<box spacing={14}>
|
||||||
|
<Preview item={item} />
|
||||||
|
<box
|
||||||
|
orientation={Gtk.Orientation.VERTICAL}
|
||||||
|
hexpand
|
||||||
|
valign={Gtk.Align.CENTER}
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="item-title"
|
||||||
|
xalign={0}
|
||||||
|
ellipsize={3}
|
||||||
|
label={`${item.icon || ""} ${item.name}`}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
class="item-subtitle"
|
||||||
|
xalign={0}
|
||||||
|
ellipsize={3}
|
||||||
|
label={item.type === "theme"
|
||||||
|
? basename(item.path)
|
||||||
|
: item.active ? "Aktuelles Wallpaper" : item.path}
|
||||||
|
/>
|
||||||
|
</box>
|
||||||
|
{item.type === "theme" ? <Swatches item={item} /> : <box />}
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
|
||||||
|
const applyButton = (
|
||||||
|
<button
|
||||||
|
class={`item ${item.active ? "active" : ""}`}
|
||||||
|
hexpand
|
||||||
|
onClicked={() => applyItem(item)}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (item.type === "theme") {
|
||||||
|
return applyButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box class="item-row" spacing={8}>
|
||||||
|
{applyButton}
|
||||||
|
<button
|
||||||
|
class="icon-button preview-button"
|
||||||
|
tooltipText="Vorschau"
|
||||||
|
onClicked={() => previewWallpaper(item)}
|
||||||
|
>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SwitcherWindow(mode: string) {
|
||||||
|
const isTheme = mode === "theme" || mode === "themes";
|
||||||
|
const items = isTheme ? loadThemes() : loadWallpapers();
|
||||||
|
const title = isTheme ? "Theme wechseln" : "Wallpaper wechseln";
|
||||||
|
const empty = isTheme ? `Keine Themes in ${THEME_DIR}` : `Keine Bilder in ${WALLPAPER_DIR}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name="ags-hypr-switcher"
|
||||||
|
namespace="ags-hypr-switcher"
|
||||||
|
class="switcher-window"
|
||||||
|
visible
|
||||||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||||||
|
anchor={Astal.WindowAnchor.TOP}
|
||||||
|
application={app}
|
||||||
|
>
|
||||||
|
<box
|
||||||
|
class="switcher"
|
||||||
|
orientation={Gtk.Orientation.VERTICAL}
|
||||||
|
spacing={16}
|
||||||
|
marginTop={70}
|
||||||
|
>
|
||||||
|
<box class="header">
|
||||||
|
<label class="title" hexpand xalign={0} label={title} />
|
||||||
|
<button
|
||||||
|
class="icon-button close"
|
||||||
|
tooltipText="Schliessen"
|
||||||
|
onClicked={() => app.quit()}
|
||||||
|
>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
{items.length > 0
|
||||||
|
? (
|
||||||
|
<scrolledwindow
|
||||||
|
class="list-scroll"
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
maxContentHeight={600}
|
||||||
|
>
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} spacing={10}>
|
||||||
|
{items.map(item => <ItemButton item={item} />)}
|
||||||
|
</box>
|
||||||
|
</scrolledwindow>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<box class="empty">
|
||||||
|
<label label={empty} />
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.start({
|
||||||
|
css: themeCss(activeTheme()),
|
||||||
|
instanceName: "hypr-switcher",
|
||||||
|
main(mode = "wallpaper") {
|
||||||
|
SwitcherWindow(mode);
|
||||||
|
},
|
||||||
|
});
|
||||||
138
dotfiles/hypr/ags/widget-panel.css
Normal file
138
dotfiles/hypr/ags/widget-panel.css
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
* {
|
||||||
|
all: unset;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "Noto Sans", sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-window {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-scroll {
|
||||||
|
border: 1px solid alpha(@ags_fg, 0.16);
|
||||||
|
border-radius: 16px;
|
||||||
|
background: @ags_bg;
|
||||||
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.48);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
padding: 16px;
|
||||||
|
color: @ags_fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle,
|
||||||
|
.card-subtitle {
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button {
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: @ags_fg;
|
||||||
|
background: alpha(@ags_panel, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-button:hover,
|
||||||
|
.icon-button:focus {
|
||||||
|
background: alpha(@ags_accent, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
color: @ags_accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid alpha(@ags_fg, 0.10);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: alpha(@ags_panel, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric {
|
||||||
|
min-height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-name {
|
||||||
|
color: @ags_fg;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.metric-value {
|
||||||
|
color: @ags_accent;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
min-height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: alpha(@ags_bg_soft, 0.70);
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
min-height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: @ags_accent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta-row {
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sparkline {
|
||||||
|
color: @ags_accent_2;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar-grid {
|
||||||
|
min-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday,
|
||||||
|
.day {
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weekday {
|
||||||
|
color: @ags_muted;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day {
|
||||||
|
background: alpha(@ags_bg_soft, 0.42);
|
||||||
|
}
|
||||||
|
|
||||||
|
.day.today {
|
||||||
|
color: @ags_bg;
|
||||||
|
background: @ags_accent;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.day.muted {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-main {
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: @ags_accent_2;
|
||||||
|
}
|
||||||
435
dotfiles/hypr/ags/widget-panel.tsx
Normal file
435
dotfiles/hypr/ags/widget-panel.tsx
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
import app from "ags/gtk4/app";
|
||||||
|
import { Astal, Gtk } from "ags/gtk4";
|
||||||
|
import { readFile } from "ags/file";
|
||||||
|
import { execAsync } from "ags/process";
|
||||||
|
import { createRoot } from "gnim";
|
||||||
|
import css from "./widget-panel.css";
|
||||||
|
import GLib from "gi://GLib";
|
||||||
|
|
||||||
|
const HYPR_DIR = GLib.getenv("HYPR_DIR") || `${GLib.get_home_dir()}/.config/hypr`;
|
||||||
|
const THEME_DIR = `${HYPR_DIR}/Themes`;
|
||||||
|
const CURRENT_WALLPAPER = `${HYPR_DIR}/current-wallpaper`;
|
||||||
|
const REFRESH_SECONDS = 2;
|
||||||
|
const WEATHER_SECONDS = 20 * 60;
|
||||||
|
const HISTORY_LIMIT = 22;
|
||||||
|
const ESC_KEYVAL = 65307;
|
||||||
|
const START_HIDDEN = GLib.getenv("WIDGET_PANEL_START_HIDDEN") === "1";
|
||||||
|
|
||||||
|
type UiTheme = {
|
||||||
|
accent: string;
|
||||||
|
accent2: string;
|
||||||
|
background: string;
|
||||||
|
backgroundSoft: string;
|
||||||
|
foreground: string;
|
||||||
|
muted: string;
|
||||||
|
panelHex: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SystemSnapshot = {
|
||||||
|
cpu: number;
|
||||||
|
memory: number;
|
||||||
|
disk: number;
|
||||||
|
temp: string;
|
||||||
|
uptime: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastCpuTotal = 0;
|
||||||
|
let lastCpuIdle = 0;
|
||||||
|
let system: SystemSnapshot = {
|
||||||
|
cpu: 0,
|
||||||
|
memory: 0,
|
||||||
|
disk: 0,
|
||||||
|
temp: "n/a",
|
||||||
|
uptime: "n/a",
|
||||||
|
};
|
||||||
|
let cpuHistory: number[] = [];
|
||||||
|
let weather = "Wetter wird geladen...";
|
||||||
|
let weatherUpdated = 0;
|
||||||
|
let timerStarted = false;
|
||||||
|
let disposeRebuild: (() => void) | null = null;
|
||||||
|
let panelWindow: Gtk.Window | null = null;
|
||||||
|
|
||||||
|
function readText(path: string) {
|
||||||
|
try {
|
||||||
|
return readFile(path);
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function listFiles(dir: string, predicate: (path: string, name: string) => boolean) {
|
||||||
|
try {
|
||||||
|
const directory = GLib.Dir.open(dir, 0);
|
||||||
|
const files: string[] = [];
|
||||||
|
let name = directory.read_name();
|
||||||
|
|
||||||
|
while (name !== null) {
|
||||||
|
const path = `${dir}/${name}`;
|
||||||
|
if (predicate(path, name)) {
|
||||||
|
files.push(path);
|
||||||
|
}
|
||||||
|
name = directory.read_name();
|
||||||
|
}
|
||||||
|
|
||||||
|
return files.sort((a, b) => a.localeCompare(b));
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function shellValue(contents: string, key: string) {
|
||||||
|
const regex = new RegExp(`^${key}=(["']?)(.*?)\\1$`, "m");
|
||||||
|
return contents.match(regex)?.[2] || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentWallpaper() {
|
||||||
|
return readText(CURRENT_WALLPAPER).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeTheme(): UiTheme {
|
||||||
|
const fallback = {
|
||||||
|
accent: "#f38ba8",
|
||||||
|
accent2: "#cba6f7",
|
||||||
|
background: "rgba(24, 20, 31, 0.96)",
|
||||||
|
backgroundSoft: "rgba(49, 50, 68, 0.82)",
|
||||||
|
foreground: "#f5e0dc",
|
||||||
|
muted: "#cdd6f4",
|
||||||
|
panelHex: "#313244",
|
||||||
|
};
|
||||||
|
|
||||||
|
const activeWallpaper = currentWallpaper();
|
||||||
|
const themeFile = listFiles(THEME_DIR, (_path, name) => name.endsWith(".theme"))
|
||||||
|
.find(path => shellValue(readText(path), "WALLPAPER") === activeWallpaper);
|
||||||
|
|
||||||
|
if (!themeFile) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = readText(themeFile);
|
||||||
|
return {
|
||||||
|
accent: shellValue(contents, "ACCENT") || fallback.accent,
|
||||||
|
accent2: shellValue(contents, "ACCENT_2") || fallback.accent2,
|
||||||
|
background: shellValue(contents, "BACKGROUND") || fallback.background,
|
||||||
|
backgroundSoft: shellValue(contents, "BACKGROUND_SOFT") || fallback.backgroundSoft,
|
||||||
|
foreground: shellValue(contents, "FOREGROUND") || fallback.foreground,
|
||||||
|
muted: shellValue(contents, "MUTED") || fallback.muted,
|
||||||
|
panelHex: shellValue(contents, "PANEL_HEX") || fallback.panelHex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function themeCss(theme: UiTheme) {
|
||||||
|
return [
|
||||||
|
`@define-color ags_accent ${theme.accent};`,
|
||||||
|
`@define-color ags_accent_2 ${theme.accent2};`,
|
||||||
|
`@define-color ags_bg ${theme.background};`,
|
||||||
|
`@define-color ags_bg_soft ${theme.backgroundSoft};`,
|
||||||
|
`@define-color ags_fg ${theme.foreground};`,
|
||||||
|
`@define-color ags_muted ${theme.muted};`,
|
||||||
|
`@define-color ags_panel ${theme.panelHex};`,
|
||||||
|
css,
|
||||||
|
].join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(value: number, min = 0, max = 100) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function percentLabel(value: number) {
|
||||||
|
return `${Math.round(clamp(value))}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystem() {
|
||||||
|
const cpuLine = readText("/proc/stat").split("\n")[0] || "";
|
||||||
|
const cpuValues = cpuLine.trim().split(/\s+/).slice(1).map(value => Number(value) || 0);
|
||||||
|
const idle = (cpuValues[3] || 0) + (cpuValues[4] || 0);
|
||||||
|
const total = cpuValues.reduce((sum, value) => sum + value, 0);
|
||||||
|
const totalDelta = total - lastCpuTotal;
|
||||||
|
const idleDelta = idle - lastCpuIdle;
|
||||||
|
|
||||||
|
if (lastCpuTotal > 0 && totalDelta > 0) {
|
||||||
|
system.cpu = clamp(((totalDelta - idleDelta) / totalDelta) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCpuTotal = total;
|
||||||
|
lastCpuIdle = idle;
|
||||||
|
|
||||||
|
const meminfo = readText("/proc/meminfo");
|
||||||
|
const memTotal = Number(meminfo.match(/^MemTotal:\s+(\d+)/m)?.[1] || 0);
|
||||||
|
const memAvailable = Number(meminfo.match(/^MemAvailable:\s+(\d+)/m)?.[1] || 0);
|
||||||
|
if (memTotal > 0) {
|
||||||
|
system.memory = clamp(((memTotal - memAvailable) / memTotal) * 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
const uptimeSeconds = Number(readText("/proc/uptime").split(" ")[0] || 0);
|
||||||
|
const hours = Math.floor(uptimeSeconds / 3600);
|
||||||
|
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
||||||
|
system.uptime = `${hours}h ${minutes}m`;
|
||||||
|
|
||||||
|
const temps = listFiles("/sys/class/thermal", (_path, name) => name.startsWith("thermal_zone"))
|
||||||
|
.map(path => Number(readText(`${path}/temp`).trim()) / 1000)
|
||||||
|
.filter(value => Number.isFinite(value) && value > 0);
|
||||||
|
system.temp = temps.length ? `${Math.round(Math.max(...temps))} C` : "n/a";
|
||||||
|
|
||||||
|
execAsync(["bash", "-lc", "df -P / | awk 'NR==2 {gsub(/%/, \"\", $5); print $5}'"])
|
||||||
|
.then(output => {
|
||||||
|
system.disk = clamp(Number(output.trim()) || system.disk);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
cpuHistory = [...cpuHistory, system.cpu].slice(-HISTORY_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWeather(force = false) {
|
||||||
|
if (!force && Date.now() - weatherUpdated < WEATHER_SECONDS * 1000) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
weatherUpdated = Date.now();
|
||||||
|
execAsync(["bash", "-lc", "curl -fsS --max-time 5 'https://wttr.in/?format=%l:+%c+%t+%w' 2>/dev/null || true"])
|
||||||
|
.then(output => {
|
||||||
|
weather = output.trim() || "Wetter nicht erreichbar";
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
weather = "Wetter nicht erreichbar";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sparkline(values: number[]) {
|
||||||
|
const blocks = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
||||||
|
if (!values.length) {
|
||||||
|
return "·".repeat(HISTORY_LIMIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
.map(value => blocks[Math.round((clamp(value) / 100) * (blocks.length - 1))])
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function monthDays() {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = now.getMonth();
|
||||||
|
const first = new Date(year, month, 1);
|
||||||
|
const last = new Date(year, month + 1, 0);
|
||||||
|
const startOffset = (first.getDay() + 6) % 7;
|
||||||
|
const cells: { day: string; today: boolean; muted: boolean }[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < startOffset; i += 1) {
|
||||||
|
cells.push({ day: "", today: false, muted: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let day = 1; day <= last.getDate(); day += 1) {
|
||||||
|
cells.push({ day: String(day), today: day === now.getDate(), muted: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cells.length % 7 !== 0) {
|
||||||
|
cells.push({ day: "", today: false, muted: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
function monthTitle() {
|
||||||
|
return new Intl.DateTimeFormat("de-DE", { month: "long", year: "numeric" }).format(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
function Metric({ icon, label, value }: { icon: string; label: string; value: number }) {
|
||||||
|
return (
|
||||||
|
<box class="metric" orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
||||||
|
<box>
|
||||||
|
<label class="metric-name" hexpand xalign={0} label={`${icon} ${label}`} />
|
||||||
|
<label class="metric-value" label={percentLabel(value)} />
|
||||||
|
</box>
|
||||||
|
<box class="progress">
|
||||||
|
<box class="progress-fill" css={`min-width: ${Math.round(clamp(value) * 2.3)}px;`} />
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SystemMonitor() {
|
||||||
|
return (
|
||||||
|
<box class="card" orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
||||||
|
<label class="card-title" xalign={0} label="System Monitoring" />
|
||||||
|
<Metric icon="" label="CPU" value={system.cpu} />
|
||||||
|
<Metric icon="" label="RAM" value={system.memory} />
|
||||||
|
<Metric icon="" label="Disk /" value={system.disk} />
|
||||||
|
<box class="meta-row">
|
||||||
|
<label hexpand xalign={0} label={` ${system.temp}`} />
|
||||||
|
<label xalign={1} label={` ${system.uptime}`} />
|
||||||
|
</box>
|
||||||
|
<label class="sparkline" xalign={0} label={sparkline(cpuHistory)} />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Calendar() {
|
||||||
|
const cells = monthDays();
|
||||||
|
const weekdays = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box class="card" orientation={Gtk.Orientation.VERTICAL} spacing={10}>
|
||||||
|
<label class="card-title" xalign={0} label={monthTitle()} />
|
||||||
|
<box class="calendar-grid" orientation={Gtk.Orientation.VERTICAL} spacing={5}>
|
||||||
|
<box spacing={5}>
|
||||||
|
{weekdays.map(day => <label class="weekday" label={day} />)}
|
||||||
|
</box>
|
||||||
|
{Array.from({ length: Math.ceil(cells.length / 7) }, (_unused, row) => (
|
||||||
|
<box spacing={5}>
|
||||||
|
{cells.slice(row * 7, row * 7 + 7).map(cell => (
|
||||||
|
<label
|
||||||
|
class={`day ${cell.today ? "today" : ""} ${cell.muted ? "muted" : ""}`}
|
||||||
|
label={cell.day}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
))}
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Weather() {
|
||||||
|
return (
|
||||||
|
<box class="card weather" orientation={Gtk.Orientation.VERTICAL} spacing={10}>
|
||||||
|
<box>
|
||||||
|
<label class="card-title" hexpand xalign={0} label="Wetter" />
|
||||||
|
<button class="icon-button" tooltipText="Aktualisieren" onClicked={() => updateWeather(true)}>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<label class="weather-main" xalign={0} wrap label={weather} />
|
||||||
|
<label class="card-subtitle" xalign={0} label="Quelle: wttr.in" />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePanel() {
|
||||||
|
const win = panelWindow || app.get_window("widget-panel");
|
||||||
|
win?.set_visible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PanelContent() {
|
||||||
|
return (
|
||||||
|
<box class="panel" orientation={Gtk.Orientation.VERTICAL} spacing={14}>
|
||||||
|
<box class="header">
|
||||||
|
<box orientation={Gtk.Orientation.VERTICAL} hexpand>
|
||||||
|
<label class="title" xalign={0} label="Widgetbereich" />
|
||||||
|
<label class="subtitle" xalign={0} label="System, Kalender und Wetter" />
|
||||||
|
</box>
|
||||||
|
<button class="icon-button close" tooltipText="Schliessen" onClicked={hidePanel}>
|
||||||
|
<label label="" />
|
||||||
|
</button>
|
||||||
|
</box>
|
||||||
|
<SystemMonitor />
|
||||||
|
<Calendar />
|
||||||
|
<Weather />
|
||||||
|
</box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function panelLayout() {
|
||||||
|
const geometry = app.monitors[0]?.get_geometry();
|
||||||
|
const screenWidth = geometry?.width || 1280;
|
||||||
|
const screenHeight = geometry?.height || 720;
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: Math.round(clamp(screenWidth * 0.28, 360, 460)),
|
||||||
|
height: Math.round(clamp(screenHeight - 96, 560, 900)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function WidgetPanelWindow() {
|
||||||
|
const layout = panelLayout();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<window
|
||||||
|
name="widget-panel"
|
||||||
|
namespace="widget-panel"
|
||||||
|
class="widget-window"
|
||||||
|
visible={!START_HIDDEN}
|
||||||
|
keymode={Astal.Keymode.EXCLUSIVE}
|
||||||
|
anchor={Astal.WindowAnchor.LEFT | Astal.WindowAnchor.TOP | Astal.WindowAnchor.BOTTOM}
|
||||||
|
application={app}
|
||||||
|
>
|
||||||
|
<Gtk.EventControllerKey onKeyPressed={(_, keyval) => {
|
||||||
|
if (keyval === ESC_KEYVAL) {
|
||||||
|
hidePanel();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}} />
|
||||||
|
<box marginStart={18} marginTop={48} marginBottom={48}>
|
||||||
|
<scrolledwindow
|
||||||
|
class="panel-scroll"
|
||||||
|
widthRequest={layout.width}
|
||||||
|
heightRequest={layout.height}
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
<PanelContent />
|
||||||
|
</scrolledwindow>
|
||||||
|
</box>
|
||||||
|
</window>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rebuild() {
|
||||||
|
const win = app.get_window("widget-panel");
|
||||||
|
if (!win) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeRebuild?.();
|
||||||
|
const layout = panelLayout();
|
||||||
|
createRoot(dispose => {
|
||||||
|
disposeRebuild = dispose;
|
||||||
|
win.set_child(
|
||||||
|
<box marginStart={18} marginTop={48} marginBottom={48}>
|
||||||
|
<scrolledwindow
|
||||||
|
class="panel-scroll"
|
||||||
|
widthRequest={layout.width}
|
||||||
|
heightRequest={layout.height}
|
||||||
|
hscrollbarPolicy={Gtk.PolicyType.NEVER}
|
||||||
|
vscrollbarPolicy={Gtk.PolicyType.AUTOMATIC}
|
||||||
|
>
|
||||||
|
<PanelContent />
|
||||||
|
</scrolledwindow>
|
||||||
|
</box> as Gtk.Widget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startTimer() {
|
||||||
|
if (timerStarted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
timerStarted = true;
|
||||||
|
updateSystem();
|
||||||
|
updateWeather(true);
|
||||||
|
|
||||||
|
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, REFRESH_SECONDS, () => {
|
||||||
|
updateSystem();
|
||||||
|
updateWeather();
|
||||||
|
rebuild();
|
||||||
|
return GLib.SOURCE_CONTINUE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
app.start({
|
||||||
|
instanceName: "widget-panel",
|
||||||
|
css: themeCss(activeTheme()),
|
||||||
|
main() {
|
||||||
|
panelWindow = WidgetPanelWindow() as Gtk.Window;
|
||||||
|
app.add_window(panelWindow);
|
||||||
|
if (!START_HIDDEN) {
|
||||||
|
panelWindow.present();
|
||||||
|
}
|
||||||
|
GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
|
||||||
|
startTimer();
|
||||||
|
rebuild();
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
5
dotfiles/hypr/current-theme.conf
Normal file
5
dotfiles/hypr/current-theme.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Generated by ~/.config/hypr/Scripts/theme-menu.sh
|
||||||
|
general {
|
||||||
|
col.active_border = rgba(00ff9cee) rgba(00cc88ee) 45deg
|
||||||
|
col.inactive_border = rgba(2a2a2aaa)
|
||||||
|
}
|
||||||
1
dotfiles/hypr/current-wallpaper
Normal file
1
dotfiles/hypr/current-wallpaper
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/home/pascal/Bilder/Wallpaper/forest.jpg
|
||||||
310
dotfiles/hypr/hyprland.conf
Normal file
310
dotfiles/hypr/hyprland.conf
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
# #######################################################################################
|
||||||
|
# AUTOGENERATED HYPRLAND CONFIG.
|
||||||
|
# EDIT THIS CONFIG ACCORDING TO THE WIKI INSTRUCTIONS.
|
||||||
|
# #######################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# This is an example Hyprland config file.
|
||||||
|
# Refer to the wiki for more information.
|
||||||
|
# https://wiki.hypr.land/Configuring/
|
||||||
|
|
||||||
|
# Please note not all available settings / options are set here.
|
||||||
|
# For a full list, see the wiki
|
||||||
|
|
||||||
|
# You can split this configuration into multiple files
|
||||||
|
# Create your files separately and then link them to this file like this:
|
||||||
|
# source = ~/.config/hypr/myColors.conf
|
||||||
|
|
||||||
|
|
||||||
|
################
|
||||||
|
### MONITORS ###
|
||||||
|
################
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Monitors/
|
||||||
|
monitor=,preferred,auto,1
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
### MY PROGRAMS ###
|
||||||
|
###################
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Keywords/
|
||||||
|
|
||||||
|
# Set programs that you use
|
||||||
|
$terminal = kitty
|
||||||
|
$fileManager = nautilus
|
||||||
|
$menu = wofi
|
||||||
|
|
||||||
|
|
||||||
|
#################
|
||||||
|
### AUTOSTART ###
|
||||||
|
#################
|
||||||
|
|
||||||
|
# Autostart necessary processes (like notifications daemons, status bars, etc.)
|
||||||
|
# Or execute your favorite apps at launch like this:
|
||||||
|
|
||||||
|
exec-once = sh -c 'if command -v awww-daemon >/dev/null 2>&1; then awww-daemon; elif command -v swww-daemon >/dev/null 2>&1; then swww-daemon; else hyprpaper; fi'
|
||||||
|
exec-once = waybar
|
||||||
|
exec-once = swaync
|
||||||
|
exec-once = env WIDGET_PANEL_START_HIDDEN=1 ~/.config/hypr/Scripts/widget-panel.sh
|
||||||
|
|
||||||
|
#############################
|
||||||
|
### ENVIRONMENT VARIABLES ###
|
||||||
|
#############################
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Environment-variables/
|
||||||
|
|
||||||
|
env = XCURSOR_SIZE,24
|
||||||
|
env = HYPRCURSOR_SIZE,24
|
||||||
|
env = QT_QPA_PLATFORMTHEME,qt6ct
|
||||||
|
env = QT_STYLE_OVERRIDE,Fusion
|
||||||
|
|
||||||
|
# https://wiki.hypr.land/Configuring/Variables/#general
|
||||||
|
general {
|
||||||
|
gaps_in = 6
|
||||||
|
gaps_out = 25
|
||||||
|
|
||||||
|
border_size = 2
|
||||||
|
|
||||||
|
col.active_border = rgba(00ff9cee) rgba(00cc88ee) 45deg
|
||||||
|
col.inactive_border = rgba(2a2a2aaa)
|
||||||
|
|
||||||
|
resize_on_border = false
|
||||||
|
allow_tearing = false
|
||||||
|
|
||||||
|
layout = dwindle
|
||||||
|
}
|
||||||
|
|
||||||
|
source = ~/.config/hypr/current-theme.conf
|
||||||
|
|
||||||
|
# https://wiki.hypr.land/Configuring/Variables/#decoration
|
||||||
|
decoration {
|
||||||
|
rounding = 14
|
||||||
|
rounding_power = 3
|
||||||
|
|
||||||
|
active_opacity = 0.90
|
||||||
|
inactive_opacity = 0.75
|
||||||
|
|
||||||
|
shadow {
|
||||||
|
enabled = true
|
||||||
|
range = 20
|
||||||
|
render_power = 4
|
||||||
|
color = rgba(000000aa)
|
||||||
|
}
|
||||||
|
|
||||||
|
blur {
|
||||||
|
enabled = true
|
||||||
|
size = 6
|
||||||
|
passes = 2
|
||||||
|
|
||||||
|
vibrancy = 0.25
|
||||||
|
vibrancy_darkness = 0.3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# https://wiki.hypr.land/Configuring/Variables/#animations
|
||||||
|
animations {
|
||||||
|
enabled = yes
|
||||||
|
|
||||||
|
# Curves
|
||||||
|
bezier = smoothOut, 0.22, 1, 0.36, 1
|
||||||
|
bezier = smoothIn, 0.64, 0, 0.78, 0
|
||||||
|
bezier = smoothInOut, 0.83, 0, 0.17, 1
|
||||||
|
bezier = soft, 0.25, 0.1, 0.25, 1
|
||||||
|
bezier = quick, 0.3, 0, 0.1, 1
|
||||||
|
|
||||||
|
# Animations
|
||||||
|
animation = global, 1, 8, soft
|
||||||
|
|
||||||
|
animation = border, 1, 5, smoothOut
|
||||||
|
animation = fade, 1, 5, soft
|
||||||
|
|
||||||
|
animation = windows, 1, 6, smoothOut
|
||||||
|
animation = windowsIn, 1, 6, smoothOut, popin 85%
|
||||||
|
animation = windowsOut, 1, 5, smoothIn, popin 85%
|
||||||
|
|
||||||
|
animation = layers, 1, 5, smoothOut
|
||||||
|
animation = layersIn, 1, 5, smoothOut, fade
|
||||||
|
animation = layersOut, 1, 4, smoothIn, fade
|
||||||
|
|
||||||
|
animation = fadeIn, 1, 4, soft
|
||||||
|
animation = fadeOut, 1, 4, soft
|
||||||
|
animation = fadeLayersIn, 1, 4, soft
|
||||||
|
animation = fadeLayersOut, 1, 4, soft
|
||||||
|
|
||||||
|
animation = workspaces, 1, 5, smoothInOut, slide
|
||||||
|
animation = workspacesIn, 1, 5, smoothInOut, slide
|
||||||
|
animation = workspacesOut, 1, 5, smoothInOut, slide
|
||||||
|
|
||||||
|
animation = zoomFactor, 1, 6, quick
|
||||||
|
}
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
|
||||||
|
dwindle {
|
||||||
|
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
|
||||||
|
preserve_split = true # You probably want this
|
||||||
|
}
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Master-Layout/ for more
|
||||||
|
master {
|
||||||
|
new_status = master
|
||||||
|
}
|
||||||
|
|
||||||
|
# https://wiki.hypr.land/Configuring/Variables/#misc
|
||||||
|
misc {
|
||||||
|
force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers
|
||||||
|
disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :(
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#############
|
||||||
|
### INPUT ###
|
||||||
|
#############
|
||||||
|
|
||||||
|
# https://wiki.hypr.land/Configuring/Variables/#input
|
||||||
|
input {
|
||||||
|
kb_layout = de
|
||||||
|
kb_variant =
|
||||||
|
kb_model =
|
||||||
|
kb_options =
|
||||||
|
kb_rules =
|
||||||
|
|
||||||
|
follow_mouse = 1
|
||||||
|
|
||||||
|
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
|
||||||
|
|
||||||
|
touchpad {
|
||||||
|
natural_scroll = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Gestures
|
||||||
|
gesture = 3, horizontal, workspace
|
||||||
|
|
||||||
|
# Example per-device config
|
||||||
|
# See https://wiki.hypr.land/Configuring/Keywords/#per-device-input-configs for more
|
||||||
|
device {
|
||||||
|
name = epic-mouse-v1
|
||||||
|
sensitivity = -0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
### KEYBINDINGS ###
|
||||||
|
###################
|
||||||
|
|
||||||
|
# See https://wiki.hypr.land/Configuring/Keywords/
|
||||||
|
$mainMod = SUPER # Sets "Windows" key as main modifier
|
||||||
|
|
||||||
|
# Example binds, see https://wiki.hypr.land/Configuring/Binds/ for more
|
||||||
|
bind = $mainMod, T, exec, $terminal
|
||||||
|
bind = $mainMod, Q, killactive,
|
||||||
|
bind = $mainMod, M, exec, ~/.config/hypr/Scripts/main-menu.sh
|
||||||
|
bind = $mainMod, E, exec, $fileManager
|
||||||
|
bind = $mainMod, N, exec, swaync-client -t
|
||||||
|
bind = $mainMod, V, togglefloating,
|
||||||
|
bind = $mainMod, R, exec, $menu
|
||||||
|
bind = $mainMod, W, exec, ~/.config/hypr/Scripts/widget-panel.sh
|
||||||
|
bind = $mainMod SHIFT, W, exec, ~/.config/hypr/Scripts/ags-switcher.sh wallpaper
|
||||||
|
bind = $mainMod, P, exec, ~/.config/hypr/Scripts/power-menu.py
|
||||||
|
bind = $mainMod, L, exec, hyprlock
|
||||||
|
bind = $mainMod SHIFT, T, exec, ~/.config/hypr/Scripts/ags-switcher.sh theme
|
||||||
|
bind = $mainMod SHIFT, S, exec, ~/.config/hypr/Scripts/screenshot-menu.sh annotate-region
|
||||||
|
bind = $mainMod SHIFT, P, pseudo, # dwindle
|
||||||
|
bind = $mainMod, J, layoutmsg, togglesplit # dwindle
|
||||||
|
bind = $mainMod, SPACE, exec, ~/.config/hypr/Scripts/toggle-wofi.sh
|
||||||
|
# Move focus with mainMod + arrow keys
|
||||||
|
bind = $mainMod, left, movefocus, l
|
||||||
|
bind = $mainMod, right, movefocus, r
|
||||||
|
bind = $mainMod, up, movefocus, u
|
||||||
|
bind = $mainMod, down, movefocus, d
|
||||||
|
|
||||||
|
# Switch existing workspaces with CTRL + mainMod + arrow keys
|
||||||
|
bind = CTRL $mainMod, left, workspace, e-1
|
||||||
|
bind = CTRL $mainMod, right, workspace, e+1
|
||||||
|
|
||||||
|
# Switch workspaces with mainMod + [0-9]
|
||||||
|
bind = $mainMod, 1, workspace, 1
|
||||||
|
bind = $mainMod, 2, workspace, 2
|
||||||
|
bind = $mainMod, 3, workspace, 3
|
||||||
|
bind = $mainMod, 4, workspace, 4
|
||||||
|
bind = $mainMod, 5, workspace, 5
|
||||||
|
bind = $mainMod, 6, workspace, 6
|
||||||
|
bind = $mainMod, 7, workspace, 7
|
||||||
|
bind = $mainMod, 8, workspace, 8
|
||||||
|
bind = $mainMod, 9, workspace, 9
|
||||||
|
bind = $mainMod, 0, workspace, 10
|
||||||
|
|
||||||
|
# Move active window to a workspace with mainMod + SHIFT + [0-9]
|
||||||
|
bind = $mainMod SHIFT, 1, movetoworkspace, 1
|
||||||
|
bind = $mainMod SHIFT, 2, movetoworkspace, 2
|
||||||
|
bind = $mainMod SHIFT, 3, movetoworkspace, 3
|
||||||
|
bind = $mainMod SHIFT, 4, movetoworkspace, 4
|
||||||
|
bind = $mainMod SHIFT, 5, movetoworkspace, 5
|
||||||
|
bind = $mainMod SHIFT, 6, movetoworkspace, 6
|
||||||
|
bind = $mainMod SHIFT, 7, movetoworkspace, 7
|
||||||
|
bind = $mainMod SHIFT, 8, movetoworkspace, 8
|
||||||
|
bind = $mainMod SHIFT, 9, movetoworkspace, 9
|
||||||
|
bind = $mainMod SHIFT, 0, movetoworkspace, 10
|
||||||
|
|
||||||
|
# Example special workspace (scratchpad)
|
||||||
|
bind = $mainMod, S, togglespecialworkspace, magic
|
||||||
|
bind = CTRL $mainMod, S, movetoworkspace, special:magic
|
||||||
|
|
||||||
|
# Scroll through existing workspaces with mainMod + scroll
|
||||||
|
bind = $mainMod, mouse_down, workspace, e+1
|
||||||
|
bind = $mainMod, mouse_up, workspace, e-1
|
||||||
|
|
||||||
|
# Move/resize windows with mainMod + LMB/RMB and dragging
|
||||||
|
bindm = $mainMod, mouse:272, movewindow
|
||||||
|
bindm = $mainMod, mouse:273, resizewindow
|
||||||
|
|
||||||
|
# Laptop multimedia keys for volume and LCD brightness
|
||||||
|
bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+
|
||||||
|
bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-
|
||||||
|
bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle
|
||||||
|
bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle
|
||||||
|
bindel = ,XF86MonBrightnessUp, exec, brightnessctl -e4 -n2 set 5%+
|
||||||
|
bindel = ,XF86MonBrightnessDown, exec, brightnessctl -e4 -n2 set 5%-
|
||||||
|
|
||||||
|
# Requires playerctl
|
||||||
|
bindl = , XF86AudioNext, exec, playerctl next
|
||||||
|
bindl = , XF86AudioPause, exec, playerctl play-pause
|
||||||
|
bindl = , XF86AudioPlay, exec, playerctl play-pause
|
||||||
|
bindl = , XF86AudioPrev, exec, playerctl previous
|
||||||
|
|
||||||
|
##############################
|
||||||
|
### WINDOWS AND WORKSPACES ###
|
||||||
|
##############################
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
# Ignore maximize requests from all apps. You'll probably like this.
|
||||||
|
name = suppress-maximize-events
|
||||||
|
match:class = .*
|
||||||
|
|
||||||
|
suppress_event = maximize
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
# Fix some dragging issues with XWayland
|
||||||
|
name = fix-xwayland-drags
|
||||||
|
match:class = ^$
|
||||||
|
match:title = ^$
|
||||||
|
match:xwayland = true
|
||||||
|
match:float = true
|
||||||
|
match:fullscreen = false
|
||||||
|
match:pin = false
|
||||||
|
|
||||||
|
no_focus = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Hyprland-run windowrule
|
||||||
|
windowrule {
|
||||||
|
name = move-hyprland-run
|
||||||
|
|
||||||
|
match:class = hyprland-run
|
||||||
|
|
||||||
|
move = 20 monitor_h-120
|
||||||
|
float = yes
|
||||||
|
}
|
||||||
98
dotfiles/hypr/hyprlock.conf
Normal file
98
dotfiles/hypr/hyprlock.conf
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Generated by ~/.config/hypr/Scripts/theme-menu.sh
|
||||||
|
|
||||||
|
auth {
|
||||||
|
pam:enabled = true
|
||||||
|
pam:module = hyprlock
|
||||||
|
|
||||||
|
fingerprint:enabled = true
|
||||||
|
fingerprint:ready_message = Finger auflegen zum Entsperren
|
||||||
|
fingerprint:present_message = Fingerabdruck wird gelesen...
|
||||||
|
fingerprint:retry_delay = 250
|
||||||
|
}
|
||||||
|
|
||||||
|
general {
|
||||||
|
disable_loading_bar = true
|
||||||
|
hide_cursor = true
|
||||||
|
grace = 0
|
||||||
|
ignore_empty_input = true
|
||||||
|
}
|
||||||
|
|
||||||
|
background {
|
||||||
|
monitor =
|
||||||
|
path = /home/pascal/Bilder/Wallpaper/forest.jpg
|
||||||
|
blur_passes = 3
|
||||||
|
blur_size = 8
|
||||||
|
noise = 0.011
|
||||||
|
contrast = 1.05
|
||||||
|
brightness = 0.72
|
||||||
|
vibrancy = 0.18
|
||||||
|
vibrancy_darkness = 0.25
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
monitor =
|
||||||
|
text = Forest Neon
|
||||||
|
color = rgba(0,204,136, 0.90)
|
||||||
|
font_size = 22
|
||||||
|
font_family = JetBrainsMono Nerd Font
|
||||||
|
position = 0, 260
|
||||||
|
halign = center
|
||||||
|
valign = center
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
monitor =
|
||||||
|
text = cmd[update:1000] date +"%H:%M"
|
||||||
|
color = rgba(0,255,156, 0.96)
|
||||||
|
font_size = 92
|
||||||
|
font_family = JetBrainsMono Nerd Font
|
||||||
|
position = 0, 165
|
||||||
|
halign = center
|
||||||
|
valign = center
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
monitor =
|
||||||
|
text = cmd[update:60000] date +"%A, %d. %B"
|
||||||
|
color = rgba(205,214,244, 0.82)
|
||||||
|
font_size = 22
|
||||||
|
font_family = JetBrainsMono Nerd Font
|
||||||
|
position = 0, 92
|
||||||
|
halign = center
|
||||||
|
valign = center
|
||||||
|
}
|
||||||
|
|
||||||
|
input-field {
|
||||||
|
monitor =
|
||||||
|
size = 390, 62
|
||||||
|
outline_thickness = 2
|
||||||
|
dots_size = 0.24
|
||||||
|
dots_spacing = 0.28
|
||||||
|
dots_center = true
|
||||||
|
fade_on_empty = false
|
||||||
|
rounding = 16
|
||||||
|
|
||||||
|
outer_color = rgba(0,255,156, 0.72)
|
||||||
|
inner_color = rgba(20,20,30, 0.76)
|
||||||
|
font_color = rgba(205,214,244, 0.92)
|
||||||
|
placeholder_text = <i>Passwort oder Fingerprint</i>
|
||||||
|
|
||||||
|
check_color = rgba(0,204,136, 0.88)
|
||||||
|
fail_color = rgba(243,139,168, 0.95)
|
||||||
|
capslock_color = rgba(249,226,175, 0.95)
|
||||||
|
|
||||||
|
position = 0, -20
|
||||||
|
halign = center
|
||||||
|
valign = center
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
monitor =
|
||||||
|
text = cmd[update:1000] echo " pascal@PDEV-Yoga"
|
||||||
|
color = rgba(204,204,204, 0.70)
|
||||||
|
font_size = 16
|
||||||
|
font_family = JetBrainsMono Nerd Font
|
||||||
|
position = 0, -92
|
||||||
|
halign = center
|
||||||
|
valign = center
|
||||||
|
}
|
||||||
5
dotfiles/hypr/hyprpaper.conf
Normal file
5
dotfiles/hypr/hyprpaper.conf
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
wallpaper {
|
||||||
|
monitor =
|
||||||
|
path = /home/pascal/Bilder/Wallpaper/forest.jpg
|
||||||
|
fit_mode = cover
|
||||||
|
}
|
||||||
279
dotfiles/hypr/sddm-theme/pascal-hypr/Main.qml
Normal file
279
dotfiles/hypr/sddm-theme/pascal-hypr/Main.qml
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
import SddmComponents 2.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
width: 1920
|
||||||
|
height: 1080
|
||||||
|
color: config.backgroundColor || "#18141f"
|
||||||
|
|
||||||
|
property int sessionIndex: session.index
|
||||||
|
property color accent: config.accent || "#f38ba8"
|
||||||
|
property color accent2: config.accent2 || "#cba6f7"
|
||||||
|
property color backgroundColor: config.backgroundColor || "#18141f"
|
||||||
|
property color panelColor: config.panelColor || "#313244"
|
||||||
|
property color foreground: config.foreground || "#f5e0dc"
|
||||||
|
property color muted: config.muted || "#cdd6f4"
|
||||||
|
property color selectedText: config.selectedText || "#11111b"
|
||||||
|
property string themeName: config.themeName || "Rose Night"
|
||||||
|
|
||||||
|
TextConstants { id: textConstants }
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: sddm
|
||||||
|
|
||||||
|
function onLoginSucceeded() {
|
||||||
|
message.text = textConstants.loginSucceeded
|
||||||
|
message.color = accent2
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoginFailed() {
|
||||||
|
password.text = ""
|
||||||
|
message.text = textConstants.loginFailed
|
||||||
|
message.color = "#f38ba8"
|
||||||
|
}
|
||||||
|
|
||||||
|
function onInformationMessage(text) {
|
||||||
|
message.text = text
|
||||||
|
message.color = "#f38ba8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Background {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: config.background || ""
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error) {
|
||||||
|
source = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: "#000000"
|
||||||
|
opacity: 0.42
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: Qt.rgba(0, 0, 0, 0.10) }
|
||||||
|
GradientStop { position: 0.58; color: Qt.rgba(0, 0, 0, 0.38) }
|
||||||
|
GradientStop { position: 1.0; color: Qt.rgba(0, 0, 0, 0.62) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: {
|
||||||
|
clock.text = Qt.formatDateTime(new Date(), "HH:mm")
|
||||||
|
date.text = Qt.formatDateTime(new Date(), "dddd, dd. MMMM yyyy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Math.max(42, parent.width * 0.055)
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: Math.max(40, parent.height * 0.055)
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: clock
|
||||||
|
color: foreground
|
||||||
|
font.pixelSize: Math.max(64, root.height * 0.105)
|
||||||
|
font.weight: Font.Light
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: date
|
||||||
|
color: muted
|
||||||
|
opacity: 0.88
|
||||||
|
font.pixelSize: Math.max(16, root.height * 0.023)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: panel
|
||||||
|
width: Math.min(430, root.width - 48)
|
||||||
|
height: 430
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Math.max(28, parent.width * 0.075)
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
radius: 18
|
||||||
|
color: panelColor
|
||||||
|
opacity: 0.90
|
||||||
|
border.color: accent
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
height: 4
|
||||||
|
radius: 2
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: accent }
|
||||||
|
GradientStop { position: 1.0; color: accent2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 34
|
||||||
|
spacing: 14
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: sddm.hostName
|
||||||
|
color: foreground
|
||||||
|
font.pixelSize: 26
|
||||||
|
font.bold: true
|
||||||
|
elide: Text.ElideRight
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: themeName
|
||||||
|
color: accent
|
||||||
|
font.pixelSize: 14
|
||||||
|
width: parent.width
|
||||||
|
opacity: 0.92
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { width: 1; height: 10 }
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Benutzer"
|
||||||
|
color: muted
|
||||||
|
font.pixelSize: 12
|
||||||
|
opacity: 0.78
|
||||||
|
}
|
||||||
|
|
||||||
|
TextBox {
|
||||||
|
id: username
|
||||||
|
width: parent.width
|
||||||
|
height: 42
|
||||||
|
text: userModel.lastUser
|
||||||
|
color: backgroundColor
|
||||||
|
borderColor: Qt.rgba(accent.r, accent.g, accent.b, 0.40)
|
||||||
|
focusColor: accent
|
||||||
|
hoverColor: accent2
|
||||||
|
textColor: foreground
|
||||||
|
radius: 10
|
||||||
|
font.pixelSize: 16
|
||||||
|
KeyNavigation.tab: password
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Passwort"
|
||||||
|
color: muted
|
||||||
|
font.pixelSize: 12
|
||||||
|
opacity: 0.78
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordBox {
|
||||||
|
id: password
|
||||||
|
width: parent.width
|
||||||
|
height: 42
|
||||||
|
color: backgroundColor
|
||||||
|
borderColor: Qt.rgba(accent.r, accent.g, accent.b, 0.40)
|
||||||
|
focusColor: accent
|
||||||
|
hoverColor: accent2
|
||||||
|
textColor: foreground
|
||||||
|
radius: 10
|
||||||
|
font.pixelSize: 16
|
||||||
|
KeyNavigation.backtab: username
|
||||||
|
KeyNavigation.tab: loginButton
|
||||||
|
|
||||||
|
Keys.onPressed: function(event) {
|
||||||
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||||
|
sddm.login(username.text, password.text, sessionIndex)
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComboBox {
|
||||||
|
id: session
|
||||||
|
width: parent.width
|
||||||
|
height: 36
|
||||||
|
model: sessionModel
|
||||||
|
index: sessionModel.lastIndex
|
||||||
|
color: backgroundColor
|
||||||
|
borderColor: Qt.rgba(accent.r, accent.g, accent.b, 0.40)
|
||||||
|
focusColor: accent
|
||||||
|
hoverColor: accent2
|
||||||
|
menuColor: panelColor
|
||||||
|
textColor: foreground
|
||||||
|
arrowColor: accent
|
||||||
|
arrowIcon: "angle-down.png"
|
||||||
|
KeyNavigation.backtab: password
|
||||||
|
KeyNavigation.tab: loginButton
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: loginButton
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
text: "Anmelden"
|
||||||
|
color: accent
|
||||||
|
activeColor: accent2
|
||||||
|
pressedColor: accent2
|
||||||
|
disabledColor: muted
|
||||||
|
borderColor: accent2
|
||||||
|
textColor: selectedText
|
||||||
|
font.pixelSize: 15
|
||||||
|
onClicked: sddm.login(username.text, password.text, sessionIndex)
|
||||||
|
KeyNavigation.backtab: session
|
||||||
|
KeyNavigation.tab: shutdownButton
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: message
|
||||||
|
width: parent.width
|
||||||
|
color: muted
|
||||||
|
text: ""
|
||||||
|
font.pixelSize: 13
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Math.max(28, parent.width * 0.075)
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: Math.max(26, parent.height * 0.045)
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: shutdownButton
|
||||||
|
text: "Ausschalten"
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.42)
|
||||||
|
activeColor: accent
|
||||||
|
pressedColor: accent2
|
||||||
|
borderColor: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.35)
|
||||||
|
textColor: foreground
|
||||||
|
onClicked: sddm.powerOff()
|
||||||
|
KeyNavigation.tab: rebootButton
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: rebootButton
|
||||||
|
text: "Neustart"
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.42)
|
||||||
|
activeColor: accent
|
||||||
|
pressedColor: accent2
|
||||||
|
borderColor: Qt.rgba(foreground.r, foreground.g, foreground.b, 0.35)
|
||||||
|
textColor: foreground
|
||||||
|
onClicked: sddm.reboot()
|
||||||
|
KeyNavigation.backtab: shutdownButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
dotfiles/hypr/sddm-theme/pascal-hypr/metadata.desktop
Normal file
13
dotfiles/hypr/sddm-theme/pascal-hypr/metadata.desktop
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[SddmGreeterTheme]
|
||||||
|
Name=Pascal Hypr
|
||||||
|
Description=Theme-aware SDDM login screen for Pascal's Hyprland setup
|
||||||
|
Author=Pascal + Codex
|
||||||
|
Copyright=2026
|
||||||
|
License=MIT
|
||||||
|
Type=sddm-theme
|
||||||
|
Version=1.0
|
||||||
|
MainScript=Main.qml
|
||||||
|
ConfigFile=theme.conf
|
||||||
|
Theme-Id=pascal-hypr
|
||||||
|
Theme-API=2.0
|
||||||
|
|
||||||
11
dotfiles/hypr/sddm-theme/pascal-hypr/theme.conf
Normal file
11
dotfiles/hypr/sddm-theme/pascal-hypr/theme.conf
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[General]
|
||||||
|
background=/var/lib/pascal-sddm-theme/wallpaper.jpg
|
||||||
|
themeName=Rose Night
|
||||||
|
accent=#f38ba8
|
||||||
|
accent2=#cba6f7
|
||||||
|
backgroundColor=#18141f
|
||||||
|
panelColor=#313244
|
||||||
|
foreground=#f5e0dc
|
||||||
|
muted=#cdd6f4
|
||||||
|
selectedText=#11111b
|
||||||
|
|
||||||
6
dotfiles/hypr/sddm-theme/sddm.conf
Normal file
6
dotfiles/hypr/sddm-theme/sddm.conf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[Autologin]
|
||||||
|
Session=hyprland
|
||||||
|
|
||||||
|
[Theme]
|
||||||
|
Current=pascal-hypr
|
||||||
|
|
||||||
1
dotfiles/kitty/kitty.conf
Normal file
1
dotfiles/kitty/kitty.conf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
background_opacity 0.70
|
||||||
4
dotfiles/qt5ct/colors/ForestNeon.conf
Normal file
4
dotfiles/qt5ct/colors/ForestNeon.conf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[ColorScheme]
|
||||||
|
active_colors=#ffcdd6f4, #ff282837, #ffffffff, #ffcccccc, #ff14141e, #ff282837, #ffcdd6f4, #ffffffff, #ffcdd6f4, #ff14141e, #ff14141e, #ff000000, #ff00ff9c, #ff000000, #ff00cc88, #fff38ba8, #ff282837, #ffcdd6f4, #ff282837, #ffcdd6f4, #80cccccc
|
||||||
|
disabled_colors=#ffcccccc, #ff282837, #ffffffff, #ffcccccc, #ff14141e, #ff282837, #ffcccccc, #ffffffff, #ffcccccc, #ff14141e, #ff14141e, #ff000000, #ff282837, #ffcccccc, #ff00cc88, #fff38ba8, #ff282837, #ffcccccc, #ff282837, #ffcccccc, #80cccccc
|
||||||
|
inactive_colors=#ffcdd6f4, #ff282837, #ffffffff, #ffcccccc, #ff14141e, #ff282837, #ffcdd6f4, #ffffffff, #ffcdd6f4, #ff14141e, #ff14141e, #ff000000, #ff00ff9c, #ff000000, #ff00cc88, #fff38ba8, #ff282837, #ffcdd6f4, #ff282837, #ffcdd6f4, #80cccccc
|
||||||
4
dotfiles/qt5ct/colors/RoseNight.conf
Normal file
4
dotfiles/qt5ct/colors/RoseNight.conf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[ColorScheme]
|
||||||
|
active_colors=#fff5e0dc, #ff313244, #ffffffff, #ffcdd6f4, #ff18141f, #ff313244, #fff5e0dc, #ffffffff, #fff5e0dc, #ff18141f, #ff18141f, #ff000000, #fff38ba8, #ff11111b, #ffcba6f7, #fff38ba8, #ff313244, #fff5e0dc, #ff313244, #fff5e0dc, #80cdd6f4
|
||||||
|
disabled_colors=#ffcdd6f4, #ff313244, #ffffffff, #ffcdd6f4, #ff18141f, #ff313244, #ffcdd6f4, #ffffffff, #ffcdd6f4, #ff18141f, #ff18141f, #ff000000, #ff313244, #ffcdd6f4, #ffcba6f7, #fff38ba8, #ff313244, #ffcdd6f4, #ff313244, #ffcdd6f4, #80cdd6f4
|
||||||
|
inactive_colors=#fff5e0dc, #ff313244, #ffffffff, #ffcdd6f4, #ff18141f, #ff313244, #fff5e0dc, #ffffffff, #fff5e0dc, #ff18141f, #ff18141f, #ff000000, #fff38ba8, #ff11111b, #ffcba6f7, #fff38ba8, #ff313244, #fff5e0dc, #ff313244, #fff5e0dc, #80cdd6f4
|
||||||
28
dotfiles/qt5ct/qt5ct.conf
Normal file
28
dotfiles/qt5ct/qt5ct.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[Appearance]
|
||||||
|
color_scheme_path=/home/pascal/.config/qt5ct/colors/ForestNeon.conf
|
||||||
|
custom_palette=true
|
||||||
|
icon_theme=Papirus-ForestNeon
|
||||||
|
standard_dialogs=default
|
||||||
|
style=Fusion
|
||||||
|
|
||||||
|
[Fonts]
|
||||||
|
fixed="JetBrainsMono Nerd Font,10,-1,5,50,0,0,0,0,0"
|
||||||
|
general="JetBrainsMono Nerd Font,10,-1,5,50,0,0,0,0,0"
|
||||||
|
|
||||||
|
[Interface]
|
||||||
|
activate_item_on_single_click=1
|
||||||
|
buttonbox_layout=0
|
||||||
|
cursor_flash_time=1000
|
||||||
|
dialog_buttons_have_icons=1
|
||||||
|
double_click_interval=400
|
||||||
|
gui_effects=@Invalid()
|
||||||
|
keyboard_scheme=2
|
||||||
|
menus_have_icons=true
|
||||||
|
show_shortcuts_in_context_menus=true
|
||||||
|
stylesheets=@Invalid()
|
||||||
|
toolbutton_style=4
|
||||||
|
underline_shortcut=1
|
||||||
|
wheel_scroll_lines=3
|
||||||
|
|
||||||
|
[SettingsWindow]
|
||||||
|
geometry=@ByteArray()
|
||||||
4
dotfiles/qt6ct/colors/ForestNeon.conf
Normal file
4
dotfiles/qt6ct/colors/ForestNeon.conf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[ColorScheme]
|
||||||
|
active_colors=#ffcdd6f4, #ff282837, #ffffffff, #ffcccccc, #ff14141e, #ff282837, #ffcdd6f4, #ffffffff, #ffcdd6f4, #ff14141e, #ff14141e, #ff000000, #ff00ff9c, #ff000000, #ff00cc88, #fff38ba8, #ff282837, #ffcdd6f4, #ff282837, #ffcdd6f4, #80cccccc
|
||||||
|
disabled_colors=#ffcccccc, #ff282837, #ffffffff, #ffcccccc, #ff14141e, #ff282837, #ffcccccc, #ffffffff, #ffcccccc, #ff14141e, #ff14141e, #ff000000, #ff282837, #ffcccccc, #ff00cc88, #fff38ba8, #ff282837, #ffcccccc, #ff282837, #ffcccccc, #80cccccc
|
||||||
|
inactive_colors=#ffcdd6f4, #ff282837, #ffffffff, #ffcccccc, #ff14141e, #ff282837, #ffcdd6f4, #ffffffff, #ffcdd6f4, #ff14141e, #ff14141e, #ff000000, #ff00ff9c, #ff000000, #ff00cc88, #fff38ba8, #ff282837, #ffcdd6f4, #ff282837, #ffcdd6f4, #80cccccc
|
||||||
4
dotfiles/qt6ct/colors/RoseNight.conf
Normal file
4
dotfiles/qt6ct/colors/RoseNight.conf
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[ColorScheme]
|
||||||
|
active_colors=#fff5e0dc, #ff313244, #ffffffff, #ffcdd6f4, #ff18141f, #ff313244, #fff5e0dc, #ffffffff, #fff5e0dc, #ff18141f, #ff18141f, #ff000000, #fff38ba8, #ff11111b, #ffcba6f7, #fff38ba8, #ff313244, #fff5e0dc, #ff313244, #fff5e0dc, #80cdd6f4
|
||||||
|
disabled_colors=#ffcdd6f4, #ff313244, #ffffffff, #ffcdd6f4, #ff18141f, #ff313244, #ffcdd6f4, #ffffffff, #ffcdd6f4, #ff18141f, #ff18141f, #ff000000, #ff313244, #ffcdd6f4, #ffcba6f7, #fff38ba8, #ff313244, #ffcdd6f4, #ff313244, #ffcdd6f4, #80cdd6f4
|
||||||
|
inactive_colors=#fff5e0dc, #ff313244, #ffffffff, #ffcdd6f4, #ff18141f, #ff313244, #fff5e0dc, #ffffffff, #fff5e0dc, #ff18141f, #ff18141f, #ff000000, #fff38ba8, #ff11111b, #ffcba6f7, #fff38ba8, #ff313244, #fff5e0dc, #ff313244, #fff5e0dc, #80cdd6f4
|
||||||
28
dotfiles/qt6ct/qt6ct.conf
Normal file
28
dotfiles/qt6ct/qt6ct.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[Appearance]
|
||||||
|
color_scheme_path=/home/pascal/.config/qt6ct/colors/ForestNeon.conf
|
||||||
|
custom_palette=true
|
||||||
|
icon_theme=Papirus-ForestNeon
|
||||||
|
standard_dialogs=default
|
||||||
|
style=Fusion
|
||||||
|
|
||||||
|
[Fonts]
|
||||||
|
fixed="JetBrainsMono Nerd Font,10,-1,5,50,0,0,0,0,0"
|
||||||
|
general="JetBrainsMono Nerd Font,10,-1,5,50,0,0,0,0,0"
|
||||||
|
|
||||||
|
[Interface]
|
||||||
|
activate_item_on_single_click=1
|
||||||
|
buttonbox_layout=0
|
||||||
|
cursor_flash_time=1000
|
||||||
|
dialog_buttons_have_icons=1
|
||||||
|
double_click_interval=400
|
||||||
|
gui_effects=@Invalid()
|
||||||
|
keyboard_scheme=2
|
||||||
|
menus_have_icons=true
|
||||||
|
show_shortcuts_in_context_menus=true
|
||||||
|
stylesheets=@Invalid()
|
||||||
|
toolbutton_style=4
|
||||||
|
underline_shortcut=1
|
||||||
|
wheel_scroll_lines=3
|
||||||
|
|
||||||
|
[SettingsWindow]
|
||||||
|
geometry=@ByteArray()
|
||||||
123
dotfiles/starship.toml
Normal file
123
dotfiles/starship.toml
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Generated by ~/.config/hypr/Scripts/theme-menu.sh
|
||||||
|
add_newline = true
|
||||||
|
palette = "hypr_theme"
|
||||||
|
|
||||||
|
format = """
|
||||||
|
[╭─](fg:accent_2)$os$username$hostname$directory$git_branch$git_status$nodejs$python$rust$golang$cmd_duration$status$fill$time
|
||||||
|
[╰─](fg:accent)$character
|
||||||
|
"""
|
||||||
|
|
||||||
|
[palettes.hypr_theme]
|
||||||
|
accent = "#00ff9c"
|
||||||
|
accent_2 = "#00cc88"
|
||||||
|
background = "#14141e"
|
||||||
|
panel = "#282837"
|
||||||
|
foreground = "#cdd6f4"
|
||||||
|
muted = "#cccccc"
|
||||||
|
selected = "#000000"
|
||||||
|
success = "#a6e3a1"
|
||||||
|
warning = "#f9e2af"
|
||||||
|
danger = "#f38ba8"
|
||||||
|
blue = "#89b4fa"
|
||||||
|
cyan = "#89dceb"
|
||||||
|
orange = "#fab387"
|
||||||
|
purple = "#cba6f7"
|
||||||
|
|
||||||
|
[fill]
|
||||||
|
symbol = " "
|
||||||
|
style = "fg:panel"
|
||||||
|
|
||||||
|
[os]
|
||||||
|
disabled = false
|
||||||
|
format = "[ $symbol ](fg:selected bg:accent)[](fg:accent bg:panel)"
|
||||||
|
style = "fg:selected bg:accent"
|
||||||
|
|
||||||
|
[os.symbols]
|
||||||
|
Arch = ""
|
||||||
|
CachyOS = ""
|
||||||
|
Linux = ""
|
||||||
|
Ubuntu = ""
|
||||||
|
Debian = ""
|
||||||
|
Fedora = ""
|
||||||
|
NixOS = ""
|
||||||
|
|
||||||
|
[username]
|
||||||
|
show_always = true
|
||||||
|
style_user = "fg:foreground bg:panel bold"
|
||||||
|
style_root = "fg:danger bg:panel bold"
|
||||||
|
format = "[ $user](fg:foreground bg:panel bold)"
|
||||||
|
|
||||||
|
[hostname]
|
||||||
|
ssh_only = false
|
||||||
|
style = "fg:muted bg:panel"
|
||||||
|
format = "[@$hostname ](fg:muted bg:panel)[](fg:panel bg:background)"
|
||||||
|
|
||||||
|
[directory]
|
||||||
|
style = "fg:accent bg:background bold"
|
||||||
|
read_only_style = "fg:danger bg:background bold"
|
||||||
|
truncation_length = 3
|
||||||
|
truncate_to_repo = true
|
||||||
|
read_only = " "
|
||||||
|
format = "[ $path$read_only ](fg:accent bg:background bold)"
|
||||||
|
|
||||||
|
[git_branch]
|
||||||
|
symbol = ""
|
||||||
|
style = "fg:purple bg:background bold"
|
||||||
|
format = "[on $symbol $branch ](fg:purple bg:background bold)"
|
||||||
|
|
||||||
|
[git_status]
|
||||||
|
style = "fg:warning bg:background bold"
|
||||||
|
format = "([$all_status$ahead_behind ](fg:warning bg:background bold))"
|
||||||
|
conflicted = "=${count} "
|
||||||
|
ahead = "⇡${count} "
|
||||||
|
behind = "⇣${count} "
|
||||||
|
diverged = "⇕⇡${ahead_count}⇣${behind_count} "
|
||||||
|
up_to_date = ""
|
||||||
|
untracked = "?${count} "
|
||||||
|
stashed = "*${count} "
|
||||||
|
modified = "!${count} "
|
||||||
|
staged = "+${count} "
|
||||||
|
renamed = "»${count} "
|
||||||
|
deleted = "x${count} "
|
||||||
|
|
||||||
|
[nodejs]
|
||||||
|
symbol = ""
|
||||||
|
style = "fg:success bg:background"
|
||||||
|
format = "[$symbol $version ](fg:success bg:background)"
|
||||||
|
|
||||||
|
[python]
|
||||||
|
symbol = ""
|
||||||
|
style = "fg:warning bg:background"
|
||||||
|
format = "[$symbol $version ](fg:warning bg:background)"
|
||||||
|
|
||||||
|
[rust]
|
||||||
|
symbol = ""
|
||||||
|
style = "fg:orange bg:background"
|
||||||
|
format = "[$symbol $version ](fg:orange bg:background)"
|
||||||
|
|
||||||
|
[golang]
|
||||||
|
symbol = ""
|
||||||
|
style = "fg:cyan bg:background"
|
||||||
|
format = "[$symbol $version ](fg:cyan bg:background)"
|
||||||
|
|
||||||
|
[cmd_duration]
|
||||||
|
min_time = 300
|
||||||
|
style = "fg:orange bg:background"
|
||||||
|
format = "[took $duration ](fg:orange bg:background)"
|
||||||
|
|
||||||
|
[status]
|
||||||
|
disabled = false
|
||||||
|
style = "fg:danger bg:background bold"
|
||||||
|
symbol = "✖"
|
||||||
|
format = "[$symbol $status ](fg:danger bg:background bold)"
|
||||||
|
|
||||||
|
[time]
|
||||||
|
disabled = false
|
||||||
|
time_format = "%H:%M"
|
||||||
|
style = "fg:muted"
|
||||||
|
format = "[ $time ]($style)"
|
||||||
|
|
||||||
|
[character]
|
||||||
|
success_symbol = "[❯](fg:accent bold)"
|
||||||
|
error_symbol = "[❯](fg:danger bold)"
|
||||||
|
vimcmd_symbol = "[❮](fg:accent_2 bold)"
|
||||||
70
dotfiles/swaync/config.json
Normal file
70
dotfiles/swaync/config.json
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"positionX": "right",
|
||||||
|
"positionY": "top",
|
||||||
|
"layer": "overlay",
|
||||||
|
"control-center-width": 420,
|
||||||
|
"control-center-height": 650,
|
||||||
|
"notification-window-width": 360,
|
||||||
|
"timeout": 5,
|
||||||
|
"timeout-low": 3,
|
||||||
|
"timeout-critical": 0,
|
||||||
|
"fit-to-screen": true,
|
||||||
|
"keyboard-shortcuts": true,
|
||||||
|
"image-visibility": "when-available",
|
||||||
|
"transition-time": 200,
|
||||||
|
"hide-on-clear": true,
|
||||||
|
|
||||||
|
"widgets": [
|
||||||
|
"title",
|
||||||
|
"dnd",
|
||||||
|
"mpris",
|
||||||
|
"buttons-grid",
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
|
||||||
|
"widget-config": {
|
||||||
|
"title": {
|
||||||
|
"text": "Control Center",
|
||||||
|
"clear-all-button": true,
|
||||||
|
"button-text": "Leeren"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dnd": {
|
||||||
|
"text": "Nicht stören"
|
||||||
|
},
|
||||||
|
|
||||||
|
"mpris": {
|
||||||
|
"image-size": 96,
|
||||||
|
"image-radius": 12
|
||||||
|
},
|
||||||
|
|
||||||
|
"buttons-grid": {
|
||||||
|
"actions": [
|
||||||
|
{
|
||||||
|
"label": " WLAN",
|
||||||
|
"command": "nm-connection-editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": " Bluetooth",
|
||||||
|
"command": "blueman-manager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": " Screenshot",
|
||||||
|
"command": "mkdir -p ~/Bilder/Screenshots && grim -g \"$(slurp)\" ~/Bilder/Screenshots/$(date +%F_%H-%M-%S).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": " Lock",
|
||||||
|
"command": "hyprlock"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": " Power",
|
||||||
|
"command": "wlogout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": " Terminal",
|
||||||
|
"command": "kitty"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
dotfiles/swaync/style.css
Normal file
27
dotfiles/swaync/style.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
* {
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification {
|
||||||
|
background: rgba(20, 20, 30, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px solid #00ff9c;
|
||||||
|
box-shadow: 0 0 10px #00ff9c;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-center {
|
||||||
|
background: rgba(20, 20, 30, 0.95);
|
||||||
|
border-radius: 15px;
|
||||||
|
border: 2px solid #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
color: #00cc88;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-body {
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
BIN
dotfiles/wallpapers/forest.jpg
Normal file
BIN
dotfiles/wallpapers/forest.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 MiB |
BIN
dotfiles/wallpapers/rose-pink.jpg
Normal file
BIN
dotfiles/wallpapers/rose-pink.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 500 KiB |
174
dotfiles/waybar/config.jsonc
Normal file
174
dotfiles/waybar/config.jsonc
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
{
|
||||||
|
"layer": "top",
|
||||||
|
"position": "top",
|
||||||
|
"height": 42,
|
||||||
|
"margin-top": 8,
|
||||||
|
"margin-left": 12,
|
||||||
|
"margin-right": 12,
|
||||||
|
"spacing": 6,
|
||||||
|
|
||||||
|
"modules-left": [
|
||||||
|
"hyprland/workspaces"
|
||||||
|
],
|
||||||
|
|
||||||
|
"modules-center": [
|
||||||
|
"group/dynamic-island"
|
||||||
|
],
|
||||||
|
|
||||||
|
"modules-right": [
|
||||||
|
"cpu",
|
||||||
|
"memory",
|
||||||
|
"temperature",
|
||||||
|
"pulseaudio",
|
||||||
|
"network",
|
||||||
|
"battery",
|
||||||
|
"custom/notifications",
|
||||||
|
"tray"
|
||||||
|
],
|
||||||
|
|
||||||
|
"hyprland/workspaces": {
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"1": "1",
|
||||||
|
"2": "2",
|
||||||
|
"3": "3",
|
||||||
|
"4": "4",
|
||||||
|
"5": "5",
|
||||||
|
"6": "6",
|
||||||
|
"7": "7",
|
||||||
|
"8": "8",
|
||||||
|
"9": "9",
|
||||||
|
"10": "10",
|
||||||
|
"urgent": "!",
|
||||||
|
"active": "●",
|
||||||
|
"default": "○"
|
||||||
|
},
|
||||||
|
"on-click": "activate"
|
||||||
|
},
|
||||||
|
|
||||||
|
"hyprland/window": {
|
||||||
|
"format": "{}",
|
||||||
|
"max-length": 28,
|
||||||
|
"separate-outputs": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"group/dynamic-island": {
|
||||||
|
"orientation": "horizontal",
|
||||||
|
"drawer": {
|
||||||
|
"transition-duration": 350,
|
||||||
|
"children-class": "island-drawer",
|
||||||
|
"transition-left-to-right": true
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
"custom/dynamic-island",
|
||||||
|
"custom/media-prev",
|
||||||
|
"custom/media-toggle",
|
||||||
|
"custom/media-next",
|
||||||
|
"hyprland/window"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/dynamic-island": {
|
||||||
|
"exec": "/home/pascal/.config/waybar/scripts/island-media.sh",
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 1,
|
||||||
|
"format": "{}",
|
||||||
|
"on-click": "playerctl play-pause",
|
||||||
|
"on-click-middle": "playerctl previous",
|
||||||
|
"on-click-right": "playerctl next"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/media-prev": {
|
||||||
|
"format": "",
|
||||||
|
"tooltip-format": "Zurück",
|
||||||
|
"on-click": "playerctl previous"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/media-toggle": {
|
||||||
|
"exec": "playerctl status 2>/dev/null | grep -q Playing && echo || echo ",
|
||||||
|
"interval": 1,
|
||||||
|
"format": "{}",
|
||||||
|
"tooltip-format": "Start/Pause",
|
||||||
|
"on-click": "playerctl play-pause"
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/media-next": {
|
||||||
|
"format": "",
|
||||||
|
"tooltip-format": "Weiter",
|
||||||
|
"on-click": "playerctl next"
|
||||||
|
},
|
||||||
|
|
||||||
|
"clock": {
|
||||||
|
"format": " {:%H:%M}",
|
||||||
|
"tooltip-format": "{:%A, %d. %B %Y}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"cpu": {
|
||||||
|
"format": " {usage}%",
|
||||||
|
"tooltip": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"memory": {
|
||||||
|
"format": " {}%",
|
||||||
|
"tooltip": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"temperature": {
|
||||||
|
"hwmon-path-abs": "/sys/devices/platform/coretemp.0/hwmon",
|
||||||
|
"input-filename": "temp1_input",
|
||||||
|
"critical-threshold": 85,
|
||||||
|
"format": " {temperatureC}°C",
|
||||||
|
"format-critical": " {temperatureC}°C",
|
||||||
|
"tooltip": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"battery": {
|
||||||
|
"format": "{icon} {capacity}%",
|
||||||
|
"format-icons": ["", "", "", "", ""],
|
||||||
|
"states": {
|
||||||
|
"warning": 30,
|
||||||
|
"critical": 15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"network": {
|
||||||
|
"format-wifi": " {signalStrength}%",
|
||||||
|
"format-ethernet": " LAN",
|
||||||
|
"format-disconnected": " Offline",
|
||||||
|
"tooltip-format-wifi": "{essid} ({signalStrength}%)",
|
||||||
|
"tooltip-format-ethernet": "{ifname}: {ipaddr}/{cidr}"
|
||||||
|
},
|
||||||
|
|
||||||
|
"pulseaudio": {
|
||||||
|
"format": "{icon} {volume}%",
|
||||||
|
"format-muted": " Muted",
|
||||||
|
"format-icons": {
|
||||||
|
"default": ["", "", ""]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"custom/notifications": {
|
||||||
|
"tooltip": true,
|
||||||
|
"format": "{icon}",
|
||||||
|
"format-icons": {
|
||||||
|
"notification": "",
|
||||||
|
"none": "",
|
||||||
|
"dnd-notification": "",
|
||||||
|
"dnd-none": "",
|
||||||
|
"inhibited-notification": "",
|
||||||
|
"inhibited-none": "",
|
||||||
|
"dnd-inhibited-notification": "",
|
||||||
|
"dnd-inhibited-none": ""
|
||||||
|
},
|
||||||
|
"return-type": "json",
|
||||||
|
"exec-if": "which swaync-client",
|
||||||
|
"exec": "swaync-client -swb",
|
||||||
|
"on-click": "swaync-client -t",
|
||||||
|
"on-click-right": "swaync-client -d",
|
||||||
|
"escape": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"tray": {
|
||||||
|
"spacing": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
39
dotfiles/waybar/scripts/island-media.sh
Executable file
39
dotfiles/waybar/scripts/island-media.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
json() {
|
||||||
|
jq -Rn --arg value "$1" '$value'
|
||||||
|
}
|
||||||
|
|
||||||
|
status="$(playerctl status 2>/dev/null || true)"
|
||||||
|
time_now="$(date +%H:%M)"
|
||||||
|
|
||||||
|
if [[ "$status" == "Playing" || "$status" == "Paused" ]]; then
|
||||||
|
title="$(playerctl metadata --format '{{ title }}' 2>/dev/null || true)"
|
||||||
|
artist="$(playerctl metadata --format '{{ artist }}' 2>/dev/null || true)"
|
||||||
|
player="$(playerctl metadata --format '{{ playerName }}' 2>/dev/null || true)"
|
||||||
|
|
||||||
|
[[ -z "$title" ]] && title="Media"
|
||||||
|
|
||||||
|
if [[ "$status" == "Playing" ]]; then
|
||||||
|
icon=""
|
||||||
|
class="playing"
|
||||||
|
else
|
||||||
|
icon=""
|
||||||
|
class="paused"
|
||||||
|
fi
|
||||||
|
|
||||||
|
text="$icon $title"
|
||||||
|
[[ -n "$artist" ]] && text="$text · $artist"
|
||||||
|
text="$text $time_now"
|
||||||
|
tooltip="${player:-Player} (${status})"
|
||||||
|
[[ -n "$artist$title" ]] && tooltip="$tooltip"$'\n'"$artist - $title"
|
||||||
|
else
|
||||||
|
text=" $time_now"
|
||||||
|
tooltip="$(date '+%A, %d. %B %Y')"
|
||||||
|
class="idle"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '{"text":%s,"tooltip":%s,"class":%s}\n' \
|
||||||
|
"$(json "$text")" \
|
||||||
|
"$(json "$tooltip")" \
|
||||||
|
"$(json "$class")"
|
||||||
206
dotfiles/waybar/style.css
Normal file
206
dotfiles/waybar/style.css
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
* {
|
||||||
|
min-height: 0;
|
||||||
|
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono", sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
window#waybar {
|
||||||
|
background: transparent;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip {
|
||||||
|
background: rgba(20, 20, 30, 0.95);
|
||||||
|
border: 1px solid #00ff9c;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tooltip label {
|
||||||
|
color: #cdd6f4;
|
||||||
|
padding: 6px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces {
|
||||||
|
margin: 0 0 0 2px;
|
||||||
|
padding: 4px;
|
||||||
|
background: rgba(24, 24, 37, 0.72);
|
||||||
|
border: 1px solid rgba(40, 40, 55, 0.8);
|
||||||
|
border-radius: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button {
|
||||||
|
min-width: 26px;
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0 7px;
|
||||||
|
color: #7f849c;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 14px;
|
||||||
|
transition: all 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.active {
|
||||||
|
min-width: 34px;
|
||||||
|
color: #000000;
|
||||||
|
background: linear-gradient(135deg, #00ff9c, #00cc88);
|
||||||
|
box-shadow: 0 0 14px rgba(40, 40, 55, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button.urgent {
|
||||||
|
color: #000000;
|
||||||
|
background: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces button:hover {
|
||||||
|
color: #cdd6f4;
|
||||||
|
background: rgba(40, 40, 55, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#dynamic-island {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: rgba(5, 5, 9, 0.88);
|
||||||
|
border-top: 1px solid rgba(40, 40, 55, 0.8);
|
||||||
|
border-bottom: 1px solid #00ff9c;
|
||||||
|
border-radius: 21px;
|
||||||
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.36);
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-dynamic-island,
|
||||||
|
#custom-media-prev,
|
||||||
|
#custom-media-toggle,
|
||||||
|
#custom-media-next,
|
||||||
|
#window,
|
||||||
|
#clock {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 11px;
|
||||||
|
background: transparent;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-dynamic-island {
|
||||||
|
min-width: 142px;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 14px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border-radius: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-dynamic-island.playing {
|
||||||
|
color: #a6e3a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-dynamic-island.paused {
|
||||||
|
color: #f9e2af;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-dynamic-island.idle {
|
||||||
|
color: #7f849c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-media-prev,
|
||||||
|
#custom-media-toggle,
|
||||||
|
#custom-media-next {
|
||||||
|
min-width: 20px;
|
||||||
|
padding: 0 9px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border-radius: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-media-prev:hover,
|
||||||
|
#custom-media-toggle:hover,
|
||||||
|
#custom-media-next:hover {
|
||||||
|
color: #000000;
|
||||||
|
background: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#window {
|
||||||
|
min-width: 90px;
|
||||||
|
color: #cccccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
window#waybar.empty #window {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clock {
|
||||||
|
padding-right: 16px;
|
||||||
|
color: #a6e3a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.island-drawer {
|
||||||
|
color: #7f849c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu,
|
||||||
|
#memory,
|
||||||
|
#temperature,
|
||||||
|
#pulseaudio,
|
||||||
|
#network,
|
||||||
|
#battery,
|
||||||
|
#custom-notifications,
|
||||||
|
#tray {
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0 10px;
|
||||||
|
background: rgba(24, 24, 37, 0.72);
|
||||||
|
border: 1px solid rgba(40, 40, 55, 0.8);
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cpu {
|
||||||
|
color: #89b4fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#memory {
|
||||||
|
color: #f9e2af;
|
||||||
|
}
|
||||||
|
|
||||||
|
#temperature {
|
||||||
|
color: #a6e3a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#temperature.critical {
|
||||||
|
color: #f38ba8;
|
||||||
|
background: rgba(40, 40, 55, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pulseaudio {
|
||||||
|
color: #fab387;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pulseaudio.muted {
|
||||||
|
color: #7f849c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network {
|
||||||
|
color: #89dceb;
|
||||||
|
}
|
||||||
|
|
||||||
|
#network.disconnected {
|
||||||
|
color: #f38ba8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#battery {
|
||||||
|
color: #a6e3a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#battery.warning {
|
||||||
|
color: #f9e2af;
|
||||||
|
}
|
||||||
|
|
||||||
|
#battery.critical {
|
||||||
|
color: #f38ba8;
|
||||||
|
background: rgba(40, 40, 55, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-notifications {
|
||||||
|
color: #cba6f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tray {
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
11
dotfiles/wofi/config
Normal file
11
dotfiles/wofi/config
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
width=600
|
||||||
|
height=400
|
||||||
|
location=center
|
||||||
|
show=drun
|
||||||
|
prompt= Launch
|
||||||
|
allow_images=true
|
||||||
|
term=kitty
|
||||||
|
style=/home/pascal/.config/wofi/style.css
|
||||||
|
hide_scroll=true
|
||||||
|
no_actions=true
|
||||||
|
|
||||||
55
dotfiles/wofi/style.css
Normal file
55
dotfiles/wofi/style.css
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
* {
|
||||||
|
font-family: "JetBrainsMono Nerd Font";
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
window {
|
||||||
|
margin: 0px;
|
||||||
|
border: 2px solid #00ff9c;
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: rgba(20, 20, 30, 0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
#input {
|
||||||
|
margin: 10px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
background-color: rgba(40, 40, 55, 0.8);
|
||||||
|
color: #00ff9c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#inner-box {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#outer-box {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scroll {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#text {
|
||||||
|
margin: 5px;
|
||||||
|
color: #cdd6f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry {
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry:selected {
|
||||||
|
background-color: #00ff9c;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#entry:hover {
|
||||||
|
background-color: rgba(40, 40, 55, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#img {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
338
install.sh
Executable file
338
install.sh
Executable file
@@ -0,0 +1,338 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Omeron - Modular System Setup Framework
|
||||||
|
# Usage: ./install.sh [--fresh] [--modules m1,m2] [--skip-packages] [--with-sddm] [--help]
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OMERON_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
export OMERON_ROOT
|
||||||
|
export OMERON_PROJECT_DIR="$OMERON_ROOT"
|
||||||
|
|
||||||
|
source "$OMERON_ROOT/lib/log.sh"
|
||||||
|
source "$OMERON_ROOT/lib/tui.sh"
|
||||||
|
source "$OMERON_ROOT/lib/utils.sh"
|
||||||
|
source "$OMERON_ROOT/lib/config.sh"
|
||||||
|
source "$OMERON_ROOT/lib/modules.sh"
|
||||||
|
|
||||||
|
OMERON_FRESH_INSTALL="${OMERON_FRESH_INSTALL:-0}"
|
||||||
|
export OMERON_FRESH_INSTALL
|
||||||
|
|
||||||
|
DEFAULT_MODULES=(
|
||||||
|
"core/preflight"
|
||||||
|
"core/packages"
|
||||||
|
"core/dotfiles"
|
||||||
|
"core/services"
|
||||||
|
"homelab/setup"
|
||||||
|
"optional/install"
|
||||||
|
"core/sddm"
|
||||||
|
"post/apply-theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
FRESH_MODULES=(
|
||||||
|
"core/packages"
|
||||||
|
"core/dotfiles"
|
||||||
|
"core/services"
|
||||||
|
"core/sddm"
|
||||||
|
"post/apply-theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
RUN_MODULES=()
|
||||||
|
SKIP_MODULES=()
|
||||||
|
WITH_SDDM=0
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Omeron - Modular System Setup Framework
|
||||||
|
|
||||||
|
Usage: ./install.sh [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--fresh Full system setup (Hyprland + GPU drivers + all deps)
|
||||||
|
--modules m1,m2 Run only specific modules (comma-separated)
|
||||||
|
--skip m1,m2 Skip specific modules (comma-separated)
|
||||||
|
--skip-packages Skip package installation
|
||||||
|
--with-sddm Include SDDM theme installation
|
||||||
|
--list-modules List all available modules
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./install.sh Interactive (detects fresh install automatically)
|
||||||
|
./install.sh --fresh Force full setup on a running system
|
||||||
|
./install.sh --modules core/dotfiles,post/apply-theme
|
||||||
|
./install.sh --skip core/packages
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
list_modules() {
|
||||||
|
echo "Available modules:"
|
||||||
|
echo ""
|
||||||
|
for module in $(module_list); do
|
||||||
|
local rel_path="${module#$OMERON_MODULE_DIR/}"
|
||||||
|
rel_path="${rel_path%.sh}"
|
||||||
|
|
||||||
|
local description=""
|
||||||
|
if source "$module" 2>/dev/null && declare -F "module_description" >/dev/null 2>&1; then
|
||||||
|
description="$(module_description)"
|
||||||
|
fi
|
||||||
|
printf " %-30s %s\n" "$rel_path" "$description"
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--fresh)
|
||||||
|
OMERON_FRESH_INSTALL=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--modules)
|
||||||
|
IFS=',' read -ra RUN_MODULES <<< "$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--skip)
|
||||||
|
IFS=',' read -ra SKIP_MODULES <<< "$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--skip-packages)
|
||||||
|
SKIP_MODULES+=("core/packages")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--with-sddm)
|
||||||
|
WITH_SDDM=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--list-modules)
|
||||||
|
list_modules
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1" >&2
|
||||||
|
usage
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_modules() {
|
||||||
|
if ((${#RUN_MODULES[@]})); then
|
||||||
|
local result=()
|
||||||
|
for mod in "${RUN_MODULES[@]}"; do
|
||||||
|
result+=("$OMERON_MODULE_DIR/$mod.sh")
|
||||||
|
done
|
||||||
|
printf '%s\n' "${result[@]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local modules=()
|
||||||
|
if ((OMERON_FRESH_INSTALL)); then
|
||||||
|
modules=("${FRESH_MODULES[@]}")
|
||||||
|
else
|
||||||
|
modules=("${DEFAULT_MODULES[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local module_order=()
|
||||||
|
|
||||||
|
for mod in "${modules[@]}"; do
|
||||||
|
local skip=0
|
||||||
|
for skipped in "${SKIP_MODULES[@]}"; do
|
||||||
|
if [[ "$mod" == "$skipped" || "$mod" == "core/$skipped" ]]; then
|
||||||
|
skip=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
[[ "$mod" == "core/sddm" && "$WITH_SDDM" -eq 0 && "$OMERON_FRESH_INSTALL" -eq 0 ]] && skip=1
|
||||||
|
|
||||||
|
((skip)) && continue
|
||||||
|
|
||||||
|
local module_file="$OMERON_MODULE_DIR/$mod.sh"
|
||||||
|
[[ -f "$module_file" ]] && module_order+=("$module_file")
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${module_order[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
collect_all_interactive() {
|
||||||
|
local modules=()
|
||||||
|
if ((OMERON_FRESH_INSTALL)); then
|
||||||
|
modules=("${FRESH_MODULES[@]}")
|
||||||
|
else
|
||||||
|
modules=("${DEFAULT_MODULES[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
local total=${#modules[@]}
|
||||||
|
local idx=1
|
||||||
|
local module_order=()
|
||||||
|
|
||||||
|
for mod in "${modules[@]}"; do
|
||||||
|
local module_file="$OMERON_MODULE_DIR/$mod.sh"
|
||||||
|
[[ -f "$module_file" ]] || continue
|
||||||
|
|
||||||
|
local description=""
|
||||||
|
if source "$module_file" 2>/dev/null && declare -F "module_description" >/dev/null 2>&1; then
|
||||||
|
description="$(module_description)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
log_step "$idx" "$total" "${description:-$mod}"
|
||||||
|
|
||||||
|
if declare -F "module_required" >/dev/null 2>&1; then
|
||||||
|
source "$module_file"
|
||||||
|
if module_required; then
|
||||||
|
log_info "Required module - will run"
|
||||||
|
module_order+=("$module_file")
|
||||||
|
((idx++))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tui_confirm "Run this step?"; then
|
||||||
|
module_order+=("$module_file")
|
||||||
|
else
|
||||||
|
log_info "Skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
((idx++))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%s\n' "${module_order[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_banner() {
|
||||||
|
tui_header " Ꮎ Ꮇ Ꭼ Ꮢ Ꮎ Ꮑ "
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}Modular System Setup Framework#{normal}"
|
||||||
|
tui_format "#{italic}Arch / Hyprland / CachyOS#{normal}"
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
if ((OMERON_FRESH_INSTALL)); then
|
||||||
|
tui_format "#{bold}#{yellow}⚡ FRESH INSTALL MODE#{normal}"
|
||||||
|
tui_format "Will install Hyprland, GPU drivers, and all dependencies."
|
||||||
|
tui_format ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_summary() {
|
||||||
|
local modules=("$@")
|
||||||
|
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}Installation Summary:#{normal}"
|
||||||
|
tui_format " Modules to run: ${#modules[@]}"
|
||||||
|
|
||||||
|
local gpu
|
||||||
|
gpu="$(detect_gpu)"
|
||||||
|
tui_format " Detected GPU: ${gpu}"
|
||||||
|
|
||||||
|
if ((${#modules[@]})); then
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}Steps:#{normal}"
|
||||||
|
local i=1
|
||||||
|
for mod in "${modules[@]}"; do
|
||||||
|
local name
|
||||||
|
name="$(basename "$mod" .sh)"
|
||||||
|
tui_format " $i. ${name}"
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
tui_format ""
|
||||||
|
if ! tui_confirm "Proceed with installation?"; then
|
||||||
|
tui_format "#{bold}Installation cancelled by user.#{normal}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
OMERON_LOG_FILE="${OMERON_LOG_FILE:-$HOME/.local/share/omeron/install-$(date +%Y%m%d-%H%M%S).log}"
|
||||||
|
export OMERON_LOG_FILE
|
||||||
|
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
tui_style
|
||||||
|
show_banner
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$OMERON_LOG_FILE")"
|
||||||
|
log_info "Omeron installation started"
|
||||||
|
log_info "Log file: $OMERON_LOG_FILE"
|
||||||
|
log_info "Project: $OMERON_ROOT"
|
||||||
|
log_info "Fresh install: $OMERON_FRESH_INSTALL"
|
||||||
|
|
||||||
|
if ((!OMERON_FRESH_INSTALL)) && ((${#RUN_MODULES[@]} == 0)); then
|
||||||
|
log_info "Checking for fresh install..."
|
||||||
|
if is_fresh_install; then
|
||||||
|
OMERON_FRESH_INSTALL=1
|
||||||
|
export OMERON_FRESH_INSTALL
|
||||||
|
log_info "Fresh system detected — switching to full setup mode"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local modules_to_run=()
|
||||||
|
if ((${#RUN_MODULES[@]})); then
|
||||||
|
mapfile -t modules_to_run < <(collect_modules)
|
||||||
|
else
|
||||||
|
mapfile -t modules_to_run < <(collect_all_interactive)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((${#modules_to_run[@]} == 0)); then
|
||||||
|
log_warn "No modules selected. Nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
show_summary "${modules_to_run[@]}"
|
||||||
|
|
||||||
|
local total=${#modules_to_run[@]}
|
||||||
|
local idx=1
|
||||||
|
|
||||||
|
for module_path in "${modules_to_run[@]}"; do
|
||||||
|
local description=""
|
||||||
|
if source "$module_path" 2>/dev/null && declare -F "module_description" >/dev/null 2>&1; then
|
||||||
|
description="$(module_description)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
log_step "$idx" "$total" "${description:-$(basename "$module_path" .sh)}"
|
||||||
|
module_run "$module_path" || {
|
||||||
|
local rc=$?
|
||||||
|
if declare -F "module_required" >/dev/null 2>&1; then
|
||||||
|
source "$module_path"
|
||||||
|
if module_required 2>/dev/null; then
|
||||||
|
log_error "Required module failed. Aborting."
|
||||||
|
exit $rc
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
log_warn "Module completed with warnings"
|
||||||
|
}
|
||||||
|
((idx++))
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '\n'
|
||||||
|
tui_header " Ꮎ Ꮇ Ꭼ Ꮢ Ꮎ Ꮑ "
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}#{green}Installation Complete!#{normal}"
|
||||||
|
tui_format ""
|
||||||
|
tui_format "Log: ${OMERON_LOG_FILE}"
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
if ((OMERON_FRESH_INSTALL)); then
|
||||||
|
tui_format "#{bold}Your system is ready for Hyprland!#{normal}"
|
||||||
|
tui_format ""
|
||||||
|
tui_format " Start Hyprland: Hyprland"
|
||||||
|
tui_format " Or enable SDDM: sudo systemctl enable --now sddm"
|
||||||
|
tui_format " Reload config: hyprctl reload"
|
||||||
|
else
|
||||||
|
tui_format "What next?"
|
||||||
|
tui_format " - Reload config with: hyprctl reload"
|
||||||
|
tui_format " - Re-run installer: ./install.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
if have notify-send; then
|
||||||
|
notify-send "Omeron" "Installation complete! ✨" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
128
lib/config.sh
Executable file
128
lib/config.sh
Executable file
@@ -0,0 +1,128 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
OMERON_CONFIG_DIR="${OMERON_CONFIG_DIR:-$HOME/.config/omeron}"
|
||||||
|
OMERON_PROJECT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
config_load() {
|
||||||
|
local config_file="$1"
|
||||||
|
|
||||||
|
if [[ ! -f "$config_file" ]]; then
|
||||||
|
log_warn "Configuration not found: $config_file"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v yq >/dev/null 2>&1; then
|
||||||
|
config_parse_yq "$config_file"
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
config_parse_python "$config_file"
|
||||||
|
else
|
||||||
|
config_parse_shell "$config_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
config_parse_yq() {
|
||||||
|
local config_file="$1"
|
||||||
|
local key
|
||||||
|
|
||||||
|
while IFS='=' read -r key value; do
|
||||||
|
[[ -z "$key" ]] && continue
|
||||||
|
printf 'export OMERON_CFG_%s="%s"\n' "$key" "$value"
|
||||||
|
done < <(yq -o shell "$config_file" 2>/dev/null)
|
||||||
|
}
|
||||||
|
|
||||||
|
config_parse_python() {
|
||||||
|
local config_file="$1"
|
||||||
|
|
||||||
|
python3 -c "
|
||||||
|
import yaml, os, sys
|
||||||
|
|
||||||
|
with open('$config_file') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def flatten(d, prefix=''):
|
||||||
|
for k, v in d.items():
|
||||||
|
key = f'{prefix}_{k}' if prefix else k
|
||||||
|
if isinstance(v, dict):
|
||||||
|
flatten(v, key)
|
||||||
|
elif isinstance(v, list):
|
||||||
|
print(f'export OMERON_CFG_{key}=\"{chr(32).join(str(x) for x in v)}\"')
|
||||||
|
else:
|
||||||
|
print(f'export OMERON_CFG_{key}=\"{v}\"')
|
||||||
|
|
||||||
|
flatten(data)
|
||||||
|
" 2>/dev/null || config_parse_shell "$config_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_parse_shell() {
|
||||||
|
local config_file="$1"
|
||||||
|
local line key value
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
line="${line%%#*}"
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*([a-zA-Z_][a-zA-Z0-9_]*):[[:space:]]*(.*) ]]; then
|
||||||
|
key="${BASH_REMATCH[1]}"
|
||||||
|
value="${BASH_REMATCH[2]}"
|
||||||
|
value="${value#[\"\']}"
|
||||||
|
value="${value%[\"\']}"
|
||||||
|
printf 'export OMERON_CFG_%s="%s"\n' "$key" "$value"
|
||||||
|
fi
|
||||||
|
done < "$config_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_save() {
|
||||||
|
local config_file="$1"
|
||||||
|
local key value
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$config_file")"
|
||||||
|
|
||||||
|
{
|
||||||
|
printf '# Omeron Configuration\n'
|
||||||
|
printf '# Generated: %s\n\n' "$(date --rfc-3339=seconds)"
|
||||||
|
for entry in "$@"; do
|
||||||
|
key="${entry%%=*}"
|
||||||
|
value="${entry#*=}"
|
||||||
|
printf '%s: "%s"\n' "$key" "$value"
|
||||||
|
done
|
||||||
|
} > "$config_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_get() {
|
||||||
|
local key="$1"
|
||||||
|
local var_name="OMERON_CFG_${key}"
|
||||||
|
|
||||||
|
printf '%s' "${!var_name:-}"
|
||||||
|
}
|
||||||
|
|
||||||
|
config_merge() {
|
||||||
|
local base_file="$1"
|
||||||
|
local override_file="$2"
|
||||||
|
|
||||||
|
if command -v yq >/dev/null 2>&1; then
|
||||||
|
yq eval-all '. as $item ireduce ({}; . * $item)' "$base_file" "$override_file"
|
||||||
|
elif command -v python3 >/dev/null 2>&1; then
|
||||||
|
python3 -c "
|
||||||
|
import yaml, sys
|
||||||
|
|
||||||
|
with open('$base_file') as f:
|
||||||
|
base = yaml.safe_load(f) or {}
|
||||||
|
with open('$override_file') as f:
|
||||||
|
override = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
def merge(a, b):
|
||||||
|
for k in b:
|
||||||
|
if k in a and isinstance(a[k], dict) and isinstance(b[k], dict):
|
||||||
|
merge(a[k], b[k])
|
||||||
|
else:
|
||||||
|
a[k] = b[k]
|
||||||
|
return a
|
||||||
|
|
||||||
|
print(yaml.dump(merge(base, override), default_flow_style=False))
|
||||||
|
" 2>/dev/null || cat "$base_file"
|
||||||
|
else
|
||||||
|
cat "$base_file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
46
lib/log.sh
Executable file
46
lib/log.sh
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
OMERON_LOG_FILE="${OMERON_LOG_FILE:-$HOME/.local/share/omeron/install.log}"
|
||||||
|
OMERON_LOG_LEVEL="${OMERON_LOG_LEVEL:-INFO}"
|
||||||
|
|
||||||
|
__log_init() {
|
||||||
|
mkdir -p "$(dirname "$OMERON_LOG_FILE")"
|
||||||
|
}
|
||||||
|
|
||||||
|
__log_timestamp() {
|
||||||
|
date '+%Y-%m-%d %H:%M:%S'
|
||||||
|
}
|
||||||
|
|
||||||
|
__log_write() {
|
||||||
|
local level="$1"
|
||||||
|
local message="$2"
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(__log_timestamp)"
|
||||||
|
|
||||||
|
__log_init
|
||||||
|
printf '[%s] [%s] %s\n' "$timestamp" "$level" "$message" | tee -a "$OMERON_LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() { __log_write "INFO" "$1"; }
|
||||||
|
log_warn() { __log_write "WARN" "$1" >&2; }
|
||||||
|
log_error() { __log_write "ERROR" "$1" >&2; }
|
||||||
|
log_success() { __log_write "SUCCESS" "$1"; }
|
||||||
|
log_debug() {
|
||||||
|
[[ "$OMERON_LOG_LEVEL" == "DEBUG" ]] && __log_write "DEBUG" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_step() {
|
||||||
|
local num="$1"
|
||||||
|
local total="$2"
|
||||||
|
local message="$3"
|
||||||
|
printf '\n━━━ [%d/%d] %s ━━━\n' "$num" "$total" "$message"
|
||||||
|
__log_write "STEP" "[$num/$total] $message"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_section() {
|
||||||
|
local message="$1"
|
||||||
|
local line
|
||||||
|
line="$(printf '━%.0s' $(seq 1 "${#message}"))"
|
||||||
|
printf '\n%s\n%s\n%s\n' "$line" "$message" "$line"
|
||||||
|
__log_write "SECTION" "$message"
|
||||||
|
}
|
||||||
84
lib/modules.sh
Executable file
84
lib/modules.sh
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
OMERON_MODULE_DIR="${OMERON_MODULE_DIR:-$OMERON_PROJECT_DIR/modules}"
|
||||||
|
|
||||||
|
module_list() {
|
||||||
|
local category="${1:-}"
|
||||||
|
|
||||||
|
if [[ -n "$category" ]]; then
|
||||||
|
find "$OMERON_MODULE_DIR/$category" -maxdepth 1 -name '*.sh' -type f | sort
|
||||||
|
else
|
||||||
|
find "$OMERON_MODULE_DIR" -name '*.sh' -type f | sort
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
module_run() {
|
||||||
|
local module_path="$1"
|
||||||
|
local module_name
|
||||||
|
module_name="$(basename "$module_path" .sh)"
|
||||||
|
|
||||||
|
if [[ ! -f "$module_path" ]]; then
|
||||||
|
log_error "Module not found: $module_path"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Running module: $module_name"
|
||||||
|
OMERON_CURRENT_MODULE="$module_name" source "$module_path"
|
||||||
|
|
||||||
|
if declare -F "module_main" >/dev/null 2>&1; then
|
||||||
|
module_main
|
||||||
|
local rc=$?
|
||||||
|
if [[ $rc -eq 0 ]]; then
|
||||||
|
log_success "Module '$module_name' completed"
|
||||||
|
else
|
||||||
|
log_error "Module '$module_name' failed (exit: $rc)"
|
||||||
|
fi
|
||||||
|
return $rc
|
||||||
|
else
|
||||||
|
log_error "Module '$module_name' has no module_main function"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
module_check_prereqs() {
|
||||||
|
local module_path="$1"
|
||||||
|
|
||||||
|
if ! declare -F "module_prereqs" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
module_prereqs
|
||||||
|
}
|
||||||
|
|
||||||
|
module_skip() {
|
||||||
|
local module_path="$1"
|
||||||
|
|
||||||
|
if declare -F "module_should_skip" >/dev/null 2>&1; then
|
||||||
|
module_should_skip
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
module_describe() {
|
||||||
|
local module_path="$1"
|
||||||
|
|
||||||
|
if declare -F "module_description" >/dev/null 2>&1; then
|
||||||
|
module_description
|
||||||
|
else
|
||||||
|
basename "$module_path" .sh
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
module_confirm() {
|
||||||
|
local module_path="$1"
|
||||||
|
local description
|
||||||
|
description="$(module_describe "$module_path")"
|
||||||
|
|
||||||
|
if declare -F "module_required" >/dev/null 2>&1 && module_required; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
tui_confirm "Run '$description'?"
|
||||||
|
}
|
||||||
161
lib/tui.sh
Executable file
161
lib/tui.sh
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
TUI_STYLE="${TUI_STYLE:-gum}"
|
||||||
|
|
||||||
|
tui_check() {
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]] && ! command -v gum >/dev/null 2>&1; then
|
||||||
|
printf "gum is not installed. Install it or set TUI_STYLE=basic\n" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_style() {
|
||||||
|
if command -v gum >/dev/null 2>&1; then
|
||||||
|
TUI_STYLE="gum"
|
||||||
|
else
|
||||||
|
TUI_STYLE="basic"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_choose() {
|
||||||
|
local prompt="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum choose "$@"
|
||||||
|
else
|
||||||
|
select __choice in "$@"; do
|
||||||
|
printf '%s\n' "$__choice"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_confirm() {
|
||||||
|
local prompt="$1"
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum confirm "$prompt"
|
||||||
|
else
|
||||||
|
printf '%s [y/N]: ' "$prompt" >&2
|
||||||
|
read -r response
|
||||||
|
[[ "$response" =~ ^[yY](es)?$ ]]
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_input() {
|
||||||
|
local prompt="$1"
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum input --prompt "$prompt "
|
||||||
|
else
|
||||||
|
printf '%s: ' "$prompt" >&2
|
||||||
|
read -r response
|
||||||
|
printf '%s\n' "$response"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_password() {
|
||||||
|
local prompt="$1"
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum input --password --prompt "$prompt "
|
||||||
|
else
|
||||||
|
printf '%s: ' "$prompt" >&2
|
||||||
|
read -rs response
|
||||||
|
printf '\n'
|
||||||
|
printf '%s\n' "$response"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_multiselect() {
|
||||||
|
local prompt="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum choose --no-limit "$@"
|
||||||
|
else
|
||||||
|
printf '%s (space-separated indices):\n' "$prompt" >&2
|
||||||
|
local i=0
|
||||||
|
local items=("$@")
|
||||||
|
for item in "${items[@]}"; do
|
||||||
|
printf ' [%d] %s\n' "$i" "$item" >&2
|
||||||
|
((i++))
|
||||||
|
done
|
||||||
|
printf '> ' >&2
|
||||||
|
read -ra selections
|
||||||
|
for idx in "${selections[@]}"; do
|
||||||
|
printf '%s\n' "${items[$idx]}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_spin() {
|
||||||
|
local title="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum spin --title "$title" -- "$@"
|
||||||
|
else
|
||||||
|
printf '%s... ' "$title" >&2
|
||||||
|
"$@"
|
||||||
|
printf 'done\n' >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_header() {
|
||||||
|
local title="$1"
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum style --foreground 212 --border-foreground 212 --border double --align center --width 60 --margin "1 2" --padding "1 2" "$title"
|
||||||
|
else
|
||||||
|
printf '╔══════════════════════════════════════════════════╗\n'
|
||||||
|
printf '║ %s\n' "$title"
|
||||||
|
printf '╚══════════════════════════════════════════════════╝\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_status() {
|
||||||
|
local ok="$1"
|
||||||
|
local message="$2"
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
if [[ "$ok" == "0" ]]; then
|
||||||
|
gum style --foreground 10 "✓ $message"
|
||||||
|
else
|
||||||
|
gum style --foreground 9 "✗ $message"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "$ok" == "0" ]]; then
|
||||||
|
printf '✓ %s\n' "$message"
|
||||||
|
else
|
||||||
|
printf '✗ %s\n' "$message"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_file_pick() {
|
||||||
|
local directory="$1"
|
||||||
|
local pattern="$2"
|
||||||
|
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
find "$directory" -maxdepth 1 -type f -name "$pattern" -printf '%f\n' | sort | gum choose
|
||||||
|
else
|
||||||
|
local files=()
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
files+=("$(basename "$f")")
|
||||||
|
done < <(find "$directory" -maxdepth 1 -type f -name "$pattern" -print0 | sort -z)
|
||||||
|
select __file in "${files[@]}"; do
|
||||||
|
printf '%s\n' "$__file"
|
||||||
|
break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
tui_format() {
|
||||||
|
if [[ "$TUI_STYLE" == "gum" ]]; then
|
||||||
|
gum format "$@"
|
||||||
|
else
|
||||||
|
printf '%s\n' "$*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
263
lib/utils.sh
Executable file
263
lib/utils.sh
Executable file
@@ -0,0 +1,263 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
require() {
|
||||||
|
local cmd="$1"
|
||||||
|
local pkg="${2:-$1}"
|
||||||
|
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
log_error "Required command '$cmd' not found. Install with: sudo pacman -S $pkg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_optional() {
|
||||||
|
local cmd="$1"
|
||||||
|
local pkg="${2:-$1}"
|
||||||
|
|
||||||
|
if ! command -v "$cmd" >/dev/null 2>&1; then
|
||||||
|
log_warn "'$cmd' not found. Install with: sudo pacman -S $pkg"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
have() {
|
||||||
|
command -v "$1" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_arch() {
|
||||||
|
[[ -f /etc/arch-release ]] || [[ -d /etc/pacman.d ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
is_root() {
|
||||||
|
[[ "$EUID" -eq 0 ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo_run() {
|
||||||
|
if is_root; then
|
||||||
|
"$@"
|
||||||
|
else
|
||||||
|
sudo "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
backup_file() {
|
||||||
|
local target="$1"
|
||||||
|
local backup_dir="${2:-$HOME/.dotfiles-backup/$(date +%Y%m%d-%H%M%S)}"
|
||||||
|
local relative="${target#"$HOME"/}"
|
||||||
|
local backup="$backup_dir/$relative"
|
||||||
|
|
||||||
|
[[ -e "$target" || -L "$target" ]] || return 0
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$backup")"
|
||||||
|
cp -a "$target" "$backup"
|
||||||
|
log_info "Backed up $target -> $backup"
|
||||||
|
printf '%s' "$backup_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
symlink_path() {
|
||||||
|
local source="$1"
|
||||||
|
local target="$2"
|
||||||
|
|
||||||
|
backup_file "$target"
|
||||||
|
rm -rf "$target"
|
||||||
|
mkdir -p "$(dirname "$target")"
|
||||||
|
ln -sfn "$source" "$target"
|
||||||
|
log_info "Linked $source -> $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
copy_path() {
|
||||||
|
local source="$1"
|
||||||
|
local target="$2"
|
||||||
|
|
||||||
|
backup_file "$target"
|
||||||
|
rm -rf "$target"
|
||||||
|
mkdir -p "$(dirname "$target")"
|
||||||
|
cp -a "$source" "$target"
|
||||||
|
log_info "Copied $source -> $target"
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_packages() {
|
||||||
|
if is_arch; then
|
||||||
|
printf '%s\n' "arch"
|
||||||
|
fi
|
||||||
|
if [[ -f /etc/os-release ]]; then
|
||||||
|
local id
|
||||||
|
id="$(grep -oP '^ID=\K.*' /etc/os-release)"
|
||||||
|
printf '%s\n' "$id"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
is_package_installed() {
|
||||||
|
local pkg="$1"
|
||||||
|
|
||||||
|
if command -v pacman >/dev/null 2>&1; then
|
||||||
|
pacman -Qi "$pkg" >/dev/null 2>&1
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
install_pacman() {
|
||||||
|
local packages=("$@")
|
||||||
|
local to_install=()
|
||||||
|
local pkg
|
||||||
|
|
||||||
|
for pkg in "${packages[@]}"; do
|
||||||
|
if ! pacman -Si "$pkg" >/dev/null 2>&1; then
|
||||||
|
log_warn "Package '$pkg' not found in pacman repositories"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ! is_package_installed "$pkg"; then
|
||||||
|
to_install+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((${#to_install[@]})); then
|
||||||
|
log_info "Installing: ${to_install[*]}"
|
||||||
|
sudo_run pacman -S --needed --noconfirm "${to_install[@]}"
|
||||||
|
else
|
||||||
|
log_info "All packages already installed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_aur() {
|
||||||
|
local packages=("$@")
|
||||||
|
local aur_helper=""
|
||||||
|
|
||||||
|
if have paru; then
|
||||||
|
aur_helper="paru"
|
||||||
|
elif have yay; then
|
||||||
|
aur_helper="yay"
|
||||||
|
else
|
||||||
|
log_error "No AUR helper found (install paru or yay)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local to_install=()
|
||||||
|
local pkg
|
||||||
|
|
||||||
|
for pkg in "${packages[@]}"; do
|
||||||
|
if ! is_package_installed "$pkg"; then
|
||||||
|
to_install+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((${#to_install[@]})); then
|
||||||
|
log_info "Installing from AUR: ${to_install[*]}"
|
||||||
|
"$aur_helper" -S --needed --noconfirm "${to_install[@]}"
|
||||||
|
else
|
||||||
|
log_info "All AUR packages already installed"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_home_paths() {
|
||||||
|
local dir="$1"
|
||||||
|
local original_home="${2:-/home/pascal}"
|
||||||
|
|
||||||
|
if [[ "$original_home" == "$HOME" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
find "$dir" -type f -exec sed -i "s#${original_home}#${HOME}#g" {} + 2>/dev/null || true
|
||||||
|
log_info "Rewrote home paths in $dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
detect_gpu() {
|
||||||
|
if ! command -v lspci >/dev/null 2>&1; then
|
||||||
|
if have pacman; then
|
||||||
|
sudo_run pacman -S --needed --noconfirm pciutils >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local gpu_info
|
||||||
|
gpu_info="$(lspci -nn 2>/dev/null | grep -E '\[0300\]|\[0302\]|\[0380\]' || true)"
|
||||||
|
[[ -z "$gpu_info" ]] && gpu_info="$(lspci -nn 2>/dev/null | grep -iE '(VGA|3D|Display).*controller' || true)"
|
||||||
|
|
||||||
|
if printf '%s' "$gpu_info" | grep -qi "\[10de:"; then
|
||||||
|
printf 'nvidia'
|
||||||
|
elif printf '%s' "$gpu_info" | grep -qi "\[1002:\|\[1022:"; then
|
||||||
|
printf 'amd'
|
||||||
|
elif printf '%s' "$gpu_info" | grep -qi "\[8086:"; then
|
||||||
|
printf 'intel'
|
||||||
|
elif printf '%s' "$gpu_info" | grep -qi "vmware\|qemu\|virtualbox\|virtio"; then
|
||||||
|
printf 'vm'
|
||||||
|
elif printf '%s' "$gpu_info" | grep -qi "nvidia"; then
|
||||||
|
printf 'nvidia'
|
||||||
|
elif printf '%s' "$gpu_info" | grep -qi "amd\|advanced micro devices"; then
|
||||||
|
printf 'amd'
|
||||||
|
elif printf '%s' "$gpu_info" | grep -qi "intel"; then
|
||||||
|
printf 'intel'
|
||||||
|
else
|
||||||
|
printf 'unknown'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gpu_packages() {
|
||||||
|
local gpu_type
|
||||||
|
gpu_type="$(detect_gpu)"
|
||||||
|
|
||||||
|
case "$gpu_type" in
|
||||||
|
intel)
|
||||||
|
printf '%s\n' "xf86-video-intel" "vulkan-intel" "intel-media-driver" "libva-intel-driver"
|
||||||
|
;;
|
||||||
|
amd)
|
||||||
|
printf '%s\n' "xf86-video-amdgpu" "vulkan-radeon" "libva-mesa-driver" "mesa-vdpau"
|
||||||
|
;;
|
||||||
|
nvidia)
|
||||||
|
printf '%s\n' "nvidia-dkms" "nvidia-utils" "nvidia-settings" "libva-nvidia-driver" "vulkan-tools"
|
||||||
|
;;
|
||||||
|
vm)
|
||||||
|
printf '%s\n' "xf86-video-vmware" "mesa" "vulkan-swrast"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf '%s\n' "mesa" "vulkan-swrast"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
is_fresh_install() {
|
||||||
|
have hyprctl && return 1
|
||||||
|
|
||||||
|
if have hyprland; then
|
||||||
|
hyprland --version >/dev/null 2>&1 && return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${WAYLAND_DISPLAY:-}" || -n "${DISPLAY:-}" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
count_missing_packages() {
|
||||||
|
local packages=("$@")
|
||||||
|
local missing=0
|
||||||
|
local pkg
|
||||||
|
|
||||||
|
for pkg in "${packages[@]}"; do
|
||||||
|
if ! is_package_installed "$pkg"; then
|
||||||
|
((missing++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
printf '%d' "$missing"
|
||||||
|
}
|
||||||
|
|
||||||
|
system_summary() {
|
||||||
|
local gpu
|
||||||
|
gpu="$(detect_gpu)"
|
||||||
|
|
||||||
|
cat <<SUMMARY
|
||||||
|
┌─────────────────────────────────────────────┐
|
||||||
|
│ System Summary │
|
||||||
|
├─────────────────────────────────────────────┤
|
||||||
|
│ OS: $(grep -oP '^PRETTY_NAME="?\K[^"\n]+' /etc/os-release 2>/dev/null | sed 's/"$//' || echo "Arch Linux")
|
||||||
|
│ Kernel: $(uname -r)
|
||||||
|
│ GPU: ${gpu} ($(lspci -nn 2>/dev/null | grep -E '\[0300\]|\[0302\]|\[0380\]' | sed 's/.*: //' | head -1 || echo "unknown"))
|
||||||
|
│ Session: $(printenv XDG_SESSION_TYPE || echo "none (TTY)")
|
||||||
|
│ Hyprland: $(have hyprctl && echo "installed" || echo "NOT installed")
|
||||||
|
│ AUR: $(have paru && echo "paru" || (have yay && echo "yay" || echo "none"))
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
SUMMARY
|
||||||
|
}
|
||||||
84
modules/core/dotfiles.sh
Executable file
84
modules/core/dotfiles.sh
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "Dotfiles - deploy configuration files to ~/.config\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 0; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "Dotfile Deployment"
|
||||||
|
|
||||||
|
local dotfiles_dir="$OMERON_PROJECT_DIR/dotfiles"
|
||||||
|
local config_items=()
|
||||||
|
local backup_dir
|
||||||
|
|
||||||
|
if [[ -f "$OMERON_PROJECT_DIR/config/omeron.yaml" ]] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
log_info "Loading config items from omeron.yaml"
|
||||||
|
config_items=()
|
||||||
|
local raw_items
|
||||||
|
raw_items="$(python3 -c "
|
||||||
|
import yaml
|
||||||
|
with open('$OMERON_PROJECT_DIR/config/omeron.yaml') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
items = data.get('dotfiles', {}).get('items', [])
|
||||||
|
print(' '.join(items))
|
||||||
|
" 2>/dev/null)"
|
||||||
|
|
||||||
|
if [[ -n "$raw_items" ]]; then
|
||||||
|
read -ra config_items <<< "$raw_items"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((${#config_items[@]} == 0)); then
|
||||||
|
config_items=(hypr waybar wofi swaync kitty gtk-3.0 gtk-4.0 qt5ct qt6ct)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! tui_confirm "Deploy dotfiles to ~/.config? (existing files will be backed up)"; then
|
||||||
|
log_info "Dotfile deployment skipped by user"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
backup_dir="$(backup_file "$HOME/.config/hypr" "$HOME/.dotfiles-backup/$(date +%Y%m%d-%H%M%S)")"
|
||||||
|
backup_dir="$(dirname "$backup_dir" 2>/dev/null || printf '%s' "$HOME/.dotfiles-backup/$(date +%Y%m%d-%H%M%S)")"
|
||||||
|
|
||||||
|
for item in "${config_items[@]}"; do
|
||||||
|
local source="$dotfiles_dir/$item"
|
||||||
|
local target="$HOME/.config/$item"
|
||||||
|
|
||||||
|
if [[ ! -d "$source" ]] && [[ ! -f "$source" ]]; then
|
||||||
|
log_warn "Source not found: $source"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Deploying $item..."
|
||||||
|
copy_path "$source" "$target"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -f "$dotfiles_dir/starship.toml" ]]; then
|
||||||
|
copy_path "$dotfiles_dir/starship.toml" "$HOME/.config/starship.toml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local wallpaper_source="$dotfiles_dir/wallpapers"
|
||||||
|
local wallpaper_target="$HOME/Bilder/Wallpaper"
|
||||||
|
if [[ -d "$wallpaper_source" ]]; then
|
||||||
|
copy_path "$wallpaper_source" "$wallpaper_target"
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x "$HOME/.config/hypr/Scripts/"*.sh 2>/dev/null || true
|
||||||
|
chmod +x "$HOME/.config/hypr/Scripts/"*.py 2>/dev/null || true
|
||||||
|
chmod +x "$HOME/.config/waybar/scripts/"*.sh 2>/dev/null || true
|
||||||
|
|
||||||
|
replace_home_paths "$HOME/.config/hypr" "/home/pascal"
|
||||||
|
replace_home_paths "$HOME/.config/gtk-3.0" "/home/pascal"
|
||||||
|
replace_home_paths "$HOME/.config/gtk-4.0" "/home/pascal"
|
||||||
|
replace_home_paths "$HOME/.config/qt5ct" "/home/pascal"
|
||||||
|
replace_home_paths "$HOME/.config/qt6ct" "/home/pascal"
|
||||||
|
|
||||||
|
log_success "Dotfiles deployed (backup: $backup_dir)"
|
||||||
|
}
|
||||||
191
modules/core/packages.sh
Executable file
191
modules/core/packages.sh
Executable file
@@ -0,0 +1,191 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "System Packages - install Hyprland, GPU drivers, and all dependencies\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 0; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
require pacman pacman
|
||||||
|
is_arch || {
|
||||||
|
log_error "This module requires an Arch-based system"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "Package Installation"
|
||||||
|
|
||||||
|
local groups=()
|
||||||
|
local all_packages=()
|
||||||
|
|
||||||
|
if ((OMERON_FRESH_INSTALL)); then
|
||||||
|
tui_format "#{bold}Fresh install mode — installing everything#{normal}"
|
||||||
|
groups=(
|
||||||
|
"hyprland"
|
||||||
|
"gpu"
|
||||||
|
"audio"
|
||||||
|
"network"
|
||||||
|
"fonts"
|
||||||
|
"tools"
|
||||||
|
"qt"
|
||||||
|
"development"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
tui_format "#{bold}Existing system — checking for missing packages#{normal}"
|
||||||
|
groups=(
|
||||||
|
"hyprland"
|
||||||
|
"audio"
|
||||||
|
"network"
|
||||||
|
"tools"
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
for group in "${groups[@]}"; do
|
||||||
|
local pkgs=()
|
||||||
|
mapfile -t pkgs < <(get_group_packages "$group")
|
||||||
|
all_packages+=("${pkgs[@]}")
|
||||||
|
done
|
||||||
|
|
||||||
|
all_packages=("$(remove_duplicates "${all_packages[@]}")")
|
||||||
|
|
||||||
|
local total=${#all_packages[@]}
|
||||||
|
local existing=0
|
||||||
|
local missing=0
|
||||||
|
local pkg
|
||||||
|
|
||||||
|
for pkg in "${all_packages[@]}"; do
|
||||||
|
if is_package_installed "$pkg"; then
|
||||||
|
((existing++))
|
||||||
|
else
|
||||||
|
((missing++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}Package Summary:#{normal}"
|
||||||
|
tui_format " Total packages: ${total}"
|
||||||
|
tui_format " Already installed: ${existing}"
|
||||||
|
tui_format " To install: ${missing}"
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
if ((missing == 0)); then
|
||||||
|
log_success "All packages already installed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! tui_confirm "Install ${missing} missing packages?"; then
|
||||||
|
log_info "Package installation skipped by user"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((OMERON_FRESH_INSTALL)); then
|
||||||
|
log_info "Running system update first..."
|
||||||
|
sudo_run pacman -Syu --noconfirm 2>&1 | tail -3 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
local pacman_pkgs=()
|
||||||
|
local aur_pkgs=()
|
||||||
|
local gpu_pkgs=()
|
||||||
|
|
||||||
|
for pkg in "${all_packages[@]}"; do
|
||||||
|
if is_package_installed "$pkg"; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if pacman -Si "$pkg" >/dev/null 2>&1; then
|
||||||
|
pacman_pkgs+=("$pkg")
|
||||||
|
else
|
||||||
|
aur_pkgs+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((${#pacman_pkgs[@]})); then
|
||||||
|
log_info "Installing ${#pacman_pkgs[@]} pacman packages..."
|
||||||
|
tui_spin "Installing core packages..." sudo_run pacman -S --needed --noconfirm "${pacman_pkgs[@]}"
|
||||||
|
log_success "Core packages installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ((${#aur_pkgs[@]})); then
|
||||||
|
if have paru || have yay; then
|
||||||
|
log_info "Installing ${#aur_pkgs[@]} AUR packages..."
|
||||||
|
install_aur "${aur_pkgs[@]}" || log_warn "Some AUR packages may not have been installed"
|
||||||
|
else
|
||||||
|
log_warn "No AUR helper found. Install manually: ${aur_pkgs[*]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Package installation complete"
|
||||||
|
log_info "Installed: $((missing - ${#aur_pkgs[@]})) | AUR/optional: ${#aur_pkgs[@]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_group_packages() {
|
||||||
|
local group="$1"
|
||||||
|
|
||||||
|
case "$group" in
|
||||||
|
hyprland)
|
||||||
|
printf '%s\n' \
|
||||||
|
hyprland hyprpaper hyprlock hypridle \
|
||||||
|
waybar wofi swaync kitty \
|
||||||
|
brightnessctl playerctl \
|
||||||
|
grim slurp swappy hyprshot \
|
||||||
|
wl-clipboard libnotify sshpass
|
||||||
|
;;
|
||||||
|
|
||||||
|
gpu)
|
||||||
|
gpu_packages
|
||||||
|
printf '%s\n' \
|
||||||
|
mesa mesa-utils \
|
||||||
|
vulkan-tools vulkan-icd-loader \
|
||||||
|
libva-utils
|
||||||
|
;;
|
||||||
|
|
||||||
|
audio)
|
||||||
|
printf '%s\n' \
|
||||||
|
pipewire pipewire-pulse pipewire-alsa pipewire-jack \
|
||||||
|
wireplumber pavucontrol helvum \
|
||||||
|
sof-firmware
|
||||||
|
;;
|
||||||
|
|
||||||
|
network)
|
||||||
|
printf '%s\n' \
|
||||||
|
networkmanager network-manager-applet \
|
||||||
|
bluez bluez-utils blueman
|
||||||
|
;;
|
||||||
|
|
||||||
|
fonts)
|
||||||
|
printf '%s\n' \
|
||||||
|
noto-fonts noto-fonts-emoji ttf-jetbrains-mono-nerd \
|
||||||
|
ttf-font-awesome ttf-nerd-fonts-symbols ttf-dejavu
|
||||||
|
;;
|
||||||
|
|
||||||
|
tools)
|
||||||
|
printf '%s\n' \
|
||||||
|
nautilus papirus-icon-theme starship \
|
||||||
|
python-gobject gtk3 gtk4 \
|
||||||
|
polkit-gnome xdg-desktop-portal xdg-desktop-portal-hyprland \
|
||||||
|
qt5ct qt6ct \
|
||||||
|
gum
|
||||||
|
;;
|
||||||
|
|
||||||
|
qt)
|
||||||
|
printf '%s\n' \
|
||||||
|
qt5-wayland qt6-wayland \
|
||||||
|
qt5-base qt6-base
|
||||||
|
;;
|
||||||
|
|
||||||
|
development)
|
||||||
|
printf '%s\n' \
|
||||||
|
git base-devel \
|
||||||
|
zip unzip unrar p7zip \
|
||||||
|
ripgrep fd bat lsd \
|
||||||
|
htop btop fastfetch
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_duplicates() {
|
||||||
|
printf '%s\n' "$@" | sort -u
|
||||||
|
}
|
||||||
106
modules/core/preflight.sh
Executable file
106
modules/core/preflight.sh
Executable file
@@ -0,0 +1,106 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
OMERON_FRESH_INSTALL=0
|
||||||
|
OMERON_DETECTED_GPU=""
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "System Preflight - detect system state and missing dependencies\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 0; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
require pacman pacman
|
||||||
|
is_arch || {
|
||||||
|
log_error "This module requires an Arch-based system"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "System Preflight Check"
|
||||||
|
|
||||||
|
OMERON_DETECTED_GPU="$(detect_gpu)"
|
||||||
|
export OMERON_DETECTED_GPU
|
||||||
|
|
||||||
|
system_summary
|
||||||
|
|
||||||
|
if is_fresh_install; then
|
||||||
|
OMERON_FRESH_INSTALL=1
|
||||||
|
export OMERON_FRESH_INSTALL
|
||||||
|
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}#{yellow}⚠ Fresh system detected!#{normal}"
|
||||||
|
tui_format "Hyprland is not installed and no desktop session is running."
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
local gpu_name
|
||||||
|
gpu_name="$(lspci -nn 2>/dev/null | grep -i 'vga\|3d\|display' | sed 's/.*: //' | head -1 || echo "unknown")"
|
||||||
|
|
||||||
|
tui_format "#{bold}This installation will:#{normal}"
|
||||||
|
tui_format " • Hyprland compositor + tools"
|
||||||
|
tui_format " • GPU drivers for: ${gpu_name}"
|
||||||
|
tui_format " • Audio system (PipeWire)"
|
||||||
|
tui_format " • Network services (NetworkManager)"
|
||||||
|
tui_format " • Fonts and icon themes"
|
||||||
|
tui_format " • Dotfiles deployment"
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
if ! tui_confirm "Proceed with full system setup?"; then
|
||||||
|
log_info "Fresh installation aborted by user"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! have paru && ! have yay; then
|
||||||
|
log_info "No AUR helper found. Installing paru..."
|
||||||
|
install_aur_helper
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! have gum; then
|
||||||
|
log_info "Installing gum for better TUI..."
|
||||||
|
sudo_run pacman -S --needed --noconfirm gum 2>/dev/null || true
|
||||||
|
tui_style
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Preflight complete — ready for full installation"
|
||||||
|
else
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}#{green}✓ Existing Hyprland system detected#{normal}"
|
||||||
|
tui_format "Will check for missing packages and updates."
|
||||||
|
tui_format ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_aur_helper() {
|
||||||
|
if have paru || have yay; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v git >/dev/null 2>&1; then
|
||||||
|
sudo_run pacman -S --needed --noconfirm git base-devel
|
||||||
|
fi
|
||||||
|
|
||||||
|
local build_dir
|
||||||
|
build_dir="$(mktemp -d)"
|
||||||
|
|
||||||
|
log_info "Building paru from AUR..."
|
||||||
|
sudo_run pacman -S --needed --noconfirm rustup 2>/dev/null || true
|
||||||
|
|
||||||
|
if have rustup; then
|
||||||
|
rustup default stable >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git clone https://aur.archlinux.org/paru.git "$build_dir/paru" 2>/dev/null; then
|
||||||
|
(cd "$build_dir/paru" && makepkg -si --needed --noconfirm) 2>&1 | tail -5 || {
|
||||||
|
log_warn "paru build failed, trying yay..."
|
||||||
|
if git clone https://aur.archlinux.org/yay.git "$build_dir/yay" 2>/dev/null; then
|
||||||
|
(cd "$build_dir/yay" && makepkg -si --needed --noconfirm) 2>&1 | tail -5 || {
|
||||||
|
log_warn "yay build failed too. Install manually: paru -S paru-bin"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$build_dir"
|
||||||
|
}
|
||||||
50
modules/core/sddm.sh
Executable file
50
modules/core/sddm.sh
Executable file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "SDDM Theme - install custom login theme\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() {
|
||||||
|
if have sddm; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
if ! have sddm; then
|
||||||
|
log_warn "SDDM is not installed. Install with: sudo pacman -S sddm"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "SDDM Theme Installation"
|
||||||
|
|
||||||
|
local sddm_theme_dir="$OMERON_PROJECT_DIR/dotfiles/hypr/sddm-theme"
|
||||||
|
|
||||||
|
if [[ ! -d "$sddm_theme_dir/pascal-hypr" ]]; then
|
||||||
|
log_warn "SDDM theme not found at $sddm_theme_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Installing SDDM theme..."
|
||||||
|
sudo_run mkdir -p /usr/share/sddm/themes /etc/sddm.conf.d
|
||||||
|
|
||||||
|
if [[ -d "/usr/share/sddm/themes/pascal-hypr" ]]; then
|
||||||
|
sudo_run rm -rf "/usr/share/sddm/themes/pascal-hypr"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo_run cp -a "$sddm_theme_dir/pascal-hypr" /usr/share/sddm/themes/
|
||||||
|
|
||||||
|
if [[ -f "$sddm_theme_dir/sddm.conf" ]]; then
|
||||||
|
sudo_run cp -a "$sddm_theme_dir/sddm.conf" /etc/sddm.conf.d/10-pascal-hypr.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "SDDM theme installed"
|
||||||
|
|
||||||
|
if tui_confirm "Enable SDDM as display manager?"; then
|
||||||
|
sudo_run systemctl enable sddm --now 2>/dev/null || log_warn "Could not enable SDDM"
|
||||||
|
fi
|
||||||
|
}
|
||||||
52
modules/core/services.sh
Executable file
52
modules/core/services.sh
Executable file
@@ -0,0 +1,52 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "System Services - enable and start NetworkManager and Bluetooth\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 0; }
|
||||||
|
module_should_skip() {
|
||||||
|
have systemctl || return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
require systemctl systemd
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "System Services"
|
||||||
|
|
||||||
|
local services=("NetworkManager.service" "bluetooth.service")
|
||||||
|
|
||||||
|
if [[ -f "$OMERON_PROJECT_DIR/config/omeron.yaml" ]] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
local raw_services
|
||||||
|
raw_services="$(python3 -c "
|
||||||
|
import yaml
|
||||||
|
with open('$OMERON_PROJECT_DIR/config/omeron.yaml') as f:
|
||||||
|
data = yaml.safe_load(f)
|
||||||
|
svcs = data.get('services', [])
|
||||||
|
print(' '.join(svcs))
|
||||||
|
" 2>/dev/null)"
|
||||||
|
|
||||||
|
if [[ -n "$raw_services" ]]; then
|
||||||
|
read -ra services <<< "$raw_services"
|
||||||
|
for i in "${!services[@]}"; do
|
||||||
|
if ! [[ "${services[$i]}" == *".service" ]]; then
|
||||||
|
services[$i]="${services[$i]}.service"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
for svc in "${services[@]}"; do
|
||||||
|
log_info "Enabling $svc..."
|
||||||
|
if sudo_run systemctl enable --now "$svc" >/dev/null 2>&1; then
|
||||||
|
log_success "$svc enabled and started"
|
||||||
|
else
|
||||||
|
log_warn "Failed to enable $svc (may already be running)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_success "Services configured"
|
||||||
|
}
|
||||||
78
modules/homelab/setup.sh
Executable file
78
modules/homelab/setup.sh
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "Homelab Configuration - set up Unraid server access\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "Homelab Configuration"
|
||||||
|
|
||||||
|
local homelab_config_dir="$HOME/.config/homelab"
|
||||||
|
local homelab_config_file="$homelab_config_dir/config.yaml"
|
||||||
|
|
||||||
|
tui_format "#{bold}Homelab Control Center Setup#{normal}"
|
||||||
|
tui_format "This configures SSH access and connection details to your Unraid server."
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
local server_address server_username
|
||||||
|
|
||||||
|
server_address="$(tui_input "Server address (IP or domain)")"
|
||||||
|
if [[ -z "$server_address" ]]; then
|
||||||
|
log_info "Homelab setup skipped (no server address)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
server_username="$(tui_input "SSH username")"
|
||||||
|
if [[ -z "$server_username" ]]; then
|
||||||
|
server_username="root"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$homelab_config_dir"
|
||||||
|
|
||||||
|
cat > "$homelab_config_file" <<CONFIG
|
||||||
|
# Homelab Configuration
|
||||||
|
# Generated by Omeron on $(date --rfc-3339=seconds)
|
||||||
|
|
||||||
|
server:
|
||||||
|
address: "${server_address}"
|
||||||
|
username: "${server_username}"
|
||||||
|
port: 22
|
||||||
|
|
||||||
|
control_center:
|
||||||
|
refresh_interval: 5
|
||||||
|
theme: "dark"
|
||||||
|
|
||||||
|
features:
|
||||||
|
docker: true
|
||||||
|
services: true
|
||||||
|
storage: true
|
||||||
|
network: true
|
||||||
|
monitoring: true
|
||||||
|
CONFIG
|
||||||
|
|
||||||
|
log_success "Homelab configuration saved to $homelab_config_file"
|
||||||
|
|
||||||
|
tui_format ""
|
||||||
|
tui_format "#{bold}Configuration Summary:#{normal}"
|
||||||
|
tui_format " Server address: ${server_address}"
|
||||||
|
tui_format " SSH username: ${server_username}"
|
||||||
|
tui_format ""
|
||||||
|
tui_format "You can edit the configuration anytime at:"
|
||||||
|
tui_format " ${homelab_config_file}"
|
||||||
|
|
||||||
|
if tui_confirm "Test SSH connection to ${server_username}@${server_address}?"; then
|
||||||
|
log_info "Testing SSH connection..."
|
||||||
|
if ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new "${server_username}@${server_address}" "echo OK" 2>&1; then
|
||||||
|
log_success "SSH connection successful"
|
||||||
|
else
|
||||||
|
log_warn "SSH connection failed. You may need to set up SSH keys or the server may not be reachable."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
74
modules/optional/install.sh
Executable file
74
modules/optional/install.sh
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "Optional Software - select and install additional packages\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "Optional Software Selection"
|
||||||
|
|
||||||
|
tui_format "#{bold}Select software to install:#{normal}"
|
||||||
|
tui_format "Use space to select, enter to confirm."
|
||||||
|
tui_format ""
|
||||||
|
|
||||||
|
local choices
|
||||||
|
choices="$(
|
||||||
|
gum choose --no-limit \
|
||||||
|
--header "Select optional packages (space to toggle, enter to confirm)" \
|
||||||
|
"Obsidian" \
|
||||||
|
"Neovim" \
|
||||||
|
"Visual Studio Code" \
|
||||||
|
"Spotify" \
|
||||||
|
"Brave Browser" \
|
||||||
|
"Chromium" \
|
||||||
|
"VLC" \
|
||||||
|
"PipeWire Tools" \
|
||||||
|
"Docker" \
|
||||||
|
"Blender" 2>&1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "$choices" ]]; then
|
||||||
|
log_info "No optional software selected"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Selected: $(printf '%s' "$choices" | tr '\n' ' ')"
|
||||||
|
|
||||||
|
while IFS= read -r selection; do
|
||||||
|
[[ -z "$selection" ]] && continue
|
||||||
|
local module_script="$OMERON_PROJECT_DIR/modules/optional/packages/$(echo "$selection" | tr '[:upper:]' '[:lower:]' | tr ' ' '-').sh"
|
||||||
|
if [[ -f "$module_script" ]]; then
|
||||||
|
log_info "Running installer for: $selection"
|
||||||
|
module_run "$module_script"
|
||||||
|
else
|
||||||
|
log_warn "No installer found for: $selection"
|
||||||
|
local pkg_name="${selection,,}"
|
||||||
|
pkg_name="${pkg_name// /-}"
|
||||||
|
install_standard_package "$selection" "$pkg_name"
|
||||||
|
fi
|
||||||
|
done <<< "$choices"
|
||||||
|
|
||||||
|
log_success "Optional software installation complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_standard_package() {
|
||||||
|
local display_name="$1"
|
||||||
|
local pkg_name="$2"
|
||||||
|
|
||||||
|
if tui_confirm "Install $display_name (searching pacman)?"; then
|
||||||
|
if pacman -Si "$pkg_name" >/dev/null 2>&1; then
|
||||||
|
install_pacman "$pkg_name"
|
||||||
|
log_success "$display_name installed"
|
||||||
|
else
|
||||||
|
log_info "$pkg_name not in pacman, trying AUR..."
|
||||||
|
install_aur "$pkg_name" 2>/dev/null || log_warn "Could not install $display_name"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
18
modules/optional/packages/brave-browser.sh
Executable file
18
modules/optional/packages/brave-browser.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install Brave Browser\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing Brave Browser..."
|
||||||
|
|
||||||
|
if pacman -Si brave-browser >/dev/null 2>&1; then
|
||||||
|
install_pacman brave-browser
|
||||||
|
elif pacman -Si brave-bin >/dev/null 2>&1; then
|
||||||
|
install_pacman brave-bin
|
||||||
|
else
|
||||||
|
install_aur brave-bin
|
||||||
|
fi
|
||||||
|
}
|
||||||
11
modules/optional/packages/chromium.sh
Executable file
11
modules/optional/packages/chromium.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install Chromium\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing Chromium..."
|
||||||
|
install_pacman chromium
|
||||||
|
}
|
||||||
11
modules/optional/packages/neovim.sh
Executable file
11
modules/optional/packages/neovim.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install Neovim\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing Neovim..."
|
||||||
|
install_pacman neovim
|
||||||
|
}
|
||||||
17
modules/optional/packages/obsidian.sh
Executable file
17
modules/optional/packages/obsidian.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install Obsidian\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing Obsidian..."
|
||||||
|
if pacman -Si obsidian >/dev/null 2>&1; then
|
||||||
|
install_pacman obsidian
|
||||||
|
elif pacman -Si obsidian-bin >/dev/null 2>&1; then
|
||||||
|
install_pacman obsidian-bin
|
||||||
|
else
|
||||||
|
install_aur obsidian-bin
|
||||||
|
fi
|
||||||
|
}
|
||||||
43
modules/optional/packages/pipewire-tools.sh
Executable file
43
modules/optional/packages/pipewire-tools.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install PipeWire audio tools\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "PipeWire Audio Tools"
|
||||||
|
|
||||||
|
local packages=(
|
||||||
|
pipewire
|
||||||
|
pipewire-pulse
|
||||||
|
pipewire-alsa
|
||||||
|
pipewire-jack
|
||||||
|
wireplumber
|
||||||
|
pavucontrol
|
||||||
|
helvum
|
||||||
|
easyeffects
|
||||||
|
)
|
||||||
|
|
||||||
|
local to_install=()
|
||||||
|
local pkg
|
||||||
|
|
||||||
|
for pkg in "${packages[@]}"; do
|
||||||
|
if pacman -Si "$pkg" >/dev/null 2>&1 && ! is_package_installed "$pkg"; then
|
||||||
|
to_install+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if ((${#to_install[@]})); then
|
||||||
|
log_info "Installing PipeWire tools: ${to_install[*]}"
|
||||||
|
install_pacman "${to_install[@]}"
|
||||||
|
log_success "PipeWire tools installed"
|
||||||
|
else
|
||||||
|
log_info "All PipeWire tools already installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if have systemctl; then
|
||||||
|
systemctl --user enable --now pipewire pipewire-pulse wireplumber 2>/dev/null || true
|
||||||
|
log_info "PipeWire services enabled"
|
||||||
|
fi
|
||||||
|
}
|
||||||
18
modules/optional/packages/spotify.sh
Executable file
18
modules/optional/packages/spotify.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install Spotify\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing Spotify..."
|
||||||
|
|
||||||
|
if pacman -Si spotify >/dev/null 2>&1; then
|
||||||
|
install_pacman spotify
|
||||||
|
elif pacman -Si spotify-launcher >/dev/null 2>&1; then
|
||||||
|
install_pacman spotify-launcher
|
||||||
|
else
|
||||||
|
install_aur spotify-launcher
|
||||||
|
fi
|
||||||
|
}
|
||||||
18
modules/optional/packages/visual-studio-code.sh
Executable file
18
modules/optional/packages/visual-studio-code.sh
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install Visual Studio Code\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing Visual Studio Code..."
|
||||||
|
|
||||||
|
if pacman -Si code >/dev/null 2>&1; then
|
||||||
|
install_pacman code
|
||||||
|
elif pacman -Si visual-studio-code-bin >/dev/null 2>&1; then
|
||||||
|
install_pacman visual-studio-code-bin
|
||||||
|
else
|
||||||
|
install_aur visual-studio-code-bin
|
||||||
|
fi
|
||||||
|
}
|
||||||
11
modules/optional/packages/vlc.sh
Executable file
11
modules/optional/packages/vlc.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() { printf "Install VLC\n"; }
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
module_prereqs() { return 0; }
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_info "Installing VLC..."
|
||||||
|
install_pacman vlc
|
||||||
|
}
|
||||||
60
modules/post/apply-theme.sh
Executable file
60
modules/post/apply-theme.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
module_description() {
|
||||||
|
printf "Apply Theme - set default Hyprland theme and restart services\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
module_required() { return 1; }
|
||||||
|
module_should_skip() { return 1; }
|
||||||
|
|
||||||
|
module_prereqs() {
|
||||||
|
if ! have hyprctl && ! have notify-send; then
|
||||||
|
log_warn "Neither hyprctl nor notify-send found. Theme can still be applied later."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
module_main() {
|
||||||
|
log_section "Theme Application"
|
||||||
|
|
||||||
|
local theme_script="$HOME/.config/hypr/Scripts/theme-menu.sh"
|
||||||
|
local themes_dir="$HOME/.config/hypr/Themes"
|
||||||
|
|
||||||
|
if [[ ! -f "$theme_script" ]]; then
|
||||||
|
log_warn "Theme script not found (dotfiles may not be deployed yet)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local default_theme="${OMERON_DEFAULT_THEME:-forest-neon}"
|
||||||
|
|
||||||
|
local theme_file=""
|
||||||
|
if [[ -f "$themes_dir/$default_theme.theme" ]]; then
|
||||||
|
theme_file="$themes_dir/$default_theme.theme"
|
||||||
|
elif [[ -f "$themes_dir/forest-neon.theme" ]]; then
|
||||||
|
theme_file="$themes_dir/forest-neon.theme"
|
||||||
|
elif [[ -f "$themes_dir/rose-night.theme" ]]; then
|
||||||
|
theme_file="$themes_dir/rose-night.theme"
|
||||||
|
else
|
||||||
|
local available
|
||||||
|
available="$(find "$themes_dir" -name '*.theme' -type f | head -1)"
|
||||||
|
if [[ -n "$available" ]]; then
|
||||||
|
theme_file="$available"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$theme_file" ]]; then
|
||||||
|
log_warn "No theme files found in $themes_dir"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if tui_confirm "Apply theme: $(basename "$theme_file" .theme).theme?"; then
|
||||||
|
log_info "Applying theme: $(basename "$theme_file")"
|
||||||
|
tui_spin "Applying theme..." bash "$theme_script" --apply "$theme_file"
|
||||||
|
|
||||||
|
if have notify-send; then
|
||||||
|
notify-send "Omeron" "Theme applied: $(basename "$theme_file" .theme)" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Theme applied: $(basename "$theme_file" .theme)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user