Compare commits
8 Commits
1a2b75adcb
...
hyprland-l
| Author | SHA1 | Date | |
|---|---|---|---|
| 75c29eb12b | |||
| c194e91dc1 | |||
| b52b2bcb57 | |||
| ddb77a5a52 | |||
| edbe9603f0 | |||
| 8d1d78dc25 | |||
| 3eb05515a1 | |||
| 016e0aadfe |
@@ -274,9 +274,6 @@ Required behavior:
|
||||
- `Super+p` opens the application launcher.
|
||||
- `Super+Shift+p` opens the run menu.
|
||||
- `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+Shift+Tab` opens the overview in bring-window mode when supported.
|
||||
- `Super+g` opens the go-to-window picker.
|
||||
|
||||
2
dotfiles/codex/.gitignore
vendored
2
dotfiles/codex/.gitignore
vendored
@@ -3,5 +3,3 @@
|
||||
!AGENTS.md
|
||||
!config.toml
|
||||
!skills
|
||||
|
||||
# Generated/local Codex state, including config.local.toml, stays ignored.
|
||||
|
||||
@@ -2,8 +2,147 @@ model = "gpt-5.5"
|
||||
model_reasoning_effort = "high"
|
||||
personality = "pragmatic"
|
||||
|
||||
# Portable Codex defaults. Machine-local additions are appended from
|
||||
# dotfiles/codex/config.local.toml by Home Manager.
|
||||
|
||||
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"]
|
||||
|
||||
[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]
|
||||
command = "npx"
|
||||
@@ -21,6 +160,16 @@ unified_exec = true
|
||||
apps = 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"]
|
||||
enabled = true
|
||||
|
||||
@@ -47,3 +196,6 @@ enabled = true
|
||||
|
||||
[plugins."browser-use@openai-bundled"]
|
||||
enabled = true
|
||||
|
||||
[tui.model_availability_nux]
|
||||
"gpt-5.5" = 4
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
general {
|
||||
lock_cmd = pidof hyprlock || hyprlock
|
||||
before_sleep_cmd = loginctl lock-session
|
||||
after_sleep_cmd = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
listener {
|
||||
timeout = 300
|
||||
on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver start
|
||||
on-resume = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop
|
||||
timeout = 900
|
||||
on-timeout = hypr-screensaver stop && hyprctl dispatch dpms off
|
||||
on-resume = hyprctl dispatch dpms on
|
||||
}
|
||||
|
||||
@@ -19,15 +19,15 @@ monitor=,preferred,auto,1
|
||||
# =============================================================================
|
||||
$terminal = ghostty --gtk-single-instance=false
|
||||
$fileManager = dolphin
|
||||
$shellUi = hypr_shell_ui
|
||||
$menu = $shellUi launcher
|
||||
$runMenu = $shellUi run
|
||||
$menu = rofi -show drun -show-icons
|
||||
$runMenu = rofi -show run
|
||||
|
||||
# =============================================================================
|
||||
# ENVIRONMENT VARIABLES
|
||||
# =============================================================================
|
||||
env = XCURSOR_SIZE,24
|
||||
env = QT_QPA_PLATFORMTHEME,qt6ct
|
||||
env = QT_QPA_PLATFORMTHEME,qt5ct
|
||||
# Used by ~/.config/hypr/scripts/* to keep workspace IDs bounded.
|
||||
env = HYPR_MAX_WORKSPACE,9
|
||||
|
||||
# =============================================================================
|
||||
@@ -246,8 +246,6 @@ $hyper = SUPER CTRL ALT
|
||||
# -----------------------------------------------------------------------------
|
||||
bind = $mainMod, P, exec, $menu
|
||||
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
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -255,15 +253,15 @@ bind = $mainMod SHIFT, Return, exec, $terminal
|
||||
# -----------------------------------------------------------------------------
|
||||
bind = $mainMod, TAB, hyprexpo:expo, toggle
|
||||
bind = $mainMod SHIFT, TAB, hyprexpo:expo, bring
|
||||
bind = $mainMod, Q, exec, hyprctl reload
|
||||
bind = $mainMod, Q, killactive,
|
||||
bind = $mainMod SHIFT, C, killactive,
|
||||
bind = $mainMod SHIFT, Q, exit,
|
||||
# Emacs-everywhere (like XMonad's emacs-everywhere)
|
||||
bind = $mainMod, E, exec, emacsclient --eval '(emacs-everywhere)'
|
||||
bind = $mainMod, V, exec, wl-paste | xdotool type --file -
|
||||
|
||||
# Chrome/Browser
|
||||
bind = $modAlt, C, exec, google-chrome-stable
|
||||
# Chrome/Browser (raise or spawn like XMonad's bindBringAndRaise)
|
||||
bind = $modAlt, C, exec, ~/.config/hypr/scripts/raise-or-run.sh google-chrome google-chrome-stable
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# SCRATCHPADS (managed by hyprscratch daemon with auto-dismiss)
|
||||
@@ -293,11 +291,11 @@ bind = $mainMod, S, hy3:movefocus, d
|
||||
bind = $mainMod, A, hy3:movefocus, l
|
||||
bind = $mainMod, D, hy3:movefocus, r
|
||||
|
||||
# Move windows (Mod + Shift + WASD)
|
||||
bind = $mainMod SHIFT, W, hy3:movewindow, u
|
||||
bind = $mainMod SHIFT, S, hy3:movewindow, d
|
||||
bind = $mainMod SHIFT, A, hy3:movewindow, l
|
||||
bind = $mainMod SHIFT, D, hy3:movewindow, r
|
||||
# Move windows (Mod + Shift + WASD) - hy3:movewindow with once=true for swapping
|
||||
bind = $mainMod SHIFT, W, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh u once
|
||||
bind = $mainMod SHIFT, S, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh d once
|
||||
bind = $mainMod SHIFT, A, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh l once
|
||||
bind = $mainMod SHIFT, D, exec, ~/.config/hypr/scripts/movewindow-follow-cursor.sh r once
|
||||
|
||||
# Resize windows (Mod + Ctrl + WASD)
|
||||
binde = $mainMod CTRL, W, resizeactive, 0 -50
|
||||
@@ -317,6 +315,13 @@ bind = $hyper SHIFT, S, movewindow, mon:d
|
||||
bind = $hyper SHIFT, A, movewindow, mon:l
|
||||
bind = $hyper SHIFT, D, movewindow, mon:r
|
||||
|
||||
# Shift to empty workspace on screen direction (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)
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -368,6 +373,31 @@ bind = $mainMod, N, hy3:killactive
|
||||
# hy3:setswallow - set a window to swallow newly spawned windows
|
||||
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
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -418,17 +448,37 @@ bind = $mainMod CTRL, 9, focusworkspaceoncurrentmonitor, 9
|
||||
# built-in per-monitor workspace history.
|
||||
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)
|
||||
bind = $mainMod, Z, focusmonitor, +1
|
||||
bind = $mainMod SHIFT, Z, movewindow, mon:+1
|
||||
|
||||
# Shift to empty workspace and view (like XMonad's shiftToEmptyAndView)
|
||||
bind = $mainMod SHIFT, H, exec, ~/.config/hypr/scripts/workspace-move-to-empty.sh
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# WINDOW MANAGEMENT
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
bind = $mainMod, G, exec, $shellUi window go
|
||||
bind = $mainMod, B, exec, $shellUi window bring
|
||||
bind = $mainMod SHIFT, B, exec, $shellUi window replace
|
||||
# Go to window (rofi window switcher with icons)
|
||||
bind = $mainMod, G, exec, ~/.config/hypr/scripts/go-to-window.sh
|
||||
|
||||
# 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
|
||||
@@ -491,8 +541,8 @@ bindm = $mainMod, mouse:272, movewindow
|
||||
bindm = $mainMod, mouse:273, resizewindow
|
||||
|
||||
# Scroll through workspaces
|
||||
bind = $mainMod, mouse_down, workspace, e+1
|
||||
bind = $mainMod, mouse_up, workspace, e-1
|
||||
bind = $mainMod, mouse_down, exec, ~/.config/hypr/scripts/workspace-scroll.sh +1
|
||||
bind = $mainMod, mouse_up, exec, ~/.config/hypr/scripts/workspace-scroll.sh -1
|
||||
|
||||
# =============================================================================
|
||||
# AUTOSTART
|
||||
@@ -502,7 +552,7 @@ bind = $mainMod, mouse_up, workspace, e-1
|
||||
# `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
|
||||
# starting any session-bound units. Separate `exec-once` commands race.
|
||||
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'
|
||||
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'
|
||||
# Force a fresh daemon after compositor restarts so hyprscratch doesn't keep a stale socket.
|
||||
exec-once = systemctl --user restart hyprscratch.service
|
||||
exec-once = hypridle
|
||||
|
||||
@@ -3,23 +3,17 @@ local mod_alt = "SUPER + ALT"
|
||||
local hyper = "SUPER + CTRL + ALT"
|
||||
|
||||
local terminal = "ghostty --gtk-single-instance=false"
|
||||
local shell_ui_command = "hypr_shell_ui"
|
||||
local launcher_command = shell_ui_command .. " launcher"
|
||||
local run_menu = shell_ui_command .. " run"
|
||||
local menu = "rofi -show drun -show-icons"
|
||||
local run_menu = "rofi -show run"
|
||||
|
||||
local max_workspace = 9
|
||||
local scratchpad_top_margin = 60
|
||||
local columns_layout = "nStack"
|
||||
local monocle_layout = "monocle"
|
||||
local minimized_workspace = "special:minimized"
|
||||
local tabbed_group_staging_workspace = "special:tabbed-monocle-staging"
|
||||
local current_layout = columns_layout
|
||||
local enable_nstack = true
|
||||
local enable_hyprexpo = true
|
||||
local configure_nstack_plugin_from_lua = false
|
||||
local workspace_layouts = {}
|
||||
local minimized_windows = {}
|
||||
local tabbed_workspace_groups = {}
|
||||
local window_picker_mode = nil
|
||||
local window_picker_candidates = {}
|
||||
local stack_update_timer = nil
|
||||
@@ -90,13 +84,6 @@ local function exec(command)
|
||||
return hl.dsp.exec_cmd(command)
|
||||
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)
|
||||
return function()
|
||||
if hl.plugin and hl.plugin.hyprexpo and hl.plugin.hyprexpo.expo then
|
||||
@@ -114,7 +101,7 @@ local function hyprexpo(action)
|
||||
end
|
||||
|
||||
local function apply_nstack_config()
|
||||
if verify_config or not enable_nstack or not configure_nstack_plugin_from_lua then
|
||||
if verify_config then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -139,7 +126,7 @@ local function apply_nstack_config()
|
||||
end
|
||||
|
||||
local function apply_hyprexpo_config()
|
||||
if verify_config or not enable_hyprexpo then
|
||||
if verify_config then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -274,26 +261,6 @@ local function tiled_window_count(workspace)
|
||||
return #tiled_windows(workspace)
|
||||
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 workspace = hl.get_workspace(tostring(workspace_id))
|
||||
if not workspace then
|
||||
@@ -327,7 +294,7 @@ local function find_empty_workspace(target_monitor, exclude_id)
|
||||
end
|
||||
|
||||
local function update_nstack_count()
|
||||
if not enable_nstack or current_layout ~= columns_layout then
|
||||
if current_layout ~= columns_layout then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -336,7 +303,6 @@ local function update_nstack_count()
|
||||
if count == 0 then
|
||||
return
|
||||
end
|
||||
count = math.max(count, 2)
|
||||
hl.dsp.layout("setstackcount " .. tostring(count))()
|
||||
end
|
||||
|
||||
@@ -420,16 +386,8 @@ local function toggle_columns_monocle()
|
||||
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 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
|
||||
if current_layout == monocle_layout then
|
||||
hl.dsp.layout("cyclenext")()
|
||||
update_monocle_notice()
|
||||
else
|
||||
@@ -438,10 +396,7 @@ local function monocle_next()
|
||||
end
|
||||
|
||||
local function monocle_prev()
|
||||
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
|
||||
if current_layout == monocle_layout then
|
||||
hl.dsp.layout("cycleprev")()
|
||||
update_monocle_notice()
|
||||
else
|
||||
@@ -450,7 +405,7 @@ local function monocle_prev()
|
||||
end
|
||||
|
||||
local function focus_direction(direction)
|
||||
if active_group_size() > 1 or current_layout == monocle_layout then
|
||||
if current_layout == monocle_layout then
|
||||
if direction == "up" or direction == "left" then
|
||||
monocle_prev()
|
||||
else
|
||||
@@ -462,15 +417,6 @@ local function focus_direction(direction)
|
||||
hl.dsp.focus({ direction = direction })()
|
||||
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)
|
||||
hl.dsp.focus({ workspace = tostring(workspace_id), on_current_monitor = true })()
|
||||
end
|
||||
@@ -508,165 +454,9 @@ local function focus_previous_workspace_for_monitor()
|
||||
end
|
||||
|
||||
local function move_window_to_workspace(workspace_id, follow, 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 })()
|
||||
hl.dsp.window.move({ workspace = tostring(workspace_id), follow = follow, window = window })()
|
||||
if follow then
|
||||
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
|
||||
|
||||
@@ -749,7 +539,7 @@ local function move_window_to_monitor(direction, follow)
|
||||
end
|
||||
|
||||
local original_monitor = hl.get_active_monitor()
|
||||
hl.dsp.window.move({ monitor = direction, follow = follow, window = window_selector(window) })()
|
||||
hl.dsp.window.move({ monitor = direction, follow = follow, window = window })()
|
||||
|
||||
if not follow and original_monitor then
|
||||
hl.dsp.focus({ monitor = original_monitor })()
|
||||
@@ -913,16 +703,15 @@ local function apply_scratchpad_geometry(name, window, target_monitor)
|
||||
x = monitor.x + math.floor((monitor.width - width) / 2)
|
||||
y = monitor.y + scratchpad_top_margin
|
||||
end
|
||||
local selector = window_selector(window)
|
||||
|
||||
hl.dsp.window.float({ action = "enable", window = selector })()
|
||||
hl.dsp.window.tag({ tag = "+scratchpad", window = selector })()
|
||||
hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = selector })()
|
||||
hl.dsp.window.resize({ x = width, y = height, relative = false, window = selector })()
|
||||
hl.dsp.window.move({ x = x, y = y, relative = false, window = selector })()
|
||||
hl.dsp.window.float({ action = "enable", window = window })()
|
||||
hl.dsp.window.tag({ tag = "+scratchpad", window = window })()
|
||||
hl.dsp.window.tag({ tag = "+scratchpad-" .. name, window = window })()
|
||||
hl.dsp.window.resize({ x = width, y = height, relative = false, window = window })()
|
||||
hl.dsp.window.move({ x = x, y = y, relative = false, window = window })()
|
||||
if def.dropdown then
|
||||
hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = selector })()
|
||||
hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = selector })()
|
||||
hl.dsp.window.set_prop({ prop = "border_size", value = "0", window = window })()
|
||||
hl.dsp.window.set_prop({ prop = "no_shadow", value = "1", window = window })()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -945,7 +734,7 @@ local function show_scratchpad_window(name, window, workspace, target_monitor)
|
||||
|
||||
remove_minimized_window(window)
|
||||
move_window_to_workspace(workspace.id, false, window)
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
schedule_scratchpad_geometry(name, window, target_monitor or hl.get_active_monitor())
|
||||
end
|
||||
|
||||
@@ -1031,29 +820,29 @@ local function activate_window_picker_candidate(index)
|
||||
end
|
||||
|
||||
if mode == "go" then
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
return
|
||||
end
|
||||
|
||||
local workspace = active_workspace()
|
||||
if mode == "bring" and workspace then
|
||||
move_window_to_workspace(workspace.id, false, window)
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
return
|
||||
end
|
||||
|
||||
if mode == "minimized" and workspace then
|
||||
remove_minimized_window(window)
|
||||
restore_minimized_window(window, workspace)
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
return
|
||||
end
|
||||
|
||||
if mode == "replace" then
|
||||
local focused = hl.get_active_window()
|
||||
if focused and focused ~= window then
|
||||
hl.dsp.window.swap({ target = window_selector(window), window = window_selector(focused) })()
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.window.swap({ target = window, window = focused })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1150,7 +939,7 @@ local function focus_next_class()
|
||||
local next_class = classes[(current_index % #classes) + 1]
|
||||
local target = first_by_class[next_class]
|
||||
if target then
|
||||
hl.dsp.focus({ window = window_selector(target) })()
|
||||
hl.dsp.focus({ window = target })()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1189,7 +978,7 @@ local function raise_or_spawn(class_fragment, command)
|
||||
local fragment = string.lower(class_fragment)
|
||||
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
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -1219,7 +1008,7 @@ local function restore_last_minimized()
|
||||
local window = table.remove(minimized_windows)
|
||||
if window and window.address and is_minimized_window(window) then
|
||||
restore_minimized_window(window, workspace)
|
||||
hl.dsp.focus({ window = window_selector(window) })()
|
||||
hl.dsp.focus({ window = window })()
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -1314,10 +1103,8 @@ local function toggle_scratchpad(name)
|
||||
end
|
||||
end
|
||||
|
||||
if enable_nstack then
|
||||
hl.plugin.load("/run/current-system/sw/lib/libhyprNStack.so")
|
||||
end
|
||||
if enable_hyprexpo and not verify_config then
|
||||
if not verify_config then
|
||||
hl.plugin.load("/run/current-system/sw/lib/libhyprexpo.so")
|
||||
end
|
||||
|
||||
@@ -1371,28 +1158,19 @@ hl.config({
|
||||
workspace_back_and_forth = true,
|
||||
},
|
||||
group = {
|
||||
group_on_movetoworkspace = false,
|
||||
col = {
|
||||
border_active = "rgba(edb443ff)",
|
||||
border_inactive = "rgba(091f2eff)",
|
||||
},
|
||||
groupbar = {
|
||||
enabled = true,
|
||||
blur = true,
|
||||
font_size = 13,
|
||||
gradients = true,
|
||||
height = 26,
|
||||
indicator_gap = 0,
|
||||
indicator_height = 1,
|
||||
rounding = 5,
|
||||
gradient_rounding = 5,
|
||||
text_padding = 8,
|
||||
font_size = 12,
|
||||
height = 22,
|
||||
col = {
|
||||
active = "rgba(edb443ff)",
|
||||
inactive = "rgba(101820f2)",
|
||||
inactive = "rgba(091f2eff)",
|
||||
},
|
||||
text_color = "rgba(091018ff)",
|
||||
text_color_inactive = "rgba(f2f5f7ff)",
|
||||
text_color = "rgba(091f2eff)",
|
||||
},
|
||||
},
|
||||
misc = {
|
||||
@@ -1407,50 +1185,15 @@ 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("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
||||
|
||||
local animations = {
|
||||
{ leaf = "global", enabled = true, speed = 8, bezier = "default" },
|
||||
|
||||
{ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||
{ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" },
|
||||
{ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" },
|
||||
{ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" },
|
||||
|
||||
{ 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
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" })
|
||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 6, bezier = "overshoot", style = "gnomed" })
|
||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 5, bezier = "smoothInOut", style = "gnomed" })
|
||||
hl.animation({ leaf = "windowsMove", enabled = true, speed = 6, bezier = "smoothOut" })
|
||||
hl.animation({ leaf = "border", enabled = false })
|
||||
hl.animation({ leaf = "borderangle", enabled = false })
|
||||
hl.animation({ leaf = "fade", enabled = true, speed = 5, 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" })
|
||||
|
||||
local function apply_rules()
|
||||
if verify_config then
|
||||
@@ -1465,10 +1208,6 @@ local function apply_rules()
|
||||
hl.window_rule({ match = { title = "^(Open File)$" }, float = true })
|
||||
hl.window_rule({ match = { title = "^(Save File)$" }, 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({
|
||||
match = { class = "^(.*[Rr]umno.*)$" },
|
||||
float = true,
|
||||
@@ -1487,21 +1226,27 @@ local function apply_rules()
|
||||
})
|
||||
end
|
||||
|
||||
bind(main_mod .. " + P", exec(launcher_command))
|
||||
apply_rules()
|
||||
|
||||
bind(main_mod .. " + P", exec(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 .. " + Q", exec("hyprctl reload"))
|
||||
bind(main_mod .. " + Q", hl.dsp.window.close())
|
||||
bind(main_mod .. " + SHIFT + C", hl.dsp.window.close())
|
||||
bind(main_mod .. " + SHIFT + Q", hl.dsp.exit())
|
||||
bind(main_mod .. " + E", exec("emacsclient --eval '(emacs-everywhere)'"))
|
||||
bind(main_mod .. " + V", exec("wl-paste | xdotool type --file -"))
|
||||
bind(main_mod .. " + Tab", hyprexpo("toggle"))
|
||||
bind(main_mod .. " + SHIFT + Tab", hyprexpo("bring"))
|
||||
bind(main_mod .. " + G", exec(shell_ui_command .. " window go"))
|
||||
bind(main_mod .. " + B", exec(shell_ui_command .. " window bring"))
|
||||
bind(main_mod .. " + SHIFT + B", exec(shell_ui_command .. " window replace"))
|
||||
bind(main_mod .. " + G", function()
|
||||
enter_window_picker("go")
|
||||
end)
|
||||
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()
|
||||
focus_direction("up")
|
||||
@@ -1516,18 +1261,10 @@ bind(main_mod .. " + D", function()
|
||||
focus_direction("right")
|
||||
end)
|
||||
|
||||
bind(main_mod .. " + SHIFT + W", function()
|
||||
swap_direction("up")
|
||||
end)
|
||||
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 .. " + SHIFT + W", hl.dsp.window.swap({ direction = "up" }))
|
||||
bind(main_mod .. " + SHIFT + S", hl.dsp.window.swap({ direction = "down" }))
|
||||
bind(main_mod .. " + SHIFT + A", hl.dsp.window.swap({ direction = "left" }))
|
||||
bind(main_mod .. " + SHIFT + D", hl.dsp.window.swap({ direction = "right" }))
|
||||
|
||||
bind(main_mod .. " + CTRL + W", function()
|
||||
move_window_to_monitor("u", false)
|
||||
@@ -1601,9 +1338,13 @@ bind(hyper .. " + SHIFT + D", function()
|
||||
move_window_to_monitor("r", true)
|
||||
end)
|
||||
|
||||
bind(main_mod .. " + Space", gather_workspace_into_tabbed_group)
|
||||
bind(main_mod .. " + SHIFT + Space", force_columns_layout)
|
||||
bind(main_mod .. " + CTRL + Space", gather_workspace_into_tabbed_group)
|
||||
bind(main_mod .. " + Space", toggle_columns_monocle)
|
||||
bind(main_mod .. " + SHIFT + Space", function()
|
||||
set_layout(columns_layout)
|
||||
end)
|
||||
bind(main_mod .. " + CTRL + Space", function()
|
||||
set_layout(monocle_layout)
|
||||
end)
|
||||
bind(main_mod .. " + bracketright", monocle_next)
|
||||
bind(main_mod .. " + bracketleft", monocle_prev)
|
||||
bind(main_mod .. " + F", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
|
||||
@@ -1724,8 +1465,7 @@ bind(main_mod .. " + mouse:273", hl.dsp.window.resize())
|
||||
hl.on("hyprland.start", function()
|
||||
apply_nstack_config()
|
||||
apply_hyprexpo_config()
|
||||
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("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'")
|
||||
hl.exec_cmd("hypridle")
|
||||
hl.exec_cmd("wl-paste --type text --watch cliphist store")
|
||||
hl.exec_cmd("wl-paste --type image --watch cliphist store")
|
||||
@@ -1736,7 +1476,6 @@ end)
|
||||
|
||||
hl.on("config.reloaded", apply_nstack_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.destroy", schedule_nstack_count_update)
|
||||
|
||||
40
dotfiles/config/hypr/scripts/bring-window.sh
Executable file
40
dotfiles/config/hypr/scripts/bring-window.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/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
|
||||
15
dotfiles/config/hypr/scripts/cycle-layout.sh
Executable file
15
dotfiles/config/hypr/scripts/cycle-layout.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Cycle between master and dwindle layouts
|
||||
# Like XMonad's NextLayout
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CURRENT=$(hyprctl getoption general:layout -j | jq -r '.str')
|
||||
|
||||
if [ "$CURRENT" = "master" ]; then
|
||||
hyprctl keyword general:layout dwindle
|
||||
notify-send "Layout" "Switched to Dwindle (binary tree)"
|
||||
else
|
||||
hyprctl keyword general:layout master
|
||||
notify-send "Layout" "Switched to Master (XMonad-like)"
|
||||
fi
|
||||
72
dotfiles/config/hypr/scripts/find-empty-workspace.sh
Executable file
72
dotfiles/config/hypr/scripts/find-empty-workspace.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/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
|
||||
|
||||
48
dotfiles/config/hypr/scripts/focus-next-class.sh
Executable file
48
dotfiles/config/hypr/scripts/focus-next-class.sh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
# Focus next window of a different class (like XMonad's focusNextClass)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Get focused window class
|
||||
FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class')
|
||||
FOCUSED_ADDR=$(hyprctl activewindow -j | jq -r '.address')
|
||||
|
||||
if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then
|
||||
# No focused window, just focus any window
|
||||
hyprctl dispatch cyclenext
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get all unique classes
|
||||
ALL_CLASSES=$(hyprctl clients -j | jq -r '[.[] | select(.workspace.id >= 0) | .class] | unique | .[]')
|
||||
|
||||
# Get sorted list of classes
|
||||
CLASSES_ARRAY=()
|
||||
while IFS= read -r class; do
|
||||
CLASSES_ARRAY+=("$class")
|
||||
done <<< "$ALL_CLASSES"
|
||||
|
||||
# Find current class index and get next class
|
||||
CURRENT_INDEX=-1
|
||||
for i in "${!CLASSES_ARRAY[@]}"; do
|
||||
if [ "${CLASSES_ARRAY[$i]}" = "$FOCUSED_CLASS" ]; then
|
||||
CURRENT_INDEX=$i
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $CURRENT_INDEX -eq -1 ] || [ ${#CLASSES_ARRAY[@]} -le 1 ]; then
|
||||
# Only one class or class not found
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get next class (wrapping around)
|
||||
NEXT_INDEX=$(( (CURRENT_INDEX + 1) % ${#CLASSES_ARRAY[@]} ))
|
||||
NEXT_CLASS="${CLASSES_ARRAY[$NEXT_INDEX]}"
|
||||
|
||||
# Find first window of next class
|
||||
NEXT_WINDOW=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$NEXT_CLASS\" and .workspace.id >= 0) | .address" | head -1)
|
||||
|
||||
if [ -n "$NEXT_WINDOW" ]; then
|
||||
hyprctl dispatch focuswindow "address:$NEXT_WINDOW"
|
||||
fi
|
||||
30
dotfiles/config/hypr/scripts/gather-class.sh
Executable file
30
dotfiles/config/hypr/scripts/gather-class.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
# Gather all windows of the same class as focused window (like XMonad's gatherThisClass)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Get focused window class
|
||||
FOCUSED_CLASS=$(hyprctl activewindow -j | jq -r '.class')
|
||||
CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
|
||||
|
||||
if [ "$FOCUSED_CLASS" = "null" ] || [ -z "$FOCUSED_CLASS" ]; then
|
||||
notify-send "Gather Class" "No focused window"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find all windows with same class on other workspaces
|
||||
WINDOWS=$(hyprctl clients -j | jq -r ".[] | select(.class == \"$FOCUSED_CLASS\" and .workspace.id != $CURRENT_WS and .workspace.id >= 0) | .address")
|
||||
|
||||
if [ -z "$WINDOWS" ]; then
|
||||
notify-send "Gather Class" "No other windows of class '$FOCUSED_CLASS'"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Move each window to current workspace
|
||||
COUNT=0
|
||||
for ADDR in $WINDOWS; do
|
||||
hyprctl dispatch movetoworkspace "$CURRENT_WS,address:$ADDR"
|
||||
COUNT=$((COUNT + 1))
|
||||
done
|
||||
|
||||
notify-send "Gather Class" "Gathered $COUNT windows of class '$FOCUSED_CLASS'"
|
||||
33
dotfiles/config/hypr/scripts/go-to-window.sh
Executable file
33
dotfiles/config/hypr/scripts/go-to-window.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/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
|
||||
49
dotfiles/config/hypr/scripts/minimize-active.sh
Executable file
49
dotfiles/config/hypr/scripts/minimize-active.sh
Executable file
@@ -0,0 +1,49 @@
|
||||
#!/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
|
||||
39
dotfiles/config/hypr/scripts/minimized-cancel.sh
Executable file
39
dotfiles/config/hypr/scripts/minimized-cancel.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/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
|
||||
|
||||
40
dotfiles/config/hypr/scripts/minimized-mode.sh
Executable file
40
dotfiles/config/hypr/scripts/minimized-mode.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/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
|
||||
|
||||
83
dotfiles/config/hypr/scripts/movewindow-follow-cursor.sh
Executable file
83
dotfiles/config/hypr/scripts/movewindow-follow-cursor.sh
Executable file
@@ -0,0 +1,83 @@
|
||||
#!/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
|
||||
19
dotfiles/config/hypr/scripts/raise-or-run.sh
Executable file
19
dotfiles/config/hypr/scripts/raise-or-run.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Raise existing window or run command (like XMonad's raiseNextMaybe)
|
||||
# Usage: raise-or-run.sh <class-pattern> <command>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CLASS_PATTERN="$1"
|
||||
COMMAND="$2"
|
||||
|
||||
# Find windows matching the class pattern
|
||||
MATCHING=$(hyprctl clients -j | jq -r ".[] | select(.class | test(\"$CLASS_PATTERN\"; \"i\")) | .address" | head -1)
|
||||
|
||||
if [ -n "$MATCHING" ]; then
|
||||
# Window exists, focus it
|
||||
hyprctl dispatch focuswindow "address:$MATCHING"
|
||||
else
|
||||
# No matching window, run the command
|
||||
exec $COMMAND
|
||||
fi
|
||||
43
dotfiles/config/hypr/scripts/replace-window.sh
Executable file
43
dotfiles/config/hypr/scripts/replace-window.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/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
|
||||
43
dotfiles/config/hypr/scripts/shift-to-empty-on-screen.sh
Executable file
43
dotfiles/config/hypr/scripts/shift-to-empty-on-screen.sh
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/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"
|
||||
52
dotfiles/config/hypr/scripts/swap-workspaces.sh
Executable file
52
dotfiles/config/hypr/scripts/swap-workspaces.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/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
|
||||
51
dotfiles/config/hypr/scripts/toggle-scratchpad.sh
Executable file
51
dotfiles/config/hypr/scripts/toggle-scratchpad.sh
Executable file
@@ -0,0 +1,51 @@
|
||||
#!/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"
|
||||
86
dotfiles/config/hypr/scripts/unminimize-last.sh
Executable file
86
dotfiles/config/hypr/scripts/unminimize-last.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/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
|
||||
|
||||
66
dotfiles/config/hypr/scripts/window-icon-map.sh
Executable file
66
dotfiles/config/hypr/scripts/window-icon-map.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/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}"
|
||||
}
|
||||
16
dotfiles/config/hypr/scripts/workspace-goto-empty.sh
Executable file
16
dotfiles/config/hypr/scripts/workspace-goto-empty.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/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
|
||||
|
||||
16
dotfiles/config/hypr/scripts/workspace-move-to-empty.sh
Executable file
16
dotfiles/config/hypr/scripts/workspace-move-to-empty.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/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
|
||||
|
||||
42
dotfiles/config/hypr/scripts/workspace-scroll.sh
Executable file
42
dotfiles/config/hypr/scripts/workspace-scroll.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/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"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777452249,
|
||||
"narHash": "sha256-Emhn9sIFRVyIlUULDuYjeFcYJld6EAD31TGasYwQsWg=",
|
||||
"ref": "refs/heads/master",
|
||||
"rev": "9a6463e68c7bc0a712e49d9ba6c6d1b764260cd7",
|
||||
"revCount": 2295,
|
||||
"lastModified": 1777319252,
|
||||
"narHash": "sha256-mPft6i8ReJAvW2LdylFI6FF6NFGa1HMa3RNbisfAsbc=",
|
||||
"ref": "refs/heads/codex/fix-gdk-backend-strut-detection",
|
||||
"rev": "c2cee23fc57384cd322d589944129e6c31d4f0fd",
|
||||
"revCount": 2288,
|
||||
"type": "git",
|
||||
"url": "file:///home/imalison/dotfiles/dotfiles/config/taffybar/taffybar"
|
||||
},
|
||||
|
||||
@@ -121,6 +121,13 @@
|
||||
(oa: {
|
||||
doHaddock = 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).
|
||||
librarySystemDepends = (oa.librarySystemDepends or []) ++ [ pkgs.gtk-layer-shell ];
|
||||
});
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,5 +0,0 @@
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB |
Submodule dotfiles/config/taffybar/taffybar updated: 9a6463e68c...59e3c75990
@@ -40,16 +40,6 @@
|
||||
-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. */
|
||||
.ram-swap .icon-label > .icon {
|
||||
/* Different glyphs have different visual widths; fix the icon column width
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Main (main) where
|
||||
@@ -47,21 +46,11 @@ import System.Taffybar.SimpleConfig
|
||||
import System.Taffybar.Util (getPixbufFromFilePath, maybeTCombine, postGUIASync, (<|||>))
|
||||
import System.Taffybar.Widget
|
||||
import qualified System.Taffybar.Widget.ASUS as ASUS
|
||||
import System.Taffybar.Widget.AnthropicUsage
|
||||
( AnthropicUsageDisplayMode (AnthropicUsageDisplayRemaining),
|
||||
AnthropicUsageStackConfig (..),
|
||||
anthropicUsageStackNewWith,
|
||||
defaultAnthropicUsageStackConfig,
|
||||
)
|
||||
import System.Taffybar.Widget.AnthropicUsage (anthropicUsageStackNew)
|
||||
import System.Taffybar.Widget.CPUMonitor (cpuMonitorNew)
|
||||
import System.Taffybar.Widget.Generic.Graph (GraphConfig (..), GraphDirection (..), GraphStyle (..), defaultGraphConfig)
|
||||
import qualified System.Taffybar.Widget.NetworkManager as NetworkManager
|
||||
import System.Taffybar.Widget.OpenAIUsage
|
||||
( OpenAIUsageDisplayMode (OpenAIUsageDisplayRemaining),
|
||||
OpenAIUsageStackConfig (..),
|
||||
defaultOpenAIUsageStackConfig,
|
||||
openAIUsageStackNewWith,
|
||||
)
|
||||
import System.Taffybar.Widget.OpenAIUsage (openAIUsageStackNew)
|
||||
import qualified System.Taffybar.Widget.PulseAudio as PulseAudio
|
||||
import System.Taffybar.Widget.SNIMenu (withNmAppletMenu)
|
||||
import System.Taffybar.Widget.SNITray
|
||||
@@ -76,7 +65,7 @@ import System.Taffybar.Widget.SNITray.PrioritizedCollapsible
|
||||
sniTrayPrioritizedCollapsibleNewFromParams,
|
||||
)
|
||||
import qualified System.Taffybar.Widget.ScreenLock as ScreenLock
|
||||
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, pixbufNewFromFileAtScaleByHeight, widgetSetClassGI)
|
||||
import System.Taffybar.Widget.Util (backgroundLoop, buildContentsBox, buildIconLabelBox, loadPixbufByName, widgetSetClassGI)
|
||||
import qualified System.Taffybar.Widget.Wlsunset as Wlsunset
|
||||
import qualified System.Taffybar.Widget.Workspaces as Workspaces
|
||||
import System.Taffybar.WindowIcon (pixBufFromColor)
|
||||
@@ -518,10 +507,10 @@ simplifiedScreensaverWidget =
|
||||
then return False
|
||||
else case button of
|
||||
1 -> do
|
||||
void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver toggle >/dev/null 2>&1"
|
||||
void $ spawnCommand "hypr-screensaver toggle >/dev/null 2>&1"
|
||||
return True
|
||||
3 -> do
|
||||
void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop >/dev/null 2>&1"
|
||||
void $ spawnCommand "hypr-screensaver stop >/dev/null 2>&1"
|
||||
return True
|
||||
_ -> return False
|
||||
Gtk.widgetShowAll ebox
|
||||
@@ -562,40 +551,13 @@ wakeupDebugWidget :: TaffyIO Gtk.Widget
|
||||
wakeupDebugWidget =
|
||||
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 =
|
||||
usageSectionWidget "openai-usage" "openai-symbol.svg" "OpenAI usage" $
|
||||
openAIUsageStackNewWith
|
||||
defaultOpenAIUsageStackConfig
|
||||
{ openAIUsageStackDefaultDisplayMode = OpenAIUsageDisplayRemaining
|
||||
}
|
||||
decorateWithClassAndBoxM "openai-usage" openAIUsageStackNew
|
||||
|
||||
anthropicUsageWidget :: TaffyIO Gtk.Widget
|
||||
anthropicUsageWidget =
|
||||
usageSectionWidget "anthropic-usage" "claude-symbol.svg" "Anthropic usage" $
|
||||
anthropicUsageStackNewWith
|
||||
defaultAnthropicUsageStackConfig
|
||||
{ anthropicUsageStackDefaultDisplayMode = AnthropicUsageDisplayRemaining
|
||||
}
|
||||
decorateWithClassAndBoxM "anthropic-usage" anthropicUsageStackNew
|
||||
|
||||
sniPriorityVisibilityThresholdDefault :: Int
|
||||
sniPriorityVisibilityThresholdDefault = 0
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/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,48 +2,27 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
|
||||
pid_file="$state_dir/mpvpaper.pid"
|
||||
event_log="$state_dir/events.log"
|
||||
mkdir -p "$state_dir"
|
||||
|
||||
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() {
|
||||
cat <<'EOF'
|
||||
Usage: hypr-screensaver <start|stop|toggle|status|session>
|
||||
|
||||
Commands:
|
||||
start Launch the screensaver as a Wayland layer-shell overlay.
|
||||
stop Stop any running screensaver overlay.
|
||||
start Launch the screensaver on every Hyprland monitor.
|
||||
stop Stop any running screensaver windows.
|
||||
toggle Start if stopped, otherwise stop.
|
||||
status Exit 0 if the screensaver overlay is running, otherwise exit 1.
|
||||
session Compatibility alias for start.
|
||||
status Exit 0 if any screensaver window is running, otherwise exit 1.
|
||||
session Run the configured screensaver payload for one monitor.
|
||||
|
||||
By default, start chooses a random media file from:
|
||||
/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:
|
||||
The default payload is an mpv-rendered lavfi animation. You can override the
|
||||
source with HYPR_SCREENSAVER_SOURCE, for example:
|
||||
HYPR_SCREENSAVER_SOURCE='/path/to/video.mp4'
|
||||
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
|
||||
}
|
||||
|
||||
@@ -51,11 +30,19 @@ monitors_json() {
|
||||
hyprctl -j monitors
|
||||
}
|
||||
|
||||
log_event() {
|
||||
printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" >>"$event_log"
|
||||
monitor_names() {
|
||||
monitors_json | jq -r '.[].name'
|
||||
}
|
||||
|
||||
legacy_screensaver_window_pids() {
|
||||
monitor_specs() {
|
||||
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" '
|
||||
.[]
|
||||
| select((.title // "") | startswith($prefix))
|
||||
@@ -65,170 +52,106 @@ legacy_screensaver_window_pids() {
|
||||
|
||||
is_running() {
|
||||
local pid
|
||||
if [ -f "$pid_file" ]; then
|
||||
for pid in $(screensaver_window_pids); do
|
||||
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")"
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
default_source() {
|
||||
local size width height
|
||||
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}"
|
||||
|
||||
local width="$1"
|
||||
local height="$2"
|
||||
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"
|
||||
}
|
||||
|
||||
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() {
|
||||
local source output layer options pid
|
||||
local current_monitor spec monitor width height pid
|
||||
|
||||
if is_running; then
|
||||
log_event "start ignored: already running pid=$(<"$pid_file")"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
stop
|
||||
current_monitor="$(focused_monitor || true)"
|
||||
|
||||
source="${HYPR_SCREENSAVER_SOURCE:-}"
|
||||
if [ -z "$source" ]; then
|
||||
source="$(random_source || true)"
|
||||
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 &
|
||||
while IFS= read -r spec; do
|
||||
monitor="$(jq -r '.name' <<<"$spec")"
|
||||
width="$(jq -r '.width' <<<"$spec")"
|
||||
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" > "$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
|
||||
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
|
||||
log_event "start ok pid=$pid"
|
||||
}
|
||||
|
||||
stop() {
|
||||
local pid legacy_pid
|
||||
local pid pid_file
|
||||
|
||||
if [ -f "$pid_file" ]; then
|
||||
pid="$(<"$pid_file")"
|
||||
log_event "stop pid=$pid"
|
||||
for pid in $(screensaver_window_pids); do
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
done
|
||||
|
||||
shopt -s nullglob
|
||||
for pid_file in "$state_dir"/*.pid; do
|
||||
pid="$(<"$pid_file")"
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
pkill -TERM -P "$pid" >/dev/null 2>&1 || true
|
||||
rm -f "$pid_file"
|
||||
else
|
||||
log_event "stop with no pid file"
|
||||
done
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
exec nix shell nixpkgs#mpv --command mpv "${mpv_args[@]}"
|
||||
}
|
||||
|
||||
status() {
|
||||
@@ -253,7 +176,7 @@ case "${1:-}" in
|
||||
status
|
||||
;;
|
||||
session)
|
||||
start
|
||||
session
|
||||
;;
|
||||
""|-h|--help|help)
|
||||
usage
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
#!/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())
|
||||
@@ -1,78 +0,0 @@
|
||||
#!/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
|
||||
@@ -1,275 +0,0 @@
|
||||
#!/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,7 +69,6 @@
|
||||
multiplexerAliases = import ../../shared/multiplexer-aliases.nix;
|
||||
|
||||
excludedTopLevelEntries = [
|
||||
"codex"
|
||||
"config"
|
||||
];
|
||||
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
...
|
||||
}: let
|
||||
cfg = config.myModules.codexGeneratedSkills;
|
||||
oos = config.lib.file.mkOutOfStoreSymlink;
|
||||
in {
|
||||
options.myModules.codexGeneratedSkills = {
|
||||
enable = lib.mkEnableOption "Codex home setup";
|
||||
enable = lib.mkEnableOption "generated Codex skill setup";
|
||||
|
||||
codexHome = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
@@ -16,12 +15,6 @@ in {
|
||||
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 {
|
||||
type = lib.types.str;
|
||||
default = "${cfg.codexHome}/skills";
|
||||
@@ -36,67 +29,6 @@ in {
|
||||
};
|
||||
|
||||
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"] ''
|
||||
codex_home=${lib.escapeShellArg cfg.codexHome}
|
||||
skills_dir=${lib.escapeShellArg cfg.skillsDir}
|
||||
|
||||
@@ -29,14 +29,12 @@
|
||||
./laptop.nix
|
||||
./nix.nix
|
||||
./notifications-tray-icon.nix
|
||||
./noctalia.nix
|
||||
./nvidia.nix
|
||||
./options.nix
|
||||
./plasma.nix
|
||||
./postgres.nix
|
||||
./rabbitmq.nix
|
||||
./quickshell.nix
|
||||
./remote-hyprland.nix
|
||||
./secrets.nix
|
||||
./ssh.nix
|
||||
./sni.nix
|
||||
|
||||
@@ -6,20 +6,7 @@
|
||||
makeEnable,
|
||||
...
|
||||
}:
|
||||
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 {
|
||||
makeEnable config "myModules.desktop" true {
|
||||
services.greenclip.enable = true;
|
||||
imports = [
|
||||
./fonts.nix
|
||||
@@ -53,11 +40,8 @@ let
|
||||
enable = true;
|
||||
};
|
||||
|
||||
environment.sessionVariables = {
|
||||
# This is for the benefit of VSCODE running natively in wayland
|
||||
NIXOS_OZONE_WL = "1";
|
||||
IM_HYPRLAND_SHELL_UI = cfg.shellUi;
|
||||
};
|
||||
environment.sessionVariables.NIXOS_OZONE_WL = "1";
|
||||
|
||||
system.activationScripts.playwrightChromeCompat.text = lib.optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
|
||||
# Playwright's Chrome channel lookup expects the FHS path below.
|
||||
@@ -103,8 +87,6 @@ let
|
||||
|
||||
environment.systemPackages = with pkgs;
|
||||
[
|
||||
desktopShellUi
|
||||
|
||||
# Appearance
|
||||
adwaita-icon-theme
|
||||
hicolor-icon-theme
|
||||
@@ -187,18 +169,4 @@ let
|
||||
]
|
||||
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,8 +1,5 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
...
|
||||
}: let
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
# Replicate the useful part of rcm/rcup:
|
||||
# - dotfiles live in ~/dotfiles/dotfiles (no leading dots in the repo)
|
||||
# - links in $HOME add a leading '.' to the first path component
|
||||
@@ -19,9 +16,6 @@
|
||||
srcDotfiles = ../dotfiles;
|
||||
|
||||
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.
|
||||
"lib"
|
||||
# Avoid colliding with HM-generated xdg.configFile entries for now.
|
||||
@@ -30,23 +24,25 @@
|
||||
"emacs.d"
|
||||
];
|
||||
|
||||
firstComponent = rel: let
|
||||
parts = lib.splitString "/" rel;
|
||||
in
|
||||
lib.elemAt parts 0;
|
||||
firstComponent = rel:
|
||||
let parts = lib.splitString "/" rel;
|
||||
in lib.elemAt parts 0;
|
||||
|
||||
isExcluded = rel: lib.elem (firstComponent rel) excludedTop;
|
||||
|
||||
listFilesRec = dir: let
|
||||
listFilesRec = dir:
|
||||
let
|
||||
entries = builtins.readDir dir;
|
||||
names = builtins.attrNames entries;
|
||||
go = name: let
|
||||
go = name:
|
||||
let
|
||||
ty = entries.${name};
|
||||
path = dir + "/${name}";
|
||||
in
|
||||
if ty == "directory"
|
||||
then map (p: "${name}/${p}") (listFilesRec path)
|
||||
else [name];
|
||||
if ty == "directory" then
|
||||
map (p: "${name}/${p}") (listFilesRec path)
|
||||
else
|
||||
[ name ];
|
||||
in
|
||||
lib.concatLists (map go names);
|
||||
|
||||
@@ -57,10 +53,9 @@
|
||||
lib.nameValuePair ".${rel}" {
|
||||
source = oos "${worktreeDotfiles}/${rel}";
|
||||
};
|
||||
in {
|
||||
imports = [
|
||||
../nix-shared/home-manager/codex-generated-skills.nix
|
||||
];
|
||||
in
|
||||
{
|
||||
imports = [ ../nix-shared/home-manager/codex-generated-skills.nix ];
|
||||
|
||||
home.file =
|
||||
builtins.listToAttrs (map mkManaged managedRelFiles);
|
||||
@@ -79,4 +74,5 @@ in {
|
||||
echo "Skipping ~/.emacs.d relink because it is not a symlink" >&2
|
||||
fi
|
||||
'';
|
||||
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ makeEnable config "myModules.extra" false {
|
||||
signal-desktop
|
||||
gource
|
||||
gimp
|
||||
kef
|
||||
roborock-control
|
||||
texlive.combined.scheme-full
|
||||
tor
|
||||
yt-dlp
|
||||
|
||||
306
nixos/flake.lock
generated
306
nixos/flake.lock
generated
@@ -335,15 +335,15 @@
|
||||
"flake-compat_3": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -353,13 +353,13 @@
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -367,11 +367,11 @@
|
||||
"flake-compat_5": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -502,24 +502,6 @@
|
||||
"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": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
@@ -685,7 +667,6 @@
|
||||
"gitignore_3": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"imalison-taffybar",
|
||||
"taffybar",
|
||||
"weeder-nix",
|
||||
"pre-commit-hooks",
|
||||
@@ -731,7 +712,7 @@
|
||||
"hercules-ci-effects_2": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts_5",
|
||||
"nixpkgs": "nixpkgs_7"
|
||||
"nixpkgs": "nixpkgs_6"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1701009247,
|
||||
@@ -799,16 +780,15 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777475074,
|
||||
"narHash": "sha256-shgepEMtMB532/df5QfociNzTiqimuoBbJssw0WPVH4=",
|
||||
"lastModified": 1777317717,
|
||||
"narHash": "sha256-Rj4vx0RvEWtnpnizggWRtrGe092bXiGLLt0WijwYWtI=",
|
||||
"owner": "colonelpanic8",
|
||||
"repo": "hyprNStack",
|
||||
"rev": "d43c8a506b32dc5057fdce0569c38f401c01eb60",
|
||||
"rev": "94607cd53f2ddac88f6b26261393275e7dd590ef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "colonelpanic8",
|
||||
"ref": "hyprland-lua-integration",
|
||||
"repo": "hyprNStack",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -1104,16 +1084,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777471981,
|
||||
"narHash": "sha256-cd3pQg+vKv6vht4xzButsi/Kaw9P4d3itm46jYXyiDM=",
|
||||
"owner": "colonelpanic8",
|
||||
"lastModified": 1767020608,
|
||||
"narHash": "sha256-BSRT1Uu1ot4WfMfZc6KW0nwpmt2xl9wpUqmH/JoMTfk=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprland-plugins",
|
||||
"rev": "725e354bbee982566068b5b90fec4fcd787c1036",
|
||||
"rev": "d7b67e8f4ba8ebeee4ce899348fcee6291512169",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "colonelpanic8",
|
||||
"ref": "hyprexpo-v0.53.0-custom",
|
||||
"owner": "hyprwm",
|
||||
"ref": "v0.53.0",
|
||||
"repo": "hyprland-plugins",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -1135,16 +1115,16 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777492340,
|
||||
"narHash": "sha256-9uRI/opXD+zOK2BLlzQ2NluRJL1SRSdUO2tlvbUJ7Ys=",
|
||||
"lastModified": 1777413654,
|
||||
"narHash": "sha256-lVGYGUWf9ynV5lR8QAygAfmYRkko1btIe26UcQ1bGXw=",
|
||||
"owner": "colonelpanic8",
|
||||
"repo": "hyprland-plugins",
|
||||
"rev": "ca1992973b5fb8ab95e88e8ffba16792c41568ae",
|
||||
"rev": "ff36c04b270c26fcd53e623fc688e4eb41672897",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "colonelpanic8",
|
||||
"ref": "codex/fix-main-ci",
|
||||
"ref": "hyprexpo-lua-hyprland",
|
||||
"repo": "hyprland-plugins",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -1548,7 +1528,9 @@
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"taffybar": "taffybar",
|
||||
"taffybar": [
|
||||
"taffybar"
|
||||
],
|
||||
"xmonad": [
|
||||
"xmonad"
|
||||
]
|
||||
@@ -1598,11 +1580,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777434099,
|
||||
"narHash": "sha256-GutKXyfGI7o89Dge4bP0yt0CQn1rqA6LyYDOH4GemdE=",
|
||||
"lastModified": 1777261868,
|
||||
"narHash": "sha256-30E1RBr0FGrf1IdXi2OKua+vQ4sUvjwUq6lfC1qcBug=",
|
||||
"owner": "colonelpanic8",
|
||||
"repo": "keepbook",
|
||||
"rev": "240fe454c26e7dbdd25a2fa4f0b436bf1c4f937b",
|
||||
"rev": "2e178e7ba864af0a880456dca241615dd75afd24",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -1629,10 +1611,10 @@
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_4",
|
||||
"flake-compat": "flake-compat_3",
|
||||
"flake-parts": "flake-parts",
|
||||
"git-hooks-nix": "git-hooks-nix",
|
||||
"nixpkgs": "nixpkgs_5",
|
||||
"nixpkgs": "nixpkgs_4",
|
||||
"nixpkgs-23-11": "nixpkgs-23-11",
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
@@ -1689,7 +1671,7 @@
|
||||
},
|
||||
"nixos-wsl": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_5",
|
||||
"flake-compat": "flake-compat_4",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
@@ -1823,22 +1805,6 @@
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"lastModified": 1771903837,
|
||||
"narHash": "sha256-jEA8WggGKtMFeNeCKq3NK8cLEjJmG6/RLUElYYbBZ0E=",
|
||||
@@ -1851,7 +1817,7 @@
|
||||
"url": "https://channels.nixos.org/nixos-25.11/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"nixpkgs_6": {
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1776877367,
|
||||
"narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=",
|
||||
@@ -1867,7 +1833,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_7": {
|
||||
"nixpkgs_6": {
|
||||
"locked": {
|
||||
"lastModified": 1697723726,
|
||||
"narHash": "sha256-SaTWPkI8a5xSHX/rrKzUe+/uVNy6zCGMXgoeMb7T9rg=",
|
||||
@@ -1883,7 +1849,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_8": {
|
||||
"nixpkgs_7": {
|
||||
"locked": {
|
||||
"lastModified": 1703255338,
|
||||
"narHash": "sha256-Z6wfYJQKmDN9xciTwU3cOiOk+NElxdZwy/FiHctCzjU=",
|
||||
@@ -1903,7 +1869,7 @@
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts_4",
|
||||
"hercules-ci-effects": "hercules-ci-effects_2",
|
||||
"nixpkgs": "nixpkgs_8",
|
||||
"nixpkgs": "nixpkgs_7",
|
||||
"osx-kvm": "osx-kvm"
|
||||
},
|
||||
"locked": {
|
||||
@@ -1920,50 +1886,6 @@
|
||||
"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": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
@@ -2133,10 +2055,9 @@
|
||||
},
|
||||
"pre-commit-hooks_3": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat_3",
|
||||
"flake-compat": "flake-compat_5",
|
||||
"gitignore": "gitignore_3",
|
||||
"nixpkgs": [
|
||||
"imalison-taffybar",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
@@ -2227,16 +2148,16 @@
|
||||
"nixified-ai": "nixified-ai",
|
||||
"nixos-hardware": "nixos-hardware",
|
||||
"nixos-wsl": "nixos-wsl",
|
||||
"nixpkgs": "nixpkgs_6",
|
||||
"nixpkgs": "nixpkgs_5",
|
||||
"nixtheplanet": "nixtheplanet",
|
||||
"noctalia": "noctalia",
|
||||
"notifications-tray-icon": "notifications-tray-icon",
|
||||
"org-agenda-api": "org-agenda-api",
|
||||
"railbird-secrets": "railbird-secrets",
|
||||
"systems": "systems_5",
|
||||
"systems": "systems_3",
|
||||
"taffybar": "taffybar",
|
||||
"vscode-server": "vscode-server",
|
||||
"xmonad": "xmonad_2",
|
||||
"xmonad-contrib": "xmonad-contrib_2"
|
||||
"xmonad": "xmonad",
|
||||
"xmonad-contrib": "xmonad-contrib"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
@@ -2338,77 +2259,33 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_4": {
|
||||
"locked": {
|
||||
"lastModified": 1689347949,
|
||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default-linux",
|
||||
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"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": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"noctalia",
|
||||
"noctalia-qs",
|
||||
"nixpkgs"
|
||||
],
|
||||
"weeder-nix": "weeder-nix",
|
||||
"xmonad": [
|
||||
"xmonad"
|
||||
],
|
||||
"xmonad-contrib": [
|
||||
"xmonad-contrib"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775636079,
|
||||
"narHash": "sha256-pc20NRoMdiar8oPQceQT47UUZMBTiMdUuWrYu2obUP0=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "790751ff7fd3801feeaf96d7dc416a8d581265ba",
|
||||
"lastModified": 1777401169,
|
||||
"narHash": "sha256-bciN/qFjXYm8ZIKXSc/OssUsLt9GoNs/cU9xT/pw7QY=",
|
||||
"owner": "taffybar",
|
||||
"repo": "taffybar",
|
||||
"rev": "59e3c75990156dcd4353ad9fad5823303e751f0f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"owner": "taffybar",
|
||||
"repo": "taffybar",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@@ -2438,7 +2315,6 @@
|
||||
"weeder-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"imalison-taffybar",
|
||||
"taffybar",
|
||||
"nixpkgs"
|
||||
],
|
||||
@@ -2541,7 +2417,20 @@
|
||||
}
|
||||
},
|
||||
"xmonad": {
|
||||
"flake": false,
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"git-ignore-nix": [
|
||||
"git-ignore-nix"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"unstable": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776502138,
|
||||
"narHash": "sha256-mSOpNU1iJvfFh5uwayA6aPxneFMduNW1kG1gV2tGE+c=",
|
||||
@@ -2552,29 +2441,11 @@
|
||||
},
|
||||
"original": {
|
||||
"owner": "xmonad",
|
||||
"ref": "master",
|
||||
"repo": "xmonad",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
@@ -2603,35 +2474,6 @@
|
||||
"repo": "xmonad-contrib",
|
||||
"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",
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
};
|
||||
|
||||
hyprNStack = {
|
||||
url = "github:colonelpanic8/hyprNStack?ref=hyprland-lua-integration";
|
||||
url = "github:colonelpanic8/hyprNStack";
|
||||
inputs = {
|
||||
hyprland.follows = "hyprland-lua-config";
|
||||
nixpkgs.follows = "nixpkgs";
|
||||
@@ -115,12 +115,12 @@
|
||||
};
|
||||
|
||||
hyprland-plugins = {
|
||||
url = "github:colonelpanic8/hyprland-plugins?ref=hyprexpo-v0.53.0-custom";
|
||||
url = "github:hyprwm/hyprland-plugins?ref=v0.53.0";
|
||||
inputs.hyprland.follows = "hyprland";
|
||||
};
|
||||
|
||||
hyprland-plugins-lua = {
|
||||
url = "github:colonelpanic8/hyprland-plugins?ref=codex/fix-main-ci";
|
||||
url = "github:colonelpanic8/hyprland-plugins?ref=hyprexpo-lua-hyprland";
|
||||
inputs.hyprland.follows = "hyprland-lua-config";
|
||||
};
|
||||
|
||||
@@ -231,11 +231,6 @@
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
noctalia = {
|
||||
url = "github:noctalia-dev/noctalia-shell";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
outputs = inputs @ {
|
||||
@@ -448,7 +443,6 @@
|
||||
"https://colonelpanic8-dotfiles.cachix.org"
|
||||
"https://codex-cli.cachix.org"
|
||||
"https://claude-code.cachix.org"
|
||||
"https://noctalia.cachix.org"
|
||||
];
|
||||
extra-trusted-substituters = [
|
||||
"https://ai.cachix.org"
|
||||
@@ -470,7 +464,6 @@
|
||||
"colonelpanic8-dotfiles.cachix.org-1:O6GF3nptpeMFapX29okzO92eSWXR36zqW6ZF2C8P0eQ="
|
||||
"codex-cli.cachix.org-1:1Br3H1hHoRYG22n//cGKJOk3cQXgYobUel6O8DgSing="
|
||||
"claude-code.cachix.org-1:YeXf2aNu7UTX8Vwrze0za1WEDS+4DuI2kVeWEE4fsRk="
|
||||
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
|
||||
];
|
||||
};
|
||||
nixosConfigurations =
|
||||
|
||||
@@ -6,6 +6,33 @@
|
||||
...
|
||||
}: let
|
||||
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";
|
||||
imageViewer = "org.kde.gwenview.desktop";
|
||||
pdfViewer = "okularApplication_pdf.desktop";
|
||||
@@ -20,6 +47,7 @@
|
||||
wordProcessor = "writer.desktop";
|
||||
spreadsheet = "calc.desktop";
|
||||
presentation = "impress.desktop";
|
||||
|
||||
defaultApplications =
|
||||
(mimeMap imageViewer [
|
||||
"image/avif"
|
||||
@@ -135,82 +163,12 @@
|
||||
"x-scheme-handler/element" = ["element-desktop.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 {
|
||||
# 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;
|
||||
associations.added = defaultApplications;
|
||||
inherit defaultApplications;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
home.activation.refreshChromeDesktopMimeCache = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||
applications_dir="$HOME/.local/share/applications"
|
||||
@@ -222,7 +180,6 @@ in {
|
||||
do
|
||||
if [ -f "$desktop_file" ]; then
|
||||
${pkgs.gnused}/bin/sed -i \
|
||||
-e 's,application/pdf;,,g' \
|
||||
-e 's,image/gif;,,g' \
|
||||
-e 's,image/jpeg;,,g' \
|
||||
-e 's,image/png;,,g' \
|
||||
@@ -231,34 +188,10 @@ in {
|
||||
fi
|
||||
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
|
||||
fi
|
||||
'';
|
||||
|
||||
home.activation.refreshKdeServiceCache = lib.hm.dag.entryAfter ["refreshChromeDesktopMimeCache"] ''
|
||||
${pkgs.kdePackages.kservice}/bin/kbuildsycoca6 --noincremental >/dev/null 2>&1 || true
|
||||
'';
|
||||
|
||||
xsession = {
|
||||
enable = true;
|
||||
preferStatusNotifierItems = true;
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
lib,
|
||||
makeEnable,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
{ config, pkgs, lib, makeEnable, inputs, ... }:
|
||||
let
|
||||
cfg = config.myModules.hyprland;
|
||||
system = pkgs.stdenv.hostPlatform.system;
|
||||
@@ -18,29 +11,88 @@ let
|
||||
inputs.hyprNStack.packages.${system}.hyprNStack
|
||||
inputs.hyprland-plugins-lua.packages.${system}.hyprexpo
|
||||
];
|
||||
hyprRofiWindow = pkgs.writeShellApplication {
|
||||
name = "hypr_rofi_window";
|
||||
runtimeInputs = [
|
||||
pkgs.python3
|
||||
pkgs.rofi
|
||||
hyprlandInput.packages.${system}.hyprland
|
||||
hyprexpoPatched = inputs.hyprland-plugins.packages.${system}.hyprexpo.overrideAttrs (old: {
|
||||
patches = (old.patches or [ ]) ++ [
|
||||
./patches/hyprexpo-pr-612-workspace-numbers.patch
|
||||
./patches/hyprexpo-pr-616-bring-mode.patch
|
||||
];
|
||||
text = ''
|
||||
exec python3 ${../dotfiles/lib/bin/hypr_rofi_window} "$@"
|
||||
'';
|
||||
});
|
||||
enabledModule = makeEnable config "myModules.hyprland" true {
|
||||
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 {
|
||||
name = "hypr_shell_ui";
|
||||
runtimeInputs = [
|
||||
pkgs.rofi
|
||||
hyprRofiWindow
|
||||
inputs.noctalia.packages.${system}.default
|
||||
|
||||
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, ... }: {
|
||||
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";
|
||||
global_options = "";
|
||||
global_rules = "float;size monitor_w*0.95 monitor_h*0.95;center";
|
||||
@@ -94,115 +146,12 @@ let
|
||||
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
|
||||
environment.systemPackages =
|
||||
with pkgs;
|
||||
[
|
||||
environment.systemPackages = with pkgs; [
|
||||
# Hyprland utilities
|
||||
hyprpaper # Wallpaper
|
||||
hypridle # Idle daemon
|
||||
@@ -215,26 +164,21 @@ let
|
||||
slurp # Region selection
|
||||
swappy # Screenshot annotation
|
||||
nwg-displays # GUI monitor arrangement
|
||||
mpvpaper # Layer-shell video screensaver payload
|
||||
mpv # Graphical screensaver payload
|
||||
ddcutil # Monitor input switching over DDC/CI
|
||||
|
||||
# For scripts
|
||||
hyprRofiWindow
|
||||
hyprShellUi
|
||||
jq
|
||||
]
|
||||
++ luaPluginPackages
|
||||
++ lib.optionals enableExternalPluginPackages [
|
||||
] ++ luaPluginPackages ++ lib.optionals enableExternalPluginPackages [
|
||||
# External plugin packages are pinned to the stable 0.53 stack.
|
||||
# Keep hy3 on the stable stack; the Lua branch uses hyprNStack and the
|
||||
# forked Lua-compatible hyprexpo input instead.
|
||||
inputs.hy3.packages.${system}.hy3
|
||||
inputs.hyprland-plugins.packages.${system}.hyprexpo
|
||||
hyprexpoPatched
|
||||
];
|
||||
};
|
||||
in
|
||||
enabledModule
|
||||
// {
|
||||
enabledModule // {
|
||||
options = lib.recursiveUpdate enabledModule.options {
|
||||
myModules.hyprland.useLuaConfigBranch = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
|
||||
@@ -184,11 +184,16 @@
|
||||
type = "Application";
|
||||
categories = [ "Network" "WebBrowser" ];
|
||||
mimeType = [
|
||||
"application/pdf"
|
||||
"application/rdf+xml"
|
||||
"application/rss+xml"
|
||||
"application/xhtml+xml"
|
||||
"application/xhtml_xml"
|
||||
"application/xml"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"image/webp"
|
||||
"text/html"
|
||||
"text/xml"
|
||||
"x-scheme-handler/http"
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
# Disable the old multi-node railbird k3s setup
|
||||
myModules.railbird-k3s.enable = false;
|
||||
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.
|
||||
services.nginx.virtualHosts."syncthing.railbird.ai" = {
|
||||
|
||||
@@ -113,9 +113,6 @@
|
||||
codex = inputs.codex-cli-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;
|
||||
kef = final.callPackage ./packages/kef {};
|
||||
pykefcontrol = final.python3Packages.callPackage ./packages/pykefcontrol {};
|
||||
roborock-control = final.callPackage ./packages/roborock-control {};
|
||||
})
|
||||
]
|
||||
++ (
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
{
|
||||
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" ];
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
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";
|
||||
};
|
||||
}
|
||||
@@ -1,251 +0,0 @@
|
||||
#!/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())
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
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 ];
|
||||
};
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
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";
|
||||
};
|
||||
}
|
||||
228
nixos/patches/hyprexpo-pr-612-workspace-numbers.patch
Normal file
228
nixos/patches/hyprexpo-pr-612-workspace-numbers.patch
Normal file
@@ -0,0 +1,228 @@
|
||||
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
|
||||
|
||||
|
||||
147
nixos/patches/hyprexpo-pr-616-bring-mode.patch
Normal file
147
nixos/patches/hyprexpo-pr-616-bring-mode.patch
Normal file
@@ -0,0 +1,147 @@
|
||||
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
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
{ 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,19 +2,17 @@
|
||||
makeEnable,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
}: let
|
||||
shared = import ../nix-shared/syncthing.nix;
|
||||
inherit (shared) devices allDevices;
|
||||
in
|
||||
makeEnable config "myModules.syncthing" true {
|
||||
system.activationScripts.syncthingPermissions = {
|
||||
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
|
||||
chmod -R 2770 /var/lib/syncthing
|
||||
mkdir -p /var/lib/syncthing/sync
|
||||
mkdir -p /var/lib/syncthing/railbird
|
||||
'';
|
||||
};
|
||||
systemd.services.syncthing = {
|
||||
|
||||
Reference in New Issue
Block a user