Add Hyprland with hy3 plugin for XMonad-like tiling

Configure Hyprland to use the hy3 plugin for dynamic tiling similar to
XMonad. Uses official Hyprland and hy3 flakes pinned to v0.53.0 for
proper plugin compatibility (nixpkgs packaging had header issues).

Key changes:
- Add hyprland and hy3 flake inputs with version pinning
- Rewrite hyprland.conf with hy3 layout and XMonad-like keybindings
- Add helper scripts for window management (bring, replace, gather, etc.)
- WASD directional navigation using hy3:movefocus/movewindow
- Tab groups, horizontal/vertical splits via hy3:makegroup
- Scratchpads via Hyprland special workspaces

Also removes org-agenda-api flake integration (moved elsewhere).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-20 13:34:45 -05:00
parent 68b3e5d83c
commit cc05a1d790
13 changed files with 1198 additions and 428 deletions

View File

@@ -1,25 +1,38 @@
# Hyprland Configuration # Hyprland Configuration
# XMonad-like dynamic tiling using hy3 plugin
# Based on XMonad configuration from xmonad.hs # Based on XMonad configuration from xmonad.hs
# See https://wiki.hyprland.org/Configuring/Monitors/ # =============================================================================
# PLUGINS (Hyprland pinned to 0.53.0 to match hy3)
# =============================================================================
exec-once = hyprctl plugin load /run/current-system/sw/lib/libhy3.so
# hyprexpo disabled - header compatibility issue
# exec-once = hyprctl plugin load /run/current-system/sw/lib/libhyprexpo.so
# =============================================================================
# MONITORS
# =============================================================================
monitor=,preferred,auto,auto monitor=,preferred,auto,auto
# Source a file (multi-file configs) # =============================================================================
# source = ~/.config/hypr/myColors.conf # PROGRAMS
# =============================================================================
# Set programs that you use
$terminal = alacritty $terminal = alacritty
$fileManager = dolphin $fileManager = dolphin
$menu = rofi -show drun -show-icons $menu = rofi -show drun -show-icons
$runMenu = rofi -show run $runMenu = rofi -show run
# Environment variables # =============================================================================
# ENVIRONMENT VARIABLES
# =============================================================================
env = XCURSOR_SIZE,24 env = XCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt5ct env = QT_QPA_PLATFORMTHEME,qt5ct
env = GDK_DPI_SCALE,1.25 env = GDK_DPI_SCALE,1.25
env = QT_FONT_DPI,120 env = QT_FONT_DPI,120
# Input configuration # =============================================================================
# INPUT CONFIGURATION
# =============================================================================
input { input {
kb_layout = us kb_layout = us
kb_variant = kb_variant =
@@ -33,100 +46,213 @@ input {
natural_scroll = no natural_scroll = no
} }
sensitivity = 0 # -1.0 - 1.0, 0 means no modification. sensitivity = 0
} }
# General settings # =============================================================================
# GENERAL SETTINGS
# =============================================================================
general { general {
gaps_in = 5 gaps_in = 5
gaps_out = 20 gaps_out = 10
border_size = 2 border_size = 2
col.active_border = rgba(edb443ee) rgba(33ccffee) 45deg col.active_border = rgba(edb443ee) rgba(33ccffee) 45deg
col.inactive_border = rgba(595959aa) col.inactive_border = rgba(595959aa)
layout = master # Use hy3 layout for XMonad-like dynamic tiling
layout = hy3
allow_tearing = false allow_tearing = false
} }
# Decoration # =============================================================================
# DECORATION
# =============================================================================
decoration { decoration {
rounding = 10 rounding = 5
blur { blur {
enabled = true enabled = true
size = 3 size = 3
passes = 1 passes = 1
} }
# Fade inactive windows (like XMonad's fadeInactive)
active_opacity = 1.0
inactive_opacity = 0.9
} }
# Animations # =============================================================================
# ANIMATIONS
# =============================================================================
animations { animations {
enabled = yes enabled = yes
bezier = myBezier, 0.05, 0.9, 0.1, 1.05 bezier = myBezier, 0.05, 0.9, 0.1, 1.05
bezier = linear, 0, 0, 1, 1
animation = windows, 1, 7, myBezier animation = windows, 1, 4, myBezier
animation = windowsOut, 1, 7, default, popin 80% animation = windowsOut, 1, 4, default, popin 80%
animation = border, 1, 10, default animation = border, 1, 10, default
animation = borderangle, 1, 8, default animation = borderangle, 1, 8, default
animation = fade, 1, 7, default animation = fade, 1, 4, default
animation = workspaces, 1, 6, default animation = workspaces, 1, 4, default
}
# Layout configuration
dwindle {
pseudotile = yes # master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
preserve_split = yes # you probably want this
} }
# =============================================================================
# MASTER LAYOUT CONFIGURATION
# =============================================================================
master { master {
mfact = 0.25 new_status = slave
mfact = 0.5
orientation = left orientation = left
# Smart gaps - no gaps when only one window
no_gaps_when_only = 1
} }
# Misc # Dwindle layout (alternative - binary tree like i3)
dwindle {
pseudotile = yes
preserve_split = yes
no_gaps_when_only = 1
}
# Group/tabbed window configuration (built-in alternative to hy3 tabs)
group {
col.border_active = rgba(edb443ff)
col.border_inactive = rgba(091f2eff)
groupbar {
enabled = true
font_size = 12
height = 22
col.active = rgba(edb443ff)
col.inactive = rgba(091f2eff)
text_color = rgba(091f2eff)
}
}
# =============================================================================
# HY3/HYPREXPO PLUGIN CONFIG
# =============================================================================
plugin {
hy3 {
# Disable autotile to get XMonad-like manual control
autotile {
enable = false
}
# Tab configuration
tabs {
height = 22
padding = 6
render_text = true
text_font = "Sans"
text_height = 10
text_padding = 3
col.active = rgba(edb443ff)
col.inactive = rgba(091f2eff)
col.urgent = rgba(ff0000ff)
col.text.active = rgba(091f2eff)
col.text.inactive = rgba(ffffffff)
col.text.urgent = rgba(ffffffff)
}
}
# hyprexpo disabled - header compatibility issue
# hyprexpo {
# columns = 3
# gap_size = 5
# bg_col = rgba(000000ff)
# workspace_method = first 1
# enable_gesture = true
# gesture_fingers = 3
# gesture_distance = 300
# gesture_positive = false
# }
}
# =============================================================================
# MISC
# =============================================================================
misc { misc {
force_default_wallpaper = 0 force_default_wallpaper = 0
disable_hyprland_logo = true disable_hyprland_logo = true
} }
# Scratchpad-like windows # =============================================================================
windowrule = float, title:^(htop)$ # WINDOW RULES
windowrule = size 80% 80%, title:^(htop)$ # =============================================================================
# Float dialogs
windowrulev2 = float, class:^()$,title:^()$
windowrulev2 = float, title:^(Picture-in-Picture)$
windowrulev2 = float, title:^(Open File)$
windowrulev2 = float, title:^(Save File)$
windowrulev2 = float, title:^(Confirm)$
# Chrome specific rules # Scratchpad windows - float and size
windowrule = float, class:^(chrome)$,title:^(.*@gmail.com.*Gmail.*)$ windowrulev2 = float, class:^(htop-scratch)$
windowrule = float, class:^(chrome)$,title:^(Messages)$ windowrulev2 = size 90% 90%, class:^(htop-scratch)$
windowrule = size 90% 90%, class:^(chrome)$,title:^(.*@gmail.com.*Gmail.*)$ windowrulev2 = center, class:^(htop-scratch)$
windowrule = size 90% 90%, class:^(chrome)$,title:^(Messages)$
# ===== KEY BINDINGS ===== windowrulev2 = float, class:^(pavucontrol)$
# Based on XMonad configuration windowrulev2 = size 90% 90%, class:^(pavucontrol)$
windowrulev2 = center, class:^(pavucontrol)$
# Main modifier key (Super/Windows key equivalent to mod4Mask) windowrulev2 = float, class:^(Spotify)$
windowrulev2 = size 90% 90%, class:^(Spotify)$
windowrulev2 = center, class:^(Spotify)$
windowrulev2 = float, class:^(Element)$
windowrulev2 = size 90% 90%, class:^(Element)$
windowrulev2 = center, class:^(Element)$
windowrulev2 = float, class:^(Slack)$
windowrulev2 = size 90% 90%, class:^(Slack)$
windowrulev2 = center, class:^(Slack)$
windowrulev2 = float, class:^(transmission-gtk)$
windowrulev2 = size 90% 90%, class:^(transmission-gtk)$
windowrulev2 = center, class:^(transmission-gtk)$
# Gmail and Messages (Chrome windows)
windowrulev2 = float, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = size 90% 90%, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = center, class:^(google-chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrulev2 = float, class:^(google-chrome)$,title:^(Messages.*)$
windowrulev2 = size 90% 90%, class:^(google-chrome)$,title:^(Messages.*)$
windowrulev2 = center, class:^(google-chrome)$,title:^(Messages.*)$
# =============================================================================
# KEY BINDINGS
# =============================================================================
# Modifier keys
$mainMod = SUPER $mainMod = SUPER
$modAlt = SUPER ALT
$hyper = SUPER CTRL ALT
# Mod+Alt combination (equivalent to modalt in XMonad) # -----------------------------------------------------------------------------
$modAlt = SUPER_ALT # Program Launching
# -----------------------------------------------------------------------------
# Hyper key (equivalent to mod3Mask in XMonad)
$hyper = ALT_R
# Program launching
bind = $mainMod, P, exec, $menu bind = $mainMod, P, exec, $menu
bind = $mainMod SHIFT, P, exec, $runMenu bind = $mainMod SHIFT, P, exec, $runMenu
bind = $mainMod SHIFT, Return, exec, $terminal bind = $mainMod SHIFT, Return, exec, $terminal
bind = $mainMod, Q, killactive, bind = $mainMod, Q, killactive,
bind = $mainMod SHIFT, C, killactive, bind = $mainMod SHIFT, C, killactive,
bind = $mainMod SHIFT, Q, exit, bind = $mainMod SHIFT, Q, exit,
bind = $mainMod, E, exec, emacsclient -c # Emacs-everywhere (like XMonad's emacs-everywhere)
bind = $mainMod, V, exec, xclip -o | xdotool type --file - bind = $mainMod, E, exec, emacsclient --eval '(emacs-everywhere)'
bind = $mainMod, V, exec, wl-paste | xdotool type --file -
# Chrome/Browser launching (equivalent to bindBringAndRaise) # Chrome/Browser (raise or spawn like XMonad's bindBringAndRaise)
bind = $modAlt, C, exec, google-chrome-stable bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome google-chrome-stable
# Scratchpad equivalents (toggle special workspaces) # -----------------------------------------------------------------------------
# SCRATCHPADS (Special Workspaces)
# -----------------------------------------------------------------------------
# Toggle scratchpads
bind = $modAlt, E, togglespecialworkspace, element bind = $modAlt, E, togglespecialworkspace, element
bind = $modAlt, G, togglespecialworkspace, gmail bind = $modAlt, G, togglespecialworkspace, gmail
bind = $modAlt, H, togglespecialworkspace, htop bind = $modAlt, H, togglespecialworkspace, htop
@@ -136,7 +262,7 @@ bind = $modAlt, S, togglespecialworkspace, spotify
bind = $modAlt, T, togglespecialworkspace, transmission bind = $modAlt, T, togglespecialworkspace, transmission
bind = $modAlt, V, togglespecialworkspace, volume bind = $modAlt, V, togglespecialworkspace, volume
# Move windows to special workspaces # Move windows to scratchpads
bind = $modAlt SHIFT, E, movetoworkspace, special:element bind = $modAlt SHIFT, E, movetoworkspace, special:element
bind = $modAlt SHIFT, G, movetoworkspace, special:gmail bind = $modAlt SHIFT, G, movetoworkspace, special:gmail
bind = $modAlt SHIFT, H, movetoworkspace, special:htop bind = $modAlt SHIFT, H, movetoworkspace, special:htop
@@ -146,56 +272,109 @@ bind = $modAlt SHIFT, S, movetoworkspace, special:spotify
bind = $modAlt SHIFT, T, movetoworkspace, special:transmission bind = $modAlt SHIFT, T, movetoworkspace, special:transmission
bind = $modAlt SHIFT, V, movetoworkspace, special:volume bind = $modAlt SHIFT, V, movetoworkspace, special:volume
# Directional navigation (WASD keys as in XMonad) # Hidden workspace (like XMonad's NSP)
bind = $mainMod, W, movefocus, u bind = $mainMod, X, movetoworkspace, special:NSP
bind = $mainMod, S, movefocus, d bind = $mainMod SHIFT, X, togglespecialworkspace, NSP
bind = $mainMod, A, movefocus, l
bind = $mainMod, D, movefocus, r
# Move windows directionally # -----------------------------------------------------------------------------
bind = $mainMod SHIFT, W, movewindow, u # DIRECTIONAL NAVIGATION (WASD - like XMonad Navigation2D)
bind = $mainMod SHIFT, S, movewindow, d # Using hy3 dispatchers for proper tree-based navigation
bind = $mainMod SHIFT, A, movewindow, l # -----------------------------------------------------------------------------
bind = $mainMod SHIFT, D, movewindow, r
# Resize windows # Focus movement (Mod + WASD) - hy3:movefocus navigates the tree
bind = $mainMod CTRL, W, resizeactive, 0 -50 bind = $mainMod, W, hy3:movefocus, u
bind = $mainMod CTRL, S, resizeactive, 0 50 bind = $mainMod, S, hy3:movefocus, d
bind = $mainMod CTRL, A, resizeactive, -50 0 bind = $mainMod, A, hy3:movefocus, l
bind = $mainMod CTRL, D, resizeactive, 50 0 bind = $mainMod, D, hy3:movefocus, r
# Layout control # Move windows (Mod + Shift + WASD) - hy3:movewindow with once=true for swapping
bind = $mainMod, Space, exec, hyprctl dispatch layoutmsg cyclenext bind = $mainMod SHIFT, W, hy3:movewindow, u, once
bind = $mainMod CTRL, Space, fullscreen, 0 bind = $mainMod SHIFT, S, hy3:movewindow, d, once
bind = $mainMod, slash, exec, hyprctl dispatch layoutmsg orientationcycle left top bind = $mainMod SHIFT, A, hy3:movewindow, l, once
bind = $mainMod SHIFT, D, hy3:movewindow, r, once
# Resize windows (Mod + Ctrl + WASD)
binde = $mainMod CTRL, W, resizeactive, 0 -50
binde = $mainMod CTRL, S, resizeactive, 0 50
binde = $mainMod CTRL, A, resizeactive, -50 0
binde = $mainMod CTRL, D, resizeactive, 50 0
# Screen/Monitor focus (Hyper + WASD)
bind = $hyper, W, focusmonitor, u
bind = $hyper, S, focusmonitor, d
bind = $hyper, A, focusmonitor, l
bind = $hyper, D, focusmonitor, r
# Move window to monitor and follow (Hyper + Shift + WASD)
bind = $hyper SHIFT, W, movewindow, mon:u
bind = $hyper SHIFT, S, movewindow, mon:d
bind = $hyper SHIFT, A, movewindow, mon:l
bind = $hyper SHIFT, D, movewindow, mon:r
# Shift to empty workspace on screen direction (Hyper + Ctrl + WASD)
# Like XMonad's shiftToEmptyOnScreen
bind = $hyper CTRL, W, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh u
bind = $hyper CTRL, S, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh d
bind = $hyper CTRL, A, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh l
bind = $hyper CTRL, D, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh r
# -----------------------------------------------------------------------------
# LAYOUT CONTROL (XMonad-like with hy3)
# -----------------------------------------------------------------------------
# Create groups with different orientations (like XMonad layouts)
# hy3:makegroup creates a split/tab group from focused window
bind = $mainMod, Space, hy3:changegroup, toggletab
bind = $mainMod SHIFT, Space, hy3:changegroup, opposite
# Create specific group types
bind = $mainMod, H, hy3:makegroup, h
bind = $mainMod SHIFT, V, hy3:makegroup, v
bind = $mainMod CTRL, Space, hy3:makegroup, tab
# Change group type (cycle h -> v -> tab)
bind = $mainMod, slash, hy3:changegroup, h
bind = $mainMod SHIFT, slash, hy3:changegroup, v
# Tab navigation (like XMonad's focus next/prev in tabbed)
bind = $mainMod, bracketright, hy3:focustab, r, wrap
bind = $mainMod, bracketleft, hy3:focustab, l, wrap
# Move window within tab group
bind = $mainMod SHIFT, bracketright, hy3:movetab, r
bind = $mainMod SHIFT, bracketleft, hy3:movetab, l
# Expand focus to parent group (like XMonad's focus parent)
bind = $mainMod, grave, hy3:expand, expand
bind = $mainMod SHIFT, grave, hy3:expand, base
# Fullscreen (like XMonad's NBFULL toggle)
bind = $mainMod, F, fullscreen, 0 bind = $mainMod, F, fullscreen, 0
bind = $mainMod SHIFT, F, fullscreen, 1 bind = $mainMod SHIFT, F, fullscreen, 1
# Toggle floating
bind = $mainMod, T, togglefloating, bind = $mainMod, T, togglefloating,
bind = $mainMod SHIFT, Y, pseudo, # dwindle pseudotile
bind = $mainMod, J, togglesplit, # dwindle split direction
bind = $mainMod, comma, exec, hyprctl dispatch splitratio -0.05
bind = $mainMod, period, exec, hyprctl dispatch splitratio +0.05
bind = $mainMod, N, exec, hyprctl dispatch layoutmsg addmaster
bind = $mainMod SHIFT, N, exec, hyprctl dispatch layoutmsg removemaster
# Focus manipulation # Resize split ratio (hy3 uses resizeactive for splits)
bind = $mainMod, Tab, exec, hyprctl dispatch focuswindow "class:$(hyprctl activewindow -j | jq -r '.class')" binde = $mainMod, comma, resizeactive, -50 0
bind = $mainMod, M, exec, hyprctl dispatch focuswindow "class:master" binde = $mainMod, period, resizeactive, 50 0
# Monitor/Screen control # Kill group - removes the focused window from its group
bind = $mainMod, Z, focusmonitor, +1 bind = $mainMod, N, hy3:killactive
bind = $mainMod SHIFT, Z, movewindow, mon:+1
# Window management # hy3:setswallow - set a window to swallow newly spawned windows
bind = $mainMod, G, exec, hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0) | "\(.title) - \(.class)"' | rofi -dmenu -i -p "Go to window" | head -1 | xargs -I {} hyprctl dispatch focuswindow "title:{}" bind = $mainMod, M, hy3:setswallow, toggle
bind = $mainMod, B, exec, SELECTION=$(hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0) | "\(.title) - \(.class) [\(.address)]"' | rofi -dmenu -i -p "Bring window"); if [ -n "$SELECTION" ]; then ADDRESS=$(echo "$SELECTION" | sed 's/.*\[\(.*\)\]/\1/'); hyprctl dispatch movetoworkspace "$(hyprctl activeworkspace -j | jq -r '.id')",address:"$ADDRESS"; fi
bind = $mainMod SHIFT, B, exec, hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0) | "\(.title) - \(.class)"' | rofi -dmenu -i -p "Replace window" | head -1 | xargs -I {} hyprctl dispatch swapwindow "title:{}"
bind = $mainMod, M, exec, hyprctl dispatch togglespecialworkspace minimized
bind = $mainMod SHIFT, M, exec, hyprctl dispatch movetoworkspace special:minimized
bind = $mainMod SHIFT, H, exec, EMPTY_WS=$(hyprctl workspaces -j | jq -r 'map(select(.windows == 0)) | .[0].id // empty'); if [ -z "$EMPTY_WS" ]; then EMPTY_WS=$(hyprctl workspaces -j | jq -r 'max_by(.id).id + 1'); fi; hyprctl dispatch movetoworkspace $EMPTY_WS && hyprctl dispatch workspace $EMPTY_WS
bind = $mainMod, X, exec, hyprctl dispatch movetoworkspace special:NSP
# Workspace switching # Minimize to special workspace (like XMonad's minimizeWindow)
bind = $mainMod SHIFT, M, movetoworkspace, special:minimized
# Restore last minimized
bind = $modAlt, Return, togglespecialworkspace, minimized
# -----------------------------------------------------------------------------
# WORKSPACE CONTROL
# -----------------------------------------------------------------------------
# Switch workspaces (1-9, 0=10)
bind = $mainMod, 1, workspace, 1 bind = $mainMod, 1, workspace, 1
bind = $mainMod, 2, workspace, 2 bind = $mainMod, 2, workspace, 2
bind = $mainMod, 3, workspace, 3 bind = $mainMod, 3, workspace, 3
@@ -207,7 +386,7 @@ bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9 bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10 bind = $mainMod, 0, workspace, 10
# Move windows to workspaces # Move window to workspace
bind = $mainMod SHIFT, 1, movetoworkspace, 1 bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2 bind = $mainMod SHIFT, 2, movetoworkspace, 2
bind = $mainMod SHIFT, 3, movetoworkspace, 3 bind = $mainMod SHIFT, 3, movetoworkspace, 3
@@ -219,27 +398,73 @@ bind = $mainMod SHIFT, 8, movetoworkspace, 8
bind = $mainMod SHIFT, 9, movetoworkspace, 9 bind = $mainMod SHIFT, 9, movetoworkspace, 9
bind = $mainMod SHIFT, 0, movetoworkspace, 10 bind = $mainMod SHIFT, 0, movetoworkspace, 10
# Move and follow to workspace # Move and follow to workspace (like XMonad's shiftThenView)
bind = $mainMod CTRL, 1, movetoworkspace, 1 bind = $mainMod CTRL, 1, movetoworkspacesilent, 1
bind = $mainMod CTRL, 2, movetoworkspace, 2 bind = $mainMod CTRL, 1, workspace, 1
bind = $mainMod CTRL, 3, movetoworkspace, 3 bind = $mainMod CTRL, 2, movetoworkspacesilent, 2
bind = $mainMod CTRL, 4, movetoworkspace, 4 bind = $mainMod CTRL, 2, workspace, 2
bind = $mainMod CTRL, 5, movetoworkspace, 5 bind = $mainMod CTRL, 3, movetoworkspacesilent, 3
bind = $mainMod CTRL, 6, movetoworkspace, 6 bind = $mainMod CTRL, 3, workspace, 3
bind = $mainMod CTRL, 7, movetoworkspace, 7 bind = $mainMod CTRL, 4, movetoworkspacesilent, 4
bind = $mainMod CTRL, 8, movetoworkspace, 8 bind = $mainMod CTRL, 4, workspace, 4
bind = $mainMod CTRL, 9, movetoworkspace, 9 bind = $mainMod CTRL, 5, movetoworkspacesilent, 5
bind = $mainMod CTRL, 0, movetoworkspace, 10 bind = $mainMod CTRL, 5, workspace, 5
bind = $mainMod CTRL, 6, movetoworkspacesilent, 6
bind = $mainMod CTRL, 6, workspace, 6
bind = $mainMod CTRL, 7, movetoworkspacesilent, 7
bind = $mainMod CTRL, 7, workspace, 7
bind = $mainMod CTRL, 8, movetoworkspacesilent, 8
bind = $mainMod CTRL, 8, workspace, 8
bind = $mainMod CTRL, 9, movetoworkspacesilent, 9
bind = $mainMod CTRL, 9, workspace, 9
bind = $mainMod CTRL, 0, movetoworkspacesilent, 10
bind = $mainMod CTRL, 0, workspace, 10
# Media keys and volume control # Workspace cycling (like XMonad's cycleWorkspaceOnCurrentScreen)
bind = , XF86AudioRaiseVolume, exec, set_volume --unmute --change-volume +5 bind = $mainMod, backslash, workspace, previous
bind = , XF86AudioLowerVolume, exec, set_volume --unmute --change-volume -5
# Go to next empty workspace (like XMonad's moveTo Next emptyWS)
bind = $hyper, E, workspace, empty
# Move to next screen (like XMonad's shiftToNextScreenX)
bind = $mainMod, Z, focusmonitor, +1
bind = $mainMod SHIFT, Z, movewindow, mon:+1
# Shift to empty workspace and view (like XMonad's shiftToEmptyAndView)
bind = $mainMod SHIFT, H, movetoworkspace, empty
# -----------------------------------------------------------------------------
# WINDOW MANAGEMENT
# -----------------------------------------------------------------------------
# Go to window (rofi window switcher)
bind = $mainMod, G, exec, rofi -show window -show-icons
# Bring window (move to current workspace)
bind = $mainMod, B, exec, ~/.config/hypr/scripts/bring-window.sh
# Replace window (swap focused with selected - like XMonad's myReplaceWindow)
bind = $mainMod SHIFT, B, exec, ~/.config/hypr/scripts/replace-window.sh
# Gather windows of same class (like XMonad's gatherThisClass)
bind = $hyper, G, exec, ~/.config/hypr/scripts/gather-class.sh
# Focus next window of different class (like XMonad's focusNextClass)
bind = $mainMod, Tab, exec, ~/.config/hypr/scripts/focus-next-class.sh
# -----------------------------------------------------------------------------
# MEDIA KEYS
# -----------------------------------------------------------------------------
# Volume control (matching XMonad: Mod+I=up, Mod+K=down, Mod+U=mute)
binde = , XF86AudioRaiseVolume, exec, set_volume --unmute --change-volume +5
binde = , XF86AudioLowerVolume, exec, set_volume --unmute --change-volume -5
bind = , XF86AudioMute, exec, set_volume --toggle-mute bind = , XF86AudioMute, exec, set_volume --toggle-mute
bind = $mainMod, I, exec, set_volume --unmute --change-volume +5 binde = $mainMod, I, exec, set_volume --unmute --change-volume +5
bind = $mainMod, K, exec, set_volume --unmute --change-volume -5 binde = $mainMod, K, exec, set_volume --unmute --change-volume -5
bind = $mainMod, U, exec, set_volume --toggle-mute bind = $mainMod, U, exec, set_volume --toggle-mute
# Media player controls # Media player controls (matching XMonad: Mod+;=play, Mod+L=next, Mod+J=prev)
bind = $mainMod, semicolon, exec, playerctl play-pause bind = $mainMod, semicolon, exec, playerctl play-pause
bind = , XF86AudioPlay, exec, playerctl play-pause bind = , XF86AudioPlay, exec, playerctl play-pause
bind = , XF86AudioPause, exec, playerctl play-pause bind = , XF86AudioPause, exec, playerctl play-pause
@@ -248,47 +473,67 @@ bind = , XF86AudioNext, exec, playerctl next
bind = $mainMod, J, exec, playerctl previous bind = $mainMod, J, exec, playerctl previous
bind = , XF86AudioPrev, exec, playerctl previous bind = , XF86AudioPrev, exec, playerctl previous
# Brightness control # Mute current window (like XMonad's toggle_mute_current_window)
bind = , XF86MonBrightnessUp, exec, brightness.sh 5 bind = $hyper SHIFT, Q, exec, toggle_mute_current_window.sh
bind = , XF86MonBrightnessDown, exec, brightness.sh -5 bind = $hyper CTRL, Q, exec, toggle_mute_current_window.sh only
# Utility bindings # Brightness control
bind = $hyper, V, exec, rofi_clipit.sh binde = , XF86MonBrightnessUp, exec, brightness.sh up
bind = $hyper, Y, exec, rofi-pass binde = , XF86MonBrightnessDown, exec, brightness.sh down
bind = $hyper, H, exec, rofi_shutter
# -----------------------------------------------------------------------------
# UTILITY BINDINGS
# -----------------------------------------------------------------------------
bind = $hyper, V, exec, cliphist list | rofi -dmenu -p "Clipboard" | cliphist decode | wl-copy
bind = $hyper, P, exec, rofi-pass
bind = $hyper, H, exec, grim -g "$(slurp)" - | swappy -f -
bind = $hyper, C, exec, shell_command.sh bind = $hyper, C, exec, shell_command.sh
bind = $hyper, X, exec, rofi_command.sh bind = $hyper, X, exec, rofi_command.sh
bind = $hyper SHIFT, L, exec, dm-tool lock bind = $hyper SHIFT, L, exec, hyprlock
bind = $hyper, K, exec, rofi_kill_process.sh bind = $hyper, K, exec, rofi_kill_process.sh
bind = $hyper SHIFT, K, exec, rofi_kill_all.sh bind = $hyper SHIFT, K, exec, rofi_kill_all.sh
bind = $hyper, R, exec, rofi-systemd bind = $hyper, R, exec, rofi-systemd
bind = $hyper, 9, exec, start_synergy.sh bind = $hyper, 9, exec, start_synergy.sh
bind = $hyper, slash, exec, toggle_taffybar # Overview - show all windows (Hyper+Space)
bind = $hyper, Space, exec, skippy-xd # hyprexpo disabled - using rofi window switcher as fallback
bind = $hyper, Space, exec, rofi -show window -show-icons
bind = $hyper, I, exec, rofi_select_input.hs bind = $hyper, I, exec, rofi_select_input.hs
bind = $hyper, O, exec, rofi_paswitch bind = $hyper, O, exec, rofi_paswitch
bind = $mainMod, apostrophe, exec, load_default_map bind = $mainMod, apostrophe, exec, load_default_map
bind = $modAlt, apostrophe, exec, load_xkb_map bind = $modAlt, apostrophe, exec, load_xkb_map
# Reload config
bind = $mainMod, R, exec, hyprctl reload bind = $mainMod, R, exec, hyprctl reload
# Workspace cycling # -----------------------------------------------------------------------------
bind = $mainMod, backslash, workspace, previous # MOUSE BINDINGS
bind = $mainMod, Tab, workspace, previous # -----------------------------------------------------------------------------
# Mouse bindings
bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:272, movewindow
bindm = $mainMod, mouse:273, resizewindow bindm = $mainMod, mouse:273, resizewindow
# Scroll through existing workspaces with mainMod + scroll # Scroll through workspaces
bind = $mainMod, mouse_down, workspace, e+1 bind = $mainMod, mouse_down, workspace, e+1
bind = $mainMod, mouse_up, workspace, e-1 bind = $mainMod, mouse_up, workspace, e-1
# Special workspaces for "scratchpad" applications # =============================================================================
# AUTOSTART
# =============================================================================
# Scratchpad applications (start in special workspaces)
exec-once = [workspace special:element silent] element-desktop exec-once = [workspace special:element silent] element-desktop
exec-once = [workspace special:gmail silent] google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox exec-once = [workspace special:gmail silent] google-chrome-stable --new-window https://mail.google.com/mail/u/0/#inbox
exec-once = [workspace special:htop silent] alacritty --title htop -e htop exec-once = [workspace special:htop silent] alacritty --class htop-scratch --title htop -e htop
exec-once = [workspace special:messages silent] google-chrome-stable --new-window https://messages.google.com/web/conversations exec-once = [workspace special:messages silent] google-chrome-stable --new-window https://messages.google.com/web/conversations
exec-once = [workspace special:slack silent] slack exec-once = [workspace special:slack silent] slack
exec-once = [workspace special:spotify silent] spotify exec-once = [workspace special:spotify silent] spotify
exec-once = [workspace special:transmission silent] transmission-gtk exec-once = [workspace special:transmission silent] transmission-gtk
exec-once = [workspace special:volume silent] pavucontrol exec-once = [workspace special:volume silent] pavucontrol
# Clipboard history daemon
exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
# Night light (optional - comment out if not needed)
# exec-once = wlsunset -l 37.7 -L -122.4

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Bring window to current workspace (like XMonad's bringWindow)
# Uses rofi to select a window and moves it to the current workspace
set -euo pipefail
# Get current workspace
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
# Get all windows and format for rofi
WINDOWS=$(hyprctl clients -j | jq -r '.[] | select(.workspace.id >= 0 and .workspace.id != '"$CURRENT_WS"') | "\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)"')
if [ -z "$WINDOWS" ]; then
notify-send "Bring Window" "No windows on other workspaces"
exit 0
fi
# Show rofi menu
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Bring window" -format 's')
if [ -n "$SELECTION" ]; then
# Extract the window address (after the last |)
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//')
# Move window to current workspace
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDRESS"
# Focus the window
hyprctl dispatch focuswindow "address:$ADDRESS"
fi

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Cycle between master and dwindle layouts
# Like XMonad's NextLayout
set -euo pipefail
CURRENT=$(hyprctl getoption general:layout -j | jq -r '.str')
if [ "$CURRENT" = "master" ]; then
hyprctl keyword general:layout dwindle
notify-send "Layout" "Switched to Dwindle (binary tree)"
else
hyprctl keyword general:layout master
notify-send "Layout" "Switched to Master (XMonad-like)"
fi

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Focus next window of a different class (like XMonad's focusNextClass)
set -euo pipefail
# Get focused window class
FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class')
FOCUSED_ADDR=$(hyprctl activewindow -j | jq -r '.address')
if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then
# No focused window, just focus any window
hyprctl dispatch cyclenext
exit 0
fi
# Get all unique classes
ALL_CLASSES=$(hyprctl clients -j | jq -r '[.[] | select(.workspace.id >= 0) | .class] | unique | .[]')
# Get sorted list of classes
CLASSES_ARRAY=()
while IFS= read -r class; do
CLASSES_ARRAY+=("$class")
done <<< "$ALL_CLASSES"
# Find current class index and get next class
CURRENT_INDEX=-1
for i in "${!CLASSES_ARRAY[@]}"; do
if [ "${CLASSES_ARRAY[$i]}" = "$FOCUSED_CLASS" ]; then
CURRENT_INDEX=$i
break
fi
done
if [ $CURRENT_INDEX -eq -1 ] || [ ${#CLASSES_ARRAY[@]} -le 1 ]; then
# Only one class or class not found
exit 0
fi
# Get next class (wrapping around)
NEXT_INDEX=$(( (CURRENT_INDEX + 1) % ${#CLASSES_ARRAY[@]} ))
NEXT_CLASS="${CLASSES_ARRAY[$NEXT_INDEX]}"
# Find first window of next class
NEXT_WINDOW=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$NEXT_CLASS\" and .workspace.id >= 0) | .address" | head -1)
if [ -n "$NEXT_WINDOW" ]; then
hyprctl dispatch focuswindow "address:$NEXT_WINDOW"
fi

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Gather all windows of the same class as focused window (like XMonad's gatherThisClass)
set -euo pipefail
# Get focused window class
FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class')
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then
notify-send "Gather Class" "No focused window"
exit 0
fi
# Find all windows with same class on other workspaces
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$FOCUSED_CLASS\" and .workspace.id != $CURRENT_WS and .workspace.id >= 0) | .address")
if [ -z "$WINDOWS" ]; then
notify-send "Gather Class" "No other windows of class '$FOCUSED_CLASS'"
exit 0
fi
# Move each window to current workspace
COUNT=0
for ADDR in $WINDOWS; do
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDR"
((COUNT++))
done
notify-send "Gather Class" "Gathered $COUNT windows of class '$FOCUSED_CLASS'"

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
# Raise existing window or run command (like XMonad's raiseNextMaybe)
# Usage: raise-or-run.sh <class-pattern> <command>
set -euo pipefail
CLASS_PATTERN="$1"
COMMAND="$2"
# Find windows matching the class pattern
MATCHING=$(hyprctl clients -j | jq -r ".[] | select(.class | test(\"$CLASS_PATTERN\"; \"i\")) | .address" | head -1)
if [ -n "$MATCHING" ]; then
# Window exists, focus it
hyprctl dispatch focuswindow "address:$MATCHING"
else
# No matching window, run the command
exec $COMMAND
fi

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Replace focused window with selected window (like XMonad's myReplaceWindow)
# Swaps the positions of focused window and selected window
set -euo pipefail
# Get current workspace and focused window
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
FOCUSED=$(hyprctl activewindow -j | jq -r '.address')
if [ "$FOCUSED" = "null" ] || [ -z "$FOCUSED" ]; then
notify-send "Replace Window" "No focused window"
exit 0
fi
# Get all windows except focused
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.workspace.id >= 0 and .address != \"$FOCUSED\") | \"\(.title) [\(.class)] - WS:\(.workspace.id) |\(.address)\"")
if [ -z "$WINDOWS" ]; then
notify-send "Replace Window" "No other windows available"
exit 0
fi
# Show rofi menu
SELECTION=$(echo "$WINDOWS" | rofi -dmenu -i -p "Replace with" -format 's')
if [ -n "$SELECTION" ]; then
# Extract the window address
ADDRESS=$(echo "$SELECTION" | sed 's/.*|//')
# Swap windows using hy3
hyprctl dispatch hy3:movewindow "address:$ADDRESS"
fi

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
# Shift window to empty workspace on screen in given direction
# Like XMonad's shiftToEmptyOnScreen
# Usage: shift-to-empty-on-screen.sh <direction: u|d|l|r>
set -euo pipefail
DIRECTION="$1"
# First, move focus to the screen in that direction
hyprctl dispatch focusmonitor "$DIRECTION"
# Get the monitor we're now on
MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor')
# Find an empty workspace or create one
# First check if there's an empty workspace on this monitor
EMPTY_WS=$(hyprctl workspaces -j | jq -r ".[] | select(.windows == 0 and .monitor == \"$MONITOR\") | .id" | head -1)
if [ -z "$EMPTY_WS" ]; then
# No empty workspace, find next available workspace number
MAX_WS=$(hyprctl workspaces -j | jq -r 'map(.id) | max')
EMPTY_WS=$((MAX_WS + 1))
fi
# Go back to original monitor and move the window
hyprctl dispatch focusmonitor "$DIRECTION" # This actually toggles back
# Move window to the empty workspace and follow
hyprctl dispatch movetoworkspace "$EMPTY_WS"

View File

@@ -2,6 +2,7 @@
makeEnable config "myModules.desktop" true { makeEnable config "myModules.desktop" true {
imports = [ imports = [
./fonts.nix ./fonts.nix
./hyprland.nix
]; ];
services.xserver = { services.xserver = {
@@ -21,8 +22,6 @@ makeEnable config "myModules.desktop" true {
}; };
}; };
programs.hyprland.enable = true;
services.autorandr = { services.autorandr = {
enable = true; enable = true;
}; };

View File

@@ -1,7 +1,5 @@
{ config, pkgs, makeEnable, ... }: { config, pkgs, makeEnable, ... }:
makeEnable config "myModules.extra" false { makeEnable config "myModules.extra" false {
programs.hyprland.enable = true;
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
android-studio android-studio
gradle gradle

872
nixos/flake.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,16 @@
agenix = {url = "github:ryantm/agenix";}; agenix = {url = "github:ryantm/agenix";};
# Hyprland and plugins from official flakes for proper plugin compatibility
hyprland = {
url = "git+https://github.com/hyprwm/Hyprland?submodules=1&ref=refs/tags/v0.53.0";
};
hy3 = {
url = "github:outfoxxed/hy3?ref=hl0.53.0";
inputs.hyprland.follows = "hyprland";
};
railbird-secrets = { railbird-secrets = {
url = "git+ssh://gitea@dev.railbird.ai:1123/railbird/secrets-flake.git"; url = "git+ssh://gitea@dev.railbird.ai:1123/railbird/secrets-flake.git";
}; };
@@ -111,10 +121,6 @@
nixtheplanet.url = "github:matthewcroughan/nixtheplanet"; nixtheplanet.url = "github:matthewcroughan/nixtheplanet";
org-agenda-api = {
url = "github:colonelpanic8/org-agenda-api";
inputs.nixpkgs.follows = "nixpkgs";
};
}; };
outputs = inputs @ { outputs = inputs @ {
@@ -130,6 +136,8 @@
nix, nix,
agenix, agenix,
imalison-taffybar, imalison-taffybar,
hyprland,
hy3,
... ...
}: let }: let
# Nixpkgs PR patches - just specify PR number and hash # Nixpkgs PR patches - just specify PR number and hash
@@ -199,11 +207,10 @@
mkConfigurationParams = filename: { mkConfigurationParams = filename: {
name = machineNameFromFilename filename; name = machineNameFromFilename filename;
value = { value = {
modules = [ baseModules = [
(machinesFilepath + ("/" + filename)) (machinesFilepath + ("/" + filename))
agenix.nixosModules.default agenix.nixosModules.default
nixtheplanet.nixosModules.macos-ventura nixtheplanet.nixosModules.macos-ventura
inputs.org-agenda-api.nixosModules.default
]; ];
}; };
}; };
@@ -325,15 +332,5 @@
mkConfig (params // machineParams) mkConfig (params // machineParams)
) )
defaultConfigurationParams; defaultConfigurationParams;
} };
//
# Per-system packages (using flake-utils)
inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
orgAgendaPackages = import ./org-agenda-api.nix { inherit pkgs inputs system; };
in {
packages = orgAgendaPackages;
}
);
} }

View File

@@ -1,12 +1,9 @@
{ config, pkgs, lib, makeEnable, ... }: { config, pkgs, lib, makeEnable, inputs, ... }:
makeEnable config "myModules.hyprland" true { makeEnable config "myModules.hyprland" true {
programs.hyprland = { programs.hyprland = {
enable = true; enable = true;
# Plugins for XMonad-like experience # Use Hyprland from the flake for proper plugin compatibility
plugins = [ package = inputs.hyprland.packages.${pkgs.system}.hyprland;
pkgs.hyprlandPlugins.hy3 # Dynamic tiling like XMonad
pkgs.hyprlandPlugins.hyprexpo # Expose/overview (like skippy-xd)
];
}; };
# Hyprland-specific packages # Hyprland-specific packages
@@ -23,7 +20,10 @@ makeEnable config "myModules.hyprland" true {
swappy # Screenshot annotation swappy # Screenshot annotation
wlsunset # Night light / blue light filter wlsunset # Night light / blue light filter
# For hy3 directional focus # hy3 plugin from flake (properly built against matching Hyprland)
jq # JSON processing (used in scripts) inputs.hy3.packages.${pkgs.system}.hy3
# For scripts
jq
]; ];
} }