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
# XMonad-like dynamic tiling using hy3 plugin
# 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
# Source a file (multi-file configs)
# source = ~/.config/hypr/myColors.conf
# Set programs that you use
# =============================================================================
# PROGRAMS
# =============================================================================
$terminal = alacritty
$fileManager = dolphin
$menu = rofi -show drun -show-icons
$runMenu = rofi -show run
# Environment variables
# =============================================================================
# ENVIRONMENT VARIABLES
# =============================================================================
env = XCURSOR_SIZE,24
env = QT_QPA_PLATFORMTHEME,qt5ct
env = GDK_DPI_SCALE,1.25
env = QT_FONT_DPI,120
# Input configuration
# =============================================================================
# INPUT CONFIGURATION
# =============================================================================
input {
kb_layout = us
kb_variant =
@@ -33,100 +46,213 @@ input {
natural_scroll = no
}
sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
sensitivity = 0
}
# General settings
# =============================================================================
# GENERAL SETTINGS
# =============================================================================
general {
gaps_in = 5
gaps_out = 20
gaps_out = 10
border_size = 2
col.active_border = rgba(edb443ee) rgba(33ccffee) 45deg
col.inactive_border = rgba(595959aa)
layout = master
# Use hy3 layout for XMonad-like dynamic tiling
layout = hy3
allow_tearing = false
}
# Decoration
# =============================================================================
# DECORATION
# =============================================================================
decoration {
rounding = 10
rounding = 5
blur {
enabled = true
size = 3
passes = 1
}
# Fade inactive windows (like XMonad's fadeInactive)
active_opacity = 1.0
inactive_opacity = 0.9
}
# Animations
# =============================================================================
# ANIMATIONS
# =============================================================================
animations {
enabled = yes
bezier = myBezier, 0.05, 0.9, 0.1, 1.05
bezier = linear, 0, 0, 1, 1
animation = windows, 1, 7, myBezier
animation = windowsOut, 1, 7, default, popin 80%
animation = windows, 1, 4, myBezier
animation = windowsOut, 1, 4, default, popin 80%
animation = border, 1, 10, default
animation = borderangle, 1, 8, default
animation = fade, 1, 7, default
animation = workspaces, 1, 6, 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
animation = fade, 1, 4, default
animation = workspaces, 1, 4, default
}
# =============================================================================
# MASTER LAYOUT CONFIGURATION
# =============================================================================
master {
mfact = 0.25
new_status = slave
mfact = 0.5
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 {
force_default_wallpaper = 0
disable_hyprland_logo = true
}
# Scratchpad-like windows
windowrule = float, title:^(htop)$
windowrule = size 80% 80%, title:^(htop)$
# =============================================================================
# WINDOW RULES
# =============================================================================
# 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
windowrule = float, class:^(chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrule = float, class:^(chrome)$,title:^(Messages)$
windowrule = size 90% 90%, class:^(chrome)$,title:^(.*@gmail.com.*Gmail.*)$
windowrule = size 90% 90%, class:^(chrome)$,title:^(Messages)$
# Scratchpad windows - float and size
windowrulev2 = float, class:^(htop-scratch)$
windowrulev2 = size 90% 90%, class:^(htop-scratch)$
windowrulev2 = center, class:^(htop-scratch)$
# ===== KEY BINDINGS =====
# Based on XMonad configuration
windowrulev2 = float, class:^(pavucontrol)$
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
$modAlt = SUPER ALT
$hyper = SUPER CTRL ALT
# Mod+Alt combination (equivalent to modalt in XMonad)
$modAlt = SUPER_ALT
# Hyper key (equivalent to mod3Mask in XMonad)
$hyper = ALT_R
# Program launching
# -----------------------------------------------------------------------------
# Program Launching
# -----------------------------------------------------------------------------
bind = $mainMod, P, exec, $menu
bind = $mainMod SHIFT, P, exec, $runMenu
bind = $mainMod SHIFT, Return, exec, $terminal
bind = $mainMod, Q, killactive,
bind = $mainMod SHIFT, C, killactive,
bind = $mainMod SHIFT, Q, exit,
bind = $mainMod, E, exec, emacsclient -c
bind = $mainMod, V, exec, xclip -o | xdotool type --file -
# Emacs-everywhere (like XMonad's emacs-everywhere)
bind = $mainMod, E, exec, emacsclient --eval '(emacs-everywhere)'
bind = $mainMod, V, exec, wl-paste | xdotool type --file -
# Chrome/Browser launching (equivalent to bindBringAndRaise)
bind = $modAlt, C, exec, google-chrome-stable
# Chrome/Browser (raise or spawn like XMonad's bindBringAndRaise)
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, G, togglespecialworkspace, gmail
bind = $modAlt, H, togglespecialworkspace, htop
@@ -136,7 +262,7 @@ bind = $modAlt, S, togglespecialworkspace, spotify
bind = $modAlt, T, togglespecialworkspace, transmission
bind = $modAlt, V, togglespecialworkspace, volume
# Move windows to special workspaces
# Move windows to scratchpads
bind = $modAlt SHIFT, E, movetoworkspace, special:element
bind = $modAlt SHIFT, G, movetoworkspace, special:gmail
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, V, movetoworkspace, special:volume
# Directional navigation (WASD keys as in XMonad)
bind = $mainMod, W, movefocus, u
bind = $mainMod, S, movefocus, d
bind = $mainMod, A, movefocus, l
bind = $mainMod, D, movefocus, r
# Hidden workspace (like XMonad's NSP)
bind = $mainMod, X, movetoworkspace, special:NSP
bind = $mainMod SHIFT, X, togglespecialworkspace, NSP
# Move windows directionally
bind = $mainMod SHIFT, W, movewindow, u
bind = $mainMod SHIFT, S, movewindow, d
bind = $mainMod SHIFT, A, movewindow, l
bind = $mainMod SHIFT, D, movewindow, r
# -----------------------------------------------------------------------------
# DIRECTIONAL NAVIGATION (WASD - like XMonad Navigation2D)
# Using hy3 dispatchers for proper tree-based navigation
# -----------------------------------------------------------------------------
# Resize windows
bind = $mainMod CTRL, W, resizeactive, 0 -50
bind = $mainMod CTRL, S, resizeactive, 0 50
bind = $mainMod CTRL, A, resizeactive, -50 0
bind = $mainMod CTRL, D, resizeactive, 50 0
# Focus movement (Mod + WASD) - hy3:movefocus navigates the tree
bind = $mainMod, W, hy3:movefocus, u
bind = $mainMod, S, hy3:movefocus, d
bind = $mainMod, A, hy3:movefocus, l
bind = $mainMod, D, hy3:movefocus, r
# Layout control
bind = $mainMod, Space, exec, hyprctl dispatch layoutmsg cyclenext
bind = $mainMod CTRL, Space, fullscreen, 0
bind = $mainMod, slash, exec, hyprctl dispatch layoutmsg orientationcycle left top
# Move windows (Mod + Shift + WASD) - hy3:movewindow with once=true for swapping
bind = $mainMod SHIFT, W, hy3:movewindow, u, once
bind = $mainMod SHIFT, S, hy3:movewindow, d, once
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 SHIFT, F, fullscreen, 1
# Toggle floating
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
bind = $mainMod, Tab, exec, hyprctl dispatch focuswindow "class:$(hyprctl activewindow -j | jq -r '.class')"
bind = $mainMod, M, exec, hyprctl dispatch focuswindow "class:master"
# Resize split ratio (hy3 uses resizeactive for splits)
binde = $mainMod, comma, resizeactive, -50 0
binde = $mainMod, period, resizeactive, 50 0
# Monitor/Screen control
bind = $mainMod, Z, focusmonitor, +1
bind = $mainMod SHIFT, Z, movewindow, mon:+1
# Kill group - removes the focused window from its group
bind = $mainMod, N, hy3:killactive
# Window management
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, 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
# hy3:setswallow - set a window to swallow newly spawned windows
bind = $mainMod, M, hy3:setswallow, toggle
# 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, 2, workspace, 2
bind = $mainMod, 3, workspace, 3
@@ -207,7 +386,7 @@ bind = $mainMod, 8, workspace, 8
bind = $mainMod, 9, workspace, 9
bind = $mainMod, 0, workspace, 10
# Move windows to workspaces
# Move window to workspace
bind = $mainMod SHIFT, 1, movetoworkspace, 1
bind = $mainMod SHIFT, 2, movetoworkspace, 2
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, 0, movetoworkspace, 10
# Move and follow to workspace
bind = $mainMod CTRL, 1, movetoworkspace, 1
bind = $mainMod CTRL, 2, movetoworkspace, 2
bind = $mainMod CTRL, 3, movetoworkspace, 3
bind = $mainMod CTRL, 4, movetoworkspace, 4
bind = $mainMod CTRL, 5, movetoworkspace, 5
bind = $mainMod CTRL, 6, movetoworkspace, 6
bind = $mainMod CTRL, 7, movetoworkspace, 7
bind = $mainMod CTRL, 8, movetoworkspace, 8
bind = $mainMod CTRL, 9, movetoworkspace, 9
bind = $mainMod CTRL, 0, movetoworkspace, 10
# Move and follow to workspace (like XMonad's shiftThenView)
bind = $mainMod CTRL, 1, movetoworkspacesilent, 1
bind = $mainMod CTRL, 1, workspace, 1
bind = $mainMod CTRL, 2, movetoworkspacesilent, 2
bind = $mainMod CTRL, 2, workspace, 2
bind = $mainMod CTRL, 3, movetoworkspacesilent, 3
bind = $mainMod CTRL, 3, workspace, 3
bind = $mainMod CTRL, 4, movetoworkspacesilent, 4
bind = $mainMod CTRL, 4, workspace, 4
bind = $mainMod CTRL, 5, movetoworkspacesilent, 5
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
bind = , XF86AudioRaiseVolume, exec, set_volume --unmute --change-volume +5
bind = , XF86AudioLowerVolume, exec, set_volume --unmute --change-volume -5
# Workspace cycling (like XMonad's cycleWorkspaceOnCurrentScreen)
bind = $mainMod, backslash, workspace, previous
# 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 = $mainMod, I, exec, set_volume --unmute --change-volume +5
bind = $mainMod, K, exec, set_volume --unmute --change-volume -5
binde = $mainMod, I, 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
# Media player controls
# Media player controls (matching XMonad: Mod+;=play, Mod+L=next, Mod+J=prev)
bind = $mainMod, semicolon, exec, playerctl play-pause
bind = , XF86AudioPlay, 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 = , XF86AudioPrev, exec, playerctl previous
# Brightness control
bind = , XF86MonBrightnessUp, exec, brightness.sh 5
bind = , XF86MonBrightnessDown, exec, brightness.sh -5
# Mute current window (like XMonad's toggle_mute_current_window)
bind = $hyper SHIFT, Q, exec, toggle_mute_current_window.sh
bind = $hyper CTRL, Q, exec, toggle_mute_current_window.sh only
# Utility bindings
bind = $hyper, V, exec, rofi_clipit.sh
bind = $hyper, Y, exec, rofi-pass
bind = $hyper, H, exec, rofi_shutter
# Brightness control
binde = , XF86MonBrightnessUp, exec, brightness.sh up
binde = , XF86MonBrightnessDown, exec, brightness.sh down
# -----------------------------------------------------------------------------
# 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, 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 SHIFT, K, exec, rofi_kill_all.sh
bind = $hyper, R, exec, rofi-systemd
bind = $hyper, 9, exec, start_synergy.sh
bind = $hyper, slash, exec, toggle_taffybar
bind = $hyper, Space, exec, skippy-xd
# Overview - show all windows (Hyper+Space)
# 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, O, exec, rofi_paswitch
bind = $mainMod, apostrophe, exec, load_default_map
bind = $modAlt, apostrophe, exec, load_xkb_map
# Reload config
bind = $mainMod, R, exec, hyprctl reload
# Workspace cycling
bind = $mainMod, backslash, workspace, previous
bind = $mainMod, Tab, workspace, previous
# -----------------------------------------------------------------------------
# MOUSE BINDINGS
# -----------------------------------------------------------------------------
# Mouse bindings
bindm = $mainMod, mouse:272, movewindow
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_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: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:slack silent] slack
exec-once = [workspace special:spotify silent] spotify
exec-once = [workspace special:transmission silent] transmission-gtk
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 {
imports = [
./fonts.nix
./hyprland.nix
];
services.xserver = {
@@ -21,8 +22,6 @@ makeEnable config "myModules.desktop" true {
};
};
programs.hyprland.enable = true;
services.autorandr = {
enable = true;
};

View File

@@ -1,7 +1,5 @@
{ config, pkgs, makeEnable, ... }:
makeEnable config "myModules.extra" false {
programs.hyprland.enable = true;
environment.systemPackages = with pkgs; [
android-studio
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";};
# 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 = {
url = "git+ssh://gitea@dev.railbird.ai:1123/railbird/secrets-flake.git";
};
@@ -111,10 +121,6 @@
nixtheplanet.url = "github:matthewcroughan/nixtheplanet";
org-agenda-api = {
url = "github:colonelpanic8/org-agenda-api";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs @ {
@@ -130,6 +136,8 @@
nix,
agenix,
imalison-taffybar,
hyprland,
hy3,
...
}: let
# Nixpkgs PR patches - just specify PR number and hash
@@ -199,11 +207,10 @@
mkConfigurationParams = filename: {
name = machineNameFromFilename filename;
value = {
modules = [
baseModules = [
(machinesFilepath + ("/" + filename))
agenix.nixosModules.default
nixtheplanet.nixosModules.macos-ventura
inputs.org-agenda-api.nixosModules.default
];
};
};
@@ -325,15 +332,5 @@
mkConfig (params // machineParams)
)
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 {
programs.hyprland = {
enable = true;
# Plugins for XMonad-like experience
plugins = [
pkgs.hyprlandPlugins.hy3 # Dynamic tiling like XMonad
pkgs.hyprlandPlugins.hyprexpo # Expose/overview (like skippy-xd)
];
# Use Hyprland from the flake for proper plugin compatibility
package = inputs.hyprland.packages.${pkgs.system}.hyprland;
};
# Hyprland-specific packages
@@ -23,7 +20,10 @@ makeEnable config "myModules.hyprland" true {
swappy # Screenshot annotation
wlsunset # Night light / blue light filter
# For hy3 directional focus
jq # JSON processing (used in scripts)
# hy3 plugin from flake (properly built against matching Hyprland)
inputs.hy3.packages.${pkgs.system}.hy3
# For scripts
jq
];
}