fix: add wayland dev headers and scanner for pywayland build on NixOS

This commit is contained in:
Celes Renata
2026-05-08 15:55:01 -07:00
commit f143bce273
740 changed files with 86018 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
# Configuration override system - allows complete manual control
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland;
in
{
options.programs.dots-hyprland.overrides = {
# Complete file overrides - when set, completely replaces any generated config
hyprlandConf = mkOption {
type = types.nullOr types.lines;
default = null;
description = ''
Complete hyprland.conf content. When set, completely overrides:
- Any rich hyprland.* configuration options
- Any copied hyprland.conf from source
- Generates the entire file from this content
'';
example = ''
# Custom Hyprland configuration
general {
gaps_in = 10
gaps_out = 20
}
'';
};
quickshellConfig = mkOption {
type = types.nullOr types.lines;
default = null;
description = ''
Complete Config.qml content. When set, completely overrides:
- Any rich quickshell.* configuration options
- Any copied Config.qml from source
- Generates the entire file from this content
'';
};
footConfig = mkOption {
type = types.nullOr types.lines;
default = null;
description = ''
Complete foot.ini content. When set, completely overrides:
- Any rich terminal.* configuration options
- Any copied foot.ini from source
- Generates the entire file from this content
'';
};
toucheggConf = mkOption {
type = types.nullOr types.lines;
default = null;
description = ''
Complete touchegg.conf content. When set, completely overrides:
- Any copied touchegg.conf from source
- Generates the entire file from this content
'';
};
# Directory-level overrides
hyprDirectory = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Complete hypr directory override. When set, copies entire directory
and ignores all hyprland configuration options.
'';
};
quickshellDirectory = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
Complete quickshell directory override. When set, copies entire directory
and ignores all quickshell configuration options.
'';
};
};
config = mkIf cfg.enable {
# Override warnings
warnings =
(optional (cfg.overrides.hyprlandConf != null && cfg.hyprland != {})
"dots-hyprland: overrides.hyprlandConf is set, ignoring all hyprland.* options") ++
(optional (cfg.overrides.quickshellConfig != null && cfg.quickshell != {})
"dots-hyprland: overrides.quickshellConfig is set, ignoring all quickshell.* options") ++
(optional (cfg.overrides.footConfig != null && cfg.terminal != {})
"dots-hyprland: overrides.footConfig is set, ignoring all terminal.* options");
# File overrides take absolute priority
xdg.configFile = mkMerge [
# Hyprland complete override
(mkIf (cfg.overrides.hyprlandConf != null) {
"hypr/hyprland.conf".text = cfg.overrides.hyprlandConf;
})
# Quickshell complete override
(mkIf (cfg.overrides.quickshellConfig != null) {
"quickshell/ii/modules/common/Config.qml".text = cfg.overrides.quickshellConfig;
})
# Terminal complete override
(mkIf (cfg.overrides.footConfig != null) {
"foot/foot.ini".text = cfg.overrides.footConfig;
})
# Touchegg complete override
(mkIf (cfg.overrides.toucheggConf != null) {
"touchegg/touchegg.conf".text = cfg.overrides.toucheggConf;
})
# Directory overrides
(mkIf (cfg.overrides.hyprDirectory != null) {
"hypr" = {
source = cfg.overrides.hyprDirectory;
recursive = true;
};
})
(mkIf (cfg.overrides.quickshellDirectory != null) {
"quickshell" = {
source = cfg.overrides.quickshellDirectory;
recursive = true;
};
})
];
};
}
+138
View File
@@ -0,0 +1,138 @@
# Hyprland configuration options for dots-hyprland
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.hyprland;
in
{
options.programs.dots-hyprland.hyprland = {
# General settings
general = {
gapsIn = mkOption {
type = types.int;
default = 4;
description = "Inner gaps between windows";
};
gapsOut = mkOption {
type = types.int;
default = 7;
description = "Outer gaps around windows";
};
borderSize = mkOption {
type = types.int;
default = 2;
description = "Border width around windows";
};
allowTearing = mkOption {
type = types.bool;
default = false;
description = "Allow screen tearing (useful for gaming)";
};
};
# Decoration settings
decoration = {
rounding = mkOption {
type = types.int;
default = 16;
description = "Corner rounding radius";
};
blurEnabled = mkOption {
type = types.bool;
default = true;
description = "Enable background blur";
};
};
# Gesture settings
gestures = {
workspaceSwipe = mkOption {
type = types.bool;
default = true;
description = "Enable workspace swipe gestures";
};
};
# Monitor configuration
monitors = mkOption {
type = types.listOf types.str;
default = [];
description = "Monitor configuration strings";
example = [ "eDP-1,1920x1080@60,0x0,1" ];
};
};
config = mkIf (config.programs.dots-hyprland.enable && config.programs.dots-hyprland.overrides.hyprlandConf == null) {
# Only generate if no manual override is set
xdg.configFile."hypr/general.conf".text = ''
# General Hyprland configuration for dots-hyprland (NixOS-managed)
${optionalString (cfg.monitors != []) ''
# Monitor configuration
${concatMapStringsSep "\n" (monitor: "monitor=${monitor}") cfg.monitors}
''}
# Gestures (Hyprland 0.51+ syntax)
gestures {
gesture = 3, horizontal, workspace
}
general {
# Gaps and border
gaps_in = ${toString cfg.general.gapsIn}
gaps_out = ${toString cfg.general.gapsOut}
gaps_workspaces = 50
border_size = ${toString cfg.general.borderSize}
col.active_border = rgba(cba6f7ff)
col.inactive_border = rgba(313244ff)
resize_on_border = true
no_focus_fallback = true
allow_tearing = ${boolToString cfg.general.allowTearing}
snap {
enabled = true
}
}
dwindle {
preserve_split = true
smart_split = false
smart_resizing = false
}
decoration {
rounding = ${toString cfg.decoration.rounding}
blur {
enabled = ${boolToString cfg.decoration.blurEnabled}
xray = true
special = false
new_optimizations = true
size = 14
passes = 4
brightness = 1
noise = 0.01
contrast = 1
popups = true
popups_ignorealpha = 0.6
}
drop_shadow = true
shadow_ignore_window = true
shadow_offset = 0 2
shadow_range = 20
shadow_render_power = 3
col.shadow = rgba(00000055)
}
'';
};
}
+436
View File
@@ -0,0 +1,436 @@
# Quickshell configuration options for dots-hyprland
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.quickshell;
in
{
options.programs.dots-hyprland.quickshell = {
# Appearance settings
appearance = {
extraBackgroundTint = mkOption {
type = types.bool;
default = true;
description = "Enable extra background tint";
};
fakeScreenRounding = mkOption {
type = types.enum [ 0 1 2 ];
default = 2;
description = "Screen rounding mode: 0=None, 1=Always, 2=When not fullscreen";
};
transparency = mkOption {
type = types.bool;
default = false;
description = "Enable transparency effects";
};
};
# Bar configuration
bar = {
bottom = mkOption {
type = types.bool;
default = false;
description = "Place bar at bottom instead of top";
};
cornerStyle = mkOption {
type = types.enum [ 0 1 2 ];
default = 0;
description = "Bar corner style: 0=Hug, 1=Float, 2=Plain rectangle";
};
borderless = mkOption {
type = types.bool;
default = false;
description = "Remove grouping of bar items";
};
topLeftIcon = mkOption {
type = types.enum [ "distro" "spark" ];
default = "spark";
description = "Icon to show in top-left of bar";
};
showBackground = mkOption {
type = types.bool;
default = true;
description = "Show bar background";
};
verbose = mkOption {
type = types.bool;
default = true;
description = "Show detailed information in bar";
};
utilButtons = {
showScreenSnip = mkOption {
type = types.bool;
default = true;
description = "Show screen snip button";
};
showColorPicker = mkOption {
type = types.bool;
default = false;
description = "Show color picker button";
};
showMicToggle = mkOption {
type = types.bool;
default = false;
description = "Show microphone toggle button";
};
showKeyboardToggle = mkOption {
type = types.bool;
default = true;
description = "Show keyboard layout toggle";
};
showDarkModeToggle = mkOption {
type = types.bool;
default = true;
description = "Show dark mode toggle";
};
showPerformanceProfileToggle = mkOption {
type = types.bool;
default = false;
description = "Show performance profile toggle";
};
};
workspaces = {
monochromeIcons = mkOption {
type = types.bool;
default = true;
description = "Use monochrome workspace icons";
};
shown = mkOption {
type = types.int;
default = 10;
description = "Number of workspaces to show";
};
showAppIcons = mkOption {
type = types.bool;
default = true;
description = "Show application icons in workspaces";
};
alwaysShowNumbers = mkOption {
type = types.bool;
default = false;
description = "Always show workspace numbers";
};
showNumberDelay = mkOption {
type = types.int;
default = 300;
description = "Delay before showing workspace numbers (milliseconds)";
};
};
};
# Battery settings
battery = {
low = mkOption {
type = types.int;
default = 20;
description = "Low battery threshold (%)";
};
critical = mkOption {
type = types.int;
default = 5;
description = "Critical battery threshold (%)";
};
automaticSuspend = mkOption {
type = types.bool;
default = true;
description = "Enable automatic suspend on critical battery";
};
suspend = mkOption {
type = types.int;
default = 3;
description = "Minutes before suspend on critical battery";
};
};
# Application settings
apps = {
terminal = mkOption {
type = types.str;
default = "kitty -1";
description = "Terminal command for shell actions";
};
bluetooth = mkOption {
type = types.str;
default = "kcmshell6 kcm_bluetooth";
description = "Bluetooth settings command";
};
network = mkOption {
type = types.str;
default = "plasmawindowed org.kde.plasma.networkmanagement";
description = "Network settings command";
};
taskManager = mkOption {
type = types.str;
default = "plasma-systemmonitor --page-name Processes";
description = "Task manager command";
};
};
# Time format
time = {
format = mkOption {
type = types.str;
default = "hh:mm";
description = "Time format string";
};
dateFormat = mkOption {
type = types.str;
default = "ddd, dd/MM";
description = "Date format string";
};
};
};
config = mkIf (config.programs.dots-hyprland.enable &&
config.programs.dots-hyprland.overrides.quickshellConfig == null &&
!(config.programs.dots-hyprland.configuration.enable or false)) {
# Only generate if no manual override is set AND configuration copying is disabled
xdg.configFile."quickshell/ii/modules/common/Config.qml".text = ''
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
Singleton {
id: root
property string filePath: Directories.shellConfigPath
property alias options: configOptionsJsonAdapter
property bool ready: true // Always ready for NixOS-generated config
function setNestedValue(nestedKey, value) {
// NixOS-managed config - values are set at build time
console.log("NixOS-managed configuration - ignoring runtime changes");
}
JsonAdapter {
id: configOptionsJsonAdapter
property JsonObject policies: JsonObject {
property int ai: 1
property int weeb: 1
}
property JsonObject ai: JsonObject {
property string systemPrompt: "## Style\n- Use casual tone, don't be formal! Make sure you answer precisely without hallucination and prefer bullet points over walls of text. You can have a friendly greeting at the beginning of the conversation, but don't repeat the user's question\n\n## Context (ignore when irrelevant)\n- You are a helpful and inspiring sidebar assistant on a NixOS Linux system\n- Desktop environment: Hyprland + dots-hyprland\n- Current date & time: {DATETIME}\n- Focused app: {WINDOWCLASS}\n\n## Presentation\n- Use Markdown features in your response"
property string tool: "functions"
property list<var> extraModels: []
}
property JsonObject appearance: JsonObject {
property bool extraBackgroundTint: ${boolToString cfg.appearance.extraBackgroundTint}
property int fakeScreenRounding: ${toString cfg.appearance.fakeScreenRounding}
property bool transparency: ${boolToString cfg.appearance.transparency}
property JsonObject wallpaperTheming: JsonObject {
property bool enableAppsAndShell: true
property bool enableQtApps: true
property bool enableTerminal: true
}
property JsonObject palette: JsonObject {
property string type: "auto"
}
}
property JsonObject audio: JsonObject {
property JsonObject protection: JsonObject {
property bool enable: true
property real maxAllowedIncrease: 10
property real maxAllowed: 90
}
}
property JsonObject apps: JsonObject {
property string bluetooth: "${cfg.apps.bluetooth}"
property string network: "${cfg.apps.network}"
property string networkEthernet: "kcmshell6 kcm_networkmanagement"
property string taskManager: "${cfg.apps.taskManager}"
property string terminal: "${cfg.apps.terminal}"
}
property JsonObject background: JsonObject {
property bool fixedClockPosition: false
property real clockX: -500
property real clockY: -500
property string wallpaperPath: ""
property string thumbnailPath: ""
property JsonObject parallax: JsonObject {
property bool enableWorkspace: true
property real workspaceZoom: 1.07
property bool enableSidebar: true
}
}
property JsonObject bar: JsonObject {
property bool bottom: ${boolToString cfg.bar.bottom}
property int cornerStyle: ${toString cfg.bar.cornerStyle}
property bool borderless: ${boolToString cfg.bar.borderless}
property string topLeftIcon: "${cfg.bar.topLeftIcon}"
property bool showBackground: ${boolToString cfg.bar.showBackground}
property bool verbose: ${boolToString cfg.bar.verbose}
property JsonObject resources: JsonObject {
property bool alwaysShowSwap: true
property bool alwaysShowCpu: false
}
property list<string> screenList: []
property JsonObject utilButtons: JsonObject {
property bool showScreenSnip: ${boolToString cfg.bar.utilButtons.showScreenSnip}
property bool showColorPicker: ${boolToString cfg.bar.utilButtons.showColorPicker}
property bool showMicToggle: ${boolToString cfg.bar.utilButtons.showMicToggle}
property bool showKeyboardToggle: ${boolToString cfg.bar.utilButtons.showKeyboardToggle}
property bool showDarkModeToggle: ${boolToString cfg.bar.utilButtons.showDarkModeToggle}
property bool showPerformanceProfileToggle: ${boolToString cfg.bar.utilButtons.showPerformanceProfileToggle}
}
property JsonObject tray: JsonObject {
property bool monochromeIcons: true
}
property JsonObject workspaces: JsonObject {
property bool monochromeIcons: ${boolToString cfg.bar.workspaces.monochromeIcons}
property int shown: ${toString cfg.bar.workspaces.shown}
property bool showAppIcons: ${boolToString cfg.bar.workspaces.showAppIcons}
property bool alwaysShowNumbers: ${boolToString cfg.bar.workspaces.alwaysShowNumbers}
property int showNumberDelay: ${toString cfg.bar.workspaces.showNumberDelay}
}
property JsonObject weather: JsonObject {
property bool enable: false
property bool enableGPS: true
property string city: ""
property bool useUSCS: false
property int fetchInterval: 10
}
}
property JsonObject battery: JsonObject {
property int low: ${toString cfg.battery.low}
property int critical: ${toString cfg.battery.critical}
property bool automaticSuspend: ${boolToString cfg.battery.automaticSuspend}
property int suspend: ${toString cfg.battery.suspend}
}
property JsonObject dock: JsonObject {
property bool enable: false
property bool monochromeIcons: true
property real height: 60
property real hoverRegionHeight: 2
property bool pinnedOnStartup: false
property bool hoverToReveal: true
property list<string> pinnedApps: ["org.kde.dolphin", "kitty"]
property list<string> ignoredAppRegexes: []
}
property JsonObject language: JsonObject {
property JsonObject translator: JsonObject {
property string engine: "auto"
property string targetLanguage: "auto"
property string sourceLanguage: "auto"
}
}
property JsonObject light: JsonObject {
property JsonObject night: JsonObject {
property bool automatic: true
property string from: "19:00"
property string to: "06:30"
property int colorTemperature: 5000
}
}
property JsonObject networking: JsonObject {
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
}
property JsonObject osd: JsonObject {
property int timeout: 1000
}
property JsonObject osk: JsonObject {
property string layout: "qwerty_full"
property bool pinnedOnStartup: false
}
property JsonObject overview: JsonObject {
property bool enable: true
property real scale: 0.18
property real rows: 2
property real columns: 5
}
property JsonObject resources: JsonObject {
property int updateInterval: 3000
}
property JsonObject search: JsonObject {
property int nonAppResultDelay: 30
property string engineBaseUrl: "https://www.google.com/search?q="
property list<string> excludedSites: ["quora.com"]
property bool sloppy: false
property JsonObject prefix: JsonObject {
property string action: "/"
property string clipboard: ";"
property string emojis: ":"
}
}
property JsonObject sidebar: JsonObject {
property bool keepRightSidebarLoaded: true
property JsonObject translator: JsonObject {
property int delay: 300
}
property JsonObject booru: JsonObject {
property bool allowNsfw: false
property string defaultProvider: "yandere"
property int limit: 20
property JsonObject zerochan: JsonObject {
property string username: "[unset]"
}
}
}
property JsonObject time: JsonObject {
property string format: "${cfg.time.format}"
property string dateFormat: "${cfg.time.dateFormat}"
}
property JsonObject windows: JsonObject {
property bool showTitlebar: true
property bool centerTitle: true
}
property JsonObject hacks: JsonObject {
property int arbitraryRaceConditionDelay: 20
}
property JsonObject screenshotTool: JsonObject {
property bool showContentRegions: true
}
}
}
'';
};
}
+248
View File
@@ -0,0 +1,248 @@
# Quickshell service integration with staging system
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.quickshell;
mainCfg = config.programs.dots-hyprland;
# Service startup script that handles initial setup
# Note: quickshell must be in home.packages
quickshellStartup = pkgs.writeShellScript "quickshell-startup" ''
#!/usr/bin/env bash
set -e
# Colors for logging
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo -e "''${GREEN}[quickshell-service]''${NC} $1" >&2
}
warn() {
echo -e "''${YELLOW}[quickshell-service]''${NC} WARNING: $1" >&2
}
error() {
echo -e "''${RED}[quickshell-service]''${NC} ERROR: $1" >&2
}
STAGING_DIR="$HOME/${mainCfg.writable-mode.stagingDir}"
CONFIG_DIR="$HOME/.config"
SETUP_SCRIPT="$HOME/${mainCfg.writable-mode.setupScript}"
SETUP_MARKER="$HOME/.cache/dots-hyprland/setup-complete"
# Ensure cache directory exists
mkdir -p "$(dirname "$SETUP_MARKER")"
# Check if initial setup has been run
if [[ ! -f "$SETUP_MARKER" ]]; then
log "🚀 First run detected - running initial setup"
# Check if staging directory exists
if [[ ! -d "$STAGING_DIR" ]]; then
error "Staging directory not found: $STAGING_DIR"
error "Please run 'home-manager switch' first"
exit 1
fi
# Check if setup script exists
if [[ ! -x "$SETUP_SCRIPT" ]]; then
error "Setup script not found or not executable: $SETUP_SCRIPT"
exit 1
fi
log "📋 Running initial setup script..."
if "$SETUP_SCRIPT"; then
# Mark setup as complete
echo "$(date)" > "$SETUP_MARKER"
log " Initial setup completed successfully"
else
error " Initial setup failed"
exit 1
fi
else
log " Setup already completed ($(cat "$SETUP_MARKER"))"
fi
# Verify quickshell configuration exists
if [[ ! -d "$CONFIG_DIR/quickshell" ]]; then
error "Quickshell configuration not found at $CONFIG_DIR/quickshell"
error "Initial setup may have failed"
exit 1
fi
# Set up environment variables
export ILLOGICAL_IMPULSE_VIRTUAL_ENV="$HOME/.local/state/quickshell/.venv"
export QT_SCALE_FACTOR="${toString cfg.scaling}"
export QT_QUICK_CONTROLS_STYLE="Basic"
export QT_QUICK_FLICKABLE_WHEEL_DECELERATION="10000"
# Ensure PATH includes user applications - CRITICAL for app launching
export PATH="${config.home.profileDirectory}/bin:/run/wrappers/bin:${config.home.homeDirectory}/.nix-profile/bin:/etc/profiles/per-user/${config.home.username}/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:$PATH"
export XDG_DATA_DIRS="${config.home.profileDirectory}/share:${config.home.homeDirectory}/.nix-profile/share:/etc/profiles/per-user/${config.home.username}/share:/nix/var/nix/profiles/default/share:/run/current-system/sw/share:$XDG_DATA_DIRS"
# Create application launcher wrapper that quickshell can use
LAUNCHER_WRAPPER="$HOME/.cache/dots-hyprland/app-launcher"
mkdir -p "$(dirname "$LAUNCHER_WRAPPER")"
cat > "$LAUNCHER_WRAPPER" << 'EOF'
#!/usr/bin/env bash
# Application launcher wrapper for quickshell
# Ensures proper PATH and environment for launched applications
# Use the same PATH that quickshell has
export PATH="${config.home.profileDirectory}/bin:/run/wrappers/bin:${config.home.homeDirectory}/.nix-profile/bin:/etc/profiles/per-user/${config.home.username}/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"
export XDG_DATA_DIRS="${config.home.profileDirectory}/share:${config.home.homeDirectory}/.nix-profile/share:/etc/profiles/per-user/${config.home.username}/share:/nix/var/nix/profiles/default/share:/run/current-system/sw/share"
# Launch the application
exec "$@"
EOF
chmod +x "$LAUNCHER_WRAPPER"
# Export the launcher wrapper path for quickshell to use
export DOTS_HYPRLAND_APP_LAUNCHER="$LAUNCHER_WRAPPER"
# Verify Python virtual environment
if [[ ! -d "$ILLOGICAL_IMPULSE_VIRTUAL_ENV" ]]; then
warn "Python virtual environment not found at $ILLOGICAL_IMPULSE_VIRTUAL_ENV"
warn "Some features may not work correctly"
fi
log "🎯 Starting quickshell with dots-hyprland configuration"
log "📁 Config: $CONFIG_DIR/quickshell/ii/shell.qml"
log "🐍 Python venv: $ILLOGICAL_IMPULSE_VIRTUAL_ENV"
log "🚀 App launcher: $LAUNCHER_WRAPPER"
# Set up Qt environment for dots-hyprland
# Let quickshell use its own Qt libraries
export XDG_DATA_DIRS="$XDG_DATA_DIRS:${pkgs.gsettings-desktop-schemas}/share"
# Start quickshell (from PATH - must be in home.packages)
exec quickshell -p "$CONFIG_DIR/quickshell/ii/shell.qml"
'';
in
{
options.programs.dots-hyprland.quickshell = {
enable = mkEnableOption "Quickshell service with staging integration";
autoStart = mkEnableOption "Auto-start with Hyprland session" // { default = true; };
restartOnFailure = mkEnableOption "Restart service on failure" // { default = true; };
scaling = mkOption {
type = types.float;
default = 1.0;
description = "UI scaling factor";
};
logLevel = mkOption {
type = types.enum [ "debug" "info" "warning" "error" ];
default = "info";
description = "Logging level for quickshell service";
};
};
config = mkIf cfg.enable {
# Install service management scripts (quickshell itself must be in home.packages)
home.packages = (with pkgs; [
(writeShellScriptBin "quickshell-restart" ''
systemctl --user restart quickshell.service
echo " Quickshell service restarted"
'')
(writeShellScriptBin "quickshell-status" ''
echo "🔍 Quickshell Service Status"
echo "=========================="
systemctl --user status quickshell.service --no-pager
echo ""
echo "📋 Recent logs:"
journalctl --user -u quickshell.service -n 10 --no-pager
'')
(writeShellScriptBin "quickshell-logs" ''
echo "📋 Following quickshell logs (Ctrl+C to exit):"
journalctl --user -u quickshell.service -f
'')
(writeShellScriptBin "quickshell-debug" ''
echo "🐛 Starting quickshell in debug mode..."
systemctl --user stop quickshell.service
QT_LOGGING_RULES="quickshell.*=true" ${quickshellStartup}
'')
]);
# Systemd user service for quickshell
systemd.user.services.quickshell = {
Unit = {
Description = "Quickshell - QtQuick based desktop shell with dots-hyprland";
Documentation = [ "https://quickshell.org" "https://end-4.github.io/dots-hyprland-wiki/" ];
PartOf = [ "hyprland-session.target" ];
After = [ "hyprland-session.target" "graphical-session.target" ];
Wants = [ "hyprland-session.target" ];
};
Service = {
Type = "simple";
ExecStart = quickshellStartup;
ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR2 $MAINPID";
Restart = if cfg.restartOnFailure then "on-failure" else "no";
RestartSec = 2;
TimeoutStartSec = 30;
TimeoutStopSec = 10;
# Environment variables - include full user environment
PassEnvironment = [ "HYPRLAND_INSTANCE_SIGNATURE" "WAYLAND_DISPLAY" ];
Environment = [
"QT_SCALE_FACTOR=${toString cfg.scaling}"
"QT_QUICK_CONTROLS_STYLE=Basic"
"QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000"
"QT_LOGGING_RULES=${
if cfg.logLevel == "debug" then "quickshell.*=true"
else if cfg.logLevel == "warning" then "*.warning=true"
else if cfg.logLevel == "error" then "*.critical=true"
else "*.info=true"
}"
# Include user's full PATH so applications can be launched
"PATH=${config.home.profileDirectory}/bin:/run/wrappers/bin:${config.home.homeDirectory}/.nix-profile/bin:/etc/profiles/per-user/${config.home.username}/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin"
# Include XDG data directories for application discovery
"XDG_DATA_DIRS=${config.home.profileDirectory}/share:${config.home.homeDirectory}/.nix-profile/share:/etc/profiles/per-user/${config.home.username}/share:/nix/var/nix/profiles/default/share:/run/current-system/sw/share"
# Application launcher wrapper path
"DOTS_HYPRLAND_APP_LAUNCHER=%h/.cache/dots-hyprland/app-launcher"
];
# Working directory
WorkingDirectory = "%h";
# Security settings
PrivateNetwork = false;
ProtectSystem = "strict";
ProtectHome = false; # Need access to home directory
NoNewPrivileges = true;
# Resource limits
MemoryMax = "2G";
CPUQuota = "200%";
};
Install = mkIf cfg.autoStart {
WantedBy = [ "hyprland-session.target" ];
};
};
# Create hyprland session target if it doesn't exist
systemd.user.targets.hyprland-session = {
Unit = {
Description = "Hyprland compositor session";
Documentation = [ "man:systemd.special(7)" ];
BindsTo = [ "graphical-session.target" ];
Wants = [ "graphical-session-pre.target" ];
After = [ "graphical-session-pre.target" ];
};
};
};
}
+7
View File
@@ -0,0 +1,7 @@
# System services required for dots-hyprland
{ config, lib, pkgs, ... }:
{
# UPower for battery monitoring in quickshell bar
services.upower.enable = lib.mkDefault true;
}
+71
View File
@@ -0,0 +1,71 @@
# Terminal configuration options for dots-hyprland
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.terminal;
in
{
options.programs.dots-hyprland.terminal = {
# Terminal settings
scrollback = {
lines = mkOption {
type = types.int;
default = 1000;
description = "Number of scrollback lines";
};
multiplier = mkOption {
type = types.float;
default = 3.0;
description = "Scrollback multiplier";
};
};
cursor = {
style = mkOption {
type = types.enum [ "block" "beam" "underline" ];
default = "beam";
description = "Cursor style";
};
blink = mkOption {
type = types.bool;
default = false;
description = "Enable cursor blinking";
};
beamThickness = mkOption {
type = types.float;
default = 1.5;
description = "Beam cursor thickness";
};
};
colors = {
alpha = mkOption {
type = types.float;
default = 0.95;
description = "Terminal transparency (0.0 - 1.0)";
};
};
mouse = {
hideWhenTyping = mkOption {
type = types.bool;
default = false;
description = "Hide mouse cursor when typing";
};
alternateScrollMode = mkOption {
type = types.bool;
default = true;
description = "Enable alternate scroll mode";
};
};
};
# Foot configuration disabled - let Quickshell transparency system handle it dynamically
config = {};
}
+271
View File
@@ -0,0 +1,271 @@
# Touchegg gesture support for dots-hyprland
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.touchegg;
mainCfg = config.programs.dots-hyprland;
in
{
options.programs.dots-hyprland.touchegg = {
enable = mkEnableOption "Touchegg gesture support";
config = mkOption {
type = types.lines;
default = ''
<touchégg>
<settings>
<property name="animation_delay">150</property>
<property name="action_execute_threshold">80</property>
<property name="color">auto</property>
<property name="borderColor">auto</property>
</settings>
<application name="All">
<!-- 3-finger pinch in: Close window -->
<gesture type="PINCH" fingers="3" direction="IN">
<action type="CLOSE_WINDOW">
<animate>true</animate>
<color>F84A53</color>
<borderColor>F84A53</borderColor>
</action>
</gesture>
<!-- 2-finger tap: Right click -->
<gesture type="TAP" fingers="2" direction="UNKNOWN">
<action type="MOUSE_CLICK">
<button>3</button>
<on>begin</on>
</action>
</gesture>
<!-- 3-finger tap: Middle click -->
<gesture type="TAP" fingers="3" direction="UNKNOWN">
<action type="MOUSE_CLICK">
<button>2</button>
<on>begin</on>
</action>
</gesture>
<!-- 4-finger pinch in: Fullscreen mode 0 -->
<gesture type="PINCH" fingers="4" direction="IN">
<action type="RUN_COMMAND">
<command>hyprctl dispatch fullscreen 0</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- 4-finger pinch out: Fullscreen mode 1 -->
<gesture type="PINCH" fingers="4" direction="OUT">
<action type="RUN_COMMAND">
<command>hyprctl dispatch fullscreen 1</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- Note: 3-finger left/right swipes removed - handled by Hyprland's built-in workspace_swipe -->
<!-- 3-finger swipe up: Show overview -->
<gesture type="SWIPE" fingers="3" direction="UP">
<action type="RUN_COMMAND">
<command>hyprctl dispatch global quickshell:overviewToggle</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- 3-finger swipe down: Show all windows -->
<gesture type="SWIPE" fingers="3" direction="DOWN">
<action type="RUN_COMMAND">
<command>hyprctl dispatch overview</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- 4-finger swipe left: Move window left -->
<gesture type="SWIPE" fingers="4" direction="LEFT">
<action type="RUN_COMMAND">
<command>hyprctl dispatch movewindow l</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- 4-finger swipe right: Move window right -->
<gesture type="SWIPE" fingers="4" direction="RIGHT">
<action type="RUN_COMMAND">
<command>hyprctl dispatch movewindow r</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- 4-finger swipe up: Move window up -->
<gesture type="SWIPE" fingers="4" direction="UP">
<action type="RUN_COMMAND">
<command>hyprctl dispatch movewindow u</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
<!-- 4-finger swipe down: Move window down -->
<gesture type="SWIPE" fingers="4" direction="DOWN">
<action type="RUN_COMMAND">
<command>hyprctl dispatch movewindow d</command>
<repeat>false</repeat>
<animation>NONE</animation>
<on>begin</on>
</action>
</gesture>
</application>
<!-- Browser-specific gestures for zoom -->
<application name="chromium-browser">
<gesture type="PINCH" fingers="2" direction="IN">
<action type="SEND_KEYS">
<repeat>true</repeat>
<modifiers>Control_L</modifiers>
<keys>KP_Subtract</keys>
<decreaseKeys>KP_Add</decreaseKeys>
</action>
</gesture>
<gesture type="PINCH" fingers="2" direction="OUT">
<action type="SEND_KEYS">
<repeat>true</repeat>
<modifiers>Control_L</modifiers>
<keys>KP_Add</keys>
<decreaseKeys>KP_Subtract</decreaseKeys>
</action>
</gesture>
</application>
<application name="google-chrome">
<gesture type="PINCH" fingers="2" direction="IN">
<action type="SEND_KEYS">
<repeat>true</repeat>
<modifiers>Control_L</modifiers>
<keys>KP_Subtract</keys>
<decreaseKeys>KP_Add</decreaseKeys>
</action>
</gesture>
<gesture type="PINCH" fingers="2" direction="OUT">
<action type="SEND_KEYS">
<repeat>true</repeat>
<modifiers>Control_L</modifiers>
<keys>KP_Add</keys>
<decreaseKeys>KP_Subtract</decreaseKeys>
</action>
</gesture>
</application>
<application name="firefox">
<gesture type="PINCH" fingers="2" direction="IN">
<action type="SEND_KEYS">
<repeat>true</repeat>
<modifiers>Control_L</modifiers>
<keys>KP_Subtract</keys>
<decreaseKeys>KP_Add</decreaseKeys>
</action>
</gesture>
<gesture type="PINCH" fingers="2" direction="OUT">
<action type="SEND_KEYS">
<repeat>true</repeat>
<modifiers>Control_L</modifiers>
<keys>KP_Add</keys>
<decreaseKeys>KP_Subtract</decreaseKeys>
</action>
</gesture>
</application>
</touchégg>
'';
description = "Touchegg configuration XML";
};
};
config = mkIf cfg.enable {
# Note: touchegg service needs to be enabled at system level
# Add this to your NixOS configuration: services.touchegg.enable = true;
# Install touchegg configuration (both user and system locations)
xdg.configFile."touchegg/touchegg.conf" = {
text = cfg.config;
};
# Also create system config that touchegg service can read
# Note: This requires the touchegg service to be enabled at system level
home.activation.toucheggSystemConfig = lib.hm.dag.entryAfter ["writeBoundary"] ''
echo "📄 Creating system-wide touchegg configuration..."
$DRY_RUN_CMD sudo mkdir -p /etc/touchegg
$DRY_RUN_CMD sudo cp ${config.xdg.configHome}/touchegg/touchegg.conf /etc/touchegg/touchegg.conf
echo " System touchegg config updated"
'';
# Create touchegg client service (required for gesture execution)
systemd.user.services.touchegg-client = {
Unit = {
Description = "Touchegg Client";
After = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "${pkgs.touchegg}/bin/touchegg --client";
Restart = "on-failure";
RestartSec = 3;
};
Install = {
WantedBy = [ "default.target" ];
};
};
# Install touchegg and management scripts
home.packages = [ pkgs.touchegg ] ++ [
(pkgs.writeShellScriptBin "touchegg-restart" ''
echo "🔄 Restarting touchegg service..."
sudo systemctl restart touchegg
echo " Touchegg restarted"
'')
(pkgs.writeShellScriptBin "touchegg-status" ''
echo "📊 Touchegg service status:"
systemctl status touchegg --no-pager
echo ""
echo "📄 Touchegg configuration:"
echo " ~/.config/touchegg/touchegg.conf"
if [[ -f ~/.config/touchegg/touchegg.conf ]]; then
echo " Configuration file exists"
else
echo " Configuration file missing"
fi
'')
(pkgs.writeShellScriptBin "touchegg-reload-config" ''
echo "🔄 Reloading touchegg configuration..."
if systemctl is-active touchegg >/dev/null 2>&1; then
sudo systemctl reload touchegg 2>/dev/null || sudo systemctl restart touchegg
echo " Touchegg configuration reloaded"
else
echo " Touchegg service is not running"
echo "💡 Try: sudo systemctl start touchegg"
fi
'')
];
# Session variables for touchegg
home.sessionVariables = {
TOUCHEGG_CONFIG_PATH = "$HOME/.config/touchegg/touchegg.conf";
};
};
}
+154
View File
@@ -0,0 +1,154 @@
# Configuration management for dots-hyprland
# Replicates the installer's rsync behavior exactly
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.configuration;
mainCfg = config.programs.dots-hyprland;
in
{
options.programs.dots-hyprland.configuration = {
enable = mkEnableOption "dots-hyprland configuration management";
source = mkOption {
type = types.path;
description = "Source path for dots-hyprland configuration";
example = "inputs.dots-hyprland";
};
copyMiscConfig = mkOption {
type = types.bool;
default = true;
description = "Copy miscellaneous config files (everything except fish and hypr)";
};
copyFishConfig = mkOption {
type = types.bool;
default = true;
description = "Copy fish shell configuration";
};
copyHyprlandConfig = mkOption {
type = types.bool;
default = true;
description = "Copy Hyprland configuration";
};
# Individual application enable options
applications = {
foot = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable foot terminal configuration";
};
};
kitty = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable kitty terminal configuration";
};
};
fuzzel = {
enable = mkOption {
type = types.bool;
default = true;
description = "Enable fuzzel launcher configuration";
};
};
};
};
config = mkIf cfg.enable {
# Replicate installer's MISC config copying
# "for i in $(find .config/ -mindepth 1 -maxdepth 1 ! -name 'fish' ! -name 'hypr' -exec basename {} \;)"
xdg.configFile = mkMerge [
# MISC configs (everything except fish and hypr)
(mkIf cfg.copyMiscConfig (
let
# Get all directories in .config except fish, hypr, and quickshell (quickshell handled specially)
# Now with individual enable options
configDirs = lib.optionals cfg.applications.kitty.enable [ "kitty" ] ++
lib.optionals cfg.applications.foot.enable [ "foot" ] ++
lib.optionals cfg.applications.fuzzel.enable [ "fuzzel" ] ++
[ "wlogout" ]; # Always enabled applications
configFiles = listToAttrs (map (dir: {
name = dir;
value = {
source = "${cfg.source}/.config/${dir}";
recursive = true;
};
}) configDirs);
in
configFiles
))
# Fish configuration
(mkIf cfg.copyFishConfig {
"fish" = {
source = "${cfg.source}/.config/fish";
recursive = true;
};
})
# Hyprland configuration (special handling like installer)
(mkIf cfg.copyHyprlandConfig {
# Copy hypr directory excluding specific files
# rsync -av --delete --exclude '/custom' --exclude '/hyprlock.conf' --exclude '/hypridle.conf' --exclude '/hyprland.conf'
"hypr" = {
source = pkgs.runCommand "hypr-config-filtered" {} ''
mkdir -p $out
# Copy everything from source hypr directory
cp -r ${cfg.source}/.config/hypr/* $out/ 2>/dev/null || true
# Remove excluded files (replicating installer --exclude logic)
rm -rf $out/custom 2>/dev/null || true
rm -f $out/hyprlock.conf 2>/dev/null || true
rm -f $out/hypridle.conf 2>/dev/null || true
rm -f $out/hyprland.conf 2>/dev/null || true
# Ensure we have the directory structure
mkdir -p $out
'';
recursive = true;
};
# Copy the main config files separately (installer does this)
"hypr/hyprland.conf" = {
source = "${cfg.source}/.config/hypr/hyprland.conf";
};
"hypr/hypridle.conf" = {
source = "${cfg.source}/.config/hypr/hypridle.conf";
};
"hypr/hyprlock.conf" = {
source = "${cfg.source}/.config/hypr/hyprlock.conf";
};
})
];
# Copy .local/share files (replicating installer)
home.file = {
".local/share/icons" = mkIf cfg.copyMiscConfig {
source = "${cfg.source}/.local/share/icons";
recursive = true;
};
# konsole removed - managed by kde-material-you-colors
};
# Ensure XDG directories exist (installer creates these)
home.activation.createXdgDirs = lib.hm.dag.entryAfter ["writeBoundary"] ''
$DRY_RUN_CMD mkdir -p $HOME/.local/bin
$DRY_RUN_CMD mkdir -p $HOME/.cache
$DRY_RUN_CMD mkdir -p $HOME/.config
$DRY_RUN_CMD mkdir -p $HOME/.local/share
'';
};
}
+237
View File
@@ -0,0 +1,237 @@
# Main Home Manager module for dots-hyprland
# Supports both declarative and writable modes
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland;
packages = import ../packages { inherit pkgs; };
in
{
imports = [
./python-environment.nix
./configuration.nix
./writable-mode.nix
./components/quickshell-service.nix
./components/quickshell-config.nix
./components/hyprland-config.nix
./components/terminal-config.nix
./components/touchegg.nix
./components/config-override.nix
];
options.programs.dots-hyprland = {
enable = mkEnableOption "dots-hyprland desktop environment";
source = mkOption {
type = types.path;
description = "Source path for clean dots-hyprland configuration";
example = "inputs.dots-hyprland";
};
packageSet = mkOption {
type = types.enum [ "minimal" "essential" "all" ];
default = "essential";
description = "Which package set to install";
};
mode = mkOption {
type = types.enum [ "declarative" "writable" "hybrid" ];
default = "hybrid";
description = ''
Configuration mode:
- hybrid: Hyprland declarative + Quickshell copied (recommended)
- declarative: Files managed by Home Manager (read-only)
- writable: Files staged to .configstaging, user copies and modifies
'';
};
writable = mkOption {
type = types.submodule {
options = {
stagingDir = mkOption {
type = types.str;
default = ".configstaging";
description = "Directory to stage configuration files";
};
setupScript = mkOption {
type = types.str;
default = "initialSetup.sh";
description = "Name of the setup script in ~/.local/bin/";
};
backupExisting = mkOption {
type = types.bool;
default = true;
description = "Backup existing configuration files";
};
symlinkMode = mkOption {
type = types.bool;
default = false;
description = "Create symlinks instead of copying files";
};
};
};
default = {};
description = "Writable mode configuration";
};
};
config = mkIf cfg.enable {
# Enable Python environment for color generation
programs.dots-hyprland.python = {
enable = true;
autoSetup = true;
};
# Install packages based on selected set
home.packages =
let
packageSets = import ../packages/dots-hyprland-packages.nix { inherit lib pkgs; };
in
if cfg.packageSet == "minimal" then packageSets.minimalPackages
else if cfg.packageSet == "essential" then packageSets.essentialPackages
else packageSets.allPackages;
# Enable configuration management based on mode
programs.dots-hyprland.configuration = mkIf (cfg.mode == "declarative" || cfg.mode == "hybrid") {
enable = mkDefault (cfg.mode == "hybrid"); # Enable copying for hybrid mode
source = cfg.source;
# In hybrid mode, copy Quickshell but not Hyprland (use overrides instead)
copyMiscConfig = mkDefault (cfg.mode == "hybrid");
copyFishConfig = mkDefault true;
copyHyprlandConfig = mkDefault (cfg.mode == "declarative"); # Only copy in pure declarative mode
};
# Enable writable mode
programs.dots-hyprland.writable-mode = mkIf (cfg.mode == "writable") {
enable = true;
source = cfg.source;
inherit (cfg.writable) stagingDir setupScript backupExisting symlinkMode;
};
# Enable quickshell service (works with both modes)
programs.dots-hyprland.quickshell = {
enable = true;
autoStart = true;
restartOnFailure = true;
logLevel = "info";
};
# Enable touchegg gesture support
programs.dots-hyprland.touchegg = {
enable = true;
};
# Enable custom keybindings
# Set critical environment variables (required for both modes)
home.sessionVariables = {
ILLOGICAL_IMPULSE_VIRTUAL_ENV = "$HOME/.local/state/quickshell/.venv";
# Ensure GNOME schemas are available for gsettings
XDG_DATA_DIRS = "$XDG_DATA_DIRS:${pkgs.gsettings-desktop-schemas}/share";
};
# Ensure ~/.local/bin is in PATH for user scripts
home.sessionPath = [ "$HOME/.local/bin" ];
# Generate qmldir files for all modes (runs after all config is in place)
home.activation.generateQmldirFiles = lib.hm.dag.entryAfter ["linkGeneration"] ''
if [[ -d "$HOME/.config/quickshell/ii" ]]; then
$DRY_RUN_CMD echo "🔧 Generating qmldir files with singleton detection..."
$DRY_RUN_CMD ${packages.generate-qmldir}/bin/generate-qmldir "$HOME/.config/quickshell/ii"
$DRY_RUN_CMD echo " qmldir files generated successfully for ${cfg.mode} mode"
else
$DRY_RUN_CMD echo " Warning: quickshell/ii directory not found, skipping qmldir generation"
fi
'';
# Use quickshell directly with proper environment
home.activation.createWorkingQsScript = lib.hm.dag.entryAfter ["linkGeneration"] ''
$DRY_RUN_CMD echo " Using quickshell directly (no wrapper script needed)"
# Also install the quickshell reset script
$DRY_RUN_CMD echo "🔧 Installing quickshell reset script..."
$DRY_RUN_CMD cp "${packages.quickshell-reset}/bin/quickshell-reset.sh" "$HOME/.local/bin/"
$DRY_RUN_CMD chmod +x "$HOME/.local/bin/quickshell-reset.sh"
$DRY_RUN_CMD echo " Quickshell reset script installed successfully"
'';
# Custom activation script to copy quickshell configs (needed for relative imports)
home.activation.copyQuickshellConfigs = lib.hm.dag.entryBefore ["linkGeneration"] ''
$DRY_RUN_CMD echo "🔧 Setting up quickshell configuration for ${cfg.mode} mode..."
# Remove any existing symlinked configs to avoid conflicts with system home-manager
if [[ -L "$HOME/.config/quickshell" ]]; then
$DRY_RUN_CMD rm "$HOME/.config/quickshell"
$DRY_RUN_CMD echo " Removed conflicting symlinked quickshell config"
fi
# Handle conflicting .local/share symlinks that may interfere with home-manager
if [[ -L "$HOME/.local/share/icons" ]]; then
$DRY_RUN_CMD rm "$HOME/.local/share/icons"
$DRY_RUN_CMD echo " Removed conflicting symlinked icons directory"
fi
if [[ -L "$HOME/.local/share/konsole" ]]; then
$DRY_RUN_CMD rm "$HOME/.local/share/konsole"
$DRY_RUN_CMD echo " Removed conflicting symlinked konsole directory"
fi
# Handle conflicting .config directories
if [[ -L "$HOME/.config/fish" ]]; then
$DRY_RUN_CMD rm "$HOME/.config/fish"
$DRY_RUN_CMD echo " Removed conflicting symlinked fish config"
fi
if [[ -L "$HOME/.config/matugen" ]]; then
$DRY_RUN_CMD rm "$HOME/.config/matugen"
$DRY_RUN_CMD echo " Removed conflicting symlinked matugen config"
fi
# Also handle if they exist as regular directories
if [[ -d "$HOME/.local/share/konsole" && ! -L "$HOME/.local/share/konsole" ]]; then
$DRY_RUN_CMD mv "$HOME/.local/share/konsole" "$HOME/.local/share/konsole.backup-$(date +%Y%m%d-%H%M%S)"
$DRY_RUN_CMD echo " Backed up existing konsole directory"
fi
if [[ -d "$HOME/.config/fish" && ! -L "$HOME/.config/fish" ]]; then
$DRY_RUN_CMD mv "$HOME/.config/fish" "$HOME/.config/fish.backup-$(date +%Y%m%d-%H%M%S)"
$DRY_RUN_CMD echo " Backed up existing fish config directory"
fi
'';
# Copy quickshell config after link generation
home.activation.setupQuickshellConfig = lib.hm.dag.entryAfter ["linkGeneration"] ''
${optionalString (cfg.mode == "hybrid") ''
# Copy quickshell config to enable relative imports
if [[ ! -d "$HOME/.config/quickshell" ]] || [[ -L "$HOME/.config/quickshell" ]]; then
$DRY_RUN_CMD mkdir -p "$HOME/.config"
$DRY_RUN_CMD cp -r "${cfg.source}/.config/quickshell" "$HOME/.config/"
$DRY_RUN_CMD chmod -R u+w "$HOME/.config/quickshell"
$DRY_RUN_CMD echo " Quickshell configuration copied successfully"
else
$DRY_RUN_CMD echo " Quickshell configuration already exists"
# Always update critical scripts that need environment fixes
# Use the flake's own configs directory, not the GitHub source
if [ -f "${./../configs}/quickshell/ii/scripts/colors/switchwall.sh" ]; then
cp "${./../configs}/quickshell/ii/scripts/colors/switchwall.sh" "$HOME/.config/quickshell/ii/scripts/colors/switchwall.sh"
chmod +x "$HOME/.config/quickshell/ii/scripts/colors/switchwall.sh"
$DRY_RUN_CMD echo " Updated switchwall.sh script"
fi
fi
# Ensure quickshell uses the proper environment variables
$DRY_RUN_CMD mkdir -p "$HOME/.local/bin"
$DRY_RUN_CMD echo " Ensuring ~/.local/bin is in PATH for hybrid mode"
''}
'';
# Ensure XDG directories exist (installer requirement)
xdg.enable = true;
xdg.userDirs.enable = true;
};
}
+268
View File
@@ -0,0 +1,268 @@
# Python Virtual Environment for dots-hyprland
# This replicates the installer's Python setup exactly
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.python;
mainCfg = config.programs.dots-hyprland;
# Virtual environment setup script that replicates installer behavior
setupVenvScript = pkgs.writeShellScript "setup-dots-hyprland-venv" ''
#!/usr/bin/env bash
set -e
VENV_PATH="$HOME/.local/state/quickshell/.venv"
echo "🐍 Setting up dots-hyprland Python virtual environment..."
echo "📁 Target: $VENV_PATH"
# Create directory structure
mkdir -p "$(dirname "$VENV_PATH")"
# Only create venv if it doesn't exist
if [[ ! -d "$VENV_PATH" ]]; then
echo "🏗 Creating Python 3.12 virtual environment..."
${pkgs.python312}/bin/python -m venv "$VENV_PATH" --prompt .venv
else
echo " Virtual environment already exists at $VENV_PATH"
fi
# Set up proper library path for Python packages (64-bit only)
export LD_LIBRARY_PATH="${lib.makeLibraryPath (with pkgs; [
stdenv.cc.cc.lib # provides libstdc++.so.6
gcc-unwrapped.lib
glibc
zlib
libffi
openssl
bzip2
xz.out
ncurses
readline
sqlite
])}"
# Clear Python path to avoid conflicts
export PYTHONPATH=""
export PYTHONDONTWRITEBYTECODE=1
echo "📚 Library path: $LD_LIBRARY_PATH"
# Activate and install exact requirements from installer
echo "📦 Installing Python packages with proper library linking..."
source "$VENV_PATH/bin/activate"
# Add build tools to PATH for building Python packages
export PATH="${pkgs.cmake}/bin:${pkgs.pkg-config}/bin:${pkgs.gcc}/bin:${pkgs.gnumake}/bin:$PATH"
export CMAKE_GENERATOR="Unix Makefiles"
export CMAKE_MAKE_PROGRAM="${pkgs.gnumake}/bin/make"
export CC="${pkgs.gcc}/bin/gcc"
export CXX="${pkgs.gcc}/bin/g++"
# Set wayland protocol path for pywayland
export PKG_CONFIG_PATH="${pkgs.wayland.dev}/lib/pkgconfig:${pkgs.wayland-protocols}/share/pkgconfig:${pkgs.wayland-scanner.dev}/lib/pkgconfig"
export WAYLAND_PROTOCOLS_DIR="${pkgs.wayland-scanner}/share/wayland"
export C_INCLUDE_PATH="${pkgs.wayland.dev}/include:${C_INCLUDE_PATH:-}"
export PATH="${pkgs.wayland-scanner}/bin:$PATH"
# Upgrade pip first
pip install --upgrade pip
# Install exact versions from scriptdata/requirements.txt
pip install --no-cache-dir --force-reinstall \
build==1.2.2.post1 \
cffi==1.17.1 \
libsass==0.23.0 \
material-color-utilities==0.2.1 \
materialyoucolor==2.0.10 \
numpy==2.2.2 \
packaging==24.2 \
pillow==11.1.0 \
psutil==6.1.1 \
pycparser==2.22 \
pyproject-hooks==1.2.0 \
setproctitle==1.3.4 \
setuptools==80.9.0 \
setuptools-scm==8.1.0 \
wheel==0.45.1 \
pywayland==0.4.18
# Test critical imports
echo "🧪 Testing critical package imports..."
python -c "
import sys
print(f'Python: {sys.version}')
tests = [
('materialyoucolor', 'materialyoucolor'),
('material_color_utilities', 'material_color_utilities'),
('sass', 'sass'),
('numpy', 'numpy'),
('PIL', 'PIL'),
('pywayland.client', 'pywayland.client'),
('psutil', 'psutil'),
('setproctitle', 'setproctitle')
]
working = 0
for name, module in tests:
try:
__import__(module)
print(f' {name}')
working += 1
except Exception as e:
print(f' {name}: {e}')
print(f'📊 {working}/{len(tests)} packages working')
if working == len(tests):
print('🎉 All critical packages imported successfully!')
else:
print(' Some packages failed - may need additional system libraries')
"
deactivate
echo " Python virtual environment setup complete!"
echo "🔗 Environment variable: ILLOGICAL_IMPULSE_VIRTUAL_ENV=$VENV_PATH"
echo "📚 Library path configured for NixOS compatibility"
'';
# Test script to verify the Python environment works
testVenvScript = pkgs.writeShellScript "test-dots-hyprland-venv" ''
#!/usr/bin/env bash
VENV_PATH="$HOME/.local/state/quickshell/.venv"
echo "🧪 Testing dots-hyprland Python virtual environment..."
if [[ ! -d "$VENV_PATH" ]]; then
echo " Virtual environment not found at $VENV_PATH"
exit 1
fi
source "$VENV_PATH/bin/activate"
# Test critical packages
echo "📦 Testing Python packages..."
python -c "import material_color_utilities; print(' material-color-utilities')" || echo " material-color-utilities"
python -c "import materialyoucolor; print(' materialyoucolor')" || echo " materialyoucolor"
python -c "import pywayland; print(' pywayland')" || echo " pywayland"
python -c "import PIL; print(' pillow')" || echo " pillow"
python -c "import numpy; print(' numpy')" || echo " numpy"
python -c "import psutil; print(' psutil')" || echo " psutil"
deactivate
echo "🎉 Python environment test complete!"
'';
in
{
options.programs.dots-hyprland.python = {
enable = mkEnableOption "Python virtual environment for dots-hyprland";
venvPath = mkOption {
type = types.str;
default = "$HOME/.local/state/quickshell/.venv";
description = "Path to Python virtual environment";
};
autoSetup = mkOption {
type = types.bool;
default = true;
description = "Automatically set up virtual environment on activation";
};
};
config = mkIf cfg.enable {
# Install system Python and required build dependencies + test script
home.packages = with pkgs; [
python312
python312Packages.pip
python312Packages.virtualenv
# System dependencies for Python packages (from illogical-impulse-python PKGBUILD)
clang
gtk4
libadwaita
libsoup_3
libportal-gtk4
gobject-introspection
sassc
opencv4
# Critical system libraries for Python packages (64-bit)
gcc-unwrapped.lib # Provides proper libstdc++.so.6
glibc
zlib
libffi
openssl
# Additional libraries that might be needed
bzip2
xz
ncurses
readline
sqlite
# Development tools
pkg-config
cairo
gdk-pixbuf
glib
# Test script
(writeShellScriptBin "test-dots-hyprland-venv" ''
${testVenvScript}
'')
];
# Set up virtual environment on Home Manager activation
# Only rebuilds if packages change
home.activation.setupDotsHyprlandVenv = mkIf cfg.autoSetup (
lib.hm.dag.entryAfter ["writeBoundary"] ''
VENV_PATH="${cfg.venvPath}"
MARKER_FILE="$VENV_PATH/.nix-built"
EXPECTED_HASH="${builtins.hashString "sha256" (builtins.readFile setupVenvScript)}"
# Only rebuild if venv doesn't exist or script changed
if [[ ! -f "$MARKER_FILE" ]] || [[ "$(cat "$MARKER_FILE" 2>/dev/null)" != "$EXPECTED_HASH" ]]; then
echo "🐍 Building Python venv (this takes ~10 minutes on first run)..."
$DRY_RUN_CMD ${setupVenvScript}
$DRY_RUN_CMD echo "$EXPECTED_HASH" > "$MARKER_FILE"
else
echo " Python venv already up to date"
fi
''
);
# Set critical environment variable and library paths
home.sessionVariables = {
ILLOGICAL_IMPULSE_VIRTUAL_ENV = cfg.venvPath;
# Ensure Python packages can find system libraries (64-bit only)
LD_LIBRARY_PATH = lib.makeLibraryPath (with pkgs; [
gcc-unwrapped.lib
glibc
zlib
libffi
openssl
bzip2
xz.out
ncurses
readline
sqlite
]);
# Additional environment variables for Python
PYTHONPATH = ""; # Clear to avoid conflicts
PYTHONDONTWRITEBYTECODE = "1"; # Prevent .pyc files
# QML import paths for quickshell
QML2_IMPORT_PATH = lib.concatStringsSep ":" (with pkgs; [
"${kdePackages.qt5compat}/lib/qt-6/qml"
"${kdePackages.qtdeclarative}/lib/qt-6/qml"
"${kdePackages.qtwayland}/lib/qt-6/qml"
]);
};
};
}
+348
View File
@@ -0,0 +1,348 @@
# Writable mode for dots-hyprland
# Stages configuration to .configstaging and provides setup script
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.writable-mode;
mainCfg = config.programs.dots-hyprland;
# Create the initial setup script
setupScript = pkgs.writeShellScript "dots-hyprland-setup" ''
#!/usr/bin/env bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
log() {
echo -e "''${GREEN}[dots-hyprland]''${NC} $1"
}
warn() {
echo -e "''${YELLOW}[dots-hyprland]''${NC} WARNING: $1"
}
error() {
echo -e "''${RED}[dots-hyprland]''${NC} ERROR: $1"
}
info() {
echo -e "''${BLUE}[dots-hyprland]''${NC} $1"
}
STAGING_DIR="$HOME/${cfg.stagingDir}"
CONFIG_DIR="$HOME/.config"
BACKUP_DIR="$HOME/.config-backup-$(date +%Y%m%d-%H%M%S)"
log "🚀 dots-hyprland Initial Setup"
log "📁 Staging: $STAGING_DIR"
log "🎯 Target: $CONFIG_DIR"
# Check if staging directory exists
if [[ ! -d "$STAGING_DIR" ]]; then
error "Staging directory not found: $STAGING_DIR"
error "Please run 'home-manager switch' first to create the staging area"
exit 1
fi
# Backup existing configuration if requested
${optionalString cfg.backupExisting ''
if [[ -d "$CONFIG_DIR" ]]; then
log "💾 Creating backup at $BACKUP_DIR"
mkdir -p "$BACKUP_DIR"
# Backup specific directories that will be overwritten
for dir in quickshell hypr fish foot kitty fuzzel wlogout matugen; do
if [[ -d "$CONFIG_DIR/$dir" ]]; then
info " Backing up $dir"
cp -r "$CONFIG_DIR/$dir" "$BACKUP_DIR/" 2>/dev/null || true
fi
done
log " Backup complete"
fi
''}
# Function to copy or symlink files
copy_config() {
local src="$1"
local dst="$2"
local name="$(basename "$src")"
if [[ -d "$src" ]]; then
info "📂 Processing directory: $name"
mkdir -p "$dst"
${if cfg.symlinkMode then ''
# Create symlink
if [[ -L "$dst" ]]; then
rm "$dst"
elif [[ -d "$dst" ]]; then
rm -rf "$dst"
fi
ln -sf "$src" "$dst"
info " 🔗 Symlinked: $name"
'' else ''
# Copy files
cp -rf "$src"/* "$dst/" 2>/dev/null || true
info " 📋 Copied: $name"
''}
elif [[ -f "$src" ]]; then
info "📄 Processing file: $name"
mkdir -p "$(dirname "$dst")"
${if cfg.symlinkMode then ''
# Create symlink
if [[ -L "$dst" ]] || [[ -f "$dst" ]]; then
rm "$dst"
fi
ln -sf "$src" "$dst"
info " 🔗 Symlinked: $name"
'' else ''
# Copy file
cp "$src" "$dst"
info " 📋 Copied: $name"
''}
fi
}
# Copy/symlink all staged configuration
log "🔄 ${if cfg.symlinkMode then "Symlinking" else "Copying"} configuration files..."
# Process all directories in staging
for item in "$STAGING_DIR"/*; do
if [[ -e "$item" ]]; then
name="$(basename "$item")"
copy_config "$item" "$CONFIG_DIR/$name"
fi
done
# Copy .local/share files if they exist
if [[ -d "$STAGING_DIR/.local/share" ]]; then
log "📦 Processing .local/share files..."
mkdir -p "$HOME/.local/share"
copy_config "$STAGING_DIR/.local/share/icons" "$HOME/.local/share/icons"
copy_config "$STAGING_DIR/.local/share/konsole" "$HOME/.local/share/konsole"
fi
log " Configuration setup complete!"
log ""
log "📋 Next steps:"
log " 1. Your configuration is now ${if cfg.symlinkMode then "symlinked" else "copied"} to ~/.config/"
log " 2. ${if cfg.symlinkMode then "Files are symlinked - changes to staging will reflect immediately" else "Files are copied - you can now modify them freely"}"
log " 3. Test quickshell: quickshell"
log " 4. Test Python environment: test-dots-hyprland-venv"
${optionalString cfg.backupExisting ''
log " 5. Your original config was backed up to: $BACKUP_DIR"
''}
log ""
log "🎉 Enjoy your dots-hyprland setup!"
'';
# Create a status/info script
statusScript = pkgs.writeShellScript "dots-hyprland-status" ''
#!/usr/bin/env bash
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo -e "''${GREEN}dots-hyprland Status''${NC}"
echo "===================="
# Check staging directory
STAGING_DIR="$HOME/${cfg.stagingDir}"
if [[ -d "$STAGING_DIR" ]]; then
echo -e " Staging directory: ''${GREEN}$STAGING_DIR''${NC}"
echo " $(find "$STAGING_DIR" -type f | wc -l) files staged"
else
echo -e " Staging directory: ''${RED}Not found''${NC}"
fi
# Check Python virtual environment
VENV_PATH="$HOME/.local/state/quickshell/.venv"
if [[ -d "$VENV_PATH" ]]; then
echo -e " Python venv: ''${GREEN}$VENV_PATH''${NC}"
if [[ -f "$VENV_PATH/bin/python" ]]; then
VERSION=$("$VENV_PATH/bin/python" --version 2>&1)
echo " $VERSION"
fi
else
echo -e " Python venv: ''${RED}Not found''${NC}"
fi
# Check quickshell config
if [[ -d "$HOME/.config/quickshell" ]]; then
echo -e " Quickshell config: ''${GREEN}~/.config/quickshell''${NC}"
if [[ -L "$HOME/.config/quickshell" ]]; then
echo " (symlinked to staging)"
else
echo " (copied from staging)"
fi
else
echo -e " Quickshell config: ''${RED}Not found''${NC}"
fi
# Check environment variable
if [[ -n "$ILLOGICAL_IMPULSE_VIRTUAL_ENV" ]]; then
echo -e " Environment variable: ''${GREEN}ILLOGICAL_IMPULSE_VIRTUAL_ENV''${NC}"
echo " $ILLOGICAL_IMPULSE_VIRTUAL_ENV"
else
echo -e " Environment variable: ''${RED}ILLOGICAL_IMPULSE_VIRTUAL_ENV not set''${NC}"
fi
echo ""
echo "Commands:"
echo " ${cfg.setupScript} - Run initial setup"
echo " dots-hyprland-status - Show this status"
echo " test-dots-hyprland-venv - Test Python environment"
'';
in
{
options.programs.dots-hyprland.writable-mode = {
enable = mkEnableOption "Writable mode for dots-hyprland configuration";
source = mkOption {
type = types.path;
description = "Source path for dots-hyprland configuration";
};
stagingDir = mkOption {
type = types.str;
default = ".configstaging";
description = "Directory to stage configuration files";
};
setupScript = mkOption {
type = types.str;
default = "initialSetup.sh";
description = "Name of the setup script";
};
backupExisting = mkOption {
type = types.bool;
default = true;
description = "Backup existing configuration files";
};
symlinkMode = mkOption {
type = types.bool;
default = false;
description = "Create symlinks instead of copying files";
};
};
config = mkIf cfg.enable {
# Stage all configuration files and install scripts
home.file =
let
# Get all config directories from source
configDirs = [
"quickshell" "hypr" "fish" "foot" "kitty" "fuzzel" "wlogout" "matugen"
];
# Create staging entries for each config directory
stagingEntries = listToAttrs (map (dir: {
name = "${cfg.stagingDir}/${dir}";
value = {
source = "${cfg.source}/.config/${dir}";
recursive = true;
};
}) configDirs);
# Add NixOS-specific patches
nixosPatches = {
};
# Add .local/share files to staging
localShareEntries = {
"${cfg.stagingDir}/.local/share/icons" = {
source = "${cfg.source}/.local/share/icons";
recursive = true;
};
"${cfg.stagingDir}/.local/share/konsole" = {
source = "${cfg.source}/.local/share/konsole";
recursive = true;
};
};
# Scripts and utilities
scriptEntries = {
".local/bin/${cfg.setupScript}" = {
source = setupScript;
executable = true;
};
".local/bin/dots-hyprland-status" = {
source = statusScript;
executable = true;
};
"${cfg.stagingDir}/README.md" = {
text = ''
# dots-hyprland Configuration Staging
This directory contains the staged configuration files from the original dots-hyprland repository.
## Setup
Run the setup script to copy/symlink these files to your ~/.config directory:
```bash
~/.local/bin/${cfg.setupScript}
```
## Mode: ${if cfg.symlinkMode then "Symlink" else "Copy"}
${if cfg.symlinkMode then ''
**Symlink Mode**: Files will be symlinked to ~/.config/
- Changes to files in staging will reflect immediately
- Useful for development and testing
- Files remain managed by Home Manager
'' else ''
**Copy Mode**: Files will be copied to ~/.config/
- You can modify the copied files freely
- Changes won't affect the staging area
- Full user control over configuration
''}
## Status
Check the current status with:
```bash
dots-hyprland-status
```
## Files Staged
- quickshell/ - Widget system configuration
- hypr/ - Hyprland window manager configuration
- fish/ - Fish shell configuration
- foot/ - Foot terminal configuration
- kitty/ - Kitty terminal configuration
- fuzzel/ - Fuzzel launcher configuration
- wlogout/ - Logout menu configuration
- .local/share/icons/ - Custom icons
- .local/share/konsole/ - Konsole profiles
## Python Environment
The Python virtual environment is managed separately and will be created at:
`~/.local/state/quickshell/.venv`
Test it with: `test-dots-hyprland-venv`
'';
};
};
in
stagingEntries // localShareEntries // scriptEntries // nixosPatches;
};
}