Compare commits
28 Commits
hyprland-l
...
1a2b75adcb
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a2b75adcb | |||
| 4e52e81a50 | |||
| df0b7b6db4 | |||
| a7769545f1 | |||
| bb32668387 | |||
| 8ccf5fb7de | |||
| 52861430da | |||
| d9ebb812c5 | |||
| 5cf2eda008 | |||
| 6299ad2c7d | |||
| 672cc14713 | |||
| 64c45e1060 | |||
| a5413331d9 | |||
| 1044565bf7 | |||
| d684f6fbc5 | |||
| 71deb64ed0 | |||
| bb909849bd | |||
| a37e83fb23 | |||
| 53d8a69a31 | |||
| 87fd1681e2 | |||
| 8933f8e545 | |||
| ed90130233 | |||
| aa1fbf9699 | |||
| 3e05939ce3 | |||
| 8e2128b8d4 | |||
| 1696845579 | |||
| 5522b8bacd | |||
| 9c9af9f856 |
@@ -274,6 +274,9 @@ Required behavior:
|
|||||||
- `Super+p` opens the application launcher.
|
- `Super+p` opens the application launcher.
|
||||||
- `Super+Shift+p` opens the run menu.
|
- `Super+Shift+p` opens the run menu.
|
||||||
- `Super+Shift+Return` opens a terminal.
|
- `Super+Shift+Return` opens a terminal.
|
||||||
|
- `Super+q` reloads the window manager config.
|
||||||
|
- `Super+Shift+c` closes the focused window.
|
||||||
|
- `Super+Shift+q` exits the window manager session.
|
||||||
- `Super+Tab` opens the overview.
|
- `Super+Tab` opens the overview.
|
||||||
- `Super+Shift+Tab` opens the overview in bring-window mode when supported.
|
- `Super+Shift+Tab` opens the overview in bring-window mode when supported.
|
||||||
- `Super+g` opens the go-to-window picker.
|
- `Super+g` opens the go-to-window picker.
|
||||||
|
|||||||
2
dotfiles/codex/.gitignore
vendored
2
dotfiles/codex/.gitignore
vendored
@@ -3,3 +3,5 @@
|
|||||||
!AGENTS.md
|
!AGENTS.md
|
||||||
!config.toml
|
!config.toml
|
||||||
!skills
|
!skills
|
||||||
|
|
||||||
|
# Generated/local Codex state, including config.local.toml, stays ignored.
|
||||||
|
|||||||
@@ -2,147 +2,8 @@ model = "gpt-5.5"
|
|||||||
model_reasoning_effort = "high"
|
model_reasoning_effort = "high"
|
||||||
personality = "pragmatic"
|
personality = "pragmatic"
|
||||||
|
|
||||||
|
# Portable Codex defaults. Machine-local additions are appended from
|
||||||
notify = ["/Users/kat/dotfiles/dotfiles/codex/plugins/cache/openai-bundled/computer-use/1.0.755/Codex Computer Use.app/Contents/SharedSupport/SkyComputerUseClient.app/Contents/MacOS/SkyComputerUseClient", "turn-ended"]
|
# dotfiles/codex/config.local.toml by Home Manager.
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/nixpkgs"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/railbird"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/subtr-actor"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/google-messages-api"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/scrobble-scrubber"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/temp"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/org-agenda-api"]
|
|
||||||
trust_level = "untrusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/org"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/.git/modules/dotfiles/config/taffybar"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/notifications-tray-icon"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/hyprland"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/git-sync-rs"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/keepbook"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/boxcars"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/rumno"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/git-blame-rank"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/hatchet"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/dotfiles/emacs.d/elpaca/sources/org-project-capture"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar/packages"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/scrobble-tools"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/.password-store"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/subtr-actor-mechanics"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/lastfm-edit"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/mova"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/rofi-systemd"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/map-quiz"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/run/media/imalison/NETDEBUGUSB"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Projects/coqui-tts-streamer"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Downloads"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/keysmith_generated"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/run/media/imalison/NIXOS_SD"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/dotfiles"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/org"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/Documents/Codex/2026-04-25/do-you-see-the-sandisk-external"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Volumes/Extreme SSD/Projects/keepbook"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/Documents/Codex/2026-04-25/it-seems-like-maybe-we-dont"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/Users/kat/Documents/Codex/2026-04-25/what-is-the-state-of-tiling"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/Pictures/ai/2026/celeb"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[projects."/home/imalison/.local/share/keepbook"]
|
|
||||||
trust_level = "trusted"
|
|
||||||
|
|
||||||
[notice]
|
|
||||||
hide_gpt5_1_migration_prompt = true
|
|
||||||
"hide_gpt-5.1-codex-max_migration_prompt" = true
|
|
||||||
|
|
||||||
[notice.model_migrations]
|
|
||||||
"gpt-5.2" = "gpt-5.2-codex"
|
|
||||||
|
|
||||||
[mcp_servers.chrome-devtools]
|
[mcp_servers.chrome-devtools]
|
||||||
command = "npx"
|
command = "npx"
|
||||||
@@ -160,16 +21,6 @@ unified_exec = true
|
|||||||
apps = true
|
apps = true
|
||||||
steer = true
|
steer = true
|
||||||
|
|
||||||
[marketplaces.openai-bundled]
|
|
||||||
last_updated = "2026-04-21T17:43:57Z"
|
|
||||||
source_type = "local"
|
|
||||||
source = "/Users/kat/.codex/.tmp/bundled-marketplaces/openai-bundled"
|
|
||||||
|
|
||||||
[marketplaces.openai-primary-runtime]
|
|
||||||
last_updated = "2026-04-25T23:49:36Z"
|
|
||||||
source_type = "local"
|
|
||||||
source = "/Users/kat/.cache/codex-runtimes/codex-primary-runtime/plugins/openai-primary-runtime"
|
|
||||||
|
|
||||||
[plugins."google-calendar@openai-curated"]
|
[plugins."google-calendar@openai-curated"]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
@@ -196,6 +47,3 @@ enabled = true
|
|||||||
|
|
||||||
[plugins."browser-use@openai-bundled"]
|
[plugins."browser-use@openai-bundled"]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|
||||||
[tui.model_availability_nux]
|
|
||||||
"gpt-5.5" = 4
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
general {
|
general {
|
||||||
lock_cmd = pidof hyprlock || hyprlock
|
lock_cmd = pidof hyprlock || hyprlock
|
||||||
before_sleep_cmd = loginctl lock-session
|
before_sleep_cmd = loginctl lock-session
|
||||||
after_sleep_cmd = hyprctl dispatch dpms on
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listener {
|
listener {
|
||||||
timeout = 900
|
timeout = 300
|
||||||
on-timeout = hypr-screensaver stop && hyprctl dispatch dpms off
|
on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver start
|
||||||
on-resume = hyprctl dispatch dpms on
|
on-resume = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,15 @@ monitor=,preferred,auto,1
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
$terminal = ghostty --gtk-single-instance=false
|
$terminal = ghostty --gtk-single-instance=false
|
||||||
$fileManager = dolphin
|
$fileManager = dolphin
|
||||||
$menu = rofi -show drun -show-icons
|
$shellUi = hypr_shell_ui
|
||||||
$runMenu = rofi -show run
|
$menu = $shellUi launcher
|
||||||
|
$runMenu = $shellUi run
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# ENVIRONMENT VARIABLES
|
# ENVIRONMENT VARIABLES
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
env = XCURSOR_SIZE,24
|
env = XCURSOR_SIZE,24
|
||||||
env = QT_QPA_PLATFORMTHEME,qt5ct
|
env = QT_QPA_PLATFORMTHEME,qt6ct
|
||||||
# Used by ~/.config/hypr/scripts/* to keep workspace IDs bounded.
|
|
||||||
env = HYPR_MAX_WORKSPACE,9
|
env = HYPR_MAX_WORKSPACE,9
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -246,6 +246,8 @@ $hyper = SUPER CTRL ALT
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
bind = $mainMod, P, exec, $menu
|
bind = $mainMod, P, exec, $menu
|
||||||
bind = $mainMod SHIFT, P, exec, $runMenu
|
bind = $mainMod SHIFT, P, exec, $runMenu
|
||||||
|
bind = $hyper SHIFT, N, exec, $shellUi control-center
|
||||||
|
bind = $hyper CTRL, N, exec, $shellUi settings
|
||||||
bind = $mainMod SHIFT, Return, exec, $terminal
|
bind = $mainMod SHIFT, Return, exec, $terminal
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -253,15 +255,15 @@ bind = $mainMod SHIFT, Return, exec, $terminal
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
bind = $mainMod, TAB, hyprexpo:expo, toggle
|
bind = $mainMod, TAB, hyprexpo:expo, toggle
|
||||||
bind = $mainMod SHIFT, TAB, hyprexpo:expo, bring
|
bind = $mainMod SHIFT, TAB, hyprexpo:expo, bring
|
||||||
bind = $mainMod, Q, killactive,
|
bind = $mainMod, Q, exec, hyprctl reload
|
||||||
bind = $mainMod SHIFT, C, killactive,
|
bind = $mainMod SHIFT, C, killactive,
|
||||||
bind = $mainMod SHIFT, Q, exit,
|
bind = $mainMod SHIFT, Q, exit,
|
||||||
# Emacs-everywhere (like XMonad's emacs-everywhere)
|
# Emacs-everywhere (like XMonad's emacs-everywhere)
|
||||||
bind = $mainMod, E, exec, emacsclient --eval '(emacs-everywhere)'
|
bind = $mainMod, E, exec, emacsclient --eval '(emacs-everywhere)'
|
||||||
bind = $mainMod, V, exec, wl-paste | xdotool type --file -
|
bind = $mainMod, V, exec, wl-paste | xdotool type --file -
|
||||||
|
|
||||||
# Chrome/Browser (raise or spawn like XMonad's bindBringAndRaise)
|
# Chrome/Browser
|
||||||
bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome google-chrome-stable
|
bind = $modAlt, C, exec, google-chrome-stable
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# SCRATCHPADS (managed by hyprscratch daemon with auto-dismiss)
|
# SCRATCHPADS (managed by hyprscratch daemon with auto-dismiss)
|
||||||
@@ -291,11 +293,11 @@ bind = $mainMod, S, hy3:movefocus, d
|
|||||||
bind = $mainMod, A, hy3:movefocus, l
|
bind = $mainMod, A, hy3:movefocus, l
|
||||||
bind = $mainMod, D, hy3:movefocus, r
|
bind = $mainMod, D, hy3:movefocus, r
|
||||||
|
|
||||||
# Move windows (Mod + Shift + WASD) - hy3:movewindow with once=true for swapping
|
# Move windows (Mod + Shift + WASD)
|
||||||
bind = $mainMod SHIFT, W, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh u once
|
bind = $mainMod SHIFT, W, hy3:movewindow, u
|
||||||
bind = $mainMod SHIFT, S, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh d once
|
bind = $mainMod SHIFT, S, hy3:movewindow, d
|
||||||
bind = $mainMod SHIFT, A, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh l once
|
bind = $mainMod SHIFT, A, hy3:movewindow, l
|
||||||
bind = $mainMod SHIFT, D, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh r once
|
bind = $mainMod SHIFT, D, hy3:movewindow, r
|
||||||
|
|
||||||
# Resize windows (Mod + Ctrl + WASD)
|
# Resize windows (Mod + Ctrl + WASD)
|
||||||
binde = $mainMod CTRL, W, resizeactive, 0 -50
|
binde = $mainMod CTRL, W, resizeactive, 0 -50
|
||||||
@@ -315,13 +317,6 @@ bind = $hyper SHIFT, S, movewindow, mon:d
|
|||||||
bind = $hyper SHIFT, A, movewindow, mon:l
|
bind = $hyper SHIFT, A, movewindow, mon:l
|
||||||
bind = $hyper SHIFT, D, movewindow, mon:r
|
bind = $hyper SHIFT, D, movewindow, mon:r
|
||||||
|
|
||||||
# Shift to empty workspace on screen direction (Super + Ctrl + Shift + WASD)
|
|
||||||
# Like XMonad's shiftToEmptyOnScreen
|
|
||||||
bind = $mainMod CTRL SHIFT, W, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh u
|
|
||||||
bind = $mainMod CTRL SHIFT, S, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh d
|
|
||||||
bind = $mainMod CTRL SHIFT, A, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh l
|
|
||||||
bind = $mainMod CTRL SHIFT, D, exec, ~/.config/hypr/scripts/shift-to-empty-on-screen.sh r
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# LAYOUT CONTROL (XMonad-like with hy3)
|
# LAYOUT CONTROL (XMonad-like with hy3)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -373,31 +368,6 @@ bind = $mainMod, N, hy3:killactive
|
|||||||
# hy3:setswallow - set a window to swallow newly spawned windows
|
# hy3:setswallow - set a window to swallow newly spawned windows
|
||||||
bind = $mainMod CTRL, M, hy3:setswallow, toggle
|
bind = $mainMod CTRL, M, hy3:setswallow, toggle
|
||||||
|
|
||||||
# Minimize/unminimize (via special workspace)
|
|
||||||
bind = $mainMod, M, exec, ~/.config/hypr/scripts/minimize-active.sh minimized
|
|
||||||
bind = $mainMod SHIFT, M, exec, ~/.config/hypr/scripts/unminimize-last.sh minimized
|
|
||||||
|
|
||||||
# Minimized "picker" mode:
|
|
||||||
# Open the minimized special workspace, focus a window, press Enter to restore it.
|
|
||||||
bind = $modAlt, Return, exec, ~/.config/hypr/scripts/minimized-mode.sh minimized
|
|
||||||
|
|
||||||
submap = minimized
|
|
||||||
bind = , Return, exec, ~/.config/hypr/scripts/unminimize-last.sh minimized; hyprctl dispatch submap reset
|
|
||||||
bind = , Escape, exec, ~/.config/hypr/scripts/minimized-cancel.sh minimized
|
|
||||||
bind = $modAlt, Return, exec, ~/.config/hypr/scripts/minimized-cancel.sh minimized
|
|
||||||
|
|
||||||
# Optional: basic focus navigation inside the picker.
|
|
||||||
bind = , H, movefocus, l
|
|
||||||
bind = , J, movefocus, d
|
|
||||||
bind = , K, movefocus, u
|
|
||||||
bind = , L, movefocus, r
|
|
||||||
bind = , left, movefocus, l
|
|
||||||
bind = , down, movefocus, d
|
|
||||||
bind = , up, movefocus, u
|
|
||||||
bind = , right, movefocus, r
|
|
||||||
|
|
||||||
submap = reset
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# WORKSPACE CONTROL
|
# WORKSPACE CONTROL
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -448,37 +418,17 @@ bind = $mainMod CTRL, 9, focusworkspaceoncurrentmonitor, 9
|
|||||||
# built-in per-monitor workspace history.
|
# built-in per-monitor workspace history.
|
||||||
bind = $mainMod, backslash, workspace, previous_per_monitor
|
bind = $mainMod, backslash, workspace, previous_per_monitor
|
||||||
|
|
||||||
# Swap current workspace with another (like XMonad's swapWithCurrent)
|
|
||||||
bind = $hyper, 5, exec, ~/.config/hypr/scripts/swap-workspaces.sh
|
|
||||||
|
|
||||||
# Go to next empty workspace (like XMonad's moveTo Next emptyWS)
|
|
||||||
bind = $hyper, E, exec, ~/.config/hypr/scripts/workspace-goto-empty.sh
|
|
||||||
|
|
||||||
# Move to next screen (like XMonad's shiftToNextScreenX)
|
# Move to next screen (like XMonad's shiftToNextScreenX)
|
||||||
bind = $mainMod, Z, focusmonitor, +1
|
bind = $mainMod, Z, focusmonitor, +1
|
||||||
bind = $mainMod SHIFT, Z, movewindow, mon:+1
|
bind = $mainMod SHIFT, Z, movewindow, mon:+1
|
||||||
|
|
||||||
# Shift to empty workspace and view (like XMonad's shiftToEmptyAndView)
|
|
||||||
bind = $mainMod SHIFT, H, exec, ~/.config/hypr/scripts/workspace-move-to-empty.sh
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# WINDOW MANAGEMENT
|
# WINDOW MANAGEMENT
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
# Go to window (rofi window switcher with icons)
|
bind = $mainMod, G, exec, $shellUi window go
|
||||||
bind = $mainMod, G, exec, ~/.config/hypr/scripts/go-to-window.sh
|
bind = $mainMod, B, exec, $shellUi window bring
|
||||||
|
bind = $mainMod SHIFT, B, exec, $shellUi window replace
|
||||||
# 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, apostrophe, exec, ~/.config/hypr/scripts/focus-next-class.sh
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# MEDIA KEYS
|
# MEDIA KEYS
|
||||||
@@ -541,8 +491,8 @@ bindm = $mainMod, mouse:272, movewindow
|
|||||||
bindm = $mainMod, mouse:273, resizewindow
|
bindm = $mainMod, mouse:273, resizewindow
|
||||||
|
|
||||||
# Scroll through workspaces
|
# Scroll through workspaces
|
||||||
bind = $mainMod, mouse_down, exec, ~/.config/hypr/scripts/workspace-scroll.sh +1
|
bind = $mainMod, mouse_down, workspace, e+1
|
||||||
bind = $mainMod, mouse_up, exec, ~/.config/hypr/scripts/workspace-scroll.sh -1
|
bind = $mainMod, mouse_up, workspace, e-1
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# AUTOSTART
|
# AUTOSTART
|
||||||
@@ -552,7 +502,7 @@ bind = $mainMod, mouse_up, exec, ~/.config/hypr/scripts/workspace-scroll.sh -1
|
|||||||
# `graphical-session.target` pulls in most tray/SNI applets (which in turn pull in `tray.target`).
|
# `graphical-session.target` pulls in most tray/SNI applets (which in turn pull in `tray.target`).
|
||||||
# Keep the systemd user manager in sync with the current Hyprland session before
|
# Keep the systemd user manager in sync with the current Hyprland session before
|
||||||
# starting any session-bound units. Separate `exec-once` commands race.
|
# starting any session-bound units. Separate `exec-once` commands race.
|
||||||
exec-once = sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'
|
exec-once = sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd XDG_RUNTIME_DIR WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'
|
||||||
# Force a fresh daemon after compositor restarts so hyprscratch doesn't keep a stale socket.
|
# Force a fresh daemon after compositor restarts so hyprscratch doesn't keep a stale socket.
|
||||||
exec-once = systemctl --user restart hyprscratch.service
|
exec-once = systemctl --user restart hyprscratch.service
|
||||||
exec-once = hypridle
|
exec-once = hypridle
|
||||||
|
|||||||
@@ -3,17 +3,23 @@ local mod_alt = "SUPER + ALT"
|
|||||||
local hyper = "SUPER + CTRL + ALT"
|
local hyper = "SUPER + CTRL + ALT"
|
||||||
|
|
||||||
local terminal = "ghostty --gtk-single-instance=false"
|
local terminal = "ghostty --gtk-single-instance=false"
|
||||||
local menu = "rofi -show drun -show-icons"
|
local shell_ui_command = "hypr_shell_ui"
|
||||||
local run_menu = "rofi -show run"
|
local launcher_command = shell_ui_command .. " launcher"
|
||||||
|
local run_menu = shell_ui_command .. " run"
|
||||||
|
|
||||||
local max_workspace = 9
|
local max_workspace = 9
|
||||||
local scratchpad_top_margin = 60
|
local scratchpad_top_margin = 60
|
||||||
local columns_layout = "nStack"
|
local columns_layout = "nStack"
|
||||||
local monocle_layout = "monocle"
|
local monocle_layout = "monocle"
|
||||||
local minimized_workspace = "special:minimized"
|
local minimized_workspace = "special:minimized"
|
||||||
|
local tabbed_group_staging_workspace = "special:tabbed-monocle-staging"
|
||||||
local current_layout = columns_layout
|
local current_layout = columns_layout
|
||||||
|
local enable_nstack = true
|
||||||
|
local enable_hyprexpo = true
|
||||||
|
local configure_nstack_plugin_from_lua = false
|
||||||
local workspace_layouts = {}
|
local workspace_layouts = {}
|
||||||
local minimized_windows = {}
|
local minimized_windows = {}
|
||||||
|
local tabbed_workspace_groups = {}
|
||||||
local window_picker_mode = nil
|
local window_picker_mode = nil
|
||||||
local window_picker_candidates = {}
|
local window_picker_candidates = {}
|
||||||
local stack_update_timer = nil
|
local stack_update_timer = nil
|
||||||
@@ -84,6 +90,13 @@ local function exec(command)
|
|||||||
return hl.dsp.exec_cmd(command)
|
return hl.dsp.exec_cmd(command)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function window_selector(window)
|
||||||
|
if not window or not window.address then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return "address:" .. tostring(window.address)
|
||||||
|
end
|
||||||
|
|
||||||
local function hyprexpo(action)
|
local function hyprexpo(action)
|
||||||
return function()
|
return function()
|
||||||
if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then
|
if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then
|
||||||
@@ -101,7 +114,7 @@ local function hyprexpo(action)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function apply_nstack_config()
|
local function apply_nstack_config()
|
||||||
if verify_config then
|
if verify_config or not enable_nstack or not configure_nstack_plugin_from_lua then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -126,7 +139,7 @@ local function apply_nstack_config()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function apply_hyprexpo_config()
|
local function apply_hyprexpo_config()
|
||||||
if verify_config then
|
if verify_config or not enable_hyprexpo then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -261,6 +274,26 @@ local function tiled_window_count(workspace)
|
|||||||
return #tiled_windows(workspace)
|
return #tiled_windows(workspace)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function sort_windows_by_focus_history(windows)
|
||||||
|
table.sort(windows, function(left, right)
|
||||||
|
return (left.focus_history_id or 0) < (right.focus_history_id or 0)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function window_address_set(windows)
|
||||||
|
local addresses = {}
|
||||||
|
for _, window in ipairs(windows) do
|
||||||
|
if window and window.address then
|
||||||
|
addresses[window.address] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return addresses
|
||||||
|
end
|
||||||
|
|
||||||
|
local function window_address_in_set(window, addresses)
|
||||||
|
return window and window.address and addresses[window.address] or false
|
||||||
|
end
|
||||||
|
|
||||||
local function workspace_window_count(workspace_id)
|
local function workspace_window_count(workspace_id)
|
||||||
local workspace = hl.get_workspace(tostring(workspace_id))
|
local workspace = hl.get_workspace(tostring(workspace_id))
|
||||||
if not workspace then
|
if not workspace then
|
||||||
@@ -294,7 +327,7 @@ local function find_empty_workspace(target_monitor, exclude_id)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function update_nstack_count()
|
local function update_nstack_count()
|
||||||
if current_layout ~= columns_layout then
|
if not enable_nstack or current_layout ~= columns_layout then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -303,6 +336,7 @@ local function update_nstack_count()
|
|||||||
if count == 0 then
|
if count == 0 then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
count = math.max(count, 2)
|
||||||
hl.dsp.layout("setstackcount " .. tostring(count))()
|
hl.dsp.layout("setstackcount " .. tostring(count))()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -386,8 +420,16 @@ local function toggle_columns_monocle()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function active_group_size()
|
||||||
|
local window = hl.get_active_window()
|
||||||
|
return window and window.group and window.group.size or 0
|
||||||
|
end
|
||||||
|
|
||||||
local function monocle_next()
|
local function monocle_next()
|
||||||
if current_layout == monocle_layout then
|
local window = hl.get_active_window()
|
||||||
|
if window and window.group and window.group.size and window.group.size > 1 then
|
||||||
|
hl.dsp.group.next({ window = window_selector(window) })()
|
||||||
|
elseif current_layout == monocle_layout then
|
||||||
hl.dsp.layout("cyclenext")()
|
hl.dsp.layout("cyclenext")()
|
||||||
update_monocle_notice()
|
update_monocle_notice()
|
||||||
else
|
else
|
||||||
@@ -396,7 +438,10 @@ local function monocle_next()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function monocle_prev()
|
local function monocle_prev()
|
||||||
if current_layout == monocle_layout then
|
local window = hl.get_active_window()
|
||||||
|
if window and window.group and window.group.size and window.group.size > 1 then
|
||||||
|
hl.dsp.group.prev({ window = window_selector(window) })()
|
||||||
|
elseif current_layout == monocle_layout then
|
||||||
hl.dsp.layout("cycleprev")()
|
hl.dsp.layout("cycleprev")()
|
||||||
update_monocle_notice()
|
update_monocle_notice()
|
||||||
else
|
else
|
||||||
@@ -405,7 +450,7 @@ local function monocle_prev()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function focus_direction(direction)
|
local function focus_direction(direction)
|
||||||
if current_layout == monocle_layout then
|
if active_group_size() > 1 or current_layout == monocle_layout then
|
||||||
if direction == "up" or direction == "left" then
|
if direction == "up" or direction == "left" then
|
||||||
monocle_prev()
|
monocle_prev()
|
||||||
else
|
else
|
||||||
@@ -417,6 +462,15 @@ local function focus_direction(direction)
|
|||||||
hl.dsp.focus({ direction = direction })()
|
hl.dsp.focus({ direction = direction })()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function swap_direction(direction)
|
||||||
|
if enable_nstack and current_layout == columns_layout and active_group_size() <= 1 then
|
||||||
|
hl.dsp.layout("swapdirection " .. direction)()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
hl.dsp.window.swap({ direction = direction })()
|
||||||
|
end
|
||||||
|
|
||||||
local function focus_workspace(workspace_id)
|
local function focus_workspace(workspace_id)
|
||||||
hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })()
|
hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })()
|
||||||
end
|
end
|
||||||
@@ -454,9 +508,165 @@ local function focus_previous_workspace_for_monitor()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function move_window_to_workspace(workspace_id, follow, window)
|
local function move_window_to_workspace(workspace_id, follow, window)
|
||||||
hl.dsp.window.move({ workspace = tostring(workspace_id), follow = follow, window = window })()
|
local target_window = window or hl.get_active_window()
|
||||||
|
local target_selector = window_selector(target_window)
|
||||||
|
hl.dsp.window.move({ workspace = tostring(workspace_id), follow = false, window = target_selector })()
|
||||||
if follow then
|
if follow then
|
||||||
focus_workspace(workspace_id)
|
focus_workspace(workspace_id)
|
||||||
|
if target_selector then
|
||||||
|
hl.dsp.focus({ window = target_selector })()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function notify_tabbed_group(text)
|
||||||
|
hl.notification.create({
|
||||||
|
text = text,
|
||||||
|
duration = 1800,
|
||||||
|
icon = "info",
|
||||||
|
color = "rgba(edb443ff)",
|
||||||
|
font_size = 13,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function workspace_visible_normal_windows(workspace)
|
||||||
|
local windows = {}
|
||||||
|
if not workspace then
|
||||||
|
return windows
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, window in ipairs(hl.get_workspace_windows(workspace)) do
|
||||||
|
if is_normal_window(window) and not window.hidden then
|
||||||
|
windows[#windows + 1] = window
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return windows
|
||||||
|
end
|
||||||
|
|
||||||
|
local function active_workspace_tiled_group_candidates(workspace)
|
||||||
|
local candidates = tiled_windows(workspace)
|
||||||
|
sort_windows_by_focus_history(candidates)
|
||||||
|
return candidates
|
||||||
|
end
|
||||||
|
|
||||||
|
local function find_tabbed_group_anchor(state)
|
||||||
|
local active = hl.get_active_window()
|
||||||
|
if active and active.group and active.group.size and active.group.size > 1 then
|
||||||
|
return active
|
||||||
|
end
|
||||||
|
|
||||||
|
if not state then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, window in ipairs(hl.get_windows()) do
|
||||||
|
if window and window.address == state.anchor and window.group and window.group.size and window.group.size > 1 then
|
||||||
|
return window
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function restore_workspace_tabbed_group()
|
||||||
|
local key = workspace_key()
|
||||||
|
local anchor = find_tabbed_group_anchor(tabbed_workspace_groups[key])
|
||||||
|
local anchor_selector = window_selector(anchor)
|
||||||
|
|
||||||
|
if not anchor_selector then
|
||||||
|
tabbed_workspace_groups[key] = nil
|
||||||
|
set_layout(columns_layout)
|
||||||
|
notify_tabbed_group("No tabbed group to restore")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
hl.dsp.focus({ window = anchor_selector })()
|
||||||
|
hl.dsp.group.toggle({ window = anchor_selector })()
|
||||||
|
tabbed_workspace_groups[key] = nil
|
||||||
|
set_layout(columns_layout)
|
||||||
|
schedule_nstack_count_update()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function gather_workspace_into_tabbed_group()
|
||||||
|
local workspace = active_workspace()
|
||||||
|
if not is_normal_workspace(workspace) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local key = workspace_key(workspace)
|
||||||
|
if tabbed_workspace_groups[key] or active_group_size() > 1 then
|
||||||
|
restore_workspace_tabbed_group()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local candidates = active_workspace_tiled_group_candidates(workspace)
|
||||||
|
if #candidates <= 1 then
|
||||||
|
set_layout(columns_layout)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local candidate_addresses = window_address_set(candidates)
|
||||||
|
local focused = hl.get_active_window()
|
||||||
|
local anchor = nil
|
||||||
|
if focused and not focused.floating and not focused.group and window_address_in_set(focused, candidate_addresses) then
|
||||||
|
anchor = focused
|
||||||
|
end
|
||||||
|
|
||||||
|
if not anchor then
|
||||||
|
for _, window in ipairs(candidates) do
|
||||||
|
if not window.group then
|
||||||
|
anchor = window
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local anchor_selector = window_selector(anchor)
|
||||||
|
if not anchor_selector then
|
||||||
|
notify_tabbed_group("Current tiled windows are already grouped")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
set_layout(columns_layout)
|
||||||
|
|
||||||
|
local staged_windows = {}
|
||||||
|
for _, window in ipairs(workspace_visible_normal_windows(workspace)) do
|
||||||
|
if window ~= anchor then
|
||||||
|
staged_windows[#staged_windows + 1] = window
|
||||||
|
move_window_to_workspace(tabbed_group_staging_workspace, false, window)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
hl.dsp.focus({ window = anchor_selector })()
|
||||||
|
hl.dsp.group.toggle({ window = anchor_selector })()
|
||||||
|
|
||||||
|
hl.config({ group = { group_on_movetoworkspace = true } })
|
||||||
|
for _, window in ipairs(candidates) do
|
||||||
|
if window ~= anchor then
|
||||||
|
move_window_to_workspace(workspace.id, false, window)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
hl.config({ group = { group_on_movetoworkspace = false } })
|
||||||
|
|
||||||
|
for _, window in ipairs(staged_windows) do
|
||||||
|
if not window_address_in_set(window, candidate_addresses) then
|
||||||
|
move_window_to_workspace(workspace.id, false, window)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tabbed_workspace_groups[key] = {
|
||||||
|
anchor = anchor.address,
|
||||||
|
windows = candidate_addresses,
|
||||||
|
}
|
||||||
|
hl.dsp.focus({ window = anchor_selector })()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function force_columns_layout()
|
||||||
|
if active_group_size() > 1 or tabbed_workspace_groups[workspace_key()] then
|
||||||
|
restore_workspace_tabbed_group()
|
||||||
|
else
|
||||||
|
set_layout(columns_layout)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -539,7 +749,7 @@ local function move_window_to_monitor(direction, follow)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local original_monitor = hl.get_active_monitor()
|
local original_monitor = hl.get_active_monitor()
|
||||||
hl.dsp.window.move({ monitor = direction, follow = follow, window = window })()
|
hl.dsp.window.move({ monitor = direction, follow = follow, window = window_selector(window) })()
|
||||||
|
|
||||||
if not follow and original_monitor then
|
if not follow and original_monitor then
|
||||||
hl.dsp.focus({ monitor = original_monitor })()
|
hl.dsp.focus({ monitor = original_monitor })()
|
||||||
@@ -703,15 +913,16 @@ local function apply_scratchpad_geometry(name, window, target_monitor)
|
|||||||
x = monitor.x + math.floor((monitor.width - width) / 2)
|
x = monitor.x + math.floor((monitor.width - width) / 2)
|
||||||
y = monitor.y + scratchpad_top_margin
|
y = monitor.y + scratchpad_top_margin
|
||||||
end
|
end
|
||||||
|
local selector = window_selector(window)
|
||||||
|
|
||||||
hl.dsp.window.float({ action = "enable", window = window })()
|
hl.dsp.window.float({ action = "enable", window = selector })()
|
||||||
hl.dsp.window.tag({ tag = "+scratchpad", window = window })()
|
hl.dsp.window.tag({ tag = "+scratchpad", window = selector })()
|
||||||
hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = window })()
|
hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = selector })()
|
||||||
hl.dsp.window.resize({ x = width, y = height, relative = false, window = window })()
|
hl.dsp.window.resize({ x = width, y = height, relative = false, window = selector })()
|
||||||
hl.dsp.window.move({ x = x, y = y, relative = false, window = window })()
|
hl.dsp.window.move({ x = x, y = y, relative = false, window = selector })()
|
||||||
if def.dropdown then
|
if def.dropdown then
|
||||||
hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = window })()
|
hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = selector })()
|
||||||
hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = window })()
|
hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = selector })()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -734,7 +945,7 @@ local function show_scratchpad_window(name, window, workspace, target_monitor)
|
|||||||
|
|
||||||
remove_minimized_window(window)
|
remove_minimized_window(window)
|
||||||
move_window_to_workspace(workspace.id, false, window)
|
move_window_to_workspace(workspace.id, false, window)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor())
|
schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor())
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -820,29 +1031,29 @@ local function activate_window_picker_candidate(index)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if mode == "go" then
|
if mode == "go" then
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local workspace = active_workspace()
|
local workspace = active_workspace()
|
||||||
if mode == "bring" and workspace then
|
if mode == "bring" and workspace then
|
||||||
move_window_to_workspace(workspace.id, false, window)
|
move_window_to_workspace(workspace.id, false, window)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == "minimized" and workspace then
|
if mode == "minimized" and workspace then
|
||||||
remove_minimized_window(window)
|
remove_minimized_window(window)
|
||||||
restore_minimized_window(window, workspace)
|
restore_minimized_window(window, workspace)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == "replace" then
|
if mode == "replace" then
|
||||||
local focused = hl.get_active_window()
|
local focused = hl.get_active_window()
|
||||||
if focused and focused ~= window then
|
if focused and focused ~= window then
|
||||||
hl.dsp.window.swap({ target = window, window = focused })()
|
hl.dsp.window.swap({ target = window_selector(window), window = window_selector(focused) })()
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -939,7 +1150,7 @@ local function focus_next_class()
|
|||||||
local next_class = classes[(current_index % #classes) + 1]
|
local next_class = classes[(current_index % #classes) + 1]
|
||||||
local target = first_by_class[next_class]
|
local target = first_by_class[next_class]
|
||||||
if target then
|
if target then
|
||||||
hl.dsp.focus({ window = target })()
|
hl.dsp.focus({ window = window_selector(target) })()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -978,7 +1189,7 @@ local function raise_or_spawn(class_fragment, command)
|
|||||||
local fragment = string.lower(class_fragment)
|
local fragment = string.lower(class_fragment)
|
||||||
for _, window in ipairs(hl.get_windows()) do
|
for _, window in ipairs(hl.get_windows()) do
|
||||||
if is_normal_window(window) and window.class and string.find(string.lower(window.class), fragment, 1, true) then
|
if is_normal_window(window) and window.class and string.find(string.lower(window.class), fragment, 1, true) then
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1008,7 +1219,7 @@ local function restore_last_minimized()
|
|||||||
local window = table.remove(minimized_windows)
|
local window = table.remove(minimized_windows)
|
||||||
if window and window.address and is_minimized_window(window) then
|
if window and window.address and is_minimized_window(window) then
|
||||||
restore_minimized_window(window, workspace)
|
restore_minimized_window(window, workspace)
|
||||||
hl.dsp.focus({ window = window })()
|
hl.dsp.focus({ window = window_selector(window) })()
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -1103,8 +1314,10 @@ local function toggle_scratchpad(name)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if enable_nstack then
|
||||||
hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so")
|
hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so")
|
||||||
if not verify_config then
|
end
|
||||||
|
if enable_hyprexpo and not verify_config then
|
||||||
hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so")
|
hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1158,19 +1371,28 @@ hl.config({
|
|||||||
workspace_back_and_forth = true,
|
workspace_back_and_forth = true,
|
||||||
},
|
},
|
||||||
group = {
|
group = {
|
||||||
|
group_on_movetoworkspace = false,
|
||||||
col = {
|
col = {
|
||||||
border_active = "rgba(edb443ff)",
|
border_active = "rgba(edb443ff)",
|
||||||
border_inactive = "rgba(091f2eff)",
|
border_inactive = "rgba(091f2eff)",
|
||||||
},
|
},
|
||||||
groupbar = {
|
groupbar = {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
font_size = 12,
|
blur = true,
|
||||||
height = 22,
|
font_size = 13,
|
||||||
|
gradients = true,
|
||||||
|
height = 26,
|
||||||
|
indicator_gap = 0,
|
||||||
|
indicator_height = 1,
|
||||||
|
rounding = 5,
|
||||||
|
gradient_rounding = 5,
|
||||||
|
text_padding = 8,
|
||||||
col = {
|
col = {
|
||||||
active = "rgba(edb443ff)",
|
active = "rgba(edb443ff)",
|
||||||
inactive = "rgba(091f2eff)",
|
inactive = "rgba(101820f2)",
|
||||||
},
|
},
|
||||||
text_color = "rgba(091f2eff)",
|
text_color = "rgba(091018ff)",
|
||||||
|
text_color_inactive = "rgba(f2f5f7ff)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
misc = {
|
misc = {
|
||||||
@@ -1185,15 +1407,50 @@ hl.curve("smoothOut", { type = "bezier", points = { { 0.36, 1 }, { 0.3, 1 } } })
|
|||||||
hl.curve("smoothInOut", { type = "bezier", points = { { 0.42, 0 }, { 0.58, 1 } } })
|
hl.curve("smoothInOut", { type = "bezier", points = { { 0.42, 0 }, { 0.58, 1 } } })
|
||||||
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
||||||
|
|
||||||
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" })
|
local animations = {
|
||||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" })
|
{ leaf = "global", enabled = true, speed = 8, bezier = "default" },
|
||||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" })
|
|
||||||
hl.animation({ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" })
|
{ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||||
hl.animation({ leaf = "border", enabled = false })
|
{ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||||
hl.animation({ leaf = "borderangle", enabled = false })
|
{ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" },
|
||||||
hl.animation({ leaf = "fade", enabled = true, speed = 5, bezier = "smoothOut" })
|
{ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" },
|
||||||
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" })
|
|
||||||
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" })
|
{ leaf = "border", enabled = false },
|
||||||
|
{ leaf = "borderangle", enabled = false },
|
||||||
|
|
||||||
|
{ leaf = "fade", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeSwitch", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeShadow", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeGlow", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeDim", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeLayers", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeLayersIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeLayersOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadePopups", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadePopupsIn", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadePopupsOut", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
{ leaf = "fadeDpms", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
|
||||||
|
{ leaf = "layers", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||||
|
{ leaf = "layersIn", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||||
|
{ leaf = "layersOut", enabled = true, speed = 5, bezier = "smoothOut", style = "fade" },
|
||||||
|
|
||||||
|
{ leaf = "workspaces", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||||
|
{ leaf = "workspacesIn", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||||
|
{ leaf = "workspacesOut", enabled = true, speed = 6, bezier = "smoothOut", style = "slidefade 15%" },
|
||||||
|
{ leaf = "specialWorkspace", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||||
|
{ leaf = "specialWorkspaceIn", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||||
|
{ leaf = "specialWorkspaceOut", enabled = true, speed = 6, bezier = "smoothOut", style = "slidevert" },
|
||||||
|
|
||||||
|
{ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "smoothOut" },
|
||||||
|
{ leaf = "monitorAdded", enabled = true, speed = 5, bezier = "smoothOut" },
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, animation in ipairs(animations) do
|
||||||
|
hl.animation(animation)
|
||||||
|
end
|
||||||
|
|
||||||
local function apply_rules()
|
local function apply_rules()
|
||||||
if verify_config then
|
if verify_config then
|
||||||
@@ -1208,6 +1465,10 @@ local function apply_rules()
|
|||||||
hl.window_rule({ match = { title = "^(Open File)$" }, float = true })
|
hl.window_rule({ match = { title = "^(Open File)$" }, float = true })
|
||||||
hl.window_rule({ match = { title = "^(Save File)$" }, float = true })
|
hl.window_rule({ match = { title = "^(Save File)$" }, float = true })
|
||||||
hl.window_rule({ match = { title = "^(Confirm)$" }, float = true })
|
hl.window_rule({ match = { title = "^(Confirm)$" }, float = true })
|
||||||
|
hl.window_rule({
|
||||||
|
match = { class = "^(com\\.mitchellh\\.ghostty\\.dropdown)$" },
|
||||||
|
animation = "slide top",
|
||||||
|
})
|
||||||
hl.window_rule({
|
hl.window_rule({
|
||||||
match = { class = "^(.*[Rr]umno.*)$" },
|
match = { class = "^(.*[Rr]umno.*)$" },
|
||||||
float = true,
|
float = true,
|
||||||
@@ -1226,27 +1487,21 @@ local function apply_rules()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
apply_rules()
|
bind(main_mod .. " + P", exec(launcher_command))
|
||||||
|
|
||||||
bind(main_mod .. " + P", exec(menu))
|
|
||||||
bind(main_mod .. " + SHIFT + P", exec(run_menu))
|
bind(main_mod .. " + SHIFT + P", exec(run_menu))
|
||||||
|
bind(hyper .. " + SHIFT + N", exec(shell_ui_command .. " control-center"))
|
||||||
|
bind(hyper .. " + CTRL + N", exec(shell_ui_command .. " settings"))
|
||||||
bind(main_mod .. " + SHIFT + Return", exec(terminal))
|
bind(main_mod .. " + SHIFT + Return", exec(terminal))
|
||||||
bind(main_mod .. " + Q", hl.dsp.window.close())
|
bind(main_mod .. " + Q", exec("hyprctl reload"))
|
||||||
bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
|
bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
|
||||||
bind(main_mod .. " + SHIFT + Q", hl.dsp.exit())
|
bind(main_mod .. " + SHIFT + Q", hl.dsp.exit())
|
||||||
bind(main_mod .. " + E", exec("emacsclient --eval '(emacs-everywhere)'"))
|
bind(main_mod .. " + E", exec("emacsclient --eval '(emacs-everywhere)'"))
|
||||||
bind(main_mod .. " + V", exec("wl-paste | xdotool type --file -"))
|
bind(main_mod .. " + V", exec("wl-paste | xdotool type --file -"))
|
||||||
bind(main_mod .. " + Tab", hyprexpo("toggle"))
|
bind(main_mod .. " + Tab", hyprexpo("toggle"))
|
||||||
bind(main_mod .. " + SHIFT + Tab", hyprexpo("bring"))
|
bind(main_mod .. " + SHIFT + Tab", hyprexpo("bring"))
|
||||||
bind(main_mod .. " + G", function()
|
bind(main_mod .. " + G", exec(shell_ui_command .. " window go"))
|
||||||
enter_window_picker("go")
|
bind(main_mod .. " + B", exec(shell_ui_command .. " window bring"))
|
||||||
end)
|
bind(main_mod .. " + SHIFT + B", exec(shell_ui_command .. " window replace"))
|
||||||
bind(main_mod .. " + B", function()
|
|
||||||
enter_window_picker("bring")
|
|
||||||
end)
|
|
||||||
bind(main_mod .. " + SHIFT + B", function()
|
|
||||||
enter_window_picker("replace")
|
|
||||||
end)
|
|
||||||
|
|
||||||
bind(main_mod .. " + W", function()
|
bind(main_mod .. " + W", function()
|
||||||
focus_direction("up")
|
focus_direction("up")
|
||||||
@@ -1261,10 +1516,18 @@ bind(main_mod .. " + D", function()
|
|||||||
focus_direction("right")
|
focus_direction("right")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
bind(main_mod .. " + SHIFT + W", hl.dsp.window.swap({ direction = "up" }))
|
bind(main_mod .. " + SHIFT + W", function()
|
||||||
bind(main_mod .. " + SHIFT + S", hl.dsp.window.swap({ direction = "down" }))
|
swap_direction("up")
|
||||||
bind(main_mod .. " + SHIFT + A", hl.dsp.window.swap({ direction = "left" }))
|
end)
|
||||||
bind(main_mod .. " + SHIFT + D", hl.dsp.window.swap({ direction = "right" }))
|
bind(main_mod .. " + SHIFT + S", function()
|
||||||
|
swap_direction("down")
|
||||||
|
end)
|
||||||
|
bind(main_mod .. " + SHIFT + A", function()
|
||||||
|
swap_direction("left")
|
||||||
|
end)
|
||||||
|
bind(main_mod .. " + SHIFT + D", function()
|
||||||
|
swap_direction("right")
|
||||||
|
end)
|
||||||
|
|
||||||
bind(main_mod .. " + CTRL + W", function()
|
bind(main_mod .. " + CTRL + W", function()
|
||||||
move_window_to_monitor("u", false)
|
move_window_to_monitor("u", false)
|
||||||
@@ -1338,13 +1601,9 @@ bind(hyper .. " + SHIFT + D", function()
|
|||||||
move_window_to_monitor("r", true)
|
move_window_to_monitor("r", true)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
bind(main_mod .. " + Space", toggle_columns_monocle)
|
bind(main_mod .. " + Space", gather_workspace_into_tabbed_group)
|
||||||
bind(main_mod .. " + SHIFT + Space", function()
|
bind(main_mod .. " + SHIFT + Space", force_columns_layout)
|
||||||
set_layout(columns_layout)
|
bind(main_mod .. " + CTRL + Space", gather_workspace_into_tabbed_group)
|
||||||
end)
|
|
||||||
bind(main_mod .. " + CTRL + Space", function()
|
|
||||||
set_layout(monocle_layout)
|
|
||||||
end)
|
|
||||||
bind(main_mod .. " + bracketright", monocle_next)
|
bind(main_mod .. " + bracketright", monocle_next)
|
||||||
bind(main_mod .. " + bracketleft", monocle_prev)
|
bind(main_mod .. " + bracketleft", monocle_prev)
|
||||||
bind(main_mod .. " + F", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
|
bind(main_mod .. " + F", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
|
||||||
@@ -1465,7 +1724,8 @@ bind(main_mod .. " + mouse:273", hl.dsp.window.resize())
|
|||||||
hl.on("hyprland.start", function()
|
hl.on("hyprland.start", function()
|
||||||
apply_nstack_config()
|
apply_nstack_config()
|
||||||
apply_hyprexpo_config()
|
apply_hyprexpo_config()
|
||||||
hl.exec_cmd("sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'")
|
apply_rules()
|
||||||
|
hl.exec_cmd("sh -lc 'export IMALISON_SESSION_TYPE=wayland; dbus-update-activation-environment --systemd XDG_RUNTIME_DIR WAYLAND_DISPLAY DISPLAY XAUTHORITY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP XDG_SESSION_TYPE IMALISON_SESSION_TYPE; systemctl --user start graphical-session.target hyprland-session.target'")
|
||||||
hl.exec_cmd("hypridle")
|
hl.exec_cmd("hypridle")
|
||||||
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
||||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||||
@@ -1476,6 +1736,7 @@ end)
|
|||||||
|
|
||||||
hl.on("config.reloaded", apply_nstack_config)
|
hl.on("config.reloaded", apply_nstack_config)
|
||||||
hl.on("config.reloaded", apply_hyprexpo_config)
|
hl.on("config.reloaded", apply_hyprexpo_config)
|
||||||
|
hl.on("config.reloaded", apply_rules)
|
||||||
|
|
||||||
hl.on("window.open", schedule_nstack_count_update)
|
hl.on("window.open", schedule_nstack_count_update)
|
||||||
hl.on("window.destroy", schedule_nstack_count_update)
|
hl.on("window.destroy", schedule_nstack_count_update)
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Bring window to current workspace (like XMonad's bringWindow)
|
|
||||||
# Uses rofi with icons to select a window, then moves it here.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
|
||||||
|
|
||||||
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
|
|
||||||
|
|
||||||
# Get windows on OTHER workspaces as TSV
|
|
||||||
WINDOW_DATA=$(hyprctl clients -j | jq -r --argjson cws "$CURRENT_WS" '
|
|
||||||
.[] | select(.workspace.id >= 0 and .workspace.id != $cws)
|
|
||||||
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
|
|
||||||
| @tsv')
|
|
||||||
|
|
||||||
if [ -z "$WINDOW_DATA" ]; then
|
|
||||||
notify-send "Bring Window" "No windows on other workspaces"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
addresses=()
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap 'rm -f "$TMPFILE"' EXIT
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r address class title ws_id; do
|
|
||||||
icon=$(icon_for_class "$class")
|
|
||||||
addresses+=("$address")
|
|
||||||
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
|
|
||||||
"$class" "$title" "$ws_id" "$icon"
|
|
||||||
done <<< "$WINDOW_DATA" > "$TMPFILE"
|
|
||||||
|
|
||||||
INDEX=$(rofi -dmenu -i -show-icons -p "Bring window" -format i < "$TMPFILE") || exit 0
|
|
||||||
|
|
||||||
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
|
|
||||||
ADDRESS="${addresses[$INDEX]}"
|
|
||||||
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDRESS"
|
|
||||||
hyprctl dispatch focuswindow "address:$ADDRESS"
|
|
||||||
fi
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Print an "empty" workspace id within 1..$HYPR_MAX_WORKSPACE (default 9).
|
|
||||||
#
|
|
||||||
# Preference order (lowest id wins within each tier):
|
|
||||||
# 1. Workspace exists on the target monitor and has 0 windows
|
|
||||||
# 2. Workspace id does not exist at all (will be created on dispatch)
|
|
||||||
# 3. Workspace exists (elsewhere) and has 0 windows
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# find-empty-workspace.sh [monitor] [exclude_id]
|
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
|
|
||||||
monitor="${1:-}"
|
|
||||||
exclude_id="${2:-}"
|
|
||||||
|
|
||||||
if [[ -z "${monitor}" ]]; then
|
|
||||||
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${monitor}" || "${monitor}" == "null" ]]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
workspaces_json="$(hyprctl workspaces -j 2>/dev/null || echo '[]')"
|
|
||||||
|
|
||||||
unused_candidate=""
|
|
||||||
elsewhere_empty_candidate=""
|
|
||||||
|
|
||||||
for i in $(seq 1 "${max_ws}"); do
|
|
||||||
if [[ -n "${exclude_id}" && "${i}" == "${exclude_id}" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
exists="$(jq -r --argjson id "${i}" '[.[] | select(.id == $id)] | length' <<<"${workspaces_json}")"
|
|
||||||
if [[ "${exists}" == "0" ]]; then
|
|
||||||
if [[ -z "${unused_candidate}" ]]; then
|
|
||||||
unused_candidate="${i}"
|
|
||||||
fi
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
windows="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .windows] | .[0]) // 0' <<<"${workspaces_json}")"
|
|
||||||
if [[ "${windows}" != "0" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
ws_monitor="$(jq -r --argjson id "${i}" '([.[] | select(.id == $id) | .monitor] | .[0]) // ""' <<<"${workspaces_json}")"
|
|
||||||
if [[ "${ws_monitor}" == "${monitor}" ]]; then
|
|
||||||
printf '%s\n' "${i}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${elsewhere_empty_candidate}" ]]; then
|
|
||||||
elsewhere_empty_candidate="${i}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -n "${unused_candidate}" ]]; then
|
|
||||||
printf '%s\n' "${unused_candidate}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -n "${elsewhere_empty_candidate}" ]]; then
|
|
||||||
printf '%s\n' "${elsewhere_empty_candidate}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/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=$((COUNT + 1))
|
|
||||||
done
|
|
||||||
|
|
||||||
notify-send "Gather Class" "Gathered $COUNT windows of class '$FOCUSED_CLASS'"
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Go to a window selected via rofi (with icons from desktop entries).
|
|
||||||
# Replaces "rofi -show window" which doesn't work well on Wayland.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
|
||||||
|
|
||||||
# Get all windows on regular workspaces as TSV
|
|
||||||
WINDOW_DATA=$(hyprctl clients -j | jq -r '
|
|
||||||
.[] | select(.workspace.id >= 0)
|
|
||||||
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
|
|
||||||
| @tsv')
|
|
||||||
|
|
||||||
[ -n "$WINDOW_DATA" ] || exit 0
|
|
||||||
|
|
||||||
addresses=()
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap 'rm -f "$TMPFILE"' EXIT
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r address class title ws_id; do
|
|
||||||
icon=$(icon_for_class "$class")
|
|
||||||
addresses+=("$address")
|
|
||||||
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
|
|
||||||
"$class" "$title" "$ws_id" "$icon"
|
|
||||||
done <<< "$WINDOW_DATA" > "$TMPFILE"
|
|
||||||
|
|
||||||
INDEX=$(rofi -dmenu -i -show-icons -p "Go to window" -format i < "$TMPFILE") || exit 0
|
|
||||||
|
|
||||||
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
|
|
||||||
hyprctl dispatch focuswindow "address:${addresses[$INDEX]}"
|
|
||||||
fi
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Minimize the active window by moving it to a special workspace without
|
|
||||||
# toggling that special workspace open.
|
|
||||||
#
|
|
||||||
# Usage: minimize-active.sh <name>
|
|
||||||
# Example: minimize-active.sh minimized
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
# We could parse plain output, but jq should exist in this setup; if it
|
|
||||||
# doesn't, fail soft.
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')"
|
|
||||||
if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If the minimized special workspace is currently visible, closing it after the
|
|
||||||
# move keeps the window hidden (what "minimize" usually means).
|
|
||||||
MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')"
|
|
||||||
SPECIAL_OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --arg n "special:$NAME" --argjson mid "${MONITOR_ID:-0}" '
|
|
||||||
.[]
|
|
||||||
| select(.id == $mid)
|
|
||||||
| (.specialWorkspace.name // "")
|
|
||||||
| select(. == $n)
|
|
||||||
' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
|
|
||||||
hyprctl dispatch movetoworkspacesilent "special:${NAME},address:${ADDR}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
if [ -n "$SPECIAL_OPEN" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Exit minimized picker mode:
|
|
||||||
# - Hide the minimized special workspace on the active monitor (if visible)
|
|
||||||
# - Reset the submap
|
|
||||||
#
|
|
||||||
# Usage: minimized-cancel.sh <name>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
SPECIAL_WS="special:${NAME}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)"
|
|
||||||
if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then
|
|
||||||
MONITOR_ID=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [ "$OPEN" = "$SPECIAL_WS" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch submap reset >/dev/null 2>&1 || true
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Enter a "picker" mode for minimized windows:
|
|
||||||
# - Ensure the minimized special workspace is visible on the active monitor
|
|
||||||
# - Switch Hyprland into a submap so Enter restores and Escape cancels
|
|
||||||
#
|
|
||||||
# Usage: minimized-mode.sh <name>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
SPECIAL_WS="special:${NAME}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
MONITOR_ID="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.monitorID // empty' || true)"
|
|
||||||
if [ -z "$MONITOR_ID" ] || [ "$MONITOR_ID" = "null" ]; then
|
|
||||||
MONITOR_ID=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --argjson mid "$MONITOR_ID" '.[] | select(.id == $mid) | (.specialWorkspace.name // "")' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
|
|
||||||
# Ensure it's visible (but don't toggle it off if already open).
|
|
||||||
if [ "$OPEN" != "$SPECIAL_WS" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch submap minimized >/dev/null 2>&1 || true
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Move the active window in a direction and warp the cursor to keep its
|
|
||||||
# relative position inside the moved window.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
export PATH="/run/current-system/sw/bin:${PATH}"
|
|
||||||
|
|
||||||
if [[ $# -lt 1 ]]; then
|
|
||||||
echo "usage: $0 <dir> [mode]" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
dir="$1"
|
|
||||||
mode="${2:-}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
move_window() {
|
|
||||||
if [[ -n "$mode" ]]; then
|
|
||||||
hyprctl dispatch hy3:movewindow "$dir, $mode" >/dev/null 2>&1 || true
|
|
||||||
else
|
|
||||||
hyprctl dispatch hy3:movewindow "$dir" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
win_json="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
cur_json="$(hyprctl -j cursorpos 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [[ -z "$win_json" || "$win_json" == "null" || -z "$cur_json" || "$cur_json" == "null" ]]; then
|
|
||||||
move_window
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
cur_x="$(jq -er '.x' <<<"$cur_json" 2>/dev/null || true)"
|
|
||||||
cur_y="$(jq -er '.y' <<<"$cur_json" 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ || ! "$cur_x" =~ ^-?[0-9]+$ || ! "$cur_y" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
move_window
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
rel_x=$((cur_x - win_x))
|
|
||||||
rel_y=$((cur_y - win_y))
|
|
||||||
|
|
||||||
move_window
|
|
||||||
|
|
||||||
win_json="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
if [[ -z "$win_json" || "$win_json" == "null" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
win_x="$(jq -er '.at[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_y="$(jq -er '.at[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_w="$(jq -er '.size[0]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
win_h="$(jq -er '.size[1]' <<<"$win_json" 2>/dev/null || true)"
|
|
||||||
|
|
||||||
if [[ ! "$win_x" =~ ^-?[0-9]+$ || ! "$win_y" =~ ^-?[0-9]+$ || ! "$win_w" =~ ^-?[0-9]+$ || ! "$win_h" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ((rel_x < 0)); then
|
|
||||||
rel_x=0
|
|
||||||
elif ((rel_x > win_w)); then
|
|
||||||
rel_x=$win_w
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ((rel_y < 0)); then
|
|
||||||
rel_y=0
|
|
||||||
elif ((rel_y > win_h)); then
|
|
||||||
rel_y=$win_h
|
|
||||||
fi
|
|
||||||
|
|
||||||
new_x=$((win_x + rel_x))
|
|
||||||
new_y=$((win_y + rel_y))
|
|
||||||
|
|
||||||
hyprctl dispatch movecursor "$new_x" "$new_y" >/dev/null 2>&1 || true
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#!/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
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/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
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/window-icon-map.sh"
|
|
||||||
|
|
||||||
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 as TSV
|
|
||||||
WINDOW_DATA=$(hyprctl clients -j | jq -r --arg focused "$FOCUSED" '
|
|
||||||
.[] | select(.workspace.id >= 0 and .address != $focused)
|
|
||||||
| [.address, .class, (.title | gsub("\t"; " ")), (.workspace.id | tostring)]
|
|
||||||
| @tsv')
|
|
||||||
|
|
||||||
if [ -z "$WINDOW_DATA" ]; then
|
|
||||||
notify-send "Replace Window" "No other windows available"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
addresses=()
|
|
||||||
TMPFILE=$(mktemp)
|
|
||||||
trap 'rm -f "$TMPFILE"' EXIT
|
|
||||||
|
|
||||||
while IFS=$'\t' read -r address class title ws_id; do
|
|
||||||
icon=$(icon_for_class "$class")
|
|
||||||
addresses+=("$address")
|
|
||||||
printf '%-24s %s WS:%s\0icon\x1f%s\n' \
|
|
||||||
"$class" "$title" "$ws_id" "$icon"
|
|
||||||
done <<< "$WINDOW_DATA" > "$TMPFILE"
|
|
||||||
|
|
||||||
INDEX=$(rofi -dmenu -i -show-icons -p "Replace with" -format i < "$TMPFILE") || exit 0
|
|
||||||
|
|
||||||
if [ -n "$INDEX" ] && [ -n "${addresses[$INDEX]:-}" ]; then
|
|
||||||
hyprctl dispatch hy3:movewindow "address:${addresses[$INDEX]}"
|
|
||||||
fi
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
#!/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"
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
|
|
||||||
# Track the current monitor so we can return
|
|
||||||
ORIG_MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor')
|
|
||||||
|
|
||||||
# Move focus to the screen in that direction
|
|
||||||
hyprctl dispatch focusmonitor "$DIRECTION"
|
|
||||||
|
|
||||||
# Get the monitor we're now on (target monitor)
|
|
||||||
MONITOR=$(hyprctl activeworkspace -j | jq -r '.monitor')
|
|
||||||
|
|
||||||
# If there is no monitor in that direction, bail
|
|
||||||
if [ "$MONITOR" = "$ORIG_MONITOR" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find an empty workspace within 1..$HYPR_MAX_WORKSPACE.
|
|
||||||
EMPTY_WS="$(~/.config/hypr/scripts/find-empty-workspace.sh "${MONITOR}" 2>/dev/null || true)"
|
|
||||||
if [[ -z "${EMPTY_WS}" ]]; then
|
|
||||||
# No empty workspace available within the cap; restore focus and bail.
|
|
||||||
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( EMPTY_WS < 1 || EMPTY_WS > max_ws )); then
|
|
||||||
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ensure the workspace exists on the target monitor
|
|
||||||
hyprctl dispatch workspace "$EMPTY_WS"
|
|
||||||
|
|
||||||
# Go back to original monitor and move the window (without following)
|
|
||||||
hyprctl dispatch focusmonitor "$ORIG_MONITOR"
|
|
||||||
hyprctl dispatch movetoworkspacesilent "$EMPTY_WS"
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Swap the contents of the current workspace with another workspace.
|
|
||||||
# Intended to mirror XMonad's swapWithCurrent behavior.
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
|
|
||||||
CURRENT_WS="$(hyprctl activeworkspace -j | jq -r '.id')"
|
|
||||||
if [[ -z "${CURRENT_WS}" || "${CURRENT_WS}" == "null" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
TARGET_WS="${1:-}"
|
|
||||||
|
|
||||||
if [[ -z "${TARGET_WS}" ]]; then
|
|
||||||
WS_LIST="$({
|
|
||||||
seq 1 "${max_ws}"
|
|
||||||
hyprctl workspaces -j | jq -r '.[].id' 2>/dev/null || true
|
|
||||||
} | awk 'NF {print $1}' | awk '!seen[$0]++' | sort -n)"
|
|
||||||
|
|
||||||
TARGET_WS="$(printf "%s\n" "${WS_LIST}" | rofi -dmenu -p "Swap with workspace")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z "${TARGET_WS}" || "${TARGET_WS}" == "null" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${TARGET_WS}" == "${CURRENT_WS}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! [[ "${TARGET_WS}" =~ ^-?[0-9]+$ ]]; then
|
|
||||||
notify-send "Swap Workspace" "Invalid workspace: ${TARGET_WS}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( TARGET_WS < 1 || TARGET_WS > max_ws )); then
|
|
||||||
notify-send "Swap Workspace" "Workspace out of range (1-${max_ws}): ${TARGET_WS}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
WINDOWS_CURRENT="$(hyprctl clients -j | jq -r --arg ws "${CURRENT_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')"
|
|
||||||
WINDOWS_TARGET="$(hyprctl clients -j | jq -r --arg ws "${TARGET_WS}" '.[] | select((.workspace.id|tostring) == $ws) | .address')"
|
|
||||||
|
|
||||||
for ADDR in ${WINDOWS_CURRENT}; do
|
|
||||||
hyprctl dispatch movetoworkspace "${TARGET_WS},address:${ADDR}"
|
|
||||||
done
|
|
||||||
|
|
||||||
for ADDR in ${WINDOWS_TARGET}; do
|
|
||||||
hyprctl dispatch movetoworkspace "${CURRENT_WS},address:${ADDR}"
|
|
||||||
done
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Toggle a named Hyprland scratchpad, spawning it if needed.
|
|
||||||
# Usage: toggle-scratchpad.sh <name> <class_regex|-> <title_regex|-> <command...>
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ "$#" -lt 4 ]; then
|
|
||||||
echo "usage: $0 <name> <class_regex|-> <title_regex|-> <command...>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
NAME="$1"
|
|
||||||
shift
|
|
||||||
CLASS_REGEX="$1"
|
|
||||||
shift
|
|
||||||
TITLE_REGEX="$1"
|
|
||||||
shift
|
|
||||||
COMMAND=("$@")
|
|
||||||
|
|
||||||
if [ "$CLASS_REGEX" = "-" ]; then
|
|
||||||
CLASS_REGEX=""
|
|
||||||
fi
|
|
||||||
if [ "$TITLE_REGEX" = "-" ]; then
|
|
||||||
TITLE_REGEX=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$CLASS_REGEX" ] && [ -z "$TITLE_REGEX" ]; then
|
|
||||||
echo "toggle-scratchpad: provide a class or title regex" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
MATCHING=$(hyprctl clients -j | jq -r --arg cre "$CLASS_REGEX" --arg tre "$TITLE_REGEX" '
|
|
||||||
.[]
|
|
||||||
| select(
|
|
||||||
(($cre == "") or (.class | test($cre; "i")))
|
|
||||||
and
|
|
||||||
(($tre == "") or (.title | test($tre; "i")))
|
|
||||||
)
|
|
||||||
| .address
|
|
||||||
')
|
|
||||||
|
|
||||||
if [ -z "$MATCHING" ]; then
|
|
||||||
"${COMMAND[@]}" &
|
|
||||||
else
|
|
||||||
while IFS= read -r ADDR; do
|
|
||||||
[ -n "$ADDR" ] || continue
|
|
||||||
hyprctl dispatch movetoworkspacesilent "special:$NAME,address:$ADDR"
|
|
||||||
done <<< "$MATCHING"
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME"
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Restore a minimized window by moving it out of a special workspace.
|
|
||||||
#
|
|
||||||
# Usage: unminimize-last.sh <name>
|
|
||||||
# Example: unminimize-last.sh minimized
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
NAME="${1:-minimized}"
|
|
||||||
NAME="${NAME#special:}"
|
|
||||||
SPECIAL_WS="special:${NAME}"
|
|
||||||
|
|
||||||
if ! command -v hyprctl >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
ACTIVE_JSON="$(hyprctl -j activewindow 2>/dev/null || true)"
|
|
||||||
ACTIVE_ADDR="$(printf '%s' "$ACTIVE_JSON" | jq -r '.address // empty')"
|
|
||||||
ACTIVE_WS="$(printf '%s' "$ACTIVE_JSON" | jq -r '.workspace.name // empty')"
|
|
||||||
MONITOR_ID="$(printf '%s' "$ACTIVE_JSON" | jq -r '.monitor // empty')"
|
|
||||||
|
|
||||||
# Destination is the normal active workspace for the active monitor.
|
|
||||||
DEST_WS="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --argjson mid "${MONITOR_ID:-0}" '.[] | select(.id == $mid) | .activeWorkspace.name' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then
|
|
||||||
DEST_WS="$(hyprctl -j activeworkspace 2>/dev/null | jq -r '.name // empty' || true)"
|
|
||||||
fi
|
|
||||||
if [ -z "$DEST_WS" ] || [ "$DEST_WS" = "null" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If we're focused on a minimized window already, restore that one.
|
|
||||||
ADDR=""
|
|
||||||
if [ "$ACTIVE_WS" = "$SPECIAL_WS" ] && [ -n "$ACTIVE_ADDR" ] && [ "$ACTIVE_ADDR" != "null" ]; then
|
|
||||||
ADDR="$ACTIVE_ADDR"
|
|
||||||
else
|
|
||||||
# Otherwise, restore the "most recent" minimized window we can find.
|
|
||||||
# focusHistoryID tends to have 0 as most recent; pick the smallest value.
|
|
||||||
ADDR="$(
|
|
||||||
hyprctl -j clients 2>/dev/null \
|
|
||||||
| jq -r --arg sw "$SPECIAL_WS" '
|
|
||||||
[ .[]
|
|
||||||
| select(.workspace.name == $sw)
|
|
||||||
| { addr: .address, fh: (.focusHistoryID // 999999999) }
|
|
||||||
]
|
|
||||||
| sort_by(.fh)
|
|
||||||
| (.[0].addr // empty)
|
|
||||||
' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$ADDR" ] || [ "$ADDR" = "null" ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch movetoworkspacesilent "${DEST_WS},address:${ADDR}" >/dev/null 2>&1 || true
|
|
||||||
hyprctl dispatch focuswindow "address:${ADDR}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
# If the minimized special workspace is currently visible, close it so we don't
|
|
||||||
# leave things in a special state after a restore.
|
|
||||||
SPECIAL_OPEN="$(
|
|
||||||
hyprctl -j monitors 2>/dev/null \
|
|
||||||
| jq -r --arg n "$SPECIAL_WS" --argjson mid "${MONITOR_ID:-0}" '
|
|
||||||
.[]
|
|
||||||
| select(.id == $mid)
|
|
||||||
| (.specialWorkspace.name // "")
|
|
||||||
| select(. == $n)
|
|
||||||
' \
|
|
||||||
| head -n 1 \
|
|
||||||
|| true
|
|
||||||
)"
|
|
||||||
if [ -n "$SPECIAL_OPEN" ]; then
|
|
||||||
hyprctl dispatch togglespecialworkspace "$NAME" >/dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Source this file to get icon_for_class function.
|
|
||||||
# Builds a mapping from window class → freedesktop icon name
|
|
||||||
# by scanning .desktop files for StartupWMClass and Icon fields.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
# source "$(dirname "$0")/window-icon-map.sh"
|
|
||||||
# icon=$(icon_for_class "google-chrome")
|
|
||||||
|
|
||||||
declare -A _WINDOW_ICON_MAP
|
|
||||||
|
|
||||||
_build_window_icon_map() {
|
|
||||||
local IFS=':'
|
|
||||||
local -a search_dirs=()
|
|
||||||
local dir
|
|
||||||
|
|
||||||
for dir in ${XDG_DATA_DIRS:-/run/current-system/sw/share:/usr/share:/usr/local/share}; do
|
|
||||||
[ -d "$dir/applications" ] && search_dirs+=("$dir/applications")
|
|
||||||
done
|
|
||||||
[ -d "$HOME/.local/share/applications" ] && search_dirs+=("$HOME/.local/share/applications")
|
|
||||||
[ ${#search_dirs[@]} -eq 0 ] && return
|
|
||||||
|
|
||||||
# Expand globs per-directory so the pattern works correctly
|
|
||||||
local -a desktop_files=()
|
|
||||||
for dir in "${search_dirs[@]}"; do
|
|
||||||
desktop_files+=("$dir"/*.desktop)
|
|
||||||
done
|
|
||||||
[ ${#desktop_files[@]} -eq 0 ] && return
|
|
||||||
|
|
||||||
# Single grep pass across all desktop files
|
|
||||||
local -A file_icons file_wmclass
|
|
||||||
local filepath line
|
|
||||||
while IFS=: read -r filepath line; do
|
|
||||||
case "$line" in
|
|
||||||
Icon=*)
|
|
||||||
[ -z "${file_icons[$filepath]:-}" ] && file_icons["$filepath"]="${line#Icon=}"
|
|
||||||
;;
|
|
||||||
StartupWMClass=*)
|
|
||||||
[ -z "${file_wmclass[$filepath]:-}" ] && file_wmclass["$filepath"]="${line#StartupWMClass=}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done < <(grep -H '^Icon=\|^StartupWMClass=' "${desktop_files[@]}" 2>/dev/null)
|
|
||||||
|
|
||||||
# Build class → icon map
|
|
||||||
local icon wm_class bn name
|
|
||||||
for filepath in "${!file_icons[@]}"; do
|
|
||||||
icon="${file_icons[$filepath]}"
|
|
||||||
[ -n "$icon" ] || continue
|
|
||||||
|
|
||||||
wm_class="${file_wmclass[$filepath]:-}"
|
|
||||||
if [ -n "$wm_class" ]; then
|
|
||||||
_WINDOW_ICON_MAP["${wm_class,,}"]="$icon"
|
|
||||||
fi
|
|
||||||
|
|
||||||
bn="${filepath##*/}"
|
|
||||||
name="${bn%.desktop}"
|
|
||||||
_WINDOW_ICON_MAP["${name,,}"]="$icon"
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
_build_window_icon_map
|
|
||||||
|
|
||||||
icon_for_class() {
|
|
||||||
local class_lower="${1,,}"
|
|
||||||
echo "${_WINDOW_ICON_MAP[$class_lower]:-$class_lower}"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
|
||||||
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
|
|
||||||
|
|
||||||
ws="$(
|
|
||||||
~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ -z "${ws}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch workspace "${ws}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cur_ws="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
|
||||||
monitor="$(hyprctl activeworkspace -j | jq -r '.monitor' 2>/dev/null || true)"
|
|
||||||
|
|
||||||
ws="$(
|
|
||||||
~/.config/hypr/scripts/find-empty-workspace.sh "${monitor}" "${cur_ws}" 2>/dev/null || true
|
|
||||||
)"
|
|
||||||
|
|
||||||
if [[ -z "${ws}" ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch movetoworkspace "${ws}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
|
||||||
delta="${1:-}"
|
|
||||||
|
|
||||||
case "${delta}" in
|
|
||||||
+1|-1) ;;
|
|
||||||
next) delta="+1" ;;
|
|
||||||
prev) delta="-1" ;;
|
|
||||||
*)
|
|
||||||
exit 2
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
cur="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
|
||||||
if ! [[ "${cur}" =~ ^[0-9]+$ ]]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
if (( cur < 1 )); then
|
|
||||||
cur=1
|
|
||||||
elif (( cur > max_ws )); then
|
|
||||||
cur="${max_ws}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "${delta}" == "+1" ]]; then
|
|
||||||
if (( cur >= max_ws )); then
|
|
||||||
nxt=1
|
|
||||||
else
|
|
||||||
nxt=$((cur + 1))
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if (( cur <= 1 )); then
|
|
||||||
nxt="${max_ws}"
|
|
||||||
else
|
|
||||||
nxt=$((cur - 1))
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch workspace "${nxt}" >/dev/null 2>&1 || true
|
|
||||||
|
|
||||||
10
dotfiles/config/taffybar/flake.lock
generated
10
dotfiles/config/taffybar/flake.lock
generated
@@ -136,11 +136,11 @@
|
|||||||
"xmonad-contrib": "xmonad-contrib"
|
"xmonad-contrib": "xmonad-contrib"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777319252,
|
"lastModified": 1777452249,
|
||||||
"narHash": "sha256-mPft6i8ReJAvW2LdylFI6FF6NFGa1HMa3RNbisfAsbc=",
|
"narHash": "sha256-Emhn9sIFRVyIlUULDuYjeFcYJld6EAD31TGasYwQsWg=",
|
||||||
"ref": "refs/heads/codex/fix-gdk-backend-strut-detection",
|
"ref": "refs/heads/master",
|
||||||
"rev": "c2cee23fc57384cd322d589944129e6c31d4f0fd",
|
"rev": "9a6463e68c7bc0a712e49d9ba6c6d1b764260cd7",
|
||||||
"revCount": 2288,
|
"revCount": 2295,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -121,13 +121,6 @@
|
|||||||
(oa: {
|
(oa: {
|
||||||
doHaddock = false;
|
doHaddock = false;
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
# Legacy fix for older GHC (harmless on newer)
|
|
||||||
postPatch = (oa.postPatch or "") + ''
|
|
||||||
substituteInPlace src/System/Taffybar/DBus/Client/Util.hs \
|
|
||||||
--replace-fail "import Control.Monad (forM)" \
|
|
||||||
"import Control.Monad (forM)
|
|
||||||
import Control.Applicative (liftA2)"
|
|
||||||
'';
|
|
||||||
# Needed for gi-gtk-layer-shell (introspection data).
|
# Needed for gi-gtk-layer-shell (introspection data).
|
||||||
librarySystemDepends = (oa.librarySystemDepends or []) ++ [ pkgs.gtk-layer-shell ];
|
librarySystemDepends = (oa.librarySystemDepends or []) ++ [ pkgs.gtk-layer-shell ];
|
||||||
});
|
});
|
||||||
|
|||||||
8
dotfiles/config/taffybar/icons/claude-symbol.svg
Normal file
8
dotfiles/config/taffybar/icons/claude-symbol.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 174 148.18">
|
||||||
|
<path
|
||||||
|
fill="#d97757"
|
||||||
|
d="m 105.01,322.07 29.14,-16.35 0.49,-1.42 -0.49,-0.79 h -1.42 l -4.87,-0.3 -16.65,-0.45 -14.44,-0.6 -13.99,-0.75 -3.52,-0.75 -3.3,-4.35 0.34,-2.17 2.96,-1.99 4.24,0.37 9.37,0.64 14.06,0.97 10.2,0.6 15.11,1.57 h 2.4 l 0.34,-0.97 -0.82,-0.6 -0.64,-0.6 -14.55,-9.86 -15.75,-10.42 -8.25,-6 -4.46,-3.04 -2.25,-2.85 -0.97,-6.22 4.05,-4.46 5.44,0.37 1.39,0.37 5.51,4.24 11.77,9.11 15.37,11.32 2.25,1.87 0.9,-0.64 0.11,-0.45 -1.01,-1.69 -8.36,-15.11 -8.92,-15.37 -3.97,-6.37 -1.05,-3.82 c -0.37,-1.57 -0.64,-2.89 -0.64,-4.5 l 4.61,-6.26 2.55,-0.82 6.15,0.82 2.59,2.25 3.82,8.74 6.19,13.76 9.6,18.71 2.81,5.55 1.5,5.14 0.56,1.57 h 0.97 v -0.9 l 0.79,-10.54 1.46,-12.94 1.42,-16.65 0.49,-4.69 2.32,-5.62 4.61,-3.04 3.6,1.72 2.96,4.24 -0.41,2.74 -1.76,11.44 -3.45,17.92 -2.25,12 h 1.31 l 1.5,-1.5 6.07,-8.06 10.2,-12.75 4.5,-5.06 5.25,-5.59 3.37,-2.66 h 6.37 l 4.69,6.97 -2.1,7.2 -6.56,8.32 -5.44,7.05 -7.8,10.5 -4.87,8.4 0.45,0.67 1.16,-0.11 17.62,-3.75 9.52,-1.72 11.36,-1.95 5.14,2.4 0.56,2.44 -2.02,4.99 -12.15,3 -14.25,2.85 -21.22,5.02 -0.26,0.19 0.3,0.37 9.56,0.9 4.09,0.22 h 10.01 l 18.64,1.39 4.87,3.22 2.92,3.94 -0.49,3 -7.5,3.82 -10.12,-2.4 -23.62,-5.62 -8.1,-2.02 h -1.12 v 0.67 l 6.75,6.6 12.37,11.17 15.49,14.4 0.79,3.56 -1.99,2.81 -2.1,-0.3 -13.61,-10.24 -5.25,-4.61 -11.89,-10.01 h -0.79 v 1.05 l 2.74,4.01 14.47,21.75 0.75,6.67 -1.05,2.17 -3.75,1.31 -4.12,-0.75 -8.47,-11.89 -8.74,-13.39 -7.05,-12 -0.86,0.49 -4.16,44.81 -1.95,2.29 -4.5,1.72 -3.75,-2.85 -1.99,-4.61 1.99,-9.11 2.4,-11.89 1.95,-9.45 1.76,-11.74 1.05,-3.9 -0.07,-0.26 -0.86,0.11 -8.85,12.15 -13.46,18.19 -10.65,11.4 -2.55,1.01 -4.42,-2.29 0.41,-4.09 2.47,-3.64 14.74,-18.75 8.89,-11.62 5.74,-6.71 -0.04,-0.97 h -0.34 l -39.15,25.42 -6.97,0.9 -3,-2.81 0.37,-4.61 1.42,-1.5 11.77,-8.1 -0.04,0.04 z"
|
||||||
|
transform="translate(-75.96,-223.53)"
|
||||||
|
shape-rendering="optimizeQuality" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
5
dotfiles/config/taffybar/icons/openai-symbol.svg
Normal file
5
dotfiles/config/taffybar/icons/openai-symbol.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 158.7128 157.296">
|
||||||
|
<!-- Generator: Adobe Illustrator 29.2.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 116) -->
|
||||||
|
<path fill="#e7e4ee" d="M60.8734,57.2556v-14.9432c0-1.2586.4722-2.2029,1.5728-2.8314l30.0443-17.3023c4.0899-2.3593,8.9662-3.4599,13.9988-3.4599,18.8759,0,30.8307,14.6289,30.8307,30.2006,0,1.1007,0,2.3593-.158,3.6178l-31.1446-18.2467c-1.8872-1.1006-3.7754-1.1006-5.6629,0l-39.4812,22.9651ZM131.0276,115.4561v-35.7074c0-2.2028-.9446-3.7756-2.8318-4.8763l-39.481-22.9651,12.8982-7.3934c1.1007-.6285,2.0453-.6285,3.1458,0l30.0441,17.3024c8.6523,5.0341,14.4708,15.7296,14.4708,26.1107,0,11.9539-7.0769,22.965-18.2461,27.527v.0021ZM51.593,83.9964l-12.8982-7.5497c-1.1007-.6285-1.5728-1.5728-1.5728-2.8314v-34.6048c0-16.8303,12.8982-29.5722,30.3585-29.5722,6.607,0,12.7403,2.2029,17.9324,6.1349l-30.987,17.9324c-1.8871,1.1007-2.8314,2.6735-2.8314,4.8764v45.6159l-.0014-.0015ZM79.3562,100.0403l-18.4829-10.3811v-22.0209l18.4829-10.3811,18.4812,10.3811v22.0209l-18.4812,10.3811ZM91.2319,147.8591c-6.607,0-12.7403-2.2031-17.9324-6.1344l30.9866-17.9333c1.8872-1.1005,2.8318-2.6728,2.8318-4.8759v-45.616l13.0564,7.5498c1.1005.6285,1.5723,1.5728,1.5723,2.8314v34.6051c0,16.8297-13.0564,29.5723-30.5147,29.5723v.001ZM53.9522,112.7822l-30.0443-17.3024c-8.652-5.0343-14.471-15.7296-14.471-26.1107,0-12.1119,7.2356-22.9652,18.403-27.5272v35.8634c0,2.2028.9443,3.7756,2.8314,4.8763l39.3248,22.8068-12.8982,7.3938c-1.1007.6287-2.045.6287-3.1456,0ZM52.2229,138.5791c-17.7745,0-30.8306-13.3713-30.8306-29.8871,0-1.2585.1578-2.5169.3143-3.7754l30.987,17.9323c1.8871,1.1005,3.7757,1.1005,5.6628,0l39.4811-22.807v14.9435c0,1.2585-.4721,2.2021-1.5728,2.8308l-30.0443,17.3025c-4.0898,2.359-8.9662,3.4605-13.9989,3.4605h.0014ZM91.2319,157.296c19.0327,0,34.9188-13.5272,38.5383-31.4594,17.6164-4.562,28.9425-21.0779,28.9425-37.908,0-11.0112-4.719-21.7066-13.2133-29.4143.7867-3.3035,1.2595-6.607,1.2595-9.909,0-22.4929-18.2471-39.3247-39.3251-39.3247-4.2461,0-8.3363.6285-12.4262,2.045-7.0792-6.9213-16.8318-11.3254-27.5271-11.3254-19.0331,0-34.9191,13.5268-38.5384,31.4591C11.3255,36.0212,0,52.5373,0,69.3675c0,11.0112,4.7184,21.7065,13.2125,29.4142-.7865,3.3035-1.2586,6.6067-1.2586,9.9092,0,22.4923,18.2466,39.3241,39.3248,39.3241,4.2462,0,8.3362-.6277,12.426-2.0441,7.0776,6.921,16.8302,11.3251,27.5271,11.3251Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
Submodule dotfiles/config/taffybar/taffybar updated: 59e3c75990...9a6463e68c
@@ -40,6 +40,16 @@
|
|||||||
-GtkLabel-justify: left;
|
-GtkLabel-justify: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Compact logo column for stacked AI usage sections. */
|
||||||
|
.usage-section.icon-label > .icon {
|
||||||
|
min-width: 22px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-section.icon-label > .label {
|
||||||
|
padding-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Compact two-line RAM/SWAP widget: reduce icon padding a bit. */
|
/* Compact two-line RAM/SWAP widget: reduce icon padding a bit. */
|
||||||
.ram-swap .icon-label > .icon {
|
.ram-swap .icon-label > .icon {
|
||||||
/* Different glyphs have different visual widths; fix the icon column width
|
/* Different glyphs have different visual widths; fix the icon column width
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE DataKinds #-}
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
@@ -46,11 +47,21 @@ import System.Taffybar.SimpleConfig
|
|||||||
import System.Taffybar.Util (getPixbufFromFilePath, maybeTCombine, postGUIASync, (<|||>))
|
import System.Taffybar.Util (getPixbufFromFilePath, maybeTCombine, postGUIASync, (<|||>))
|
||||||
import System.Taffybar.Widget
|
import System.Taffybar.Widget
|
||||||
import qualified System.Taffybar.Widget.ASUS as ASUS
|
import qualified System.Taffybar.Widget.ASUS as ASUS
|
||||||
import System.Taffybar.Widget.AnthropicUsage (anthropicUsageStackNew)
|
import System.Taffybar.Widget.AnthropicUsage
|
||||||
|
( AnthropicUsageDisplayMode (AnthropicUsageDisplayRemaining),
|
||||||
|
AnthropicUsageStackConfig (..),
|
||||||
|
anthropicUsageStackNewWith,
|
||||||
|
defaultAnthropicUsageStackConfig,
|
||||||
|
)
|
||||||
import System.Taffybar.Widget.CPUMonitor (cpuMonitorNew)
|
import System.Taffybar.Widget.CPUMonitor (cpuMonitorNew)
|
||||||
import System.Taffybar.Widget.Generic.Graph (GraphConfig (..), GraphDirection (..), GraphStyle (..), defaultGraphConfig)
|
import System.Taffybar.Widget.Generic.Graph (GraphConfig (..), GraphDirection (..), GraphStyle (..), defaultGraphConfig)
|
||||||
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
|
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
|
||||||
import System.Taffybar.Widget.OpenAIUsage (openAIUsageStackNew)
|
import System.Taffybar.Widget.OpenAIUsage
|
||||||
|
( OpenAIUsageDisplayMode (OpenAIUsageDisplayRemaining),
|
||||||
|
OpenAIUsageStackConfig (..),
|
||||||
|
defaultOpenAIUsageStackConfig,
|
||||||
|
openAIUsageStackNewWith,
|
||||||
|
)
|
||||||
import qualified System.Taffybar.Widget.PulseAudio as PulseAudio
|
import qualified System.Taffybar.Widget.PulseAudio as PulseAudio
|
||||||
import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
|
import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
|
||||||
import System.Taffybar.Widget.SNITray
|
import System.Taffybar.Widget.SNITray
|
||||||
@@ -65,7 +76,7 @@ import System.Taffybar.Widget.SNITray.PrioritizedCollapsible
|
|||||||
sniTrayPrioritizedCollapsibleNewFromParams,
|
sniTrayPrioritizedCollapsibleNewFromParams,
|
||||||
)
|
)
|
||||||
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
|
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
|
||||||
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI)
|
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, pixbufNewFromFileAtScaleByHeight, widgetSetClassGI)
|
||||||
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
|
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
|
||||||
import qualified System.Taffybar.Widget.Workspaces as Workspaces
|
import qualified System.Taffybar.Widget.Workspaces as Workspaces
|
||||||
import System.Taffybar.WindowIcon (pixBufFromColor)
|
import System.Taffybar.WindowIcon (pixBufFromColor)
|
||||||
@@ -507,10 +518,10 @@ simplifiedScreensaverWidget =
|
|||||||
then return False
|
then return False
|
||||||
else case button of
|
else case button of
|
||||||
1 -> do
|
1 -> do
|
||||||
void $ spawnCommand "hypr-screensaver toggle >/dev/null 2>&1"
|
void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver toggle >/dev/null 2>&1"
|
||||||
return True
|
return True
|
||||||
3 -> do
|
3 -> do
|
||||||
void $ spawnCommand "hypr-screensaver stop >/dev/null 2>&1"
|
void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop >/dev/null 2>&1"
|
||||||
return True
|
return True
|
||||||
_ -> return False
|
_ -> return False
|
||||||
Gtk.widgetShowAll ebox
|
Gtk.widgetShowAll ebox
|
||||||
@@ -551,13 +562,40 @@ wakeupDebugWidget :: TaffyIO Gtk.Widget
|
|||||||
wakeupDebugWidget =
|
wakeupDebugWidget =
|
||||||
decorateWithClassAndBoxM "wakeup-debug" wakeupDebugWidgetNew
|
decorateWithClassAndBoxM "wakeup-debug" wakeupDebugWidgetNew
|
||||||
|
|
||||||
|
usageLogoWidget :: FilePath -> Text -> IO Gtk.Widget
|
||||||
|
usageLogoWidget iconFile tooltip = do
|
||||||
|
iconPath <- getUserConfigFile "taffybar" ("icons/" <> iconFile)
|
||||||
|
iconWidget <-
|
||||||
|
pixbufNewFromFileAtScaleByHeight 18 iconPath >>= \case
|
||||||
|
Right pixbuf -> Gtk.toWidget =<< Gtk.imageNewFromPixbuf (Just pixbuf)
|
||||||
|
Left _ -> Gtk.toWidget =<< Gtk.labelNew (Just "?")
|
||||||
|
Gtk.widgetSetTooltipText iconWidget (Just tooltip)
|
||||||
|
widgetSetClassGI iconWidget "usage-logo"
|
||||||
|
|
||||||
|
usageSectionWidget :: Text -> FilePath -> Text -> TaffyIO Gtk.Widget -> TaffyIO Gtk.Widget
|
||||||
|
usageSectionWidget klass iconFile tooltip stackBuilder =
|
||||||
|
decorateWithClassAndBoxM klass $ do
|
||||||
|
stack <- stackBuilder
|
||||||
|
liftIO $ do
|
||||||
|
iconWidget <- usageLogoWidget iconFile tooltip
|
||||||
|
section <- buildIconLabelBox iconWidget stack
|
||||||
|
widgetSetClassGI section "usage-section"
|
||||||
|
|
||||||
openAIUsageWidget :: TaffyIO Gtk.Widget
|
openAIUsageWidget :: TaffyIO Gtk.Widget
|
||||||
openAIUsageWidget =
|
openAIUsageWidget =
|
||||||
decorateWithClassAndBoxM "openai-usage" openAIUsageStackNew
|
usageSectionWidget "openai-usage" "openai-symbol.svg" "OpenAI usage" $
|
||||||
|
openAIUsageStackNewWith
|
||||||
|
defaultOpenAIUsageStackConfig
|
||||||
|
{ openAIUsageStackDefaultDisplayMode = OpenAIUsageDisplayRemaining
|
||||||
|
}
|
||||||
|
|
||||||
anthropicUsageWidget :: TaffyIO Gtk.Widget
|
anthropicUsageWidget :: TaffyIO Gtk.Widget
|
||||||
anthropicUsageWidget =
|
anthropicUsageWidget =
|
||||||
decorateWithClassAndBoxM "anthropic-usage" anthropicUsageStackNew
|
usageSectionWidget "anthropic-usage" "claude-symbol.svg" "Anthropic usage" $
|
||||||
|
anthropicUsageStackNewWith
|
||||||
|
defaultAnthropicUsageStackConfig
|
||||||
|
{ anthropicUsageStackDefaultDisplayMode = AnthropicUsageDisplayRemaining
|
||||||
|
}
|
||||||
|
|
||||||
sniPriorityVisibilityThresholdDefault :: Int
|
sniPriorityVisibilityThresholdDefault :: Int
|
||||||
sniPriorityVisibilityThresholdDefault = 0
|
sniPriorityVisibilityThresholdDefault = 0
|
||||||
|
|||||||
93
dotfiles/lib/bin/desktop_shell_ui
Executable file
93
dotfiles/lib/bin/desktop_shell_ui
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
state_file="${IM_DESKTOP_SHELL_UI_STATE:-${XDG_STATE_HOME:-$HOME/.local/state}/imalison/desktop-shell-ui}"
|
||||||
|
default_shell_ui="${IM_HYPRLAND_SHELL_UI:-taffybar}"
|
||||||
|
|
||||||
|
normalize_shell_ui() {
|
||||||
|
case "${1:-}" in
|
||||||
|
noctalia)
|
||||||
|
printf '%s\n' "noctalia"
|
||||||
|
;;
|
||||||
|
taffybar|rofi)
|
||||||
|
printf '%s\n' "taffybar"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
current_shell_ui() {
|
||||||
|
local configured=""
|
||||||
|
|
||||||
|
if [[ -r "$state_file" ]]; then
|
||||||
|
IFS= read -r configured < "$state_file" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
normalize_shell_ui "$configured" 2>/dev/null \
|
||||||
|
|| normalize_shell_ui "$default_shell_ui" 2>/dev/null \
|
||||||
|
|| printf '%s\n' "taffybar"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_shell_ui() {
|
||||||
|
local shell_ui="$1"
|
||||||
|
mkdir -p "$(dirname "$state_file")"
|
||||||
|
printf '%s\n' "$shell_ui" > "$state_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_shell_ui() {
|
||||||
|
local shell_ui="$1"
|
||||||
|
|
||||||
|
export IM_HYPRLAND_SHELL_UI="$shell_ui"
|
||||||
|
systemctl --user import-environment IM_HYPRLAND_SHELL_UI 2>/dev/null || true
|
||||||
|
|
||||||
|
case "$shell_ui" in
|
||||||
|
noctalia)
|
||||||
|
systemctl --user stop taffybar.service 2>/dev/null || true
|
||||||
|
systemctl --user reset-failed taffybar.service 2>/dev/null || true
|
||||||
|
systemctl --user start noctalia-shell.service
|
||||||
|
;;
|
||||||
|
taffybar)
|
||||||
|
systemctl --user stop noctalia-shell.service 2>/dev/null || true
|
||||||
|
systemctl --user reset-failed noctalia-shell.service 2>/dev/null || true
|
||||||
|
systemctl --user start taffybar.service
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
set_shell_ui() {
|
||||||
|
local shell_ui
|
||||||
|
shell_ui="$(normalize_shell_ui "${1:-}")" || {
|
||||||
|
echo "usage: desktop_shell_ui set {taffybar|noctalia}" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
write_shell_ui "$shell_ui"
|
||||||
|
apply_shell_ui "$shell_ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-current}" in
|
||||||
|
current)
|
||||||
|
current_shell_ui
|
||||||
|
;;
|
||||||
|
set)
|
||||||
|
set_shell_ui "${2:-}"
|
||||||
|
;;
|
||||||
|
toggle)
|
||||||
|
case "$(current_shell_ui)" in
|
||||||
|
noctalia) set_shell_ui taffybar ;;
|
||||||
|
*) set_shell_ui noctalia ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
apply)
|
||||||
|
apply_shell_ui "$(current_shell_ui)"
|
||||||
|
;;
|
||||||
|
exec-condition)
|
||||||
|
[[ "$(current_shell_ui)" == "$(normalize_shell_ui "${2:-}")" ]]
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: desktop_shell_ui {current|set|toggle|apply|exec-condition}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -2,27 +2,48 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
|
|
||||||
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
|
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
|
||||||
|
pid_file="$state_dir/mpvpaper.pid"
|
||||||
|
event_log="$state_dir/events.log"
|
||||||
mkdir -p "$state_dir"
|
mkdir -p "$state_dir"
|
||||||
|
|
||||||
title_prefix="hypr-screensaver:"
|
title_prefix="hypr-screensaver:"
|
||||||
|
screensaver_dir="${HYPR_SCREENSAVER_DIR:-/var/lib/syncthing/sync/Screensaver}"
|
||||||
|
screensaver_use_dir="${HYPR_SCREENSAVER_USE_DIR:-$screensaver_dir/use}"
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
Usage: hypr-screensaver <start|stop|toggle|status|session>
|
Usage: hypr-screensaver <start|stop|toggle|status|session>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
start Launch the screensaver on every Hyprland monitor.
|
start Launch the screensaver as a Wayland layer-shell overlay.
|
||||||
stop Stop any running screensaver windows.
|
stop Stop any running screensaver overlay.
|
||||||
toggle Start if stopped, otherwise stop.
|
toggle Start if stopped, otherwise stop.
|
||||||
status Exit 0 if any screensaver window is running, otherwise exit 1.
|
status Exit 0 if the screensaver overlay is running, otherwise exit 1.
|
||||||
session Run the configured screensaver payload for one monitor.
|
session Compatibility alias for start.
|
||||||
|
|
||||||
The default payload is an mpv-rendered lavfi animation. You can override the
|
By default, start chooses a random media file from:
|
||||||
source with HYPR_SCREENSAVER_SOURCE, for example:
|
/var/lib/syncthing/sync/Screensaver/use
|
||||||
|
|
||||||
|
Populate that directory with symlinks to generated screensaver loops you want
|
||||||
|
in rotation. You can override the source with HYPR_SCREENSAVER_SOURCE, for
|
||||||
|
example:
|
||||||
HYPR_SCREENSAVER_SOURCE='/path/to/video.mp4'
|
HYPR_SCREENSAVER_SOURCE='/path/to/video.mp4'
|
||||||
HYPR_SCREENSAVER_SOURCE='av://lavfi:mandelbrot=s=2560x1440:r=60'
|
HYPR_SCREENSAVER_SOURCE='av://lavfi:mandelbrot=s=2560x1440:r=60'
|
||||||
|
|
||||||
|
You can also override the rotation directory:
|
||||||
|
HYPR_SCREENSAVER_USE_DIR='/path/to/use'
|
||||||
|
|
||||||
|
Layer-shell/output overrides:
|
||||||
|
HYPR_SCREENSAVER_OUTPUT='ALL'
|
||||||
|
HYPR_SCREENSAVER_LAYER='overlay'
|
||||||
|
|
||||||
|
HDR handling defaults to matching Hyprland's monitor color-management preset.
|
||||||
|
Only monitors with preset "hdr" or "hdredid" get HDR colorspace hints. Override
|
||||||
|
with:
|
||||||
|
HYPR_SCREENSAVER_HDR_MODE=auto
|
||||||
|
HYPR_SCREENSAVER_HDR_MODE=sdr
|
||||||
|
HYPR_SCREENSAVER_HDR_MODE=hdr
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,19 +51,11 @@ monitors_json() {
|
|||||||
hyprctl -j monitors
|
hyprctl -j monitors
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_names() {
|
log_event() {
|
||||||
monitors_json | jq -r '.[].name'
|
printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" >>"$event_log"
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_specs() {
|
legacy_screensaver_window_pids() {
|
||||||
monitors_json | jq -c '.[] | { name, width, height }'
|
|
||||||
}
|
|
||||||
|
|
||||||
focused_monitor() {
|
|
||||||
monitors_json | jq -r '.[] | select(.focused) | .name'
|
|
||||||
}
|
|
||||||
|
|
||||||
screensaver_window_pids() {
|
|
||||||
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
|
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
|
||||||
.[]
|
.[]
|
||||||
| select((.title // "") | startswith($prefix))
|
| select((.title // "") | startswith($prefix))
|
||||||
@@ -52,106 +65,170 @@ screensaver_window_pids() {
|
|||||||
|
|
||||||
is_running() {
|
is_running() {
|
||||||
local pid
|
local pid
|
||||||
for pid in $(screensaver_window_pids); do
|
if [ -f "$pid_file" ]; then
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
shopt -s nullglob
|
|
||||||
local pid_file
|
|
||||||
for pid_file in "$state_dir"/*.pid; do
|
|
||||||
pid="$(<"$pid_file")"
|
pid="$(<"$pid_file")"
|
||||||
if kill -0 "$pid" 2>/dev/null; then
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
done
|
rm -f "$pid_file"
|
||||||
|
fi
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
default_source() {
|
default_source() {
|
||||||
local width="$1"
|
local size width height
|
||||||
local height="$2"
|
size="$(
|
||||||
|
monitors_json 2>/dev/null \
|
||||||
|
| jq -r 'max_by((.width // 0) * (.height // 0)) | "\(.width // 1920)x\(.height // 1080)"' 2>/dev/null \
|
||||||
|
|| true
|
||||||
|
)"
|
||||||
|
size="${size:-1920x1080}"
|
||||||
|
width="${size%x*}"
|
||||||
|
height="${size#*x}"
|
||||||
|
|
||||||
printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \
|
printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \
|
||||||
"$width" "$height"
|
"$width" "$height"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
random_source() {
|
||||||
|
[ -d "$screensaver_use_dir" ] || return 1
|
||||||
|
|
||||||
|
local -a candidates=()
|
||||||
|
local candidate
|
||||||
|
while IFS= read -r -d '' candidate; do
|
||||||
|
candidates+=("$candidate")
|
||||||
|
done < <(
|
||||||
|
find -L "$screensaver_use_dir" -maxdepth 1 -type f \
|
||||||
|
\( \
|
||||||
|
-iname '*.mp4' -o \
|
||||||
|
-iname '*.mkv' -o \
|
||||||
|
-iname '*.mov' -o \
|
||||||
|
-iname '*.webm' -o \
|
||||||
|
-iname '*.gif' -o \
|
||||||
|
-iname '*.png' -o \
|
||||||
|
-iname '*.jpg' -o \
|
||||||
|
-iname '*.jpeg' \
|
||||||
|
\) \
|
||||||
|
-print0
|
||||||
|
)
|
||||||
|
|
||||||
|
[ "${#candidates[@]}" -gt 0 ] || return 1
|
||||||
|
printf '%s\n' "${candidates[$((RANDOM % ${#candidates[@]}))]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
screensaver_uses_hdr() {
|
||||||
|
local mode="${HYPR_SCREENSAVER_HDR_MODE:-auto}"
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
hdr)
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
sdr)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
auto)
|
||||||
|
monitors_json 2>/dev/null \
|
||||||
|
| jq -e 'any(.[]; (.colorManagementPreset // "srgb") == "hdr" or (.colorManagementPreset // "srgb") == "hdredid")' >/dev/null 2>&1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Invalid HYPR_SCREENSAVER_HDR_MODE=%s; expected auto, sdr, or hdr\n' "$mode" >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
mpv_color_options() {
|
||||||
|
if screensaver_uses_hdr; then
|
||||||
|
printf '%s ' \
|
||||||
|
target-colorspace-hint=yes \
|
||||||
|
target-colorspace-hint-mode=source
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s ' \
|
||||||
|
target-colorspace-hint=no \
|
||||||
|
target-prim=bt.709 \
|
||||||
|
target-trc=srgb \
|
||||||
|
target-gamut=bt.709 \
|
||||||
|
target-peak=80 \
|
||||||
|
inverse-tone-mapping=no
|
||||||
|
}
|
||||||
|
|
||||||
|
mpv_options() {
|
||||||
|
if [ -n "${HYPR_SCREENSAVER_MPV_OPTIONS:-}" ]; then
|
||||||
|
printf '%s\n' "$HYPR_SCREENSAVER_MPV_OPTIONS"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s %s\n' \
|
||||||
|
"no-audio loop-file=inf osc=no osd-level=0 input-default-bindings=no terminal=no image-display-duration=inf keep-open=yes" \
|
||||||
|
"$(mpv_color_options)"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mpvpaper() {
|
||||||
|
if command -v mpvpaper >/dev/null 2>&1; then
|
||||||
|
exec mpvpaper "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec nix shell nixpkgs#mpvpaper --command mpvpaper "$@"
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
local current_monitor spec monitor width height pid
|
local source output layer options pid
|
||||||
|
|
||||||
if is_running; then
|
if is_running; then
|
||||||
|
log_event "start ignored: already running pid=$(<"$pid_file")"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
current_monitor="$(focused_monitor || true)"
|
stop
|
||||||
|
|
||||||
while IFS= read -r spec; do
|
source="${HYPR_SCREENSAVER_SOURCE:-}"
|
||||||
monitor="$(jq -r '.name' <<<"$spec")"
|
if [ -z "$source" ]; then
|
||||||
width="$(jq -r '.width' <<<"$spec")"
|
source="$(random_source || true)"
|
||||||
height="$(jq -r '.height' <<<"$spec")"
|
|
||||||
[ -n "$monitor" ] || continue
|
|
||||||
HYPR_SCREENSAVER_MONITOR="$monitor" \
|
|
||||||
HYPR_SCREENSAVER_WIDTH="$width" \
|
|
||||||
HYPR_SCREENSAVER_HEIGHT="$height" \
|
|
||||||
"$script_path" session >/dev/null 2>&1 &
|
|
||||||
pid=$!
|
|
||||||
printf '%s\n' "$pid" > "$state_dir/${monitor}.pid"
|
|
||||||
sleep 0.15
|
|
||||||
done < <(monitor_specs)
|
|
||||||
|
|
||||||
if [ -n "$current_monitor" ]; then
|
|
||||||
hyprctl dispatch focusmonitor "$current_monitor" >/dev/null 2>&1 || true
|
|
||||||
fi
|
fi
|
||||||
|
if [ -z "$source" ]; then
|
||||||
|
source="$(default_source)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
output="${HYPR_SCREENSAVER_OUTPUT:-ALL}"
|
||||||
|
layer="${HYPR_SCREENSAVER_LAYER:-overlay}"
|
||||||
|
options="$(mpv_options)"
|
||||||
|
log_event "start output=$output layer=$layer source=$source"
|
||||||
|
|
||||||
|
(
|
||||||
|
exec </dev/null
|
||||||
|
run_mpvpaper --layer "$layer" --mpv-options "$options" "$output" "$source"
|
||||||
|
) >>"$state_dir/mpvpaper.log" 2>&1 &
|
||||||
|
pid=$!
|
||||||
|
printf '%s\n' "$pid" > "$pid_file"
|
||||||
|
sleep 0.2
|
||||||
|
if ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
rm -f "$pid_file"
|
||||||
|
log_event "start failed: process exited early pid=$pid"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_event "start ok pid=$pid"
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
local pid pid_file
|
local pid legacy_pid
|
||||||
|
|
||||||
for pid in $(screensaver_window_pids); do
|
if [ -f "$pid_file" ]; then
|
||||||
kill "$pid" >/dev/null 2>&1 || true
|
|
||||||
done
|
|
||||||
|
|
||||||
shopt -s nullglob
|
|
||||||
for pid_file in "$state_dir"/*.pid; do
|
|
||||||
pid="$(<"$pid_file")"
|
pid="$(<"$pid_file")"
|
||||||
|
log_event "stop pid=$pid"
|
||||||
kill "$pid" >/dev/null 2>&1 || true
|
kill "$pid" >/dev/null 2>&1 || true
|
||||||
|
pkill -TERM -P "$pid" >/dev/null 2>&1 || true
|
||||||
rm -f "$pid_file"
|
rm -f "$pid_file"
|
||||||
done
|
else
|
||||||
}
|
log_event "stop with no pid file"
|
||||||
|
|
||||||
session() {
|
|
||||||
local monitor="${HYPR_SCREENSAVER_MONITOR:?missing HYPR_SCREENSAVER_MONITOR}"
|
|
||||||
local width="${HYPR_SCREENSAVER_WIDTH:-1920}"
|
|
||||||
local height="${HYPR_SCREENSAVER_HEIGHT:-1080}"
|
|
||||||
local source="${HYPR_SCREENSAVER_SOURCE:-$(default_source "$width" "$height")}"
|
|
||||||
local -a mpv_args=(
|
|
||||||
--no-config
|
|
||||||
--really-quiet
|
|
||||||
--fullscreen
|
|
||||||
--fs-screen-name="$monitor"
|
|
||||||
--screen-name="$monitor"
|
|
||||||
--force-window=immediate
|
|
||||||
--border=no
|
|
||||||
--title-bar=no
|
|
||||||
--ontop
|
|
||||||
--keep-open=yes
|
|
||||||
--loop-file=inf
|
|
||||||
--audio=no
|
|
||||||
--osc=no
|
|
||||||
--osd-level=0
|
|
||||||
--input-default-bindings=no
|
|
||||||
--wayland-app-id=hypr-screensaver
|
|
||||||
--title="${title_prefix}${monitor}"
|
|
||||||
--image-display-duration=inf
|
|
||||||
"$source"
|
|
||||||
)
|
|
||||||
|
|
||||||
if command -v mpv >/dev/null 2>&1; then
|
|
||||||
exec mpv "${mpv_args[@]}"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec nix shell nixpkgs#mpv --command mpv "${mpv_args[@]}"
|
for legacy_pid in $(legacy_screensaver_window_pids); do
|
||||||
|
log_event "stop legacy pid=$legacy_pid"
|
||||||
|
kill "$legacy_pid" >/dev/null 2>&1 || true
|
||||||
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
status() {
|
status() {
|
||||||
@@ -176,7 +253,7 @@ case "${1:-}" in
|
|||||||
status
|
status
|
||||||
;;
|
;;
|
||||||
session)
|
session)
|
||||||
session
|
start
|
||||||
;;
|
;;
|
||||||
""|-h|--help|help)
|
""|-h|--help|help)
|
||||||
usage
|
usage
|
||||||
|
|||||||
254
dotfiles/lib/bin/hypr_rofi_window
Executable file
254
dotfiles/lib/bin/hypr_rofi_window
Executable file
@@ -0,0 +1,254 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PROMPTS = {
|
||||||
|
"go": "Go to window",
|
||||||
|
"bring": "Bring window",
|
||||||
|
"replace": "Replace with",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_hyprland_instance():
|
||||||
|
if os.environ.get("HYPRLAND_INSTANCE_SIGNATURE"):
|
||||||
|
return
|
||||||
|
|
||||||
|
runtime = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}"))
|
||||||
|
hypr_dir = runtime / "hypr"
|
||||||
|
if not hypr_dir.is_dir():
|
||||||
|
return
|
||||||
|
|
||||||
|
sockets = []
|
||||||
|
for socket in hypr_dir.glob("*/.socket.sock"):
|
||||||
|
try:
|
||||||
|
sockets.append((socket.stat().st_mtime, socket.parent.name))
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if sockets:
|
||||||
|
os.environ["HYPRLAND_INSTANCE_SIGNATURE"] = max(sockets)[1]
|
||||||
|
|
||||||
|
|
||||||
|
def run_json(*args):
|
||||||
|
result = subprocess.run(args, check=False, text=True, capture_output=True)
|
||||||
|
if result.returncode != 0 or not result.stdout.strip():
|
||||||
|
return None
|
||||||
|
return json.loads(result.stdout)
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_lua(expression):
|
||||||
|
return subprocess.run(["hyprctl", "dispatch", expression], check=False).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def dispatch_legacy(*args):
|
||||||
|
return subprocess.run(["hyprctl", "dispatch", *args], check=False).returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def normal_workspace(window):
|
||||||
|
workspace = window.get("workspace") or {}
|
||||||
|
workspace_id = workspace.get("id")
|
||||||
|
return isinstance(workspace_id, int) and workspace_id >= 0
|
||||||
|
|
||||||
|
|
||||||
|
def window_address(window):
|
||||||
|
address = window.get("address") or ""
|
||||||
|
return address if isinstance(address, str) and address else None
|
||||||
|
|
||||||
|
|
||||||
|
def clean_text(value):
|
||||||
|
return str(value or "").replace("\t", " ").replace("\n", " ").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def app_dirs():
|
||||||
|
seen = set()
|
||||||
|
for base in os.environ.get("XDG_DATA_DIRS", "/run/current-system/sw/share:/usr/share:/usr/local/share").split(":"):
|
||||||
|
path = Path(base) / "applications"
|
||||||
|
if path.is_dir() and path not in seen:
|
||||||
|
seen.add(path)
|
||||||
|
yield path
|
||||||
|
|
||||||
|
local = Path.home() / ".local/share/applications"
|
||||||
|
if local.is_dir() and local not in seen:
|
||||||
|
yield local
|
||||||
|
|
||||||
|
|
||||||
|
def parse_desktop_file(path):
|
||||||
|
icon = None
|
||||||
|
wm_class = None
|
||||||
|
try:
|
||||||
|
with path.open(errors="ignore") as handle:
|
||||||
|
for raw_line in handle:
|
||||||
|
line = raw_line.strip()
|
||||||
|
if not icon and line.startswith("Icon="):
|
||||||
|
icon = line.removeprefix("Icon=").strip()
|
||||||
|
elif not wm_class and line.startswith("StartupWMClass="):
|
||||||
|
wm_class = line.removeprefix("StartupWMClass=").strip()
|
||||||
|
if icon and wm_class:
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
return None, None
|
||||||
|
return icon, wm_class
|
||||||
|
|
||||||
|
|
||||||
|
def icon_map():
|
||||||
|
mapping = {}
|
||||||
|
for directory in app_dirs():
|
||||||
|
for desktop_file in directory.glob("*.desktop"):
|
||||||
|
icon, wm_class = parse_desktop_file(desktop_file)
|
||||||
|
if not icon:
|
||||||
|
continue
|
||||||
|
mapping.setdefault(desktop_file.stem.lower(), icon)
|
||||||
|
if wm_class:
|
||||||
|
mapping.setdefault(wm_class.lower(), icon)
|
||||||
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
|
def icon_for(mapping, class_name):
|
||||||
|
class_key = clean_text(class_name).lower()
|
||||||
|
return mapping.get(class_key, class_key or "application-x-executable")
|
||||||
|
|
||||||
|
|
||||||
|
def rofi_index(entries, prompt):
|
||||||
|
menu = b"".join(entry.encode("utf-8", errors="replace") for entry in entries)
|
||||||
|
result = subprocess.run(
|
||||||
|
["rofi", "-dmenu", "-i", "-show-icons", "-p", prompt, "-format", "i"],
|
||||||
|
input=menu,
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
return int(result.stdout.decode("utf-8").strip())
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def candidates(mode, clients, active_workspace, active_window):
|
||||||
|
current_ws = (active_workspace or {}).get("id")
|
||||||
|
focused = window_address(active_window or {}) if active_window else None
|
||||||
|
filtered = []
|
||||||
|
|
||||||
|
for window in clients:
|
||||||
|
if not normal_workspace(window):
|
||||||
|
continue
|
||||||
|
|
||||||
|
address = window_address(window)
|
||||||
|
if not address:
|
||||||
|
continue
|
||||||
|
|
||||||
|
workspace_id = (window.get("workspace") or {}).get("id")
|
||||||
|
if mode == "bring" and workspace_id == current_ws:
|
||||||
|
continue
|
||||||
|
if mode == "replace" and address == focused:
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered.append(window)
|
||||||
|
|
||||||
|
filtered.sort(
|
||||||
|
key=lambda window: (
|
||||||
|
(window.get("workspace") or {}).get("id", 9999),
|
||||||
|
window.get("focusHistoryID", 999999),
|
||||||
|
clean_text(window.get("class")).lower(),
|
||||||
|
clean_text(window.get("title")).lower(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
def menu_entry(window, icons):
|
||||||
|
class_name = clean_text(window.get("class"))
|
||||||
|
title = clean_text(window.get("title"))
|
||||||
|
workspace_id = (window.get("workspace") or {}).get("id", "?")
|
||||||
|
label = f"{class_name[:24]:<24} {title} WS:{workspace_id}"
|
||||||
|
icon = icon_for(icons, class_name)
|
||||||
|
return f"{label}\0icon\x1f{icon}\n"
|
||||||
|
|
||||||
|
|
||||||
|
def focus_window(address):
|
||||||
|
if dispatch_lua(f'hl.dsp.focus({{ window = "address:{address}" }})'):
|
||||||
|
return
|
||||||
|
dispatch_legacy("focuswindow", f"address:{address}")
|
||||||
|
|
||||||
|
|
||||||
|
def move_to_workspace(address, workspace_id):
|
||||||
|
workspace = str(workspace_id)
|
||||||
|
if dispatch_lua(f'hl.dsp.window.move({{ workspace = "{workspace}", window = "address:{address}" }})'):
|
||||||
|
return
|
||||||
|
dispatch_legacy("movetoworkspace", f"{workspace},address:{address}")
|
||||||
|
|
||||||
|
|
||||||
|
def swap_with_focused(target_address, focused_address):
|
||||||
|
if dispatch_lua(
|
||||||
|
f'hl.dsp.window.swap({{ target = "address:{target_address}", window = "address:{focused_address}" }})'
|
||||||
|
):
|
||||||
|
return
|
||||||
|
dispatch_legacy("hy3:movewindow", f"address:{target_address}")
|
||||||
|
|
||||||
|
|
||||||
|
def activate(mode, window, active_workspace, active_window):
|
||||||
|
address = window_address(window)
|
||||||
|
if not address:
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == "go":
|
||||||
|
focus_window(address)
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == "bring":
|
||||||
|
current_ws = (active_workspace or {}).get("id")
|
||||||
|
if current_ws is not None:
|
||||||
|
move_to_workspace(address, current_ws)
|
||||||
|
focus_window(address)
|
||||||
|
return
|
||||||
|
|
||||||
|
if mode == "replace":
|
||||||
|
focused = window_address(active_window or {})
|
||||||
|
if focused and focused != address:
|
||||||
|
swap_with_focused(address, focused)
|
||||||
|
focus_window(address)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Rofi window go/bring/replace for Hyprland.")
|
||||||
|
parser.add_argument("mode", choices=sorted(PROMPTS))
|
||||||
|
parser.add_argument("--print-candidates", action="store_true")
|
||||||
|
parser.add_argument("--select-index", type=int)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ensure_hyprland_instance()
|
||||||
|
|
||||||
|
clients = run_json("hyprctl", "clients", "-j") or []
|
||||||
|
active_workspace = run_json("hyprctl", "activeworkspace", "-j") or {}
|
||||||
|
active_window = run_json("hyprctl", "activewindow", "-j") or {}
|
||||||
|
windows = candidates(args.mode, clients, active_workspace, active_window)
|
||||||
|
|
||||||
|
if args.print_candidates:
|
||||||
|
print(json.dumps(windows, indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not windows:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if args.select_index is None:
|
||||||
|
icons = icon_map()
|
||||||
|
entries = [menu_entry(window, icons) for window in windows]
|
||||||
|
index = rofi_index(entries, PROMPTS[args.mode])
|
||||||
|
else:
|
||||||
|
index = args.select_index
|
||||||
|
|
||||||
|
if index is None or index < 0 or index >= len(windows):
|
||||||
|
return 0
|
||||||
|
|
||||||
|
activate(args.mode, windows[index], active_workspace, active_window)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
78
dotfiles/lib/bin/hypr_shell_ui
Executable file
78
dotfiles/lib/bin/hypr_shell_ui
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if command -v desktop_shell_ui >/dev/null 2>&1; then
|
||||||
|
shell_ui="$(desktop_shell_ui current 2>/dev/null || true)"
|
||||||
|
else
|
||||||
|
shell_ui=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
shell_ui="${shell_ui:-${IM_HYPRLAND_SHELL_UI:-taffybar}}"
|
||||||
|
|
||||||
|
run_noctalia() {
|
||||||
|
noctalia-shell ipc --any-display call "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_launcher() {
|
||||||
|
case "$shell_ui" in
|
||||||
|
noctalia)
|
||||||
|
run_noctalia launcher toggle
|
||||||
|
;;
|
||||||
|
taffybar|rofi)
|
||||||
|
exec rofi -show drun -show-icons
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown IM_HYPRLAND_SHELL_UI: $shell_ui" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
run_window_picker() {
|
||||||
|
local mode="${1:-}"
|
||||||
|
|
||||||
|
case "$mode" in
|
||||||
|
go|bring|replace) ;;
|
||||||
|
*)
|
||||||
|
echo "usage: hypr_shell_ui window {go|bring|replace}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ "$shell_ui" == "noctalia" && "${IM_HYPRLAND_NOCTALIA_WINDOW_PICKER:-0}" == "1" ]]; then
|
||||||
|
# Future Noctalia launcher-provider hook. Until that plugin exists or if it
|
||||||
|
# fails to load, keep the existing rofi picker as the working path.
|
||||||
|
run_noctalia "plugin:hypr-window-picker" "$mode" 2>/dev/null && exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec hypr_rofi_window "$mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
launcher)
|
||||||
|
run_launcher
|
||||||
|
;;
|
||||||
|
run)
|
||||||
|
exec rofi -show run
|
||||||
|
;;
|
||||||
|
control-center)
|
||||||
|
if [[ "$shell_ui" == "noctalia" ]]; then
|
||||||
|
run_noctalia controlCenter toggle || true
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
settings)
|
||||||
|
if [[ "$shell_ui" == "noctalia" ]]; then
|
||||||
|
run_noctalia settings toggle || true
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
window)
|
||||||
|
shift
|
||||||
|
run_window_picker "$@"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: hypr_shell_ui {launcher|run|control-center|settings|window}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
275
dotfiles/lib/bin/roborock-control
Executable file
275
dotfiles/lib/bin/roborock-control
Executable file
@@ -0,0 +1,275 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_PATH = Path.home() / ".config" / "roborock-control" / "config.json"
|
||||||
|
PYTHON_ENV_EXPR = (
|
||||||
|
"with import <nixpkgs> {}; "
|
||||||
|
"python313.withPackages (ps: [ ps.python-roborock ps.pyyaml ps.pyshark ])"
|
||||||
|
)
|
||||||
|
COMMON_COMMANDS = {
|
||||||
|
"start": ("app_start", None),
|
||||||
|
"pause": ("app_pause", None),
|
||||||
|
"stop": ("app_stop", None),
|
||||||
|
"dock": ("app_charge", None),
|
||||||
|
"find": ("find_me", None),
|
||||||
|
"dust": ("app_start_collect_dust", None),
|
||||||
|
"stop-dust": ("app_stop_collect_dust", None),
|
||||||
|
"wash": ("app_start_wash", None),
|
||||||
|
"stop-wash": ("app_stop_wash", None),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
if not CONFIG_PATH.exists():
|
||||||
|
return {}
|
||||||
|
return json.loads(CONFIG_PATH.read_text())
|
||||||
|
|
||||||
|
|
||||||
|
def save_config(config):
|
||||||
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
CONFIG_PATH.write_text(json.dumps(config, indent=2, sort_keys=True) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def roborock_args(*args):
|
||||||
|
if os.getenv("ROBOROCK_CONTROL_RUNNER") == "direct":
|
||||||
|
return ["roborock", *args]
|
||||||
|
return [
|
||||||
|
"nix",
|
||||||
|
"shell",
|
||||||
|
"--impure",
|
||||||
|
"--expr",
|
||||||
|
PYTHON_ENV_EXPR,
|
||||||
|
"--command",
|
||||||
|
"roborock",
|
||||||
|
*args,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def run_roborock(*args, capture=False, check=True):
|
||||||
|
kwargs = {
|
||||||
|
"text": True,
|
||||||
|
"check": check,
|
||||||
|
}
|
||||||
|
if capture:
|
||||||
|
kwargs["stdout"] = subprocess.PIPE
|
||||||
|
kwargs["stderr"] = subprocess.PIPE
|
||||||
|
return subprocess.run(roborock_args(*args), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def configured_device_id(args, config):
|
||||||
|
return args.device_id or os.getenv("ROBOROCK_DEVICE_ID") or config.get("device_id")
|
||||||
|
|
||||||
|
|
||||||
|
def infer_single_device_id():
|
||||||
|
result = run_roborock("list-devices", capture=True)
|
||||||
|
devices = json.loads(result.stdout)
|
||||||
|
if len(devices) != 1:
|
||||||
|
names = ", ".join(sorted(devices)) or "none"
|
||||||
|
raise SystemExit(
|
||||||
|
"Could not infer a default device. "
|
||||||
|
f"Found {len(devices)} devices: {names}. "
|
||||||
|
"Pass --device-id or run 'roborock-control config --device-id <id>'."
|
||||||
|
)
|
||||||
|
return next(iter(devices.values()))
|
||||||
|
|
||||||
|
|
||||||
|
def get_device_id(args, config):
|
||||||
|
return configured_device_id(args, config) or infer_single_device_id()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_config(args):
|
||||||
|
config = load_config()
|
||||||
|
changed = False
|
||||||
|
if args.clear:
|
||||||
|
config = {}
|
||||||
|
changed = True
|
||||||
|
if args.device_id is not None:
|
||||||
|
config["device_id"] = args.device_id
|
||||||
|
changed = True
|
||||||
|
if args.email is not None:
|
||||||
|
config["email"] = args.email
|
||||||
|
changed = True
|
||||||
|
if changed:
|
||||||
|
save_config(config)
|
||||||
|
print(json.dumps(config, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_login(args):
|
||||||
|
config = load_config()
|
||||||
|
email = args.email or os.getenv("ROBOROCK_EMAIL") or config.get("email")
|
||||||
|
if not email:
|
||||||
|
raise SystemExit(
|
||||||
|
"Email is required. Pass --email or run "
|
||||||
|
"'roborock-control config --email <address>'."
|
||||||
|
)
|
||||||
|
cli_args = ["login", "--email", email]
|
||||||
|
if args.reauth:
|
||||||
|
cli_args.append("--reauth")
|
||||||
|
if args.password_command:
|
||||||
|
password = subprocess.check_output(args.password_command, shell=True, text=True).strip()
|
||||||
|
cli_args.extend(["--password", password])
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_devices(args):
|
||||||
|
if args.refresh:
|
||||||
|
run_roborock("discover")
|
||||||
|
run_roborock("list-devices")
|
||||||
|
|
||||||
|
|
||||||
|
def command_with_device(args, upstream_command):
|
||||||
|
config = load_config()
|
||||||
|
return [upstream_command, "--device_id", get_device_id(args, config)]
|
||||||
|
|
||||||
|
|
||||||
|
def handle_home(args):
|
||||||
|
cli_args = command_with_device(args, "home")
|
||||||
|
if args.refresh:
|
||||||
|
cli_args.append("--refresh")
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_maps(args):
|
||||||
|
run_roborock(*command_with_device(args, "maps"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_rooms(args):
|
||||||
|
run_roborock(*command_with_device(args, "rooms"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_map_data(args):
|
||||||
|
cli_args = command_with_device(args, "map-data")
|
||||||
|
if args.include_path:
|
||||||
|
cli_args.append("--include_path")
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_map_image(args):
|
||||||
|
run_roborock(
|
||||||
|
*command_with_device(args, "map-image"),
|
||||||
|
"--output-file",
|
||||||
|
args.output_file,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_command(args):
|
||||||
|
config = load_config()
|
||||||
|
params = args.params
|
||||||
|
cli_args = [
|
||||||
|
"command",
|
||||||
|
"--device_id",
|
||||||
|
get_device_id(args, config),
|
||||||
|
"--cmd",
|
||||||
|
args.command_name,
|
||||||
|
]
|
||||||
|
if params is not None:
|
||||||
|
cli_args.extend(["--params", params])
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_common_command(args):
|
||||||
|
command_name, params = COMMON_COMMANDS[args.action]
|
||||||
|
config = load_config()
|
||||||
|
cli_args = [
|
||||||
|
"command",
|
||||||
|
"--device_id",
|
||||||
|
get_device_id(args, config),
|
||||||
|
"--cmd",
|
||||||
|
command_name,
|
||||||
|
]
|
||||||
|
if params is not None:
|
||||||
|
cli_args.extend(["--params", params])
|
||||||
|
run_roborock(*cli_args)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_status(args):
|
||||||
|
config = load_config()
|
||||||
|
run_roborock("status", "--device_id", get_device_id(args, config))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_upstream(args):
|
||||||
|
run_roborock(*args.args)
|
||||||
|
|
||||||
|
|
||||||
|
def build_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Control Roborock vacuums through python-roborock."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--device-id",
|
||||||
|
help="Roborock device id. Defaults to ROBOROCK_DEVICE_ID, saved config, or the only cached device.",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="subcommand", required=True)
|
||||||
|
|
||||||
|
config_parser = subparsers.add_parser("config", help="Show or update saved defaults")
|
||||||
|
config_parser.add_argument("--device-id")
|
||||||
|
config_parser.add_argument("--email")
|
||||||
|
config_parser.add_argument("--clear", action="store_true")
|
||||||
|
config_parser.set_defaults(func=handle_config)
|
||||||
|
|
||||||
|
login_parser = subparsers.add_parser("login", help="Login with email code or password")
|
||||||
|
login_parser.add_argument("--email")
|
||||||
|
login_parser.add_argument("--reauth", action="store_true")
|
||||||
|
login_parser.add_argument(
|
||||||
|
"--password-command",
|
||||||
|
help="Shell command that prints the Roborock password, e.g. 'pass show path/to/login'.",
|
||||||
|
)
|
||||||
|
login_parser.set_defaults(func=handle_login)
|
||||||
|
|
||||||
|
devices_parser = subparsers.add_parser("devices", help="List cached devices")
|
||||||
|
devices_parser.add_argument("--refresh", action="store_true", help="Refresh discovery first")
|
||||||
|
devices_parser.set_defaults(func=handle_devices)
|
||||||
|
|
||||||
|
subparsers.add_parser("status", help="Show vacuum status").set_defaults(func=handle_status)
|
||||||
|
|
||||||
|
home_parser = subparsers.add_parser("home", help="Discover and cache home layout")
|
||||||
|
home_parser.add_argument("--refresh", action="store_true")
|
||||||
|
home_parser.set_defaults(func=handle_home)
|
||||||
|
|
||||||
|
subparsers.add_parser("maps", help="Show map metadata").set_defaults(func=handle_maps)
|
||||||
|
subparsers.add_parser("rooms", help="Show room metadata").set_defaults(func=handle_rooms)
|
||||||
|
|
||||||
|
map_data_parser = subparsers.add_parser("map-data", help="Show parsed map data as JSON")
|
||||||
|
map_data_parser.add_argument("--include-path", action="store_true")
|
||||||
|
map_data_parser.set_defaults(func=handle_map_data)
|
||||||
|
|
||||||
|
map_image_parser = subparsers.add_parser("map-image", help="Save the map image")
|
||||||
|
map_image_parser.add_argument("output_file")
|
||||||
|
map_image_parser.set_defaults(func=handle_map_image)
|
||||||
|
|
||||||
|
raw_parser = subparsers.add_parser("raw", help="Send a raw python-roborock command")
|
||||||
|
raw_parser.add_argument("command_name")
|
||||||
|
raw_parser.add_argument("params", nargs="?")
|
||||||
|
raw_parser.set_defaults(func=handle_command)
|
||||||
|
|
||||||
|
upstream_parser = subparsers.add_parser("cli", help="Pass arguments to the upstream CLI")
|
||||||
|
upstream_parser.add_argument("args", nargs=argparse.REMAINDER)
|
||||||
|
upstream_parser.set_defaults(func=handle_upstream)
|
||||||
|
|
||||||
|
for action in sorted(COMMON_COMMANDS):
|
||||||
|
subparsers.add_parser(action).set_defaults(func=handle_common_command, action=action)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = build_parser()
|
||||||
|
args = parser.parse_args()
|
||||||
|
try:
|
||||||
|
args.func(args)
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
if error.stdout:
|
||||||
|
print(error.stdout, end="")
|
||||||
|
if error.stderr:
|
||||||
|
print(error.stderr, end="", file=sys.stderr)
|
||||||
|
raise SystemExit(error.returncode)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
multiplexerAliases = import ../../shared/multiplexer-aliases.nix;
|
multiplexerAliases = import ../../shared/multiplexer-aliases.nix;
|
||||||
|
|
||||||
excludedTopLevelEntries = [
|
excludedTopLevelEntries = [
|
||||||
|
"codex"
|
||||||
"config"
|
"config"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
cfg = config.myModules.codexGeneratedSkills;
|
cfg = config.myModules.codexGeneratedSkills;
|
||||||
|
oos = config.lib.file.mkOutOfStoreSymlink;
|
||||||
in {
|
in {
|
||||||
options.myModules.codexGeneratedSkills = {
|
options.myModules.codexGeneratedSkills = {
|
||||||
enable = lib.mkEnableOption "generated Codex skill setup";
|
enable = lib.mkEnableOption "Codex home setup";
|
||||||
|
|
||||||
codexHome = lib.mkOption {
|
codexHome = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
@@ -15,6 +16,12 @@ in {
|
|||||||
description = "Codex home directory.";
|
description = "Codex home directory.";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
worktreeCodexDir = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "${config.home.homeDirectory}/dotfiles/dotfiles/codex";
|
||||||
|
description = "Codex dotfiles directory in the live worktree.";
|
||||||
|
};
|
||||||
|
|
||||||
skillsDir = lib.mkOption {
|
skillsDir = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "${cfg.codexHome}/skills";
|
default = "${cfg.codexHome}/skills";
|
||||||
@@ -29,6 +36,67 @@ in {
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
|
home.file = {
|
||||||
|
".codex/.gitignore" = {
|
||||||
|
force = true;
|
||||||
|
source = oos "${cfg.worktreeCodexDir}/.gitignore";
|
||||||
|
};
|
||||||
|
|
||||||
|
".codex/AGENTS.md" = {
|
||||||
|
force = true;
|
||||||
|
source = oos "${cfg.worktreeCodexDir}/AGENTS.md";
|
||||||
|
};
|
||||||
|
|
||||||
|
".codex/skills" = {
|
||||||
|
force = true;
|
||||||
|
source = oos "${cfg.worktreeCodexDir}/skills";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
home.activation.prepareCodexDirectory = lib.hm.dag.entryBefore ["checkLinkTargets"] ''
|
||||||
|
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||||
|
worktree_codex=${lib.escapeShellArg cfg.worktreeCodexDir}
|
||||||
|
|
||||||
|
if [ -L "$codex_home" ] && [ "$(readlink "$codex_home")" = "$worktree_codex" ]; then
|
||||||
|
rm -f "$codex_home"
|
||||||
|
mkdir -p "$codex_home"
|
||||||
|
elif [ ! -e "$codex_home" ]; then
|
||||||
|
mkdir -p "$codex_home"
|
||||||
|
elif [ ! -d "$codex_home" ]; then
|
||||||
|
echo "Skipping Codex setup because $codex_home is not a directory" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
home.activation.generateCodexConfig = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
|
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||||
|
base=${lib.escapeShellArg "${cfg.worktreeCodexDir}/config.toml"}
|
||||||
|
local_config=${lib.escapeShellArg "${cfg.worktreeCodexDir}/config.local.toml"}
|
||||||
|
target="$codex_home/config.toml"
|
||||||
|
|
||||||
|
if [ ! -r "$base" ]; then
|
||||||
|
echo "Missing shared Codex config at $base" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$codex_home"
|
||||||
|
tmp="$(mktemp "$codex_home/config.toml.XXXXXX")"
|
||||||
|
trap 'rm -f "$tmp"' EXIT
|
||||||
|
chmod 600 "$tmp"
|
||||||
|
|
||||||
|
cat "$base" > "$tmp"
|
||||||
|
if [ -r "$local_config" ]; then
|
||||||
|
printf '\n' >> "$tmp"
|
||||||
|
cat "$local_config" >> "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e "$target" ] && cmp -s "$tmp" "$target"; then
|
||||||
|
rm -f "$tmp"
|
||||||
|
else
|
||||||
|
mv -f "$tmp" "$target"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
home.activation.setupCodexGeneratedSkills = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
home.activation.setupCodexGeneratedSkills = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
codex_home=${lib.escapeShellArg cfg.codexHome}
|
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||||
skills_dir=${lib.escapeShellArg cfg.skillsDir}
|
skills_dir=${lib.escapeShellArg cfg.skillsDir}
|
||||||
|
|||||||
@@ -29,12 +29,14 @@
|
|||||||
./laptop.nix
|
./laptop.nix
|
||||||
./nix.nix
|
./nix.nix
|
||||||
./notifications-tray-icon.nix
|
./notifications-tray-icon.nix
|
||||||
|
./noctalia.nix
|
||||||
./nvidia.nix
|
./nvidia.nix
|
||||||
./options.nix
|
./options.nix
|
||||||
./plasma.nix
|
./plasma.nix
|
||||||
./postgres.nix
|
./postgres.nix
|
||||||
./rabbitmq.nix
|
./rabbitmq.nix
|
||||||
./quickshell.nix
|
./quickshell.nix
|
||||||
|
./remote-hyprland.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
./ssh.nix
|
./ssh.nix
|
||||||
./sni.nix
|
./sni.nix
|
||||||
|
|||||||
@@ -6,7 +6,20 @@
|
|||||||
makeEnable,
|
makeEnable,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
makeEnable config "myModules.desktop" true {
|
let
|
||||||
|
cfg = config.myModules.desktop;
|
||||||
|
desktopShellUi = pkgs.writeShellApplication {
|
||||||
|
name = "desktop_shell_ui";
|
||||||
|
runtimeInputs = [
|
||||||
|
pkgs.bash
|
||||||
|
pkgs.coreutils
|
||||||
|
pkgs.systemd
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
exec ${../dotfiles/lib/bin/desktop_shell_ui} "$@"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
enabledModule = makeEnable config "myModules.desktop" true {
|
||||||
services.greenclip.enable = true;
|
services.greenclip.enable = true;
|
||||||
imports = [
|
imports = [
|
||||||
./fonts.nix
|
./fonts.nix
|
||||||
@@ -40,8 +53,11 @@ makeEnable config "myModules.desktop" true {
|
|||||||
enable = true;
|
enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
environment.sessionVariables = {
|
||||||
# This is for the benefit of VSCODE running natively in wayland
|
# This is for the benefit of VSCODE running natively in wayland
|
||||||
environment.sessionVariables.NIXOS_OZONE_WL = "1";
|
NIXOS_OZONE_WL = "1";
|
||||||
|
IM_HYPRLAND_SHELL_UI = cfg.shellUi;
|
||||||
|
};
|
||||||
|
|
||||||
system.activationScripts.playwrightChromeCompat.text = lib.optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
|
system.activationScripts.playwrightChromeCompat.text = lib.optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
|
||||||
# Playwright's Chrome channel lookup expects the FHS path below.
|
# Playwright's Chrome channel lookup expects the FHS path below.
|
||||||
@@ -87,6 +103,8 @@ makeEnable config "myModules.desktop" true {
|
|||||||
|
|
||||||
environment.systemPackages = with pkgs;
|
environment.systemPackages = with pkgs;
|
||||||
[
|
[
|
||||||
|
desktopShellUi
|
||||||
|
|
||||||
# Appearance
|
# Appearance
|
||||||
adwaita-icon-theme
|
adwaita-icon-theme
|
||||||
hicolor-icon-theme
|
hicolor-icon-theme
|
||||||
@@ -169,4 +187,18 @@ makeEnable config "myModules.desktop" true {
|
|||||||
]
|
]
|
||||||
else []
|
else []
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
in
|
||||||
|
enabledModule
|
||||||
|
// {
|
||||||
|
options = lib.recursiveUpdate enabledModule.options {
|
||||||
|
myModules.desktop.shellUi = lib.mkOption {
|
||||||
|
type = lib.types.enum [ "noctalia" "taffybar" ];
|
||||||
|
default = "taffybar";
|
||||||
|
description = ''
|
||||||
|
Desktop shell UI used by Hyprland-oriented bindings. This controls
|
||||||
|
the active shell service and Hyprland launcher/window picker dispatch.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
{ config, lib, ... }:
|
{
|
||||||
let
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}: let
|
||||||
# Replicate the useful part of rcm/rcup:
|
# Replicate the useful part of rcm/rcup:
|
||||||
# - dotfiles live in ~/dotfiles/dotfiles (no leading dots in the repo)
|
# - dotfiles live in ~/dotfiles/dotfiles (no leading dots in the repo)
|
||||||
# - links in $HOME add a leading '.' to the first path component
|
# - links in $HOME add a leading '.' to the first path component
|
||||||
@@ -16,6 +19,9 @@ let
|
|||||||
srcDotfiles = ../dotfiles;
|
srcDotfiles = ../dotfiles;
|
||||||
|
|
||||||
excludedTop = [
|
excludedTop = [
|
||||||
|
# Managed by nix-shared/home-manager/codex-generated-skills.nix so
|
||||||
|
# config.toml can be generated from shared and machine-local fragments.
|
||||||
|
"codex"
|
||||||
# Managed by Nix directly (PATH/fpath), not meant to appear as ~/.lib.
|
# Managed by Nix directly (PATH/fpath), not meant to appear as ~/.lib.
|
||||||
"lib"
|
"lib"
|
||||||
# Avoid colliding with HM-generated xdg.configFile entries for now.
|
# Avoid colliding with HM-generated xdg.configFile entries for now.
|
||||||
@@ -24,25 +30,23 @@ let
|
|||||||
"emacs.d"
|
"emacs.d"
|
||||||
];
|
];
|
||||||
|
|
||||||
firstComponent = rel:
|
firstComponent = rel: let
|
||||||
let parts = lib.splitString "/" rel;
|
parts = lib.splitString "/" rel;
|
||||||
in lib.elemAt parts 0;
|
in
|
||||||
|
lib.elemAt parts 0;
|
||||||
|
|
||||||
isExcluded = rel: lib.elem (firstComponent rel) excludedTop;
|
isExcluded = rel: lib.elem (firstComponent rel) excludedTop;
|
||||||
|
|
||||||
listFilesRec = dir:
|
listFilesRec = dir: let
|
||||||
let
|
|
||||||
entries = builtins.readDir dir;
|
entries = builtins.readDir dir;
|
||||||
names = builtins.attrNames entries;
|
names = builtins.attrNames entries;
|
||||||
go = name:
|
go = name: let
|
||||||
let
|
|
||||||
ty = entries.${name};
|
ty = entries.${name};
|
||||||
path = dir + "/${name}";
|
path = dir + "/${name}";
|
||||||
in
|
in
|
||||||
if ty == "directory" then
|
if ty == "directory"
|
||||||
map (p: "${name}/${p}") (listFilesRec path)
|
then map (p: "${name}/${p}") (listFilesRec path)
|
||||||
else
|
else [name];
|
||||||
[ name ];
|
|
||||||
in
|
in
|
||||||
lib.concatLists (map go names);
|
lib.concatLists (map go names);
|
||||||
|
|
||||||
@@ -53,9 +57,10 @@ let
|
|||||||
lib.nameValuePair ".${rel}" {
|
lib.nameValuePair ".${rel}" {
|
||||||
source = oos "${worktreeDotfiles}/${rel}";
|
source = oos "${worktreeDotfiles}/${rel}";
|
||||||
};
|
};
|
||||||
in
|
in {
|
||||||
{
|
imports = [
|
||||||
imports = [ ../nix-shared/home-manager/codex-generated-skills.nix ];
|
../nix-shared/home-manager/codex-generated-skills.nix
|
||||||
|
];
|
||||||
|
|
||||||
home.file =
|
home.file =
|
||||||
builtins.listToAttrs (map mkManaged managedRelFiles);
|
builtins.listToAttrs (map mkManaged managedRelFiles);
|
||||||
@@ -74,5 +79,4 @@ in
|
|||||||
echo "Skipping ~/.emacs.d relink because it is not a symlink" >&2
|
echo "Skipping ~/.emacs.d relink because it is not a symlink" >&2
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ makeEnable config "myModules.extra" false {
|
|||||||
signal-desktop
|
signal-desktop
|
||||||
gource
|
gource
|
||||||
gimp
|
gimp
|
||||||
|
kef
|
||||||
|
roborock-control
|
||||||
texlive.combined.scheme-full
|
texlive.combined.scheme-full
|
||||||
tor
|
tor
|
||||||
yt-dlp
|
yt-dlp
|
||||||
|
|||||||
318
nixos/flake.lock
generated
318
nixos/flake.lock
generated
@@ -335,15 +335,15 @@
|
|||||||
"flake-compat_3": {
|
"flake-compat_3": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767039857,
|
"lastModified": 1696426674,
|
||||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||||
"owner": "NixOS",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -353,13 +353,13 @@
|
|||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767039857,
|
"lastModified": 1767039857,
|
||||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||||
"owner": "edolstra",
|
"owner": "NixOS",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "edolstra",
|
"owner": "NixOS",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -367,11 +367,11 @@
|
|||||||
"flake-compat_5": {
|
"flake-compat_5": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1696426674,
|
"lastModified": 1767039857,
|
||||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||||
"owner": "edolstra",
|
"owner": "edolstra",
|
||||||
"repo": "flake-compat",
|
"repo": "flake-compat",
|
||||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -502,6 +502,24 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_3"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"git-blame-rank": {
|
"git-blame-rank": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
"fenix": "fenix",
|
||||||
@@ -667,6 +685,7 @@
|
|||||||
"gitignore_3": {
|
"gitignore_3": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
"imalison-taffybar",
|
||||||
"taffybar",
|
"taffybar",
|
||||||
"weeder-nix",
|
"weeder-nix",
|
||||||
"pre-commit-hooks",
|
"pre-commit-hooks",
|
||||||
@@ -712,7 +731,7 @@
|
|||||||
"hercules-ci-effects_2": {
|
"hercules-ci-effects_2": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts_5",
|
"flake-parts": "flake-parts_5",
|
||||||
"nixpkgs": "nixpkgs_6"
|
"nixpkgs": "nixpkgs_7"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1701009247,
|
"lastModified": 1701009247,
|
||||||
@@ -780,15 +799,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777317717,
|
"lastModified": 1777475074,
|
||||||
"narHash": "sha256-Rj4vx0RvEWtnpnizggWRtrGe092bXiGLLt0WijwYWtI=",
|
"narHash": "sha256-shgepEMtMB532/df5QfociNzTiqimuoBbJssw0WPVH4=",
|
||||||
"owner": "colonelpanic8",
|
"owner": "colonelpanic8",
|
||||||
"repo": "hyprNStack",
|
"repo": "hyprNStack",
|
||||||
"rev": "94607cd53f2ddac88f6b26261393275e7dd590ef",
|
"rev": "d43c8a506b32dc5057fdce0569c38f401c01eb60",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "colonelpanic8",
|
"owner": "colonelpanic8",
|
||||||
|
"ref": "hyprland-lua-integration",
|
||||||
"repo": "hyprNStack",
|
"repo": "hyprNStack",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -1084,16 +1104,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1767020608,
|
"lastModified": 1777471981,
|
||||||
"narHash": "sha256-BSRT1Uu1ot4WfMfZc6KW0nwpmt2xl9wpUqmH/JoMTfk=",
|
"narHash": "sha256-cd3pQg+vKv6vht4xzButsi/Kaw9P4d3itm46jYXyiDM=",
|
||||||
"owner": "hyprwm",
|
"owner": "colonelpanic8",
|
||||||
"repo": "hyprland-plugins",
|
"repo": "hyprland-plugins",
|
||||||
"rev": "d7b67e8f4ba8ebeee4ce899348fcee6291512169",
|
"rev": "725e354bbee982566068b5b90fec4fcd787c1036",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hyprwm",
|
"owner": "colonelpanic8",
|
||||||
"ref": "v0.53.0",
|
"ref": "hyprexpo-v0.53.0-custom",
|
||||||
"repo": "hyprland-plugins",
|
"repo": "hyprland-plugins",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -1115,16 +1135,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777413654,
|
"lastModified": 1777492340,
|
||||||
"narHash": "sha256-lVGYGUWf9ynV5lR8QAygAfmYRkko1btIe26UcQ1bGXw=",
|
"narHash": "sha256-9uRI/opXD+zOK2BLlzQ2NluRJL1SRSdUO2tlvbUJ7Ys=",
|
||||||
"owner": "colonelpanic8",
|
"owner": "colonelpanic8",
|
||||||
"repo": "hyprland-plugins",
|
"repo": "hyprland-plugins",
|
||||||
"rev": "ff36c04b270c26fcd53e623fc688e4eb41672897",
|
"rev": "ca1992973b5fb8ab95e88e8ffba16792c41568ae",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "colonelpanic8",
|
"owner": "colonelpanic8",
|
||||||
"ref": "hyprexpo-lua-hyprland",
|
"ref": "codex/fix-main-ci",
|
||||||
"repo": "hyprland-plugins",
|
"repo": "hyprland-plugins",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@@ -1528,9 +1548,7 @@
|
|||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
"taffybar": [
|
"taffybar": "taffybar",
|
||||||
"taffybar"
|
|
||||||
],
|
|
||||||
"xmonad": [
|
"xmonad": [
|
||||||
"xmonad"
|
"xmonad"
|
||||||
]
|
]
|
||||||
@@ -1580,11 +1598,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777261868,
|
"lastModified": 1777434099,
|
||||||
"narHash": "sha256-30E1RBr0FGrf1IdXi2OKua+vQ4sUvjwUq6lfC1qcBug=",
|
"narHash": "sha256-GutKXyfGI7o89Dge4bP0yt0CQn1rqA6LyYDOH4GemdE=",
|
||||||
"owner": "colonelpanic8",
|
"owner": "colonelpanic8",
|
||||||
"repo": "keepbook",
|
"repo": "keepbook",
|
||||||
"rev": "2e178e7ba864af0a880456dca241615dd75afd24",
|
"rev": "240fe454c26e7dbdd25a2fa4f0b436bf1c4f937b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -1611,10 +1629,10 @@
|
|||||||
},
|
},
|
||||||
"nix": {
|
"nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_3",
|
"flake-compat": "flake-compat_4",
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts",
|
||||||
"git-hooks-nix": "git-hooks-nix",
|
"git-hooks-nix": "git-hooks-nix",
|
||||||
"nixpkgs": "nixpkgs_4",
|
"nixpkgs": "nixpkgs_5",
|
||||||
"nixpkgs-23-11": "nixpkgs-23-11",
|
"nixpkgs-23-11": "nixpkgs-23-11",
|
||||||
"nixpkgs-regression": "nixpkgs-regression"
|
"nixpkgs-regression": "nixpkgs-regression"
|
||||||
},
|
},
|
||||||
@@ -1671,7 +1689,7 @@
|
|||||||
},
|
},
|
||||||
"nixos-wsl": {
|
"nixos-wsl": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_4",
|
"flake-compat": "flake-compat_5",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
@@ -1805,6 +1823,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_4": {
|
"nixpkgs_4": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776877367,
|
||||||
|
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "0726a0ecb6d4e08f6adced58726b95db924cef57",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_5": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1771903837,
|
"lastModified": 1771903837,
|
||||||
"narHash": "sha256-jEA8WggGKtMFeNeCKq3NK8cLEjJmG6/RLUElYYbBZ0E=",
|
"narHash": "sha256-jEA8WggGKtMFeNeCKq3NK8cLEjJmG6/RLUElYYbBZ0E=",
|
||||||
@@ -1817,7 +1851,7 @@
|
|||||||
"url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"
|
"url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_5": {
|
"nixpkgs_6": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776877367,
|
"lastModified": 1776877367,
|
||||||
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
||||||
@@ -1833,7 +1867,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_6": {
|
"nixpkgs_7": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1697723726,
|
"lastModified": 1697723726,
|
||||||
"narHash": "sha256-SaTWPkI8a5xSHX/rrKzUe+/uVNy6zCGMXgoeMb7T9rg=",
|
"narHash": "sha256-SaTWPkI8a5xSHX/rrKzUe+/uVNy6zCGMXgoeMb7T9rg=",
|
||||||
@@ -1849,7 +1883,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_7": {
|
"nixpkgs_8": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1703255338,
|
"lastModified": 1703255338,
|
||||||
"narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=",
|
"narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=",
|
||||||
@@ -1869,7 +1903,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts_4",
|
"flake-parts": "flake-parts_4",
|
||||||
"hercules-ci-effects": "hercules-ci-effects_2",
|
"hercules-ci-effects": "hercules-ci-effects_2",
|
||||||
"nixpkgs": "nixpkgs_7",
|
"nixpkgs": "nixpkgs_8",
|
||||||
"osx-kvm": "osx-kvm"
|
"osx-kvm": "osx-kvm"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -1886,6 +1920,50 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"noctalia": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"noctalia-qs": "noctalia-qs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777427472,
|
||||||
|
"narHash": "sha256-kqcfLdxb+CqTroMErCScvx6YQcZYJcf6X+z5I8kBJK8=",
|
||||||
|
"owner": "noctalia-dev",
|
||||||
|
"repo": "noctalia-shell",
|
||||||
|
"rev": "9f8dd48c8df5ab1f7f87ddf9842627e1e5682186",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "noctalia-dev",
|
||||||
|
"repo": "noctalia-shell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"noctalia-qs": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"noctalia",
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"systems": "systems_4",
|
||||||
|
"treefmt-nix": "treefmt-nix"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777380063,
|
||||||
|
"narHash": "sha256-q5mWOEICcZzr+KnjIwDHV9EXiBxOC9cnBpxZbDAViU8=",
|
||||||
|
"owner": "noctalia-dev",
|
||||||
|
"repo": "noctalia-qs",
|
||||||
|
"rev": "8742a7a748c43bf44eb6862a8ebd3591ed71502d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "noctalia-dev",
|
||||||
|
"repo": "noctalia-qs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"notifications-tray-icon": {
|
"notifications-tray-icon": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": [
|
"flake-utils": [
|
||||||
@@ -2055,9 +2133,10 @@
|
|||||||
},
|
},
|
||||||
"pre-commit-hooks_3": {
|
"pre-commit-hooks_3": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-compat": "flake-compat_5",
|
"flake-compat": "flake-compat_3",
|
||||||
"gitignore": "gitignore_3",
|
"gitignore": "gitignore_3",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
"imalison-taffybar",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2148,16 +2227,16 @@
|
|||||||
"nixified-ai": "nixified-ai",
|
"nixified-ai": "nixified-ai",
|
||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixos-wsl": "nixos-wsl",
|
"nixos-wsl": "nixos-wsl",
|
||||||
"nixpkgs": "nixpkgs_5",
|
"nixpkgs": "nixpkgs_6",
|
||||||
"nixtheplanet": "nixtheplanet",
|
"nixtheplanet": "nixtheplanet",
|
||||||
|
"noctalia": "noctalia",
|
||||||
"notifications-tray-icon": "notifications-tray-icon",
|
"notifications-tray-icon": "notifications-tray-icon",
|
||||||
"org-agenda-api": "org-agenda-api",
|
"org-agenda-api": "org-agenda-api",
|
||||||
"railbird-secrets": "railbird-secrets",
|
"railbird-secrets": "railbird-secrets",
|
||||||
"systems": "systems_3",
|
"systems": "systems_5",
|
||||||
"taffybar": "taffybar",
|
|
||||||
"vscode-server": "vscode-server",
|
"vscode-server": "vscode-server",
|
||||||
"xmonad": "xmonad",
|
"xmonad": "xmonad_2",
|
||||||
"xmonad-contrib": "xmonad-contrib"
|
"xmonad-contrib": "xmonad-contrib_2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-analyzer-src": {
|
||||||
@@ -2259,33 +2338,77 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"taffybar": {
|
"systems_4": {
|
||||||
"inputs": {
|
|
||||||
"flake-utils": [
|
|
||||||
"flake-utils"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"weeder-nix": "weeder-nix",
|
|
||||||
"xmonad": [
|
|
||||||
"xmonad"
|
|
||||||
],
|
|
||||||
"xmonad-contrib": [
|
|
||||||
"xmonad-contrib"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1777401169,
|
"lastModified": 1689347949,
|
||||||
"narHash": "sha256-bciN/qFjXYm8ZIKXSc/OssUsLt9GoNs/cU9xT/pw7QY=",
|
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||||
"owner": "taffybar",
|
"owner": "nix-systems",
|
||||||
"repo": "taffybar",
|
"repo": "default-linux",
|
||||||
"rev": "59e3c75990156dcd4353ad9fad5823303e751f0f",
|
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "taffybar",
|
"owner": "nix-systems",
|
||||||
"repo": "taffybar",
|
"repo": "default-linux",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_5": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"taffybar": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": "nixpkgs_4",
|
||||||
|
"weeder-nix": "weeder-nix",
|
||||||
|
"xmonad": "xmonad",
|
||||||
|
"xmonad-contrib": "xmonad-contrib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1777452249,
|
||||||
|
"narHash": "sha256-Emhn9sIFRVyIlUULDuYjeFcYJld6EAD31TGasYwQsWg=",
|
||||||
|
"ref": "refs/heads/master",
|
||||||
|
"rev": "9a6463e68c7bc0a712e49d9ba6c6d1b764260cd7",
|
||||||
|
"revCount": 2295,
|
||||||
|
"type": "git",
|
||||||
|
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"treefmt-nix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"noctalia",
|
||||||
|
"noctalia-qs",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1775636079,
|
||||||
|
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
|
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "treefmt-nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2315,6 +2438,7 @@
|
|||||||
"weeder-nix": {
|
"weeder-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
"imalison-taffybar",
|
||||||
"taffybar",
|
"taffybar",
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
],
|
],
|
||||||
@@ -2417,20 +2541,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"xmonad": {
|
"xmonad": {
|
||||||
"inputs": {
|
"flake": false,
|
||||||
"flake-utils": [
|
|
||||||
"flake-utils"
|
|
||||||
],
|
|
||||||
"git-ignore-nix": [
|
|
||||||
"git-ignore-nix"
|
|
||||||
],
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"unstable": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1776502138,
|
"lastModified": 1776502138,
|
||||||
"narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=",
|
"narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=",
|
||||||
@@ -2441,11 +2552,29 @@
|
|||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "xmonad",
|
"owner": "xmonad",
|
||||||
|
"ref": "master",
|
||||||
"repo": "xmonad",
|
"repo": "xmonad",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"xmonad-contrib": {
|
"xmonad-contrib": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769258911,
|
||||||
|
"narHash": "sha256-YGEKXs4UmS5QOIELJTdCiMzTktuue+Bd3yFoIKSHuBU=",
|
||||||
|
"owner": "xmonad",
|
||||||
|
"repo": "xmonad-contrib",
|
||||||
|
"rev": "803bc3d12bdcc512ec06856c4f119d37de1ba338",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "xmonad",
|
||||||
|
"ref": "master",
|
||||||
|
"repo": "xmonad-contrib",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xmonad-contrib_2": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": [
|
"flake-utils": [
|
||||||
"flake-utils"
|
"flake-utils"
|
||||||
@@ -2474,6 +2603,35 @@
|
|||||||
"repo": "xmonad-contrib",
|
"repo": "xmonad-contrib",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"xmonad_2": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": [
|
||||||
|
"flake-utils"
|
||||||
|
],
|
||||||
|
"git-ignore-nix": [
|
||||||
|
"git-ignore-nix"
|
||||||
|
],
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"unstable": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776502138,
|
||||||
|
"narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=",
|
||||||
|
"owner": "xmonad",
|
||||||
|
"repo": "xmonad",
|
||||||
|
"rev": "a618fb32662e44eb5d8276a3dc1925b0233e638b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "xmonad",
|
||||||
|
"repo": "xmonad",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
|
|||||||
@@ -102,7 +102,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
hyprNStack = {
|
hyprNStack = {
|
||||||
url = "github:colonelpanic8/hyprNStack";
|
url = "github:colonelpanic8/hyprNStack?ref=hyprland-lua-integration";
|
||||||
inputs = {
|
inputs = {
|
||||||
hyprland.follows = "hyprland-lua-config";
|
hyprland.follows = "hyprland-lua-config";
|
||||||
nixpkgs.follows = "nixpkgs";
|
nixpkgs.follows = "nixpkgs";
|
||||||
@@ -115,12 +115,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
hyprland-plugins = {
|
hyprland-plugins = {
|
||||||
url = "github:hyprwm/hyprland-plugins?ref=v0.53.0";
|
url = "github:colonelpanic8/hyprland-plugins?ref=hyprexpo-v0.53.0-custom";
|
||||||
inputs.hyprland.follows = "hyprland";
|
inputs.hyprland.follows = "hyprland";
|
||||||
};
|
};
|
||||||
|
|
||||||
hyprland-plugins-lua = {
|
hyprland-plugins-lua = {
|
||||||
url = "github:colonelpanic8/hyprland-plugins?ref=hyprexpo-lua-hyprland";
|
url = "github:colonelpanic8/hyprland-plugins?ref=codex/fix-main-ci";
|
||||||
inputs.hyprland.follows = "hyprland-lua-config";
|
inputs.hyprland.follows = "hyprland-lua-config";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -231,6 +231,11 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
noctalia = {
|
||||||
|
url = "github:noctalia-dev/noctalia-shell";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = inputs @ {
|
outputs = inputs @ {
|
||||||
@@ -443,6 +448,7 @@
|
|||||||
"https://colonelpanic8-dotfiles.cachix.org"
|
"https://colonelpanic8-dotfiles.cachix.org"
|
||||||
"https://codex-cli.cachix.org"
|
"https://codex-cli.cachix.org"
|
||||||
"https://claude-code.cachix.org"
|
"https://claude-code.cachix.org"
|
||||||
|
"https://noctalia.cachix.org"
|
||||||
];
|
];
|
||||||
extra-trusted-substituters = [
|
extra-trusted-substituters = [
|
||||||
"https://ai.cachix.org"
|
"https://ai.cachix.org"
|
||||||
@@ -464,6 +470,7 @@
|
|||||||
"colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ="
|
"colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ="
|
||||||
"codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing="
|
"codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing="
|
||||||
"claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk="
|
"claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk="
|
||||||
|
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
nixosConfigurations =
|
nixosConfigurations =
|
||||||
|
|||||||
@@ -6,33 +6,6 @@
|
|||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
mimeMap = desktopId: mimeTypes: lib.genAttrs mimeTypes (_: [desktopId]);
|
mimeMap = desktopId: mimeTypes: lib.genAttrs mimeTypes (_: [desktopId]);
|
||||||
in {
|
|
||||||
# Automatic garbage collection of old home-manager generations
|
|
||||||
nix.gc = {
|
|
||||||
automatic = true;
|
|
||||||
dates = "weekly";
|
|
||||||
options = "--delete-older-than 7d";
|
|
||||||
};
|
|
||||||
|
|
||||||
xdg.configFile."greenclip.toml".text = ''
|
|
||||||
[greenclip]
|
|
||||||
history_file = "~/.cache/greenclip.history"
|
|
||||||
max_history_length = 50
|
|
||||||
max_selection_size_bytes = 0
|
|
||||||
trim_space_from_selection = true
|
|
||||||
use_primary_selection_as_input = false
|
|
||||||
blacklisted_applications = []
|
|
||||||
enable_image_support = true
|
|
||||||
image_cache_directory = "~/.cache/greenclip"
|
|
||||||
static_history = []
|
|
||||||
'';
|
|
||||||
|
|
||||||
xdg.configFile."zellij/config.kdl".source =
|
|
||||||
config.lib.file.mkOutOfStoreSymlink
|
|
||||||
"${config.home.homeDirectory}/dotfiles/dotfiles/config/zellij/config.kdl";
|
|
||||||
|
|
||||||
xdg.mimeApps = lib.mkIf nixos.config.myModules.desktop.enable (
|
|
||||||
let
|
|
||||||
browser = "google-chrome.desktop";
|
browser = "google-chrome.desktop";
|
||||||
imageViewer = "org.kde.gwenview.desktop";
|
imageViewer = "org.kde.gwenview.desktop";
|
||||||
pdfViewer = "okularApplication_pdf.desktop";
|
pdfViewer = "okularApplication_pdf.desktop";
|
||||||
@@ -47,7 +20,6 @@ in {
|
|||||||
wordProcessor = "writer.desktop";
|
wordProcessor = "writer.desktop";
|
||||||
spreadsheet = "calc.desktop";
|
spreadsheet = "calc.desktop";
|
||||||
presentation = "impress.desktop";
|
presentation = "impress.desktop";
|
||||||
|
|
||||||
defaultApplications =
|
defaultApplications =
|
||||||
(mimeMap imageViewer [
|
(mimeMap imageViewer [
|
||||||
"image/avif"
|
"image/avif"
|
||||||
@@ -163,12 +135,82 @@ in {
|
|||||||
"x-scheme-handler/element" = ["element-desktop.desktop"];
|
"x-scheme-handler/element" = ["element-desktop.desktop"];
|
||||||
"x-scheme-handler/magnet" = ["transmission-gtk.desktop"];
|
"x-scheme-handler/magnet" = ["transmission-gtk.desktop"];
|
||||||
};
|
};
|
||||||
|
mimeAppsListText = let
|
||||||
|
formatApplications = applications:
|
||||||
|
lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
mimeType: desktopIds: "${mimeType}=${lib.concatStringsSep ";" desktopIds};"
|
||||||
|
)
|
||||||
|
applications
|
||||||
|
);
|
||||||
|
in ''
|
||||||
|
[Added Associations]
|
||||||
|
${formatApplications defaultApplications}
|
||||||
|
|
||||||
|
[Default Applications]
|
||||||
|
${formatApplications defaultApplications}
|
||||||
|
|
||||||
|
[Removed Associations]
|
||||||
|
'';
|
||||||
in {
|
in {
|
||||||
|
# Automatic garbage collection of old home-manager generations
|
||||||
|
nix.gc = {
|
||||||
|
automatic = true;
|
||||||
|
dates = "weekly";
|
||||||
|
options = "--delete-older-than 7d";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."greenclip.toml".text = ''
|
||||||
|
[greenclip]
|
||||||
|
history_file = "~/.cache/greenclip.history"
|
||||||
|
max_history_length = 50
|
||||||
|
max_selection_size_bytes = 0
|
||||||
|
trim_space_from_selection = true
|
||||||
|
use_primary_selection_as_input = false
|
||||||
|
blacklisted_applications = []
|
||||||
|
enable_image_support = true
|
||||||
|
image_cache_directory = "~/.cache/greenclip"
|
||||||
|
static_history = []
|
||||||
|
'';
|
||||||
|
|
||||||
|
|
||||||
|
xdg.configFile."zellij/config.kdl".source =
|
||||||
|
config.lib.file.mkOutOfStoreSymlink
|
||||||
|
"${config.home.homeDirectory}/dotfiles/dotfiles/config/zellij/config.kdl";
|
||||||
|
|
||||||
|
xdg.configFile."menus/applications.menu" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
source = "${pkgs.kdePackages.plasma-workspace}/etc/xdg/menus/plasma-applications.menu";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."kde-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."none+xmonad-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."xmonad-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."hyprland-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.dataFile."mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.dataFile."applications/kde-mimeapps.list" = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
|
text = mimeAppsListText;
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.mimeApps = lib.mkIf nixos.config.myModules.desktop.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
associations.added = defaultApplications;
|
associations.added = defaultApplications;
|
||||||
inherit defaultApplications;
|
inherit defaultApplications;
|
||||||
}
|
};
|
||||||
);
|
|
||||||
|
|
||||||
home.activation.refreshChromeDesktopMimeCache = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
home.activation.refreshChromeDesktopMimeCache = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||||
applications_dir="$HOME/.local/share/applications"
|
applications_dir="$HOME/.local/share/applications"
|
||||||
@@ -180,6 +222,7 @@ in {
|
|||||||
do
|
do
|
||||||
if [ -f "$desktop_file" ]; then
|
if [ -f "$desktop_file" ]; then
|
||||||
${pkgs.gnused}/bin/sed -i \
|
${pkgs.gnused}/bin/sed -i \
|
||||||
|
-e 's,application/pdf;,,g' \
|
||||||
-e 's,image/gif;,,g' \
|
-e 's,image/gif;,,g' \
|
||||||
-e 's,image/jpeg;,,g' \
|
-e 's,image/jpeg;,,g' \
|
||||||
-e 's,image/png;,,g' \
|
-e 's,image/png;,,g' \
|
||||||
@@ -188,10 +231,34 @@ in {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
for desktop_file in "$applications_dir"/okular*.desktop "$applications_dir"/vlc*.desktop; do
|
||||||
|
if [ -f "$desktop_file" ]; then
|
||||||
|
${pkgs.gnused}/bin/sed -i \
|
||||||
|
-e 's,image/avif;,,g' \
|
||||||
|
-e 's,image/bmp;,,g' \
|
||||||
|
-e 's,image/gif;,,g' \
|
||||||
|
-e 's,image/heic;,,g' \
|
||||||
|
-e 's,image/heif;,,g' \
|
||||||
|
-e 's,image/jpeg;,,g' \
|
||||||
|
-e 's,image/jxl;,,g' \
|
||||||
|
-e 's,image/png;,,g' \
|
||||||
|
-e 's,image/svg+xml;,,g' \
|
||||||
|
-e 's,image/svg+xml-compressed;,,g' \
|
||||||
|
-e 's,image/tiff;,,g' \
|
||||||
|
-e 's,image/vnd.microsoft.icon;,,g' \
|
||||||
|
-e 's,image/webp;,,g' \
|
||||||
|
"$desktop_file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
${pkgs.desktop-file-utils}/bin/update-desktop-database "$applications_dir" >/dev/null 2>&1 || true
|
${pkgs.desktop-file-utils}/bin/update-desktop-database "$applications_dir" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
home.activation.refreshKdeServiceCache = lib.hm.dag.entryAfter ["refreshChromeDesktopMimeCache"] ''
|
||||||
|
${pkgs.kdePackages.kservice}/bin/kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
||||||
|
'';
|
||||||
|
|
||||||
xsession = {
|
xsession = {
|
||||||
enable = true;
|
enable = true;
|
||||||
preferStatusNotifierItems = true;
|
preferStatusNotifierItems = true;
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
{ config, pkgs, lib, makeEnable, inputs, ... }:
|
{
|
||||||
|
config,
|
||||||
|
pkgs,
|
||||||
|
lib,
|
||||||
|
makeEnable,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
let
|
let
|
||||||
cfg = config.myModules.hyprland;
|
cfg = config.myModules.hyprland;
|
||||||
system = pkgs.stdenv.hostPlatform.system;
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
@@ -11,88 +18,29 @@ let
|
|||||||
inputs.hyprNStack.packages.${system}.hyprNStack
|
inputs.hyprNStack.packages.${system}.hyprNStack
|
||||||
inputs.hyprland-plugins-lua.packages.${system}.hyprexpo
|
inputs.hyprland-plugins-lua.packages.${system}.hyprexpo
|
||||||
];
|
];
|
||||||
hyprexpoPatched = inputs.hyprland-plugins.packages.${system}.hyprexpo.overrideAttrs (old: {
|
hyprRofiWindow = pkgs.writeShellApplication {
|
||||||
patches = (old.patches or [ ]) ++ [
|
name = "hypr_rofi_window";
|
||||||
./patches/hyprexpo-pr-612-workspace-numbers.patch
|
runtimeInputs = [
|
||||||
./patches/hyprexpo-pr-616-bring-mode.patch
|
pkgs.python3
|
||||||
|
pkgs.rofi
|
||||||
|
hyprlandInput.packages.${system}.hyprland
|
||||||
];
|
];
|
||||||
});
|
text = ''
|
||||||
enabledModule = makeEnable config "myModules.hyprland" true {
|
exec python3 ${../dotfiles/lib/bin/hypr_rofi_window} "$@"
|
||||||
myModules.taffybar.enable = true;
|
'';
|
||||||
|
|
||||||
# Needed for hyprlock authentication without PAM fallback warnings.
|
|
||||||
security.pam.services.hyprlock = {};
|
|
||||||
|
|
||||||
# DDC/CI monitor control for keyboard-driven input switching.
|
|
||||||
hardware.i2c = {
|
|
||||||
enable = true;
|
|
||||||
group = "video";
|
|
||||||
};
|
};
|
||||||
|
hyprShellUi = pkgs.writeShellApplication {
|
||||||
programs.hyprland = {
|
name = "hypr_shell_ui";
|
||||||
enable = true;
|
runtimeInputs = [
|
||||||
# Keep Hyprland and plugins on a matched flake input for ABI compatibility.
|
pkgs.rofi
|
||||||
package = hyprlandInput.packages.${system}.hyprland;
|
hyprRofiWindow
|
||||||
# Let UWSM manage the Hyprland session targets
|
inputs.noctalia.packages.${system}.default
|
||||||
withUWSM = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager.sharedModules = [
|
|
||||||
inputs.hyprscratch.homeModules.default
|
|
||||||
({ config, ... }: {
|
|
||||||
xdg.configFile."hypr" = {
|
|
||||||
force = true;
|
|
||||||
source =
|
|
||||||
if cfg.useLuaConfigBranch
|
|
||||||
then ../dotfiles/config/hypr
|
|
||||||
else config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr";
|
|
||||||
};
|
|
||||||
|
|
||||||
services.kanshi = {
|
|
||||||
enable = true;
|
|
||||||
systemdTarget = "graphical-session.target";
|
|
||||||
settings = [
|
|
||||||
{
|
|
||||||
# USB-C connector names can move between DP-* ports across docks/reboots.
|
|
||||||
# Match the ultrawide by make/model and allow the serial field to vary.
|
|
||||||
profile.name = "ultrawide-usbc-desk";
|
|
||||||
profile.outputs = [
|
|
||||||
{
|
|
||||||
criteria = "eDP-1";
|
|
||||||
status = "enable";
|
|
||||||
mode = "2560x1600@240Hz";
|
|
||||||
position = "0,0";
|
|
||||||
scale = 1.0;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
criteria = "Microstep MPG341CX OLED *";
|
|
||||||
status = "enable";
|
|
||||||
mode = "3440x1440@240Hz";
|
|
||||||
position = "2560,0";
|
|
||||||
scale = 1.0;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
{
|
|
||||||
# When the laptop panel is unavailable (e.g. lid-closed docked use),
|
|
||||||
# still drive the ultrawide at its full refresh rate.
|
|
||||||
profile.name = "ultrawide-only";
|
|
||||||
profile.outputs = [
|
|
||||||
{
|
|
||||||
criteria = "Microstep MPG341CX OLED *";
|
|
||||||
status = "enable";
|
|
||||||
mode = "3440x1440@240Hz";
|
|
||||||
position = "0,0";
|
|
||||||
scale = 1.0;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
text = ''
|
||||||
|
exec ${../dotfiles/lib/bin/hypr_shell_ui} "$@"
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
hyprscratchSettings = {
|
||||||
programs.hyprscratch = {
|
|
||||||
enable = !cfg.useLuaConfigBranch;
|
|
||||||
settings = {
|
|
||||||
daemon_options = "clean";
|
daemon_options = "clean";
|
||||||
global_options = "";
|
global_options = "";
|
||||||
global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center";
|
global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center";
|
||||||
@@ -146,12 +94,115 @@ let
|
|||||||
title = "Messages";
|
title = "Messages";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
enabledModule = makeEnable config "myModules.hyprland" true {
|
||||||
|
# Install both shell service units so `desktop_shell_ui set ...` can switch
|
||||||
|
# between them at runtime without a NixOS rebuild.
|
||||||
|
myModules.noctalia.enable = lib.mkDefault true;
|
||||||
|
myModules.taffybar.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
# Needed for hyprlock authentication without PAM fallback warnings.
|
||||||
|
security.pam.services.hyprlock = { };
|
||||||
|
|
||||||
|
# DDC/CI monitor control for keyboard-driven input switching.
|
||||||
|
hardware.i2c = {
|
||||||
|
enable = true;
|
||||||
|
group = "video";
|
||||||
};
|
};
|
||||||
})
|
|
||||||
|
programs.hyprland = {
|
||||||
|
enable = true;
|
||||||
|
# Keep Hyprland and plugins on a matched flake input for ABI compatibility.
|
||||||
|
package = hyprlandInput.packages.${system}.hyprland;
|
||||||
|
# Let UWSM manage the Hyprland session targets
|
||||||
|
withUWSM = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
inputs.hyprscratch.homeModules.default
|
||||||
|
(
|
||||||
|
{ config, lib, ... }:
|
||||||
|
let
|
||||||
|
hyprConfig =
|
||||||
|
name:
|
||||||
|
config.lib.file.mkOutOfStoreSymlink "${config.home.homeDirectory}/dotfiles/dotfiles/config/hypr/${name}";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.kanshi = {
|
||||||
|
enable = true;
|
||||||
|
systemdTarget = "graphical-session.target";
|
||||||
|
settings = [
|
||||||
|
{
|
||||||
|
# USB-C connector names can move between DP-* ports across docks/reboots.
|
||||||
|
# Match the ultrawide by make/model and allow the serial field to vary.
|
||||||
|
profile.name = "ultrawide-usbc-desk";
|
||||||
|
profile.outputs = [
|
||||||
|
{
|
||||||
|
criteria = "eDP-1";
|
||||||
|
status = "enable";
|
||||||
|
mode = "2560x1600@240Hz";
|
||||||
|
position = "0,0";
|
||||||
|
scale = 1.0;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
criteria = "Microstep MPG341CX OLED *";
|
||||||
|
status = "enable";
|
||||||
|
mode = "3440x1440@240Hz";
|
||||||
|
position = "2560,0";
|
||||||
|
scale = 1.0;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
{
|
||||||
|
# When the laptop panel is unavailable (e.g. lid-closed docked use),
|
||||||
|
# still drive the ultrawide at its full refresh rate.
|
||||||
|
profile.name = "ultrawide-only";
|
||||||
|
profile.outputs = [
|
||||||
|
{
|
||||||
|
criteria = "Microstep MPG341CX OLED *";
|
||||||
|
status = "enable";
|
||||||
|
mode = "3440x1440@240Hz";
|
||||||
|
position = "0,0";
|
||||||
|
scale = 1.0;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.hyprscratch = {
|
||||||
|
enable = !cfg.useLuaConfigBranch;
|
||||||
|
settings = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."hyprscratch/config.conf" = lib.mkIf (!cfg.useLuaConfigBranch) {
|
||||||
|
text = lib.hm.generators.toHyprconf {
|
||||||
|
attrs = hyprscratchSettings;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."hypr/hyprland.conf" = {
|
||||||
|
force = true;
|
||||||
|
source = hyprConfig "hyprland.conf";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."hypr/hyprland.lua" = lib.mkIf cfg.useLuaConfigBranch {
|
||||||
|
force = true;
|
||||||
|
source = hyprConfig "hyprland.lua";
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.configFile."hypr/hypridle.conf".source = hyprConfig "hypridle.conf";
|
||||||
|
|
||||||
|
xdg.configFile."hypr/hyprlock.conf".source = hyprConfig "hyprlock.conf";
|
||||||
|
|
||||||
|
xdg.configFile."hypr/scripts".enable = false;
|
||||||
|
}
|
||||||
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
# Hyprland-specific packages
|
# Hyprland-specific packages
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages =
|
||||||
|
with pkgs;
|
||||||
|
[
|
||||||
# Hyprland utilities
|
# Hyprland utilities
|
||||||
hyprpaper # Wallpaper
|
hyprpaper # Wallpaper
|
||||||
hypridle # Idle daemon
|
hypridle # Idle daemon
|
||||||
@@ -164,21 +215,26 @@ let
|
|||||||
slurp # Region selection
|
slurp # Region selection
|
||||||
swappy # Screenshot annotation
|
swappy # Screenshot annotation
|
||||||
nwg-displays # GUI monitor arrangement
|
nwg-displays # GUI monitor arrangement
|
||||||
mpv # Graphical screensaver payload
|
mpvpaper # Layer-shell video screensaver payload
|
||||||
ddcutil # Monitor input switching over DDC/CI
|
ddcutil # Monitor input switching over DDC/CI
|
||||||
|
|
||||||
# For scripts
|
# For scripts
|
||||||
|
hyprRofiWindow
|
||||||
|
hyprShellUi
|
||||||
jq
|
jq
|
||||||
] ++ luaPluginPackages ++ lib.optionals enableExternalPluginPackages [
|
]
|
||||||
|
++ luaPluginPackages
|
||||||
|
++ lib.optionals enableExternalPluginPackages [
|
||||||
# External plugin packages are pinned to the stable 0.53 stack.
|
# External plugin packages are pinned to the stable 0.53 stack.
|
||||||
# Keep hy3 on the stable stack; the Lua branch uses hyprNStack and the
|
# Keep hy3 on the stable stack; the Lua branch uses hyprNStack and the
|
||||||
# forked Lua-compatible hyprexpo input instead.
|
# forked Lua-compatible hyprexpo input instead.
|
||||||
inputs.hy3.packages.${system}.hy3
|
inputs.hy3.packages.${system}.hy3
|
||||||
hyprexpoPatched
|
inputs.hyprland-plugins.packages.${system}.hyprexpo
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
enabledModule // {
|
enabledModule
|
||||||
|
// {
|
||||||
options = lib.recursiveUpdate enabledModule.options {
|
options = lib.recursiveUpdate enabledModule.options {
|
||||||
myModules.hyprland.useLuaConfigBranch = lib.mkOption {
|
myModules.hyprland.useLuaConfigBranch = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
|
|||||||
@@ -184,16 +184,11 @@
|
|||||||
type = "Application";
|
type = "Application";
|
||||||
categories = [ "Network" "WebBrowser" ];
|
categories = [ "Network" "WebBrowser" ];
|
||||||
mimeType = [
|
mimeType = [
|
||||||
"application/pdf"
|
|
||||||
"application/rdf+xml"
|
"application/rdf+xml"
|
||||||
"application/rss+xml"
|
"application/rss+xml"
|
||||||
"application/xhtml+xml"
|
"application/xhtml+xml"
|
||||||
"application/xhtml_xml"
|
"application/xhtml_xml"
|
||||||
"application/xml"
|
"application/xml"
|
||||||
"image/gif"
|
|
||||||
"image/jpeg"
|
|
||||||
"image/png"
|
|
||||||
"image/webp"
|
|
||||||
"text/html"
|
"text/html"
|
||||||
"text/xml"
|
"text/xml"
|
||||||
"x-scheme-handler/http"
|
"x-scheme-handler/http"
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
# Disable the old multi-node railbird k3s setup
|
# Disable the old multi-node railbird k3s setup
|
||||||
myModules.railbird-k3s.enable = false;
|
myModules.railbird-k3s.enable = false;
|
||||||
myModules."keepbook-sync".enable = true;
|
myModules."keepbook-sync".enable = true;
|
||||||
|
myModules.remote-hyprland.enable = true;
|
||||||
|
|
||||||
# Mirror the old biskcomp "Syncthing hosting" pattern: serve the synced railbird tree over HTTPS with autoindex.
|
# Mirror the old biskcomp "Syncthing hosting" pattern: serve the synced railbird tree over HTTPS with autoindex.
|
||||||
services.nginx.virtualHosts."syncthing.railbird.ai" = {
|
services.nginx.virtualHosts."syncthing.railbird.ai" = {
|
||||||
|
|||||||
@@ -113,6 +113,9 @@
|
|||||||
codex = inputs.codex-cli-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
codex = inputs.codex-cli-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
claude-code = inputs.claude-code-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
claude-code = inputs.claude-code-nix.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
git-sync-rs = inputs.git-sync-rs.packages.${prev.stdenv.hostPlatform.system}.default;
|
git-sync-rs = inputs.git-sync-rs.packages.${prev.stdenv.hostPlatform.system}.default;
|
||||||
|
kef = final.callPackage ./packages/kef {};
|
||||||
|
pykefcontrol = final.python3Packages.callPackage ./packages/pykefcontrol {};
|
||||||
|
roborock-control = final.callPackage ./packages/roborock-control {};
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
++ (
|
++ (
|
||||||
|
|||||||
87
nixos/noctalia.nix
Normal file
87
nixos/noctalia.nix
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
makeEnable,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
noctaliaPackage = inputs.noctalia.packages.${system}.default;
|
||||||
|
waitForWayland = pkgs.writeShellScript "noctalia-wait-for-wayland" ''
|
||||||
|
runtime_dir="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}"
|
||||||
|
|
||||||
|
for _ in $(${pkgs.coreutils}/bin/seq 1 50); do
|
||||||
|
if [ -n "''${WAYLAND_DISPLAY:-}" ] && [ -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
${pkgs.coreutils}/bin/sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Wayland socket not ready: WAYLAND_DISPLAY=''${WAYLAND_DISPLAY:-<unset>} XDG_RUNTIME_DIR=$runtime_dir" >&2
|
||||||
|
exit 1
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
makeEnable config "myModules.noctalia" false {
|
||||||
|
environment.systemPackages = [
|
||||||
|
noctaliaPackage
|
||||||
|
];
|
||||||
|
|
||||||
|
# Noctalia's battery widget talks to UPower. Hosts that deliberately do not
|
||||||
|
# have batteries can still override this back to false.
|
||||||
|
services.upower.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
home-manager.sharedModules = [
|
||||||
|
inputs.noctalia.homeModules.default
|
||||||
|
({ lib, ... }: {
|
||||||
|
programs.noctalia-shell = {
|
||||||
|
enable = true;
|
||||||
|
# This module provides the Hyprland-scoped service below.
|
||||||
|
systemd.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
home.activation.noctaliaLauncherOverviewLayer =
|
||||||
|
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
||||||
|
settings_file="$HOME/.config/noctalia/settings.json"
|
||||||
|
settings_tmp="$(${pkgs.coreutils}/bin/mktemp)"
|
||||||
|
|
||||||
|
${pkgs.coreutils}/bin/mkdir -p "$(${pkgs.coreutils}/bin/dirname "$settings_file")"
|
||||||
|
|
||||||
|
if [ -e "$settings_file" ] && ${lib.getExe pkgs.jq} -e . "$settings_file" >/dev/null 2>&1; then
|
||||||
|
${lib.getExe pkgs.jq} \
|
||||||
|
'.appLauncher = (.appLauncher // {}) | .appLauncher.overviewLayer = true' \
|
||||||
|
"$settings_file" > "$settings_tmp"
|
||||||
|
${pkgs.coreutils}/bin/mv "$settings_tmp" "$settings_file"
|
||||||
|
else
|
||||||
|
${pkgs.coreutils}/bin/printf '%s\n' \
|
||||||
|
'{' \
|
||||||
|
' "appLauncher": {' \
|
||||||
|
' "overviewLayer": true' \
|
||||||
|
' }' \
|
||||||
|
'}' > "$settings_file"
|
||||||
|
${pkgs.coreutils}/bin/rm -f "$settings_tmp"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
systemd.user.services.noctalia-shell = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Noctalia Shell";
|
||||||
|
Documentation = "https://docs.noctalia.dev";
|
||||||
|
PartOf = [ "hyprland-session.target" ];
|
||||||
|
After = [ "hyprland-session.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
Service = {
|
||||||
|
ExecCondition = "/run/current-system/sw/bin/desktop_shell_ui exec-condition noctalia";
|
||||||
|
ExecStartPre = "${waitForWayland}";
|
||||||
|
ExecStart = "${lib.getExe noctaliaPackage} --no-duplicate";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Install.WantedBy = [ "hyprland-session.target" ];
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
30
nixos/packages/kef/default.nix
Normal file
30
nixos/packages/kef/default.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3,
|
||||||
|
python3Packages,
|
||||||
|
writeShellApplication,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
pykefcontrol = python3Packages.callPackage ../pykefcontrol {};
|
||||||
|
python = python3.withPackages (ps: [
|
||||||
|
pykefcontrol
|
||||||
|
ps.zeroconf
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
writeShellApplication {
|
||||||
|
name = "kef";
|
||||||
|
|
||||||
|
runtimeInputs = [ python ];
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
exec python ${./kef.py} "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Command-line controller for KEF W2 speakers";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
mainProgram = "kef";
|
||||||
|
};
|
||||||
|
}
|
||||||
251
nixos/packages/kef/kef.py
Normal file
251
nixos/packages/kef/kef.py
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from pykefcontrol.kef_connector import KefConnector
|
||||||
|
from zeroconf import IPVersion, ServiceBrowser, ServiceListener, Zeroconf
|
||||||
|
|
||||||
|
|
||||||
|
SOURCES = ("wifi", "bluetooth", "tv", "optic", "coaxial", "analog", "standby")
|
||||||
|
DISCOVERY_SERVICE_TYPES = (
|
||||||
|
"_kef-info._tcp.local.",
|
||||||
|
"_sues800device._tcp.local.",
|
||||||
|
"_http._tcp.local.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KefDiscoveryListener(ServiceListener):
|
||||||
|
def __init__(self):
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self.speakers = {}
|
||||||
|
|
||||||
|
def add_service(self, zeroconf, service_type, name):
|
||||||
|
self._record_service(zeroconf, service_type, name)
|
||||||
|
|
||||||
|
def update_service(self, zeroconf, service_type, name):
|
||||||
|
self._record_service(zeroconf, service_type, name)
|
||||||
|
|
||||||
|
def remove_service(self, zeroconf, service_type, name):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _record_service(self, zeroconf, service_type, name):
|
||||||
|
info = zeroconf.get_service_info(service_type, name, timeout=1000)
|
||||||
|
if info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
properties = {
|
||||||
|
key.decode("utf-8", errors="replace"): (
|
||||||
|
value.decode("utf-8", errors="replace") if value is not None else ""
|
||||||
|
)
|
||||||
|
for key, value in info.properties.items()
|
||||||
|
}
|
||||||
|
addresses = [
|
||||||
|
address
|
||||||
|
for address in info.parsed_addresses(IPVersion.V4Only)
|
||||||
|
if not address.startswith("127.")
|
||||||
|
]
|
||||||
|
if not addresses:
|
||||||
|
return
|
||||||
|
|
||||||
|
is_kef = (
|
||||||
|
service_type in ("_kef-info._tcp.local.", "_sues800device._tcp.local.")
|
||||||
|
or properties.get("manufacturer", "").lower() == "kef"
|
||||||
|
or "kef" in name.lower()
|
||||||
|
or "ls50" in name.lower()
|
||||||
|
)
|
||||||
|
if not is_kef:
|
||||||
|
return
|
||||||
|
|
||||||
|
key = addresses[0]
|
||||||
|
with self._lock:
|
||||||
|
existing = self.speakers.get(key, {})
|
||||||
|
self.speakers[key] = {
|
||||||
|
**existing,
|
||||||
|
"address": addresses[0],
|
||||||
|
"hostname": (info.server.rstrip(".") if info.server else None)
|
||||||
|
or existing.get("hostname"),
|
||||||
|
"name": properties.get("name")
|
||||||
|
or properties.get("fn")
|
||||||
|
or existing.get("name")
|
||||||
|
or strip_service_name(name),
|
||||||
|
"model": properties.get("modelName")
|
||||||
|
or properties.get("model")
|
||||||
|
or properties.get("mn")
|
||||||
|
or existing.get("model"),
|
||||||
|
"version": properties.get("version") or existing.get("version"),
|
||||||
|
"service": existing.get("service") or service_type.removesuffix(".local."),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def strip_service_name(name):
|
||||||
|
for suffix in DISCOVERY_SERVICE_TYPES:
|
||||||
|
if name.endswith("." + suffix):
|
||||||
|
return name[: -(len(suffix) + 1)]
|
||||||
|
return name.rstrip(".")
|
||||||
|
|
||||||
|
|
||||||
|
def discover_speakers(timeout):
|
||||||
|
listener = KefDiscoveryListener()
|
||||||
|
zeroconf = Zeroconf(ip_version=IPVersion.V4Only)
|
||||||
|
try:
|
||||||
|
browsers = [
|
||||||
|
ServiceBrowser(zeroconf, service_type, listener)
|
||||||
|
for service_type in DISCOVERY_SERVICE_TYPES
|
||||||
|
]
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if listener.speakers:
|
||||||
|
time.sleep(0.25)
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
return sorted(
|
||||||
|
listener.speakers.values(),
|
||||||
|
key=lambda speaker: (
|
||||||
|
speaker.get("name") or "",
|
||||||
|
speaker.get("address") or "",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
for browser in locals().get("browsers", []):
|
||||||
|
browser.cancel()
|
||||||
|
zeroconf.close()
|
||||||
|
|
||||||
|
|
||||||
|
def discover_host(timeout):
|
||||||
|
speakers = discover_speakers(timeout)
|
||||||
|
if not speakers:
|
||||||
|
raise SystemExit(
|
||||||
|
"Could not discover a KEF speaker. Pass --host <speaker-ip> or set KEF_HOST."
|
||||||
|
)
|
||||||
|
if len(speakers) > 1:
|
||||||
|
choices = ", ".join(
|
||||||
|
f"{speaker.get('name') or 'KEF'} at {speaker['address']}" for speaker in speakers
|
||||||
|
)
|
||||||
|
raise SystemExit(f"Multiple KEF speakers discovered: {choices}. Pass --host explicitly.")
|
||||||
|
return speakers[0]["address"]
|
||||||
|
|
||||||
|
|
||||||
|
def connector(args):
|
||||||
|
host = (
|
||||||
|
args.host
|
||||||
|
or os.environ.get("KEF_HOST")
|
||||||
|
or os.environ.get("KEF_IP")
|
||||||
|
or discover_host(args.discovery_timeout)
|
||||||
|
)
|
||||||
|
return KefConnector(host, model=args.model)
|
||||||
|
|
||||||
|
|
||||||
|
def print_status(speaker):
|
||||||
|
info = {
|
||||||
|
"name": speaker.speaker_name,
|
||||||
|
"model": speaker.speaker_model,
|
||||||
|
"firmware": speaker.firmware_version,
|
||||||
|
"status": speaker.status,
|
||||||
|
"source": speaker.source,
|
||||||
|
"volume": speaker.volume,
|
||||||
|
"playing": speaker.is_playing,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
song = speaker.get_song_information()
|
||||||
|
if any(song.values()):
|
||||||
|
info["song"] = song
|
||||||
|
except (KeyError, IndexError, TypeError, requests.RequestException):
|
||||||
|
pass
|
||||||
|
print(json.dumps(info, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def bounded_volume(value):
|
||||||
|
return max(0, min(100, value))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(prog="kef", description="Control KEF W2 speakers.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--host",
|
||||||
|
help="Speaker IP address. Defaults to KEF_HOST/KEF_IP, then mDNS discovery.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--model",
|
||||||
|
default="LS50W2",
|
||||||
|
help="Speaker model passed to pykefcontrol. Default: LS50W2.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--discovery-timeout",
|
||||||
|
default=2.0,
|
||||||
|
type=float,
|
||||||
|
help="Seconds to wait for mDNS discovery when --host/KEF_HOST is unset.",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||||
|
|
||||||
|
subparsers.add_parser("discover", help="List discovered KEF speakers as JSON.")
|
||||||
|
subparsers.add_parser("status", help="Print speaker status as JSON.")
|
||||||
|
subparsers.add_parser("on", help="Power on.")
|
||||||
|
subparsers.add_parser("standby", help="Put the speaker in standby.")
|
||||||
|
subparsers.add_parser("mute", help="Mute by setting volume to 0.")
|
||||||
|
|
||||||
|
volume = subparsers.add_parser("volume", help="Get or set volume.")
|
||||||
|
volume.add_argument("level", nargs="?", type=int, help="Volume level, 0-100.")
|
||||||
|
|
||||||
|
up = subparsers.add_parser("up", help="Increase volume.")
|
||||||
|
up.add_argument("step", nargs="?", default=3, type=int)
|
||||||
|
|
||||||
|
down = subparsers.add_parser("down", help="Decrease volume.")
|
||||||
|
down.add_argument("step", nargs="?", default=3, type=int)
|
||||||
|
|
||||||
|
source = subparsers.add_parser("source", help="Get or set source.")
|
||||||
|
source.add_argument("name", nargs="?", choices=SOURCES)
|
||||||
|
|
||||||
|
subparsers.add_parser("play-pause", help="Toggle play/pause.")
|
||||||
|
subparsers.add_parser("next", help="Next track.")
|
||||||
|
subparsers.add_parser("previous", help="Previous track.")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
if args.command == "discover":
|
||||||
|
print(json.dumps(discover_speakers(args.discovery_timeout), indent=2, sort_keys=True))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
speaker = connector(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if args.command == "status":
|
||||||
|
print_status(speaker)
|
||||||
|
elif args.command == "on":
|
||||||
|
speaker.power_on()
|
||||||
|
elif args.command == "standby":
|
||||||
|
speaker.shutdown()
|
||||||
|
elif args.command == "mute":
|
||||||
|
speaker.volume = 0
|
||||||
|
elif args.command == "volume":
|
||||||
|
if args.level is None:
|
||||||
|
print(speaker.volume)
|
||||||
|
else:
|
||||||
|
speaker.volume = bounded_volume(args.level)
|
||||||
|
elif args.command == "up":
|
||||||
|
speaker.volume = bounded_volume(speaker.volume + args.step)
|
||||||
|
elif args.command == "down":
|
||||||
|
speaker.volume = bounded_volume(speaker.volume - args.step)
|
||||||
|
elif args.command == "source":
|
||||||
|
if args.name is None:
|
||||||
|
print(speaker.source)
|
||||||
|
else:
|
||||||
|
speaker.source = args.name
|
||||||
|
elif args.command == "play-pause":
|
||||||
|
speaker.toggle_play_pause()
|
||||||
|
elif args.command == "next":
|
||||||
|
speaker.next_track()
|
||||||
|
elif args.command == "previous":
|
||||||
|
speaker.previous_track()
|
||||||
|
except requests.RequestException as error:
|
||||||
|
print(f"kef: failed to reach speaker: {error}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
37
nixos/packages/pykefcontrol/default.nix
Normal file
37
nixos/packages/pykefcontrol/default.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
buildPythonPackage,
|
||||||
|
fetchPypi,
|
||||||
|
hatchling,
|
||||||
|
aiohttp,
|
||||||
|
requests,
|
||||||
|
}:
|
||||||
|
|
||||||
|
buildPythonPackage rec {
|
||||||
|
pname = "pykefcontrol";
|
||||||
|
version = "0.9.2";
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
src = fetchPypi {
|
||||||
|
inherit pname version;
|
||||||
|
hash = "sha256-3kGhN+E7driiE6ePyF0EZOEnUhTm07sxHCKdzrn/MxM=";
|
||||||
|
};
|
||||||
|
|
||||||
|
build-system = [
|
||||||
|
hatchling
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
aiohttp
|
||||||
|
requests
|
||||||
|
];
|
||||||
|
|
||||||
|
pythonImportsCheck = [ "pykefcontrol" ];
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Python library for controlling KEF LS50WII, LSX II, and LS60 speakers";
|
||||||
|
homepage = "https://github.com/N0ciple/pykefcontrol";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
};
|
||||||
|
}
|
||||||
30
nixos/packages/roborock-control/default.nix
Normal file
30
nixos/packages/roborock-control/default.nix
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
python3,
|
||||||
|
writeShellApplication,
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
python = python3.withPackages (ps: [
|
||||||
|
ps.python-roborock
|
||||||
|
ps.pyshark
|
||||||
|
ps.pyyaml
|
||||||
|
]);
|
||||||
|
in
|
||||||
|
writeShellApplication {
|
||||||
|
name = "roborock-control";
|
||||||
|
|
||||||
|
runtimeInputs = [ python ];
|
||||||
|
|
||||||
|
text = ''
|
||||||
|
export ROBOROCK_CONTROL_RUNNER=direct
|
||||||
|
exec python ${../../../dotfiles/lib/bin/roborock-control} "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Command-line controller for Roborock vacuums";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = with lib.maintainers; [ imalison ];
|
||||||
|
mainProgram = "roborock-control";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
From aaefc0ff0bc4348de04f311ad0101da44c62ae94 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Ivan Malison <IvanMalison@gmail.com>
|
|
||||||
Date: Wed, 4 Feb 2026 00:54:52 -0800
|
|
||||||
Subject: [PATCH 1/2] hyprexpo: optionally render workspace numbers
|
|
||||||
|
|
||||||
---
|
|
||||||
hyprexpo/README.md | 3 +-
|
|
||||||
hyprexpo/main.cpp | 2 +
|
|
||||||
hyprexpo/overview.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
hyprexpo/overview.hpp | 4 ++
|
|
||||||
4 files changed, 117 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/README.md b/README.md
|
|
||||||
index 97bd1d4..aac2e97 100644
|
|
||||||
--- a/README.md
|
|
||||||
+++ b/README.md
|
|
||||||
@@ -28,6 +28,8 @@ gap_size | number | gap between desktops | `5`
|
|
||||||
bg_col | color | color in gaps (between desktops) | `rgb(000000)`
|
|
||||||
workspace_method | [center/first] [workspace] | position of the desktops | `center current`
|
|
||||||
skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false`
|
|
||||||
+show_workspace_numbers | boolean | show numeric labels for workspaces | `false`
|
|
||||||
+workspace_number_color | color | color of workspace number labels | `rgb(ffffff)`
|
|
||||||
gesture_distance | number | how far is the max for the gesture | `300`
|
|
||||||
|
|
||||||
### Keywords
|
|
||||||
@@ -57,4 +59,3 @@ off | hides the overview
|
|
||||||
disable | same as `off`
|
|
||||||
on | displays the overview
|
|
||||||
enable | same as `on`
|
|
||||||
-
|
|
||||||
diff --git a/main.cpp b/main.cpp
|
|
||||||
index 883fd82..ff9f380 100644
|
|
||||||
--- a/main.cpp
|
|
||||||
+++ b/main.cpp
|
|
||||||
@@ -239,6 +239,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111});
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"});
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty", Hyprlang::INT{0});
|
|
||||||
+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:show_workspace_numbers", Hyprlang::INT{0});
|
|
||||||
+ HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_number_color", Hyprlang::INT{0xFFFFFFFF});
|
|
||||||
|
|
||||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200});
|
|
||||||
|
|
||||||
diff --git a/overview.cpp b/overview.cpp
|
|
||||||
index 5721948..926a9f8 100644
|
|
||||||
--- a/overview.cpp
|
|
||||||
+++ b/overview.cpp
|
|
||||||
@@ -1,5 +1,8 @@
|
|
||||||
#include "overview.hpp"
|
|
||||||
#include <any>
|
|
||||||
+#include <algorithm>
|
|
||||||
+#include <cmath>
|
|
||||||
+#include <pango/pangocairo.h>
|
|
||||||
#define private public
|
|
||||||
#include <hyprland/src/render/Renderer.hpp>
|
|
||||||
#include <hyprland/src/Compositor.hpp>
|
|
||||||
@@ -15,6 +18,86 @@
|
|
||||||
#undef private
|
|
||||||
#include "OverviewPassElement.hpp"
|
|
||||||
|
|
||||||
+static Vector2D renderLabelTexture(SP<CTexture> out, const std::string& text, const CHyprColor& color, int fontSizePx) {
|
|
||||||
+ if (!out || text.empty() || fontSizePx <= 0)
|
|
||||||
+ return {};
|
|
||||||
+
|
|
||||||
+ auto measureSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
|
|
||||||
+ auto measureCairo = cairo_create(measureSurface);
|
|
||||||
+
|
|
||||||
+ PangoLayout* measureLayout = pango_cairo_create_layout(measureCairo);
|
|
||||||
+ pango_layout_set_text(measureLayout, text.c_str(), -1);
|
|
||||||
+ auto* fontDesc = pango_font_description_from_string("Sans Bold");
|
|
||||||
+ pango_font_description_set_size(fontDesc, fontSizePx * PANGO_SCALE);
|
|
||||||
+ pango_layout_set_font_description(measureLayout, fontDesc);
|
|
||||||
+ pango_font_description_free(fontDesc);
|
|
||||||
+
|
|
||||||
+ PangoRectangle inkRect, logicalRect;
|
|
||||||
+ pango_layout_get_extents(measureLayout, &inkRect, &logicalRect);
|
|
||||||
+
|
|
||||||
+ const int textW = std::max(1, (int)std::ceil(logicalRect.width / (double)PANGO_SCALE));
|
|
||||||
+ const int textH = std::max(1, (int)std::ceil(logicalRect.height / (double)PANGO_SCALE));
|
|
||||||
+
|
|
||||||
+ g_object_unref(measureLayout);
|
|
||||||
+ cairo_destroy(measureCairo);
|
|
||||||
+ cairo_surface_destroy(measureSurface);
|
|
||||||
+
|
|
||||||
+ const int pad = std::max(4, (int)std::round(fontSizePx * 0.35));
|
|
||||||
+ const int width = textW + pad * 2;
|
|
||||||
+ const int height = textH + pad * 2;
|
|
||||||
+
|
|
||||||
+ auto surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
|
|
||||||
+ auto cairo = cairo_create(surface);
|
|
||||||
+
|
|
||||||
+ // Clear the pixmap
|
|
||||||
+ cairo_save(cairo);
|
|
||||||
+ cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
|
|
||||||
+ cairo_paint(cairo);
|
|
||||||
+ cairo_restore(cairo);
|
|
||||||
+
|
|
||||||
+ // Background for legibility
|
|
||||||
+ cairo_set_source_rgba(cairo, 0.0, 0.0, 0.0, 0.55);
|
|
||||||
+ cairo_rectangle(cairo, 0, 0, width, height);
|
|
||||||
+ cairo_fill(cairo);
|
|
||||||
+
|
|
||||||
+ PangoLayout* layout = pango_cairo_create_layout(cairo);
|
|
||||||
+ pango_layout_set_text(layout, text.c_str(), -1);
|
|
||||||
+ fontDesc = pango_font_description_from_string("Sans Bold");
|
|
||||||
+ pango_font_description_set_size(fontDesc, fontSizePx * PANGO_SCALE);
|
|
||||||
+ pango_layout_set_font_description(layout, fontDesc);
|
|
||||||
+ pango_font_description_free(fontDesc);
|
|
||||||
+
|
|
||||||
+ pango_layout_get_extents(layout, &inkRect, &logicalRect);
|
|
||||||
+ const double xOffset = (width - logicalRect.width / (double)PANGO_SCALE) / 2.0;
|
|
||||||
+ const double yOffset = (height - logicalRect.height / (double)PANGO_SCALE) / 2.0;
|
|
||||||
+
|
|
||||||
+ cairo_set_source_rgba(cairo, color.r, color.g, color.b, color.a);
|
|
||||||
+ cairo_move_to(cairo, xOffset, yOffset);
|
|
||||||
+ pango_cairo_show_layout(cairo, layout);
|
|
||||||
+
|
|
||||||
+ g_object_unref(layout);
|
|
||||||
+
|
|
||||||
+ cairo_surface_flush(surface);
|
|
||||||
+
|
|
||||||
+ const auto DATA = cairo_image_surface_get_data(surface);
|
|
||||||
+ out->allocate();
|
|
||||||
+ glBindTexture(GL_TEXTURE_2D, out->m_texID);
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
+
|
|
||||||
+#ifndef GLES2
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
|
|
||||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
|
|
||||||
+#endif
|
|
||||||
+
|
|
||||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
|
|
||||||
+
|
|
||||||
+ cairo_destroy(cairo);
|
|
||||||
+ cairo_surface_destroy(surface);
|
|
||||||
+
|
|
||||||
+ return {width, height};
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
static void damageMonitor(WP<Hyprutils::Animation::CBaseAnimatedVariable> thisptr) {
|
|
||||||
g_pOverview->damage();
|
|
||||||
}
|
|
||||||
@@ -34,11 +117,14 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
|
|
||||||
static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr();
|
|
||||||
static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr();
|
|
||||||
static auto* const* PSKIP = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:skip_empty")->getDataStaticPtr();
|
|
||||||
+ static auto* const* PSHOWNUM = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:show_workspace_numbers")->getDataStaticPtr();
|
|
||||||
+ static auto* const* PNUMCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_number_color")->getDataStaticPtr();
|
|
||||||
static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr();
|
|
||||||
|
|
||||||
SIDE_LENGTH = **PCOLUMNS;
|
|
||||||
GAP_WIDTH = **PGAPS;
|
|
||||||
BG_COLOR = **PCOL;
|
|
||||||
+ showWorkspaceNumbers = **PSHOWNUM;
|
|
||||||
|
|
||||||
// process the method
|
|
||||||
bool methodCenter = true;
|
|
||||||
@@ -126,6 +212,17 @@ COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn
|
|
||||||
Vector2D tileRenderSize = (pMonitor->m_size - Vector2D{GAP_WIDTH * pMonitor->m_scale, GAP_WIDTH * pMonitor->m_scale} * (SIDE_LENGTH - 1)) / SIDE_LENGTH;
|
|
||||||
CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2};
|
|
||||||
|
|
||||||
+ if (showWorkspaceNumbers) {
|
|
||||||
+ const CHyprColor numberColor = **PNUMCOL;
|
|
||||||
+ const int fontSizePx = std::max(12, (int)std::round(tileRenderSize.y * pMonitor->m_scale * 0.22));
|
|
||||||
+ for (auto& image : images) {
|
|
||||||
+ if (image.workspaceID == WORKSPACE_INVALID)
|
|
||||||
+ continue;
|
|
||||||
+ image.labelTex = makeShared<CTexture>();
|
|
||||||
+ image.labelSizePx = renderLabelTexture(image.labelTex, std::to_string(image.workspaceID), numberColor, fontSizePx);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
if (!ENABLE_LOWRES)
|
|
||||||
monbox = {{0, 0}, pMonitor->m_pixelSize};
|
|
||||||
|
|
||||||
@@ -452,6 +549,18 @@ void COverview::fullRender() {
|
|
||||||
texbox.round();
|
|
||||||
CRegion damage{0, 0, INT16_MAX, INT16_MAX};
|
|
||||||
g_pHyprOpenGL->renderTextureInternal(images[x + y * SIDE_LENGTH].fb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
|
|
||||||
+
|
|
||||||
+ if (showWorkspaceNumbers) {
|
|
||||||
+ auto& image = images[x + y * SIDE_LENGTH];
|
|
||||||
+ if (image.workspaceID != WORKSPACE_INVALID && image.labelTex && image.labelTex->m_texID != 0 && image.labelSizePx.x > 0 && image.labelSizePx.y > 0) {
|
|
||||||
+ const Vector2D labelSize = image.labelSizePx / pMonitor->m_scale;
|
|
||||||
+ const float margin = std::max(4.0, tileRenderSize.y * 0.05);
|
|
||||||
+ CBox labelBox = {x * tileRenderSize.x + x * GAPSIZE + margin, y * tileRenderSize.y + y * GAPSIZE + margin, labelSize.x, labelSize.y};
|
|
||||||
+ labelBox.scale(pMonitor->m_scale).translate(pos->value());
|
|
||||||
+ labelBox.round();
|
|
||||||
+ g_pHyprOpenGL->renderTexture(image.labelTex, labelBox, {.a = 1.0});
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diff --git a/overview.hpp b/overview.hpp
|
|
||||||
index 4b02400..1f6bf3c 100644
|
|
||||||
--- a/overview.hpp
|
|
||||||
+++ b/overview.hpp
|
|
||||||
@@ -8,6 +8,7 @@
|
|
||||||
#include <hyprland/src/helpers/AnimatedVariable.hpp>
|
|
||||||
#include <hyprland/src/managers/HookSystemManager.hpp>
|
|
||||||
#include <vector>
|
|
||||||
+class CTexture;
|
|
||||||
|
|
||||||
// saves on resources, but is a bit broken rn with blur.
|
|
||||||
// hyprland's fault, but cba to fix.
|
|
||||||
@@ -58,6 +59,8 @@ class COverview {
|
|
||||||
int64_t workspaceID = -1;
|
|
||||||
PHLWORKSPACE pWorkspace;
|
|
||||||
CBox box;
|
|
||||||
+ SP<CTexture> labelTex;
|
|
||||||
+ Vector2D labelSizePx;
|
|
||||||
};
|
|
||||||
|
|
||||||
Vector2D lastMousePosLocal = Vector2D{};
|
|
||||||
@@ -81,6 +84,7 @@ class COverview {
|
|
||||||
|
|
||||||
bool swipe = false;
|
|
||||||
bool swipeWasCommenced = false;
|
|
||||||
+ bool showWorkspaceNumbers = false;
|
|
||||||
|
|
||||||
friend class COverviewPassElement;
|
|
||||||
};
|
|
||||||
--
|
|
||||||
2.52.0
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
From edc05ce88f79ceda0cdcb9aa68ec371b1af323de Mon Sep 17 00:00:00 2001
|
|
||||||
From: Ivan Malison <IvanMalison@gmail.com>
|
|
||||||
Date: Mon, 16 Feb 2026 21:50:16 -0800
|
|
||||||
Subject: [PATCH 2/2] hyprexpo: add bring selection mode
|
|
||||||
|
|
||||||
---
|
|
||||||
hyprexpo/README.md | 1 +
|
|
||||||
hyprexpo/main.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
hyprexpo/overview.cpp | 12 +++++++++--
|
|
||||||
hyprexpo/overview.hpp | 3 ++-
|
|
||||||
4 files changed, 63 insertions(+), 3 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/README.md b/README.md
|
|
||||||
index aac2e97..084f02b 100644
|
|
||||||
--- a/README.md
|
|
||||||
+++ b/README.md
|
|
||||||
@@ -55,6 +55,7 @@ Here are a list of options you can use:
|
|
||||||
| --- | --- |
|
|
||||||
toggle | displays if hidden, hide if displayed
|
|
||||||
select | selects the hovered desktop
|
|
||||||
+bring | brings a window from the hovered desktop to the current desktop
|
|
||||||
off | hides the overview
|
|
||||||
disable | same as `off`
|
|
||||||
on | displays the overview
|
|
||||||
diff --git a/main.cpp b/main.cpp
|
|
||||||
index ff9f380..78bac24 100644
|
|
||||||
--- a/main.cpp
|
|
||||||
+++ b/main.cpp
|
|
||||||
@@ -65,6 +65,47 @@ static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) {
|
|
||||||
g_pOverview->onDamageReported();
|
|
||||||
}
|
|
||||||
|
|
||||||
+static PHLWINDOW windowToBringFromWorkspace(const PHLWORKSPACE& workspace) {
|
|
||||||
+ if (!workspace)
|
|
||||||
+ return nullptr;
|
|
||||||
+
|
|
||||||
+ for (auto it = g_pCompositor->m_windows.rbegin(); it != g_pCompositor->m_windows.rend(); ++it) {
|
|
||||||
+ const auto& w = *it;
|
|
||||||
+ if (!w || w->m_workspace != workspace || !w->m_isMapped || w->isHidden())
|
|
||||||
+ continue;
|
|
||||||
+
|
|
||||||
+ return w;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return nullptr;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+static SDispatchResult bringWindowFromWorkspace(int64_t sourceWorkspaceID) {
|
|
||||||
+ if (sourceWorkspaceID == WORKSPACE_INVALID)
|
|
||||||
+ return {.success = false, .error = "selected workspace is empty"};
|
|
||||||
+
|
|
||||||
+ const auto FOCUSSTATE = Desktop::focusState();
|
|
||||||
+ const auto MONITOR = FOCUSSTATE->monitor();
|
|
||||||
+ if (!MONITOR || !MONITOR->m_activeWorkspace)
|
|
||||||
+ return {.success = false, .error = "no active monitor/workspace"};
|
|
||||||
+
|
|
||||||
+ if (sourceWorkspaceID == MONITOR->activeWorkspaceID())
|
|
||||||
+ return {};
|
|
||||||
+
|
|
||||||
+ const auto SOURCEWORKSPACE = g_pCompositor->getWorkspaceByID(sourceWorkspaceID);
|
|
||||||
+ if (!SOURCEWORKSPACE)
|
|
||||||
+ return {.success = false, .error = "selected workspace is not open"};
|
|
||||||
+
|
|
||||||
+ const auto WINDOW = windowToBringFromWorkspace(SOURCEWORKSPACE);
|
|
||||||
+ if (!WINDOW)
|
|
||||||
+ return {.success = false, .error = "selected workspace has no mapped windows"};
|
|
||||||
+
|
|
||||||
+ g_pCompositor->moveWindowToWorkspaceSafe(WINDOW, MONITOR->m_activeWorkspace);
|
|
||||||
+ FOCUSSTATE->fullWindowFocus(WINDOW);
|
|
||||||
+ g_pCompositor->warpCursorTo(WINDOW->middle());
|
|
||||||
+ return {};
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
static SDispatchResult onExpoDispatcher(std::string arg) {
|
|
||||||
|
|
||||||
if (g_pOverview && g_pOverview->m_isSwiping)
|
|
||||||
@@ -77,6 +118,15 @@ static SDispatchResult onExpoDispatcher(std::string arg) {
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
+ if (arg == "bring") {
|
|
||||||
+ if (g_pOverview) {
|
|
||||||
+ g_pOverview->selectHoveredWorkspace();
|
|
||||||
+ const auto BRINGRESULT = bringWindowFromWorkspace(g_pOverview->selectedWorkspaceID());
|
|
||||||
+ g_pOverview->close(false);
|
|
||||||
+ return BRINGRESULT;
|
|
||||||
+ }
|
|
||||||
+ return {};
|
|
||||||
+ }
|
|
||||||
if (arg == "toggle") {
|
|
||||||
if (g_pOverview)
|
|
||||||
g_pOverview->close();
|
|
||||||
diff --git a/overview.cpp b/overview.cpp
|
|
||||||
index 926a9f8..45ee982 100644
|
|
||||||
--- a/overview.cpp
|
|
||||||
+++ b/overview.cpp
|
|
||||||
@@ -343,6 +343,14 @@ void COverview::selectHoveredWorkspace() {
|
|
||||||
closeOnID = x + y * SIDE_LENGTH;
|
|
||||||
}
|
|
||||||
|
|
||||||
+int64_t COverview::selectedWorkspaceID() const {
|
|
||||||
+ const int ID = closeOnID == -1 ? openedID : closeOnID;
|
|
||||||
+ if (ID < 0 || ID >= (int)images.size())
|
|
||||||
+ return WORKSPACE_INVALID;
|
|
||||||
+
|
|
||||||
+ return images[ID].workspaceID;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
void COverview::redrawID(int id, bool forcelowres) {
|
|
||||||
if (!pMonitor)
|
|
||||||
return;
|
|
||||||
@@ -451,7 +459,7 @@ void COverview::onDamageReported() {
|
|
||||||
g_pCompositor->scheduleFrameForMonitor(pMonitor.lock());
|
|
||||||
}
|
|
||||||
|
|
||||||
-void COverview::close() {
|
|
||||||
+void COverview::close(bool switchToSelection) {
|
|
||||||
if (closing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
@@ -471,7 +479,7 @@ void COverview::close() {
|
|
||||||
|
|
||||||
redrawAll();
|
|
||||||
|
|
||||||
- if (TILE.workspaceID != pMonitor->activeWorkspaceID()) {
|
|
||||||
+ if (switchToSelection && TILE.workspaceID != pMonitor->activeWorkspaceID()) {
|
|
||||||
pMonitor->setSpecialWorkspace(0);
|
|
||||||
|
|
||||||
// If this tile's workspace was WORKSPACE_INVALID, move to the next
|
|
||||||
diff --git a/overview.hpp b/overview.hpp
|
|
||||||
index 1f6bf3c..ca59f32 100644
|
|
||||||
--- a/overview.hpp
|
|
||||||
+++ b/overview.hpp
|
|
||||||
@@ -33,8 +33,9 @@ class COverview {
|
|
||||||
void onSwipeEnd();
|
|
||||||
|
|
||||||
// close without a selection
|
|
||||||
- void close();
|
|
||||||
+ void close(bool switchToSelection = true);
|
|
||||||
void selectHoveredWorkspace();
|
|
||||||
+ int64_t selectedWorkspaceID() const;
|
|
||||||
|
|
||||||
bool blockOverviewRendering = false;
|
|
||||||
bool blockDamageReporting = false;
|
|
||||||
--
|
|
||||||
2.52.0
|
|
||||||
|
|
||||||
200
nixos/remote-hyprland.nix
Normal file
200
nixos/remote-hyprland.nix
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
{ config, lib, pkgs, makeEnable, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.myModules.remote-hyprland;
|
||||||
|
hyprlandPackage = config.programs.hyprland.package;
|
||||||
|
geometry = "${toString cfg.width}x${toString cfg.height}@${toString cfg.refreshRate}";
|
||||||
|
monitorRule = "${cfg.output},${geometry},0x0,${toString cfg.scale}";
|
||||||
|
remoteHyprlandStartVnc = pkgs.writeShellScript "remote-hyprland-start-vnc" ''
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
export WAYLAND_DISPLAY=${cfg.socket}
|
||||||
|
export XDG_CURRENT_DESKTOP=Hyprland
|
||||||
|
export XDG_SESSION_DESKTOP=Hyprland
|
||||||
|
export XDG_SESSION_TYPE=wayland
|
||||||
|
|
||||||
|
for _ in $(${pkgs.coreutils}/bin/seq 1 50); do
|
||||||
|
if ${hyprlandPackage}/bin/hyprctl -j monitors >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
${pkgs.coreutils}/bin/sleep 0.1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Give wayvnc a stable output name instead of relying on Hyprland's
|
||||||
|
# fallback HEADLESS-* naming.
|
||||||
|
${hyprlandPackage}/bin/hyprctl output create headless ${cfg.output} >/dev/null 2>&1 || true
|
||||||
|
${hyprlandPackage}/bin/hyprctl keyword monitor '${monitorRule}' >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
exec ${pkgs.wayvnc}/bin/wayvnc \
|
||||||
|
--log-level=info \
|
||||||
|
--output ${cfg.output} \
|
||||||
|
${cfg.bindAddress} ${toString cfg.port}
|
||||||
|
'';
|
||||||
|
remoteHyprlandConfig = pkgs.writeText "remote-hyprland.conf" ''
|
||||||
|
monitor=${monitorRule}
|
||||||
|
monitor=,${geometry},0x0,${toString cfg.scale}
|
||||||
|
|
||||||
|
env = XDG_CURRENT_DESKTOP,Hyprland
|
||||||
|
env = XDG_SESSION_DESKTOP,Hyprland
|
||||||
|
env = XDG_SESSION_TYPE,wayland
|
||||||
|
|
||||||
|
input {
|
||||||
|
kb_layout = us
|
||||||
|
follow_mouse = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
general {
|
||||||
|
gaps_in = 4
|
||||||
|
gaps_out = 8
|
||||||
|
border_size = 2
|
||||||
|
layout = dwindle
|
||||||
|
}
|
||||||
|
|
||||||
|
decoration {
|
||||||
|
rounding = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
dwindle {
|
||||||
|
pseudotile = true
|
||||||
|
preserve_split = true
|
||||||
|
}
|
||||||
|
|
||||||
|
misc {
|
||||||
|
disable_hyprland_logo = true
|
||||||
|
disable_splash_rendering = true
|
||||||
|
force_default_wallpaper = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
$mainMod = SUPER
|
||||||
|
bind = $mainMod, Return, exec, ${cfg.terminalCommand}
|
||||||
|
bind = $mainMod, D, exec, ${pkgs.rofi}/bin/rofi -show drun
|
||||||
|
bind = $mainMod, Q, killactive
|
||||||
|
bind = $mainMod SHIFT, M, exit
|
||||||
|
|
||||||
|
bind = $mainMod, H, movefocus, l
|
||||||
|
bind = $mainMod, J, movefocus, d
|
||||||
|
bind = $mainMod, K, movefocus, u
|
||||||
|
bind = $mainMod, L, movefocus, r
|
||||||
|
|
||||||
|
bind = $mainMod SHIFT, H, movewindow, l
|
||||||
|
bind = $mainMod SHIFT, J, movewindow, d
|
||||||
|
bind = $mainMod SHIFT, K, movewindow, u
|
||||||
|
bind = $mainMod SHIFT, L, movewindow, r
|
||||||
|
|
||||||
|
bind = $mainMod, 1, workspace, 1
|
||||||
|
bind = $mainMod, 2, workspace, 2
|
||||||
|
bind = $mainMod, 3, workspace, 3
|
||||||
|
bind = $mainMod, 4, workspace, 4
|
||||||
|
bind = $mainMod, 5, workspace, 5
|
||||||
|
bind = $mainMod, 6, workspace, 6
|
||||||
|
bind = $mainMod, 7, workspace, 7
|
||||||
|
bind = $mainMod, 8, workspace, 8
|
||||||
|
bind = $mainMod, 9, workspace, 9
|
||||||
|
|
||||||
|
bind = $mainMod SHIFT, 1, movetoworkspace, 1
|
||||||
|
bind = $mainMod SHIFT, 2, movetoworkspace, 2
|
||||||
|
bind = $mainMod SHIFT, 3, movetoworkspace, 3
|
||||||
|
bind = $mainMod SHIFT, 4, movetoworkspace, 4
|
||||||
|
bind = $mainMod SHIFT, 5, movetoworkspace, 5
|
||||||
|
bind = $mainMod SHIFT, 6, movetoworkspace, 6
|
||||||
|
bind = $mainMod SHIFT, 7, movetoworkspace, 7
|
||||||
|
bind = $mainMod SHIFT, 8, movetoworkspace, 8
|
||||||
|
bind = $mainMod SHIFT, 9, movetoworkspace, 9
|
||||||
|
|
||||||
|
exec-once = ${remoteHyprlandStartVnc}
|
||||||
|
exec-once = ${cfg.terminalCommand}
|
||||||
|
'';
|
||||||
|
enabledModule = makeEnable config "myModules.remote-hyprland" false {
|
||||||
|
myModules.hyprland.enable = true;
|
||||||
|
|
||||||
|
users.manageLingering = true;
|
||||||
|
users.users.${cfg.user}.linger = true;
|
||||||
|
|
||||||
|
environment.systemPackages = [ pkgs.wayvnc ];
|
||||||
|
|
||||||
|
home-manager.users.${cfg.user}.systemd.user.services.remote-hyprland = {
|
||||||
|
Unit = {
|
||||||
|
Description = "Headless Hyprland session for remote VNC access";
|
||||||
|
After = [ "default.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
ExecStart = "${hyprlandPackage}/bin/Hyprland --socket ${cfg.socket} --config ${remoteHyprlandConfig}";
|
||||||
|
Restart = "on-failure";
|
||||||
|
RestartSec = 5;
|
||||||
|
Environment = [
|
||||||
|
"XDG_CURRENT_DESKTOP=Hyprland"
|
||||||
|
"XDG_SESSION_DESKTOP=Hyprland"
|
||||||
|
"XDG_SESSION_TYPE=wayland"
|
||||||
|
"WAYLAND_DISPLAY=${cfg.socket}"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
Install = {
|
||||||
|
WantedBy = [ "default.target" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
enabledModule // {
|
||||||
|
options = lib.recursiveUpdate enabledModule.options {
|
||||||
|
myModules.remote-hyprland = {
|
||||||
|
user = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "imalison";
|
||||||
|
description = "User account that owns the remote Hyprland session.";
|
||||||
|
};
|
||||||
|
|
||||||
|
bindAddress = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Address for wayvnc to bind. Keep localhost when using SSH or Tailscale forwarding.";
|
||||||
|
};
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 5900;
|
||||||
|
description = "TCP port for wayvnc.";
|
||||||
|
};
|
||||||
|
|
||||||
|
socket = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "wayland-remote-hyprland";
|
||||||
|
description = "Wayland socket name used by the remote Hyprland instance.";
|
||||||
|
};
|
||||||
|
|
||||||
|
output = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "remote";
|
||||||
|
description = "Stable Hyprland headless output name captured by wayvnc.";
|
||||||
|
};
|
||||||
|
|
||||||
|
width = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 1920;
|
||||||
|
description = "Remote output width.";
|
||||||
|
};
|
||||||
|
|
||||||
|
height = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 1080;
|
||||||
|
description = "Remote output height.";
|
||||||
|
};
|
||||||
|
|
||||||
|
refreshRate = lib.mkOption {
|
||||||
|
type = lib.types.ints.positive;
|
||||||
|
default = 60;
|
||||||
|
description = "Remote output refresh rate.";
|
||||||
|
};
|
||||||
|
|
||||||
|
scale = lib.mkOption {
|
||||||
|
type = lib.types.number;
|
||||||
|
default = 1;
|
||||||
|
description = "Remote output scale.";
|
||||||
|
};
|
||||||
|
|
||||||
|
terminalCommand = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "${pkgs.ghostty}/bin/ghostty --gtk-single-instance=false";
|
||||||
|
description = "Command launched for the default terminal binding and initial window.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,17 +2,19 @@
|
|||||||
makeEnable,
|
makeEnable,
|
||||||
config,
|
config,
|
||||||
...
|
...
|
||||||
}: let
|
}:
|
||||||
|
let
|
||||||
shared = import ../nix-shared/syncthing.nix;
|
shared = import ../nix-shared/syncthing.nix;
|
||||||
inherit (shared) devices allDevices;
|
inherit (shared) devices allDevices;
|
||||||
in
|
in
|
||||||
makeEnable config "myModules.syncthing" true {
|
makeEnable config "myModules.syncthing" true {
|
||||||
system.activationScripts.syncthingPermissions = {
|
system.activationScripts.syncthingPermissions = {
|
||||||
text = ''
|
text = ''
|
||||||
|
mkdir -p /var/lib/syncthing/sync
|
||||||
|
mkdir -p /var/lib/syncthing/sync/Screensaver/use
|
||||||
|
mkdir -p /var/lib/syncthing/railbird
|
||||||
chown -R syncthing:syncthing /var/lib/syncthing
|
chown -R syncthing:syncthing /var/lib/syncthing
|
||||||
chmod -R 2770 /var/lib/syncthing
|
chmod -R 2770 /var/lib/syncthing
|
||||||
mkdir -p /var/lib/syncthing/sync
|
|
||||||
mkdir -p /var/lib/syncthing/railbird
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
systemd.services.syncthing = {
|
systemd.services.syncthing = {
|
||||||
|
|||||||
Reference in New Issue
Block a user