Initial clean flake structure - Phase 3 complete

This commit is contained in:
Celes Renata
2025-08-08 22:05:41 -07:00
commit 8011f1c842
11 changed files with 2313 additions and 0 deletions
+258
View File
@@ -0,0 +1,258 @@
# Quickshell service integration with staging system
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland.quickshell;
mainCfg = config.programs.dots-hyprland;
# Our working quickshell build with QtPositioning support
workingQuickshell =
let
quickshellSrc = pkgs.fetchFromGitHub {
owner = "quickshell-mirror";
repo = "quickshell";
rev = "a5431dd02dc23d9ef1680e67777fed00fe5f7cda";
hash = "sha256-vqkSDvh7hWhPvNjMjEDV4KbSCv2jyl2Arh73ZXe274k=";
};
quickshellBase = pkgs.callPackage (quickshellSrc + "/default.nix") {
debug = true;
gitRev = "a5431dd02dc23d9ef1680e67777fed00fe5f7cda";
};
in
quickshellBase.withModules (with pkgs.qt6; [ qtpositioning qtmultimedia ]);
# Service startup script that handles initial setup
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"
# Start quickshell
exec ${workingQuickshell}/bin/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 the working quickshell build and service management scripts
home.packages = [ workingQuickshell ] ++ (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
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" ];
};
};
};
}
+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";
};
};
}
+134
View File
@@ -0,0 +1,134 @@
# 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";
};
};
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 and hypr
configDirs = [
"quickshell"
"kitty"
"foot"
"fuzzel"
"wlogout"
"matugen"
# Add more as discovered in the source
];
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;
};
".local/share/konsole" = mkIf cfg.copyMiscConfig {
source = "${cfg.source}/.local/share/konsole";
recursive = true;
};
};
# 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
'';
};
}
+130
View File
@@ -0,0 +1,130 @@
# Main Home Manager module for dots-hyprland
# Supports both declarative and writable modes
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.programs.dots-hyprland;
in
{
imports = [
./python-environment.nix
./configuration.nix
./writable-mode.nix
./components/quickshell-service.nix
./components/touchegg.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" ];
default = "declarative";
description = ''
Configuration mode:
- 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 {
# 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 Python virtual environment (CRITICAL for both modes)
programs.dots-hyprland.python = {
enable = true;
autoSetup = true;
};
# Enable configuration management based on mode
programs.dots-hyprland.configuration = mkIf (cfg.mode == "declarative") {
enable = true;
source = cfg.source;
};
# 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 variable (required for both modes)
home.sessionVariables = {
ILLOGICAL_IMPULSE_VIRTUAL_ENV = "$HOME/.local/state/quickshell/.venv";
};
# Ensure XDG directories exist (installer requirement)
xdg.enable = true;
xdg.userDirs.enable = true;
};
}
+244
View File
@@ -0,0 +1,244 @@
# 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")"
# Remove existing venv if it exists
if [[ -d "$VENV_PATH" ]]; then
echo "🗑 Removing existing virtual environment..."
rm -rf "$VENV_PATH"
fi
# Set up proper library path for Python packages (64-bit only)
export LD_LIBRARY_PATH="${lib.makeLibraryPath (with pkgs; [
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"
# Create virtual environment with Python 3.12 (installer requirement)
echo "🏗 Creating Python 3.12 virtual environment..."
${pkgs.python312}/bin/python -m venv "$VENV_PATH" --prompt .venv
# Activate and install exact requirements from installer
echo "📦 Installing Python packages with proper library linking..."
source "$VENV_PATH/bin/activate"
# 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 \
pywayland==0.4.18 \
setproctitle==1.3.4 \
setuptools==80.9.0 \
setuptools-scm==8.1.0 \
wheel==0.45.1
# 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
home.activation.setupDotsHyprlandVenv = mkIf cfg.autoSetup (
lib.hm.dag.entryAfter ["writeBoundary"] ''
$DRY_RUN_CMD ${setupVenvScript}
''
);
# 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; 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;
};
}